Restore sound play after quitting

main
Laurent 3 years ago
parent 604e167faa
commit 2a09dc6593
  1. 2
      LeCountdown.xcodeproj/project.pbxproj
  2. 45
      LeCountdown/Conductor.swift
  3. 1
      LeCountdown/LeCountdownApp.swift
  4. 2
      LeCountdown/Model/Model+SharedExtensions.swift
  5. 41
      LeCountdown/Sound/DelaySoundPlayer.swift

@ -155,7 +155,6 @@
C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */; }; C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */; };
C4E5D66D29B753D7008E7465 /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = C4E5D66F29B753D7008E7465 /* AppShortcuts.strings */; }; C4E5D66D29B753D7008E7465 /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = C4E5D66F29B753D7008E7465 /* AppShortcuts.strings */; };
C4E5D67429B88734008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; }; 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 */; }; C4E5D67829B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; };
C4E5D67A29B8C5A1008E7465 /* VolumeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67929B8C5A1008E7465 /* VolumeView.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 */; }; 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 */, C4BA2B4A299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */,
C4BA2B4D299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, C4BA2B4D299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */,
C4F8B194298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */, C4F8B194298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */,
C4E5D67729B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */,
C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */, C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */,
C473C31929A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, C473C31929A926F50056B38A /* LaunchWidget.intentdefinition in Sources */,
C4F8B15B29892D40005C86A5 /* SoundPlayer.swift in Sources */, C4F8B15B29892D40005C86A5 /* SoundPlayer.swift in Sources */,

@ -20,15 +20,13 @@ fileprivate enum Const: String {
case confirmationSound = "PVP_Stab_Oneshot_Bleep_Em.wav" case confirmationSound = "PVP_Stab_Oneshot_Bleep_Em.wav"
} }
typealias TimerID = String
class Conductor: ObservableObject { class Conductor: ObservableObject {
static let maestro: Conductor = Conductor() static let maestro: Conductor = Conductor()
@Published var soundPlayer: SoundPlayer? = nil @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.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval]
@UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date] @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date]
@ -62,8 +60,13 @@ class Conductor: ObservableObject {
} }
func removeLiveTimer(id: String) { func removeLiveTimer(id: String) {
Logger.log("removeLiveTimer")
self.liveTimers.removeAll(where: { $0.id == id }) self.liveTimers.removeAll(where: { $0.id == id })
self.cancelledCountdowns.removeAll(where: { $0 == id }) self.cancelledCountdowns.removeAll(where: { $0 == id })
if let soundPlayer = self._delayedSoundPlayers[id] {
soundPlayer.stop()
}
} }
fileprivate func _buildLiveTimers() { fileprivate func _buildLiveTimers() {
@ -162,17 +165,15 @@ class Conductor: ObservableObject {
do { do {
let date = Date(timeIntervalSinceNow: countdown.duration) let date = Date(timeIntervalSinceNow: countdown.duration)
let soundFile = try SoundFile(fullName: countdown.soundName)
let soundPlayer = DelaySoundPlayer(soundFile: soundFile)
self.delayedSoundPlayers[countdown.stringId] = soundPlayer self.startCountdown(date, countdown: countdown)
let soundFile = try SoundFile(fullName: countdown.soundName)
let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, soundFile: soundFile)
self._delayedSoundPlayers[countdown.stringId] = soundPlayer
try soundPlayer.start(in: countdown.duration, try soundPlayer.start(in: countdown.duration,
repeatCount: Int(countdown.repeatCount)) repeatCount: Int(countdown.repeatCount))
self.startCountdown(date, countdown: countdown)
if Preferences.playConfirmationSound { if Preferences.playConfirmationSound {
self._playSound(Const.confirmationSound.rawValue) 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 // MARK: - Cleanup
func cleanup() { func cleanup() {
@ -273,9 +296,9 @@ class Conductor: ObservableObject {
} }
func cancelSoundPlayer(id: TimerID) { func cancelSoundPlayer(id: TimerID) {
if let soundPlayer = self.delayedSoundPlayers[id] { if let soundPlayer = self._delayedSoundPlayers[id] {
soundPlayer.stop() soundPlayer.stop()
self.delayedSoundPlayers.removeValue(forKey: id) self._delayedSoundPlayers.removeValue(forKey: id)
} }
} }

@ -72,6 +72,7 @@ struct LeCountdownApp: App {
case .active: case .active:
Logger.log("onChange(of: scenePhase) active") Logger.log("onChange(of: scenePhase) active")
Logger.log(Conductor.maestro.currentCountdowns.count) Logger.log(Conductor.maestro.currentCountdowns.count)
Conductor.maestro.restore()
Conductor.maestro.cleanup() Conductor.maestro.cleanup()
default: default:
break break

@ -7,6 +7,8 @@
import Foundation import Foundation
typealias TimerID = String
extension AbstractTimer { extension AbstractTimer {
var displayName: String { var displayName: String {

@ -10,44 +10,53 @@ import AVFoundation
@objc class DelaySoundPlayer: NSObject, AVAudioPlayerDelegate { @objc class DelaySoundPlayer: NSObject, AVAudioPlayerDelegate {
fileprivate var _player: AVAudioPlayer? fileprivate var _player: AVAudioPlayer
private var soundFile: SoundFile fileprivate var _timerID: TimerID
init(soundFile: SoundFile) { init(timerID: TimerID, soundFile: SoundFile) throws {
self.soundFile = soundFile self._timerID = timerID
guard let url = soundFile.url else {
throw SoundPlayerError.missingResourceError(file: soundFile)
} }
func start(in duration: TimeInterval, repeatCount: Int) throws { self._player = try AVAudioPlayer(contentsOf: url)
}
guard let url = self.soundFile.url else { func restore(for playDate: Date, repeatCount: Int) throws {
throw SoundPlayerError.missingResourceError(file: self.soundFile) 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 {
let audioSession: AVAudioSession = AVAudioSession.sharedInstance() let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, options: .duckOthers) try audioSession.setCategory(.playback, options: .duckOthers)
try audioSession.setActive(true) try audioSession.setActive(true)
let player = try AVAudioPlayer(contentsOf: url) self._player.prepareToPlay()
player.prepareToPlay() self._player.volume = 1.0
player.volume = 1.0 self._player.delegate = self
player.delegate = self self._player.numberOfLoops = repeatCount
player.numberOfLoops = repeatCount
self._player = player self._player.play(atTime: self._player.deviceCurrentTime + duration)
player.play(atTime: player.deviceCurrentTime + duration)
} }
func stop() { func stop() {
self._player?.stop() self._player.stop()
} }
// MARK: - Delegate // MARK: - Delegate
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
self.stop()
Logger.log("audioPlayerDidFinishPlaying: successfully = \(flag)") Logger.log("audioPlayerDidFinishPlaying: successfully = \(flag)")
Conductor.maestro.cancelSoundPlayer(id: self._timerID)
} }
} }

Loading…
Cancel
Save