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
+