AlarmSound.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. //
  2. // AlarmSound.swift
  3. // scoutwatch
  4. //
  5. // Created by Dirk Hermanns on 03.01.16.
  6. // Copyright © 2016 private. All rights reserved.
  7. //
  8. import Foundation
  9. import AVFoundation
  10. import MediaPlayer
  11. import UIKit
  12. /*
  13. * Class that handles the playing and the volume of the alarm sound.
  14. */
  15. class AlarmSound {
  16. static var isPlaying: Bool {
  17. return self.audioPlayer?.isPlaying == true
  18. }
  19. static var isMuted: Bool {
  20. return self.muted
  21. }
  22. static var whichAlarm: String = "none"
  23. static var soundFile = "Indeed"
  24. static var isTesting: Bool = false
  25. //static let volumeChangeDetector = VolumeChangeDetector()
  26. static let vibrate = UserDefaultsRepository.vibrate
  27. fileprivate static var systemOutputVolumeBeforeOverride: Float?
  28. fileprivate static var playingTimer: Timer?
  29. fileprivate static var soundURL = Bundle.main.url(forResource: "Indeed", withExtension: "caf")!
  30. fileprivate static var audioPlayer: AVAudioPlayer?
  31. fileprivate static let audioPlayerDelegate = AudioPlayerDelegate()
  32. fileprivate static var muted = false
  33. fileprivate static var alarmPlayingForTimer = Timer()
  34. fileprivate static let alarmPlayingForInterval = 290
  35. fileprivate func startAlarmPlayingForTimer(time: TimeInterval) {
  36. AlarmSound.alarmPlayingForTimer = Timer.scheduledTimer(timeInterval: time,
  37. target: self,
  38. selector: #selector(AlarmSound.alarmPlayingForTimerDidEnd(_:)),
  39. userInfo: nil,
  40. repeats: true)
  41. }
  42. @objc func alarmPlayingForTimerDidEnd(_ timer:Timer) {
  43. if !AlarmSound.isPlaying { return }
  44. AlarmSound.stop()
  45. }
  46. /*
  47. * Sets the audio volume to 0.
  48. */
  49. static func muteVolume() {
  50. self.audioPlayer?.volume = 0
  51. self.muted = true
  52. self.restoreSystemOutputVolume()
  53. }
  54. static func setSoundFile(str: String) {
  55. self.soundURL = Bundle.main.url(forResource: str, withExtension: "caf")!
  56. }
  57. /*
  58. * Sets the volume of the alarm back to the volume before it has been muted.
  59. */
  60. static func unmuteVolume() {
  61. if UserDefaultsRepository.fadeInTimeInterval.value > 0 {
  62. self.audioPlayer?.setVolume(1.0, fadeDuration: UserDefaultsRepository.fadeInTimeInterval.value)
  63. } else {
  64. self.audioPlayer?.volume = 1.0
  65. }
  66. self.muted = false
  67. }
  68. static func stop() {
  69. self.playingTimer?.invalidate()
  70. self.playingTimer = nil
  71. self.audioPlayer?.stop()
  72. self.audioPlayer = nil
  73. self.restoreSystemOutputVolume()
  74. }
  75. static func playTest() {
  76. guard !self.isPlaying else {
  77. return
  78. }
  79. do {
  80. self.audioPlayer = try AVAudioPlayer(contentsOf: self.soundURL)
  81. self.audioPlayer!.delegate = self.audioPlayerDelegate
  82. try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.playback)))
  83. try AVAudioSession.sharedInstance().setActive(true)
  84. self.audioPlayer?.numberOfLoops = 0
  85. // init volume before start playing (mute if fade-in)
  86. //self.audioPlayer!.volume = (self.muted || (UserDefaultsRepository.fadeInTimeInterval.value > 0)) ? 0.0 : 1.0
  87. if !self.audioPlayer!.prepareToPlay() {
  88. LogManager.shared.log(category: .alarm, message: "AlarmSound - audio player failed preparing to play")
  89. }
  90. if self.audioPlayer!.play() {
  91. if !self.isPlaying {
  92. LogManager.shared.log(category: .alarm, message: "AlarmSound - not playing after calling play")
  93. LogManager.shared.log(category: .alarm, message: "AlarmSound - rate value: \(self.audioPlayer!.rate)")
  94. }
  95. } else {
  96. LogManager.shared.log(category: .alarm, message: "AlarmSound - audio player failed to play")
  97. }
  98. } catch let error {
  99. LogManager.shared.log(category: .alarm, message: "AlarmSound - unable to play sound; error: \(error)")
  100. }
  101. }
  102. static func play(repeating: Bool) {
  103. guard !self.isPlaying else {
  104. return
  105. }
  106. enableAudio()
  107. do {
  108. self.audioPlayer = try AVAudioPlayer(contentsOf: self.soundURL)
  109. self.audioPlayer!.delegate = self.audioPlayerDelegate
  110. try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.playback)))
  111. try AVAudioSession.sharedInstance().setActive(true)
  112. self.audioPlayer!.numberOfLoops = repeating ? -1 : 0
  113. // Store existing volume
  114. if self.systemOutputVolumeBeforeOverride == nil {
  115. self.systemOutputVolumeBeforeOverride = AVAudioSession.sharedInstance().outputVolume
  116. }
  117. // init volume before start playing (mute if fade-in)
  118. //self.audioPlayer!.volume = (self.muted || (UserDefaultsRepository.fadeInTimeInterval.value > 0)) ? 0.0 : 1.0
  119. if !self.audioPlayer!.prepareToPlay() {
  120. LogManager.shared.log(category: .alarm, message: "AlarmSound - audio player failed preparing to play")
  121. }
  122. if self.audioPlayer!.play() {
  123. if !self.isPlaying {
  124. LogManager.shared.log(category: .alarm, message: "AlarmSound - not playing after calling play")
  125. LogManager.shared.log(category: .alarm, message: "AlarmSound - rate value: \(self.audioPlayer!.rate)")
  126. }
  127. } else {
  128. LogManager.shared.log(category: .alarm, message: "AlarmSound - audio player failed to play")
  129. }
  130. // do fade-in
  131. //if !self.muted && (UserDefaultsRepository.fadeInTimeInterval.value > 0) {
  132. // self.audioPlayer!.setVolume(1.0, fadeDuration: UserDefaultsRepository.fadeInTimeInterval.value)
  133. //}
  134. if Storage.shared.alarmConfiguration.value.overrideSystemOutputVolume {
  135. MPVolumeView.setVolume(Storage.shared.alarmConfiguration.value.forcedOutputVolume)
  136. }
  137. } catch let error {
  138. LogManager.shared.log(category: .alarm, message: "AlarmSound - unable to play sound; error: \(error)")
  139. }
  140. }
  141. static func playTerminated() {
  142. guard !self.isPlaying else {
  143. return
  144. }
  145. do {
  146. self.audioPlayer = try AVAudioPlayer(contentsOf: self.soundURL)
  147. self.audioPlayer!.delegate = self.audioPlayerDelegate
  148. try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.playback)))
  149. try AVAudioSession.sharedInstance().setActive(true)
  150. // Play endless loops
  151. self.audioPlayer!.numberOfLoops = 2
  152. // Store existing volume
  153. if self.systemOutputVolumeBeforeOverride == nil {
  154. self.systemOutputVolumeBeforeOverride = AVAudioSession.sharedInstance().outputVolume
  155. }
  156. if !self.audioPlayer!.prepareToPlay() {
  157. LogManager.shared.log(category: .alarm, message: "Terminate AlarmSound - audio player failed preparing to play")
  158. }
  159. if self.audioPlayer!.play() {
  160. if !self.isPlaying {
  161. LogManager.shared.log(category: .alarm, message: "Terminate AlarmSound - not playing after calling play")
  162. LogManager.shared.log(category: .alarm, message: "Terminate AlarmSound - rate value: \(self.audioPlayer!.rate)")
  163. }
  164. } else {
  165. LogManager.shared.log(category: .alarm, message: "Terminate AlarmSound - audio player failed to play")
  166. }
  167. MPVolumeView.setVolume(1.0)
  168. } catch let error {
  169. LogManager.shared.log(category: .alarm, message: "Terminate AlarmSound - unable to play sound; error: \(error)")
  170. }
  171. }
  172. fileprivate static func restoreSystemOutputVolume() {
  173. guard UserDefaultsRepository.overrideSystemOutputVolume.value else {
  174. return
  175. }
  176. // cancel any volume change observations
  177. // self.volumeChangeDetector.isActive = false
  178. // restore system output volume with its value before overriding it
  179. if let volumeBeforeOverride = self.systemOutputVolumeBeforeOverride {
  180. MPVolumeView.setVolume(volumeBeforeOverride)
  181. }
  182. self.systemOutputVolumeBeforeOverride = nil
  183. }
  184. fileprivate static func enableAudio() {
  185. do {
  186. try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: .mixWithOthers)
  187. try AVAudioSession.sharedInstance().setActive(true)
  188. } catch {
  189. LogManager.shared.log(category: .alarm, message: "Enable audio error: \(error)")
  190. }
  191. }
  192. }
  193. class AudioPlayerDelegate: NSObject, AVAudioPlayerDelegate {
  194. /* audioPlayerDidFinishPlaying:successfully: is called when a sound has finished playing. This method is NOT called if the player is stopped due to an interruption. */
  195. func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  196. LogManager.shared.log(category: .alarm, message: "AlarmRule - audioPlayerDidFinishPlaying (\(flag))", isDebug: true)
  197. }
  198. /* if an error occurs while decoding it will be reported to the delegate. */
  199. func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
  200. if let error = error {
  201. LogManager.shared.log(category: .alarm, message: "AlarmRule - audioPlayerDecodeErrorDidOccur: \(error)")
  202. } else {
  203. LogManager.shared.log(category: .alarm, message: "AlarmRule - audioPlayerDecodeErrorDidOccur")
  204. }
  205. }
  206. /* AVAudioPlayer INTERRUPTION NOTIFICATIONS ARE DEPRECATED - Use AVAudioSession instead. */
  207. /* audioPlayerBeginInterruption: is called when the audio session has been interrupted while the player was playing. The player will have been paused. */
  208. func audioPlayerBeginInterruption(_ player: AVAudioPlayer) {
  209. LogManager.shared.log(category: .alarm, message: "AlarmRule - audioPlayerBeginInterruption")
  210. }
  211. /* audioPlayerEndInterruption:withOptions: is called when the audio session interruption has ended and this player had been interrupted while playing. */
  212. /* Currently the only flag is AVAudioSessionInterruptionFlags_ShouldResume. */
  213. func audioPlayerEndInterruption(_ player: AVAudioPlayer, withOptions flags: Int) {
  214. LogManager.shared.log(category: .alarm, message: "AlarmRule - audioPlayerEndInterruption withOptions: \(flags)")
  215. }
  216. }
  217. // Helper function inserted by Swift 4.2 migrator.
  218. fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String {
  219. return input.rawValue
  220. }
  221. extension MPVolumeView {
  222. static func setVolume(_ volume: Float) {
  223. // Need to use the MPVolumeView in order to change volume, but don't care about UI set so frame to .zero
  224. let volumeView = MPVolumeView(frame: .zero)
  225. // Search for the slider
  226. let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider
  227. // Update the slider value with the desired volume.
  228. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
  229. slider?.value = volume
  230. }
  231. // Optional - Remove the HUD
  232. if let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window {
  233. volumeView.alpha = 0.000001
  234. window.addSubview(volumeView)
  235. }
  236. }
  237. }