diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index d987a14..f132de0 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -61,9 +61,8 @@ class Conductor: ObservableObject { } } - func removeLiveTimer(id: String) { - - Logger.log("removeLiveTimer") + func removeLiveTimer(id: TimerID) { +// Logger.log("removeLiveTimer") self.liveTimers.removeAll(where: { $0.id == id }) self.cancelledCountdowns.removeAll(where: { $0 == id }) if let soundPlayer = self._delayedSoundPlayers[id] { @@ -77,7 +76,7 @@ class Conductor: ObservableObject { fileprivate func _buildLiveTimers() { - Logger.log("_buildLiveTimers") +// Logger.log("_buildLiveTimers") let liveCountdowns = self.currentCountdowns.map { return LiveTimer(id: $0, date: $1.end) @@ -127,28 +126,6 @@ class Conductor: ObservableObject { // MARK: - Countdown - // func startCountdown(_ date: Date, countdown: Countdown) { - //// DispatchQueue.main.async { - // - // Logger.log("Starts countdown: \(countdown.displayName)") - // - // // cleanup existing countdowns - // self.removeLiveTimer(id: countdown.stringId) - // - //// self._cleanupTimers.removeValue(forKey: countdown.stringId) - // - // let dateInterval = DateInterval(start: Date(), end: date) - // self.currentCountdowns[countdown.stringId] = dateInterval - // - //// self._launchLiveActivity(countdown: countdown, endDate: date) - // - //// self._createTimerIntent(countdown) - // - //// Logger.log("countdowns count = \(self.currentCountdowns.count)") - // - //// } - // } - func cancelCountdown(id: TimerID) { CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) self.cancelSoundPlayer(id: id) @@ -169,7 +146,7 @@ class Conductor: ObservableObject { } } - func prepareAlarm(countdown: Countdown, handler: @escaping (Result) -> Void) { + func startCountdown(countdown: Countdown, handler: @escaping (Result) -> Void) { DispatchQueue.main.async { @@ -179,8 +156,7 @@ class Conductor: ObservableObject { self.removeLiveTimer(id: countdown.stringId) let sound = countdown.someSound - let soundFile = try SoundFile(fullName: sound.fileName) - let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, soundFile: soundFile) + let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, sound: sound) self._delayedSoundPlayers[countdown.stringId] = soundPlayer try soundPlayer.start(in: countdown.duration, repeatCount: Int(countdown.repeatCount)) @@ -240,8 +216,7 @@ class Conductor: ObservableObject { do { let sound: Sound = countdown.someSound - let soundFile: SoundFile = try SoundFile(fullName: sound.fileName) - let soundPlayer = try DelaySoundPlayer(timerID: countdownId, soundFile: soundFile) + let soundPlayer = try DelaySoundPlayer(timerID: countdownId, sound: sound) self._delayedSoundPlayers[countdown.stringId] = soundPlayer try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount)) } catch { diff --git a/LeCountdown/CountdownScheduler.swift b/LeCountdown/CountdownScheduler.swift index 5906088..2631424 100644 --- a/LeCountdown/CountdownScheduler.swift +++ b/LeCountdown/CountdownScheduler.swift @@ -16,7 +16,7 @@ class CountdownScheduler { func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result) -> Void) { self.cancelCurrentNotifications(countdownId: countdown.stringId) - Conductor.maestro.prepareAlarm(countdown: countdown, handler: handler) + Conductor.maestro.startCountdown(countdown: countdown, handler: handler) self._scheduleCountdownNotification(countdown: countdown, handler: handler) } diff --git a/LeCountdown/LeCountdownApp.swift b/LeCountdown/LeCountdownApp.swift index 85e455e..3f60bf2 100644 --- a/LeCountdown/LeCountdownApp.swift +++ b/LeCountdown/LeCountdownApp.swift @@ -40,9 +40,8 @@ struct LeCountdownApp: App { var body: some Scene { WindowGroup { - Group { + ZStack { #if os(iOS) - if UIDevice.isPhoneIdiom { CompactHomeView() .environment(\.managedObjectContext, persistenceController.container.viewContext) @@ -55,9 +54,6 @@ struct LeCountdownApp: App { .environment(\.managedObjectContext, persistenceController.container.viewContext) #endif } -// .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in -// self._willEnterForegroundNotification() -// } .onAppear { self._onAppear() } @@ -82,7 +78,6 @@ struct LeCountdownApp: App { Logger.log("preferredLanguages = \(String(describing: Locale.preferredLanguages))") - self._patch() // let voices = AVSpeechSynthesisVoice.speechVoices() diff --git a/LeCountdown/Sound/DelaySoundPlayer.swift b/LeCountdown/Sound/DelaySoundPlayer.swift index 140d3cd..c5829ba 100644 --- a/LeCountdown/Sound/DelaySoundPlayer.swift +++ b/LeCountdown/Sound/DelaySoundPlayer.swift @@ -1,5 +1,5 @@ // -// BackgroundSoundPlayer.swift +// DelaySoundPlayer.swift // LeCountdown // // Created by Laurent Morvillier on 08/03/2023. @@ -14,9 +14,16 @@ import AVFoundation fileprivate var _timerID: TimerID - init(timerID: TimerID, soundFile: SoundFile) throws { + fileprivate var _soundDuration: TimeInterval? + + fileprivate var _timer: Timer? = nil + + init(timerID: TimerID, sound: Sound) throws { self._timerID = timerID + self._soundDuration = Preferences.soundDurations[sound.fileName] + let soundFile = try sound.soundFile() + guard let url = soundFile.url else { throw SoundPlayerError.missingResourceError(file: soundFile) } @@ -46,13 +53,33 @@ import AVFoundation Logger.log("self._player.deviceCurrentTime = \(self._player.deviceCurrentTime)") self._player.play(atTime: self._player.deviceCurrentTime + duration) - + + if repeatCount == 0 { + self._scheduleFadeOut(duration: duration) + } + } func stop() { self._player.stop() } + fileprivate func _scheduleFadeOut(duration: TimeInterval) { +// Logger.log("_scheduleFadeOut") + guard let soundDuration = self._soundDuration, soundDuration > 1.0 else { + return + } + + let interval = duration + soundDuration - 1.0 +// Logger.log("Fade in \(interval)") + let date = Date(timeIntervalSinceNow: interval) + self._timer = Timer(fire: date, interval: 0.0, repeats: false) { _ in +// Logger.log("FADEOUT!") + self._player.setVolume(0.0, fadeDuration: 1.0) + } + self._timer?.fire() + } + // MARK: - Delegate func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { diff --git a/LeCountdown/Sound/SoundPlayer.swift b/LeCountdown/Sound/SoundPlayer.swift index be98d88..be46955 100644 --- a/LeCountdown/Sound/SoundPlayer.swift +++ b/LeCountdown/Sound/SoundPlayer.swift @@ -59,11 +59,11 @@ enum SoundPlayerError : Error { player.prepareToPlay() player.volume = 1.0 player.delegate = self - + self._player = player - Logger.log("Plays \(url) on player: \(String(describing: self._player))") - Logger.log("SoundPlayer > .deviceCurrentTime = \(player.deviceCurrentTime)") +// Logger.log("Plays \(url) on player: \(String(describing: self._player))") +// Logger.log("SoundPlayer > .deviceCurrentTime = \(player.deviceCurrentTime)") player.play() } diff --git a/LeCountdown/Views/ContentView.swift b/LeCountdown/Views/ContentView.swift index 2e02318..d5242a8 100644 --- a/LeCountdown/Views/ContentView.swift +++ b/LeCountdown/Views/ContentView.swift @@ -81,7 +81,8 @@ struct ContentView: View { SiriTimerView(timer: self.boringContext.siriTimer, isVisible: self.$siriTipShown) HStack(alignment: .center) { - VolumeView().padding(12.0) + VolumeView() + .padding(12.0) }.frame(width: 300.0, height: 40.0) .background(Color(white: 0.9)) .cornerRadius(16.0) diff --git a/LeCountdown/Views/Reusable/VolumeView.swift b/LeCountdown/Views/Reusable/VolumeView.swift index b4c17b5..00e3b5c 100644 --- a/LeCountdown/Views/Reusable/VolumeView.swift +++ b/LeCountdown/Views/Reusable/VolumeView.swift @@ -10,28 +10,37 @@ import MediaPlayer import UIKit struct VolumeView: UIViewRepresentable { + + var changeVolume: Bool + init(changeVolume: Bool = false) { + self.changeVolume = changeVolume + } + func makeUIView(context: Context) -> MPVolumeView { let volumeView = MPVolumeView(frame: .zero) -// volumeView.showsLargeContentViewer = false + volumeView.isHidden = self.changeVolume -// if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider { -// slider.setValue(0.15, animated: false) -// } - self._setVolume(volumeView: volumeView) + if self.changeVolume { + self._setVolume(volumeView: volumeView) + } return volumeView } func updateUIView(_ view: MPVolumeView, context: Context) { - Logger.log("update volume view") - +// Logger.log("update volume view") + if self.changeVolume { + self._setVolume(volumeView: view) + } } fileprivate func _setVolume(volumeView: MPVolumeView) { if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) { +// Logger.log("update volume view") + slider.setValue(Preferences.defaultVolume, animated: false) } }