diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 66c4392..6276340 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -66,6 +66,7 @@ C4F8B1552988751B005C86A5 /* DialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1542988751B005C86A5 /* DialView.swift */; }; C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; }; C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; }; + C4F8B15B29892D40005C86A5 /* SoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA8E2987B83B0054D761 /* SoundPlayer.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -626,6 +627,7 @@ C438C8162982BE1E00BF3EF9 /* LeCountdown.xcdatamodeld in Sources */, C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */, C438C7DF2981216300BF3EF9 /* LaunchWidget.intentdefinition in Sources */, + C4F8B15B29892D40005C86A5 /* SoundPlayer.swift in Sources */, C438C7E82981255D00BF3EF9 /* TimeInterval+Extensions.swift in Sources */, C438C7D82981216200BF3EF9 /* LaunchWidgetLiveActivity.swift in Sources */, C438C8192982BFDB00BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index 7465b23..f6b1127 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -12,6 +12,8 @@ class Conductor : ObservableObject { static let maestro: Conductor = Conductor() + var soundPlayer: SoundPlayer? = nil + @UserDefault(Key.dates.rawValue, defaultValue: [:]) static var savedDates: [String : DateInterval] init() { @@ -38,7 +40,7 @@ class Conductor : ObservableObject { } func notifyUser(countdownId: String, cancel: Bool) { -// self._playSound(countdownId: countdownId) + self._playSound(countdownId: countdownId) endCountdown(countdownId: countdownId, cancel: cancel) } @@ -49,6 +51,7 @@ class Conductor : ObservableObject { } self.notificationDates.removeValue(forKey: countdownId) +// self._updateLiveActivity(countdownId: countdownId, endDate: <#T##Date#>) self._endLiveActivity(countdownId: countdownId) } } @@ -74,7 +77,31 @@ class Conductor : ObservableObject { } } } + + // MARK: - Sound + + fileprivate func _playSound(countdownId: String) { + + let countdown = PersistenceController.shared.container.viewContext.object(stringId: countdownId) as? Countdown + + let coolSound = countdown?.coolSound ?? Sound.allCases[0] + do { + let soundFile = try coolSound.soundFile() + let soundPlayer = SoundPlayer() + self.soundPlayer = soundPlayer + try soundPlayer.playSound(soundFile: soundFile, repeats: countdown?.repeats ?? true) + } catch { + print("error = \(error)") + // TODO: manage error + } + + } + + func stopSoundIfNecessary() { + self.soundPlayer?.stop() + } + // MARK: - Live Activity fileprivate func _launchLiveActivity(countdown: Countdown, endDate: Date) { diff --git a/LeCountdown/LeCountdownApp.swift b/LeCountdown/LeCountdownApp.swift index 0b72773..2e710c2 100644 --- a/LeCountdown/LeCountdownApp.swift +++ b/LeCountdown/LeCountdownApp.swift @@ -32,16 +32,16 @@ struct LeCountdownApp: App { } fileprivate func _onAppear() { - Task { - for s in Sound.allCases { - do { - let d = try await s.duration() - print("\(s) duration = \(d)") - } catch { - print("error = \(error)") - } - } - } +// Task { +// for s in Sound.allCases { +// do { +// let d = try await s.duration() +// print("\(s) duration = \(d)") +// } catch { +// print("error = \(error)") +// } +// } +// } } } diff --git a/LeCountdown/Sound/Sound.swift b/LeCountdown/Sound/Sound.swift index dbc8b35..260116f 100644 --- a/LeCountdown/Sound/Sound.swift +++ b/LeCountdown/Sound/Sound.swift @@ -49,11 +49,9 @@ enum Sound : Int, CaseIterable, Identifiable { } -// var soundFile: SoundFile { -// switch self { -// case .trainhorn: return SoundFile(filename: "train_horn", fileExtension: "mp3") -// } -// } + func soundFile() throws -> SoundFile { + return try SoundFile(fullName: self.soundName) + } func duration() async throws -> TimeInterval { diff --git a/LeCountdown/Sound/SoundPlayer.swift b/LeCountdown/Sound/SoundPlayer.swift index a18c4d1..d7b6b2a 100644 --- a/LeCountdown/Sound/SoundPlayer.swift +++ b/LeCountdown/Sound/SoundPlayer.swift @@ -9,9 +9,20 @@ import Foundation import AVFoundation struct SoundFile { + var filename: String var fileExtension: String + init(fullName: String) throws { + let components = fullName.components(separatedBy: ".") + if components.count == 2 { + self.filename = components[0] + self.fileExtension = components[1] + } else { + throw SoundPlayerError.badFileName(name: fullName) + } + } + var url: URL? { return Bundle.main.url(forResource: self.filename, withExtension: self.fileExtension) } @@ -19,6 +30,7 @@ struct SoundFile { enum SoundPlayerError : Error { case missingResourceError(file: SoundFile) + case badFileName(name: String) } class SoundPlayer { @@ -30,9 +42,9 @@ class SoundPlayer { throw SoundPlayerError.missingResourceError(file: soundFile) } - let audioSession: AVAudioSession = AVAudioSession.sharedInstance() - try audioSession.setCategory(.playback) - try audioSession.setActive(true) +// let audioSession: AVAudioSession = AVAudioSession.sharedInstance() +// try audioSession.setCategory(.playback) +// try audioSession.setActive(true) _player = try AVAudioPlayer(contentsOf: url) _player?.prepareToPlay() @@ -40,11 +52,11 @@ class SoundPlayer { _player?.numberOfLoops = loopCount _player?.volume = 1.0 - do { - try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .defaultToSpeaker]) - } catch { - print("audioSession error = \(error)") - } +// do { +// try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .defaultToSpeaker]) +// } catch { +// print("audioSession error = \(error)") +// } _player?.play()