fade out sound

main
Laurent 3 years ago
parent 51b0430b47
commit 2ce1b66eff
  1. 37
      LeCountdown/Conductor.swift
  2. 2
      LeCountdown/CountdownScheduler.swift
  3. 7
      LeCountdown/LeCountdownApp.swift
  4. 31
      LeCountdown/Sound/DelaySoundPlayer.swift
  5. 4
      LeCountdown/Sound/SoundPlayer.swift
  6. 3
      LeCountdown/Views/ContentView.swift
  7. 21
      LeCountdown/Views/Reusable/VolumeView.swift

@ -61,9 +61,8 @@ class Conductor: ObservableObject {
} }
} }
func removeLiveTimer(id: String) { func removeLiveTimer(id: TimerID) {
// Logger.log("removeLiveTimer")
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] { if let soundPlayer = self._delayedSoundPlayers[id] {
@ -77,7 +76,7 @@ class Conductor: ObservableObject {
fileprivate func _buildLiveTimers() { fileprivate func _buildLiveTimers() {
Logger.log("_buildLiveTimers") // Logger.log("_buildLiveTimers")
let liveCountdowns = self.currentCountdowns.map { let liveCountdowns = self.currentCountdowns.map {
return LiveTimer(id: $0, date: $1.end) return LiveTimer(id: $0, date: $1.end)
@ -127,28 +126,6 @@ class Conductor: ObservableObject {
// MARK: - Countdown // 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) { func cancelCountdown(id: TimerID) {
CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) CountdownScheduler.master.cancelCurrentNotifications(countdownId: id)
self.cancelSoundPlayer(id: id) self.cancelSoundPlayer(id: id)
@ -169,7 +146,7 @@ class Conductor: ObservableObject {
} }
} }
func prepareAlarm(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> Void) { func startCountdown(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> Void) {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -179,8 +156,7 @@ class Conductor: ObservableObject {
self.removeLiveTimer(id: countdown.stringId) self.removeLiveTimer(id: countdown.stringId)
let sound = countdown.someSound let sound = countdown.someSound
let soundFile = try SoundFile(fullName: sound.fileName) let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, sound: sound)
let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, soundFile: soundFile)
self._delayedSoundPlayers[countdown.stringId] = soundPlayer 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))
@ -240,8 +216,7 @@ class Conductor: ObservableObject {
do { do {
let sound: Sound = countdown.someSound let sound: Sound = countdown.someSound
let soundFile: SoundFile = try SoundFile(fullName: sound.fileName) let soundPlayer = try DelaySoundPlayer(timerID: countdownId, sound: sound)
let soundPlayer = try DelaySoundPlayer(timerID: countdownId, soundFile: soundFile)
self._delayedSoundPlayers[countdown.stringId] = soundPlayer self._delayedSoundPlayers[countdown.stringId] = soundPlayer
try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount)) try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount))
} catch { } catch {

@ -16,7 +16,7 @@ class CountdownScheduler {
func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> Void) { func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> Void) {
self.cancelCurrentNotifications(countdownId: countdown.stringId) 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) self._scheduleCountdownNotification(countdown: countdown, handler: handler)
} }

@ -40,9 +40,8 @@ struct LeCountdownApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
Group { ZStack {
#if os(iOS) #if os(iOS)
if UIDevice.isPhoneIdiom { if UIDevice.isPhoneIdiom {
CompactHomeView() CompactHomeView()
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
@ -55,9 +54,6 @@ struct LeCountdownApp: App {
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
#endif #endif
} }
// .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
// self._willEnterForegroundNotification()
// }
.onAppear { .onAppear {
self._onAppear() self._onAppear()
} }
@ -82,7 +78,6 @@ struct LeCountdownApp: App {
Logger.log("preferredLanguages = \(String(describing: Locale.preferredLanguages))") Logger.log("preferredLanguages = \(String(describing: Locale.preferredLanguages))")
self._patch() self._patch()
// let voices = AVSpeechSynthesisVoice.speechVoices() // let voices = AVSpeechSynthesisVoice.speechVoices()

@ -1,5 +1,5 @@
// //
// BackgroundSoundPlayer.swift // DelaySoundPlayer.swift
// LeCountdown // LeCountdown
// //
// Created by Laurent Morvillier on 08/03/2023. // Created by Laurent Morvillier on 08/03/2023.
@ -14,9 +14,16 @@ import AVFoundation
fileprivate var _timerID: TimerID 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._timerID = timerID
self._soundDuration = Preferences.soundDurations[sound.fileName]
let soundFile = try sound.soundFile()
guard let url = soundFile.url else { guard let url = soundFile.url else {
throw SoundPlayerError.missingResourceError(file: soundFile) throw SoundPlayerError.missingResourceError(file: soundFile)
} }
@ -47,12 +54,32 @@ import AVFoundation
Logger.log("self._player.deviceCurrentTime = \(self._player.deviceCurrentTime)") Logger.log("self._player.deviceCurrentTime = \(self._player.deviceCurrentTime)")
self._player.play(atTime: self._player.deviceCurrentTime + duration) self._player.play(atTime: self._player.deviceCurrentTime + duration)
if repeatCount == 0 {
self._scheduleFadeOut(duration: duration)
}
} }
func stop() { func stop() {
self._player.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 // MARK: - Delegate
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {

@ -62,8 +62,8 @@ enum SoundPlayerError : Error {
self._player = player self._player = player
Logger.log("Plays \(url) on player: \(String(describing: self._player))") // Logger.log("Plays \(url) on player: \(String(describing: self._player))")
Logger.log("SoundPlayer > .deviceCurrentTime = \(player.deviceCurrentTime)") // Logger.log("SoundPlayer > .deviceCurrentTime = \(player.deviceCurrentTime)")
player.play() player.play()
} }

@ -81,7 +81,8 @@ struct ContentView<T : AbstractTimer>: View {
SiriTimerView(timer: self.boringContext.siriTimer, isVisible: self.$siriTipShown) SiriTimerView(timer: self.boringContext.siriTimer, isVisible: self.$siriTipShown)
HStack(alignment: .center) { HStack(alignment: .center) {
VolumeView().padding(12.0) VolumeView()
.padding(12.0)
}.frame(width: 300.0, height: 40.0) }.frame(width: 300.0, height: 40.0)
.background(Color(white: 0.9)) .background(Color(white: 0.9))
.cornerRadius(16.0) .cornerRadius(16.0)

@ -11,27 +11,36 @@ import UIKit
struct VolumeView: UIViewRepresentable { struct VolumeView: UIViewRepresentable {
var changeVolume: Bool
init(changeVolume: Bool = false) {
self.changeVolume = changeVolume
}
func makeUIView(context: Context) -> MPVolumeView { func makeUIView(context: Context) -> MPVolumeView {
let volumeView = MPVolumeView(frame: .zero) let volumeView = MPVolumeView(frame: .zero)
// volumeView.showsLargeContentViewer = false volumeView.isHidden = self.changeVolume
// if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider { if self.changeVolume {
// slider.setValue(0.15, animated: false)
// }
self._setVolume(volumeView: volumeView) self._setVolume(volumeView: volumeView)
}
return volumeView return volumeView
} }
func updateUIView(_ view: MPVolumeView, context: Context) { 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) { fileprivate func _setVolume(volumeView: MPVolumeView) {
if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider { if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
// Logger.log("update volume view")
slider.setValue(Preferences.defaultVolume, animated: false) slider.setValue(Preferences.defaultVolume, animated: false)
} }
} }

Loading…
Cancel
Save