From 33c738873f446fa43c5ea86fbced0c6a2bfff51b Mon Sep 17 00:00:00 2001 From: Adam Traeger Date: Sun, 9 Mar 2025 13:14:15 -0500 Subject: [PATCH] Add support for fading out on sleep timer end Playback will start to fadeout during last 60 seconds of the sleep timer. Once faded out, playback will be paused, volume reset, and playback seeked to start of fadeout. --- ios/App/App/AppDelegate.swift | 8 ++- ios/App/App/plugins/AbsDatabase.swift | 2 + ios/App/Shared/models/DeviceSettings.swift | 4 +- ios/App/Shared/player/AudioPlayer.swift | 52 ++++++++++++++++++- .../Shared/player/AudioPlayerSleepTimer.swift | 12 ++++- pages/settings.vue | 2 + 6 files changed, 75 insertions(+), 5 deletions(-) diff --git a/ios/App/App/AppDelegate.swift b/ios/App/App/AppDelegate.swift index 0a912b89..bd270f50 100644 --- a/ios/App/App/AppDelegate.swift +++ b/ios/App/App/AppDelegate.swift @@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Override point for customization after application launch. let configuration = Realm.Configuration( - schemaVersion: 18, + schemaVersion: 19, migrationBlock: { [weak self] migration, oldSchemaVersion in if (oldSchemaVersion < 1) { self?.logger.log("Realm schema version was \(oldSchemaVersion)") @@ -61,6 +61,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { newObject?["streamingUsingCellular"] = "ALWAYS" } } + if (oldSchemaVersion < 18) { + self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding disableSleepTimerFadeOut settings") + migration.enumerateObjects(ofType: PlayerSettings.className()) { oldObject, newObject in + newObject?["disableSleepTimerFadeOut"] = false + } + } } ) diff --git a/ios/App/App/plugins/AbsDatabase.swift b/ios/App/App/plugins/AbsDatabase.swift index 2a15197c..bbdf69a5 100644 --- a/ios/App/App/plugins/AbsDatabase.swift +++ b/ios/App/App/plugins/AbsDatabase.swift @@ -246,6 +246,7 @@ public class AbsDatabase: CAPPlugin { let languageCode = call.getString("languageCode") ?? "en-us" let downloadUsingCellular = call.getString("downloadUsingCellular") ?? "ALWAYS" let streamingUsingCellular = call.getString("streamingUsingCellular") ?? "ALWAYS" + let disableSleepTimerFadeOut = call.getBool("disableSleepTimerFadeOut") ?? false let settings = DeviceSettings() settings.disableAutoRewind = disableAutoRewind settings.enableAltView = enableAltView @@ -257,6 +258,7 @@ public class AbsDatabase: CAPPlugin { settings.languageCode = languageCode settings.downloadUsingCellular = downloadUsingCellular settings.streamingUsingCellular = streamingUsingCellular + settings.disableSleepTimerFadeOut = disableSleepTimerFadeOut Database.shared.setDeviceSettings(deviceSettings: settings) diff --git a/ios/App/Shared/models/DeviceSettings.swift b/ios/App/Shared/models/DeviceSettings.swift index 4cd32ecc..1ad8546d 100644 --- a/ios/App/Shared/models/DeviceSettings.swift +++ b/ios/App/Shared/models/DeviceSettings.swift @@ -19,6 +19,7 @@ class DeviceSettings: Object { @Persisted var languageCode: String = "en-us" @Persisted var downloadUsingCellular: String = "ALWAYS" @Persisted var streamingUsingCellular: String = "ALWAYS" + @Persisted var disableSleepTimerFadeOut: Bool = false } func getDefaultDeviceSettings() -> DeviceSettings { @@ -36,6 +37,7 @@ func deviceSettingsToJSON(settings: DeviceSettings) -> Dictionary { "hapticFeedback": settings.hapticFeedback, "languageCode": settings.languageCode, "downloadUsingCellular": settings.downloadUsingCellular, - "streamingUsingCellular": settings.streamingUsingCellular + "streamingUsingCellular": settings.streamingUsingCellular, + "disableSleepTimerFadeOut": settings.disableSleepTimerFadeOut ] } diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index 0ad0e9e6..80cb9010 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -365,7 +365,57 @@ class AudioPlayer: NSObject { self.status = .paused updateNowPlaying() } - + + public func startFadeOut() { + guard self.isInitialized() else { return } + guard let currentTime = self.getCurrentTime() else { return } + logger.log("fadeOut: Fading out playback") + + // Define fade parameters. + let fadeDuration: Float = 60.0 // total fade duration in seconds + let interval: Float = 1.0 // timer interval in seconds + + // Get the current volume. + let initialVolume = self.audioPlayer.volume + let targetVolume: Float = 0.0 + + // If the current volume is already at or below zero, just pause. + if initialVolume <= targetVolume { + self.pause() + return + } + + // Calculate the volume change per timer tick. + // (targetVolume - initialVolume) is negative since target < initial. + let step = (targetVolume - initialVolume) * interval / fadeDuration + + // Schedule a timer on the main queue to adjust the volume. + DispatchQueue.runOnMainQueue { [weak self] in + var timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(interval), repeats: true) { t in + guard let self = self else { + t.invalidate() + return + } + + // Calculate the new volume. + let newVolume = self.audioPlayer.volume + step + + // Check if the next step would go below zero. + if newVolume > targetVolume { + self.audioPlayer.volume = newVolume + } else { + // Ensure volume is exactly zero and end fade. + self.audioPlayer.volume = targetVolume + t.invalidate() + self.logger.log("Fadeout: Fade complete, pausing playback") + self.pause() + self.audioPlayer.volume = initialVolume + self.seek(currentTime, from: "fadeOut") + } + } + } + } + public func seek(_ to: Double, from: String) { logger.log("SEEK: Seek to \(to) from \(from)") diff --git a/ios/App/Shared/player/AudioPlayerSleepTimer.swift b/ios/App/Shared/player/AudioPlayerSleepTimer.swift index 28cfdf06..2200112f 100644 --- a/ios/App/Shared/player/AudioPlayerSleepTimer.swift +++ b/ios/App/Shared/player/AudioPlayerSleepTimer.swift @@ -125,7 +125,11 @@ extension AudioPlayer { if var sleepTimeRemaining = self.sleepTimeRemaining { sleepTimeRemaining -= 1 self.sleepTimeRemaining = sleepTimeRemaining - + + if sleepTimeRemaining == 60 && self.isSleepTimerFadeOutEnabled() { + self.startFadeOut() + } + // Handle the sleep if the timer has expired if sleepTimeRemaining <= 0 { self.handleSleepEnd() @@ -154,5 +158,9 @@ extension AudioPlayer { private func isChapterSleepTimerSet() -> Bool { return self.sleepTimeChapterStopAt != nil } - + + private func isSleepTimerFadeOutEnabled() -> Bool { + let deviceSettings = Database.shared.getDeviceSettings() + return !deviceSettings.disableSleepTimerFadeOut + } } diff --git a/pages/settings.vue b/pages/settings.vue index 258d7375..67cfbb0a 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -84,6 +84,7 @@ +
@@ -91,6 +92,7 @@

{{ $strings.LabelDisableAudioFadeOut }}

info
+