AlarmSound.swift 10 KB

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