AlarmSound.swift 11 KB

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