AlarmSound.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. NSLog("AlarmSound - audio player failed preparing to play")
  89. }
  90. if self.audioPlayer!.play() {
  91. if !self.isPlaying {
  92. NSLog("AlarmSound - not playing after calling play")
  93. NSLog("AlarmSound - rate value: \(self.audioPlayer!.rate)")
  94. }
  95. } else {
  96. NSLog("AlarmSound - audio player failed to play")
  97. }
  98. } catch let error {
  99. NSLog("AlarmSound - unable to play sound; error: \(error)")
  100. }
  101. }
  102. static func play(overrideVolume: Bool, numLoops: Int) {
  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. // Play endless loops
  113. self.audioPlayer!.numberOfLoops = numLoops
  114. // Store existing volume
  115. if self.systemOutputVolumeBeforeOverride == nil {
  116. self.systemOutputVolumeBeforeOverride = AVAudioSession.sharedInstance().outputVolume
  117. }
  118. // init volume before start playing (mute if fade-in)
  119. //self.audioPlayer!.volume = (self.muted || (UserDefaultsRepository.fadeInTimeInterval.value > 0)) ? 0.0 : 1.0
  120. if !self.audioPlayer!.prepareToPlay() {
  121. NSLog("AlarmSound - audio player failed preparing to play")
  122. }
  123. if self.audioPlayer!.play() {
  124. if !self.isPlaying {
  125. NSLog("AlarmSound - not playing after calling play")
  126. NSLog("AlarmSound - rate value: \(self.audioPlayer!.rate)")
  127. }
  128. } else {
  129. NSLog("AlarmSound - audio player failed to play")
  130. }
  131. // do fade-in
  132. //if !self.muted && (UserDefaultsRepository.fadeInTimeInterval.value > 0) {
  133. // self.audioPlayer!.setVolume(1.0, fadeDuration: UserDefaultsRepository.fadeInTimeInterval.value)
  134. //}
  135. if overrideVolume {
  136. MPVolumeView.setVolume(UserDefaultsRepository.forcedOutputVolume.value)
  137. }
  138. } catch let error {
  139. NSLog("AlarmSound - unable to play sound; error: \(error)")
  140. }
  141. }
  142. static func playTerminated() {
  143. guard !self.isPlaying else {
  144. return
  145. }
  146. do {
  147. self.audioPlayer = try AVAudioPlayer(contentsOf: self.soundURL)
  148. self.audioPlayer!.delegate = self.audioPlayerDelegate
  149. try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.playback)))
  150. try AVAudioSession.sharedInstance().setActive(true)
  151. // Play endless loops
  152. self.audioPlayer!.numberOfLoops = 2
  153. // Store existing volume
  154. if self.systemOutputVolumeBeforeOverride == nil {
  155. self.systemOutputVolumeBeforeOverride = AVAudioSession.sharedInstance().outputVolume
  156. }
  157. if !self.audioPlayer!.prepareToPlay() {
  158. NSLog("Terminate AlarmSound - audio player failed preparing to play")
  159. }
  160. if self.audioPlayer!.play() {
  161. if !self.isPlaying {
  162. NSLog("Terminate AlarmSound - not playing after calling play")
  163. NSLog("Terminate AlarmSound - rate value: \(self.audioPlayer!.rate)")
  164. }
  165. } else {
  166. NSLog("Terminate AlarmSound - audio player failed to play")
  167. }
  168. MPVolumeView.setVolume(1.0)
  169. } catch let error {
  170. NSLog("Terminate AlarmSound - unable to play sound; error: \(error)")
  171. }
  172. }
  173. fileprivate static func restoreSystemOutputVolume() {
  174. guard UserDefaultsRepository.overrideSystemOutputVolume.value else {
  175. return
  176. }
  177. // cancel any volume change observations
  178. // self.volumeChangeDetector.isActive = false
  179. // restore system output volume with its value before overriding it
  180. if let volumeBeforeOverride = self.systemOutputVolumeBeforeOverride {
  181. MPVolumeView.setVolume(volumeBeforeOverride)
  182. }
  183. self.systemOutputVolumeBeforeOverride = nil
  184. }
  185. fileprivate static func enableAudio() {
  186. do {
  187. try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: .mixWithOthers)
  188. try AVAudioSession.sharedInstance().setActive(true)
  189. } catch {
  190. print(error)
  191. }
  192. }
  193. }
  194. class AudioPlayerDelegate: NSObject, AVAudioPlayerDelegate {
  195. /* audioPlayerDidFinishPlaying:successfully: is called when a sound has finished playing. This method is NOT called if the player is stopped due to an interruption. */
  196. func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  197. NSLog("AlarmRule - audioPlayerDidFinishPlaying (\(flag))")
  198. }
  199. /* if an error occurs while decoding it will be reported to the delegate. */
  200. func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
  201. if let error = error {
  202. NSLog("AlarmRule - audioPlayerDecodeErrorDidOccur: \(error)")
  203. } else {
  204. NSLog("AlarmRule - audioPlayerDecodeErrorDidOccur")
  205. }
  206. }
  207. /* AVAudioPlayer INTERRUPTION NOTIFICATIONS ARE DEPRECATED - Use AVAudioSession instead. */
  208. /* audioPlayerBeginInterruption: is called when the audio session has been interrupted while the player was playing. The player will have been paused. */
  209. func audioPlayerBeginInterruption(_ player: AVAudioPlayer) {
  210. NSLog("AlarmRule - audioPlayerBeginInterruption")
  211. }
  212. /* audioPlayerEndInterruption:withOptions: is called when the audio session interruption has ended and this player had been interrupted while playing. */
  213. /* Currently the only flag is AVAudioSessionInterruptionFlags_ShouldResume. */
  214. func audioPlayerEndInterruption(_ player: AVAudioPlayer, withOptions flags: Int) {
  215. NSLog("AlarmRule - audioPlayerEndInterruption withOptions: \(flags)")
  216. }
  217. }
  218. // Helper function inserted by Swift 4.2 migrator.
  219. fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String {
  220. return input.rawValue
  221. }
  222. extension MPVolumeView {
  223. static func setVolume(_ volume: Float) {
  224. // Need to use the MPVolumeView in order to change volume, but don't care about UI set so frame to .zero
  225. let volumeView = MPVolumeView(frame: .zero)
  226. // Search for the slider
  227. let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider
  228. // Update the slider value with the desired volume.
  229. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
  230. slider?.value = volume
  231. }
  232. // Optional - Remove the HUD
  233. if let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window {
  234. volumeView.alpha = 0.000001
  235. window.addSubview(volumeView)
  236. }
  237. }
  238. }