AlertSoundPlayer.swift 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. //
  2. // AlertSoundPlayer.swift
  3. // Loop
  4. //
  5. // Created by Rick Pasetto on 4/27/20.
  6. // Copyright © 2020 LoopKit Authors. All rights reserved.
  7. //
  8. #if os(watchOS)
  9. import WatchKit
  10. #else
  11. import AudioToolbox
  12. #endif
  13. import AVFoundation
  14. import os.log
  15. public protocol AlertSoundPlayer {
  16. func vibrate()
  17. func play(url: URL)
  18. func stopAll()
  19. }
  20. public class DeviceAVSoundPlayer: AlertSoundPlayer {
  21. private let log = OSLog(category: "DeviceAVSoundPlayer")
  22. private let baseURL: URL?
  23. private var delegate: Delegate!
  24. private var players = [AVAudioPlayer]()
  25. @objc class Delegate: NSObject, AVAudioPlayerDelegate {
  26. weak var parent: DeviceAVSoundPlayer?
  27. init(parent: DeviceAVSoundPlayer) { self.parent = parent }
  28. func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  29. parent?.players.removeAll { $0 == player }
  30. }
  31. }
  32. public init(baseURL: URL? = nil) {
  33. self.baseURL = baseURL
  34. self.delegate = Delegate(parent: self)
  35. }
  36. enum Error: Swift.Error {
  37. case playFailed
  38. }
  39. public func vibrate() {
  40. #if os(watchOS)
  41. WKInterfaceDevice.current().play(.notification)
  42. #else
  43. AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
  44. #endif
  45. }
  46. public func play(url: URL) {
  47. DispatchQueue.main.async {
  48. do {
  49. let soundEffect = try AVAudioPlayer(contentsOf: url)
  50. soundEffect.delegate = self.delegate
  51. // The AVAudioPlayer has to remain around until the sound completes playing, which is why we hold
  52. // onto it until it completes.
  53. self.players.append(soundEffect)
  54. if !soundEffect.play() {
  55. self.log.default("couldn't play sound (app may be in the background): %@", url.absoluteString)
  56. }
  57. } catch {
  58. self.log.error("couldn't play sound %@: %@", url.absoluteString, String(describing: error))
  59. }
  60. }
  61. }
  62. public func stopAll() {
  63. DispatchQueue.main.async {
  64. for soundEffect in self.players {
  65. soundEffect.stop()
  66. }
  67. }
  68. }
  69. }
  70. public extension DeviceAVSoundPlayer {
  71. func playAlert(sound: Alert.Sound) {
  72. switch sound {
  73. case .vibrate:
  74. vibrate()
  75. default:
  76. if let baseURL = baseURL {
  77. if let name = sound.filename {
  78. self.stopAll()
  79. self.play(url: baseURL.appendingPathComponent(name))
  80. } else {
  81. log.default("No file to play for %@", "\(sound)")
  82. vibrate()
  83. }
  84. } else {
  85. log.error("No base URL, could not play %@", sound.filename ?? "")
  86. vibrate()
  87. }
  88. }
  89. }
  90. }