From 2a09dc6593256418af15539e8bce767b54f56906 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 13 Mar 2023 10:56:53 +0100 Subject: [PATCH] Restore sound play after quitting --- LeCountdown.xcodeproj/project.pbxproj | 2 - LeCountdown/Conductor.swift | 47 ++++++++++++++----- LeCountdown/LeCountdownApp.swift | 1 + .../Model/Model+SharedExtensions.swift | 2 + LeCountdown/Sound/DelaySoundPlayer.swift | 45 +++++++++++------- 5 files changed, 65 insertions(+), 32 deletions(-) diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index ed20af1..12d7237 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -155,7 +155,6 @@ C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */; }; C4E5D66D29B753D7008E7465 /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = C4E5D66F29B753D7008E7465 /* AppShortcuts.strings */; }; C4E5D67429B88734008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; }; - C4E5D67729B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; }; C4E5D67829B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; }; C4E5D67A29B8C5A1008E7465 /* VolumeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67929B8C5A1008E7465 /* VolumeView.swift */; }; C4E5D67C29B8D4A5008E7465 /* Low_Tom_Disto_Earth.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4E5D67B29B8D4A5008E7465 /* Low_Tom_Disto_Earth.wav */; }; @@ -1129,7 +1128,6 @@ C4BA2B4A299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */, C4BA2B4D299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, C4F8B194298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */, - C4E5D67729B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */, C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */, C473C31929A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, C4F8B15B29892D40005C86A5 /* SoundPlayer.swift in Sources */, diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index ba82b68..809cd5e 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -20,15 +20,13 @@ fileprivate enum Const: String { case confirmationSound = "PVP_Stab_Oneshot_Bleep_Em.wav" } -typealias TimerID = String - class Conductor: ObservableObject { static let maestro: Conductor = Conductor() @Published var soundPlayer: SoundPlayer? = nil - var delayedSoundPlayers: [TimerID : DelaySoundPlayer] = [:] + fileprivate var _delayedSoundPlayers: [TimerID : DelaySoundPlayer] = [:] @UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval] @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date] @@ -62,8 +60,13 @@ class Conductor: ObservableObject { } func removeLiveTimer(id: String) { + + Logger.log("removeLiveTimer") self.liveTimers.removeAll(where: { $0.id == id }) self.cancelledCountdowns.removeAll(where: { $0 == id }) + if let soundPlayer = self._delayedSoundPlayers[id] { + soundPlayer.stop() + } } fileprivate func _buildLiveTimers() { @@ -162,17 +165,15 @@ class Conductor: ObservableObject { do { let date = Date(timeIntervalSinceNow: countdown.duration) + + self.startCountdown(date, countdown: countdown) + let soundFile = try SoundFile(fullName: countdown.soundName) - - let soundPlayer = DelaySoundPlayer(soundFile: soundFile) - - self.delayedSoundPlayers[countdown.stringId] = soundPlayer - + let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, soundFile: soundFile) + self._delayedSoundPlayers[countdown.stringId] = soundPlayer try soundPlayer.start(in: countdown.duration, repeatCount: Int(countdown.repeatCount)) - self.startCountdown(date, countdown: countdown) - if Preferences.playConfirmationSound { self._playSound(Const.confirmationSound.rawValue) } @@ -215,6 +216,28 @@ class Conductor: ObservableObject { } } + func restore() { + + for (countdownId, interval) in self.currentCountdowns { + if !self._delayedSoundPlayers.contains(where: { $0.key == countdownId }) { + + let context = PersistenceController.shared.container.viewContext + if let countdown = context.object(stringId: countdownId) as? Countdown { + + do { + let soundFile = try SoundFile(fullName: countdown.soundName) + let soundPlayer = try DelaySoundPlayer(timerID: countdownId, soundFile: soundFile) + self._delayedSoundPlayers[countdown.stringId] = soundPlayer + try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount)) + } catch { + Logger.error(error) + } + } + } + } + + } + // MARK: - Cleanup func cleanup() { @@ -273,9 +296,9 @@ class Conductor: ObservableObject { } func cancelSoundPlayer(id: TimerID) { - if let soundPlayer = self.delayedSoundPlayers[id] { + if let soundPlayer = self._delayedSoundPlayers[id] { soundPlayer.stop() - self.delayedSoundPlayers.removeValue(forKey: id) + self._delayedSoundPlayers.removeValue(forKey: id) } } diff --git a/LeCountdown/LeCountdownApp.swift b/LeCountdown/LeCountdownApp.swift index d12b44f..64b0497 100644 --- a/LeCountdown/LeCountdownApp.swift +++ b/LeCountdown/LeCountdownApp.swift @@ -72,6 +72,7 @@ struct LeCountdownApp: App { case .active: Logger.log("onChange(of: scenePhase) active") Logger.log(Conductor.maestro.currentCountdowns.count) + Conductor.maestro.restore() Conductor.maestro.cleanup() default: break diff --git a/LeCountdown/Model/Model+SharedExtensions.swift b/LeCountdown/Model/Model+SharedExtensions.swift index adfb835..6fcc332 100644 --- a/LeCountdown/Model/Model+SharedExtensions.swift +++ b/LeCountdown/Model/Model+SharedExtensions.swift @@ -7,6 +7,8 @@ import Foundation +typealias TimerID = String + extension AbstractTimer { var displayName: String { diff --git a/LeCountdown/Sound/DelaySoundPlayer.swift b/LeCountdown/Sound/DelaySoundPlayer.swift index 79beedf..901df87 100644 --- a/LeCountdown/Sound/DelaySoundPlayer.swift +++ b/LeCountdown/Sound/DelaySoundPlayer.swift @@ -10,44 +10,53 @@ import AVFoundation @objc class DelaySoundPlayer: NSObject, AVAudioPlayerDelegate { - fileprivate var _player: AVAudioPlayer? + fileprivate var _player: AVAudioPlayer - private var soundFile: SoundFile + fileprivate var _timerID: TimerID - init(soundFile: SoundFile) { - self.soundFile = soundFile + init(timerID: TimerID, soundFile: SoundFile) throws { + self._timerID = timerID + + guard let url = soundFile.url else { + throw SoundPlayerError.missingResourceError(file: soundFile) + } + + self._player = try AVAudioPlayer(contentsOf: url) + } + + func restore(for playDate: Date, repeatCount: Int) throws { + let timeLeft = playDate.timeIntervalSinceNow + try self._play(in: timeLeft, repeatCount: repeatCount) } func start(in duration: TimeInterval, repeatCount: Int) throws { + try self._play(in: duration, repeatCount: repeatCount) + } + + fileprivate func _play(in duration: TimeInterval, repeatCount: Int) throws { - guard let url = self.soundFile.url else { - throw SoundPlayerError.missingResourceError(file: self.soundFile) - } - let audioSession: AVAudioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.playback, options: .duckOthers) try audioSession.setActive(true) - let player = try AVAudioPlayer(contentsOf: url) - player.prepareToPlay() - player.volume = 1.0 - player.delegate = self - player.numberOfLoops = repeatCount + self._player.prepareToPlay() + self._player.volume = 1.0 + self._player.delegate = self + self._player.numberOfLoops = repeatCount - self._player = player + self._player.play(atTime: self._player.deviceCurrentTime + duration) - player.play(atTime: player.deviceCurrentTime + duration) } func stop() { - self._player?.stop() + self._player.stop() } // MARK: - Delegate - + func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { - self.stop() Logger.log("audioPlayerDidFinishPlaying: successfully = \(flag)") + Conductor.maestro.cancelSoundPlayer(id: self._timerID) } }