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.
This commit is contained in:
Adam Traeger 2025-03-09 13:14:15 -05:00
parent 769ce0ade9
commit 33c738873f
No known key found for this signature in database
GPG key ID: 136E380CBA630639
6 changed files with 75 additions and 5 deletions

View file

@ -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
}
}
}
)

View file

@ -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)

View file

@ -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<String, Any> {
"hapticFeedback": settings.hapticFeedback,
"languageCode": settings.languageCode,
"downloadUsingCellular": settings.downloadUsingCellular,
"streamingUsingCellular": settings.streamingUsingCellular
"streamingUsingCellular": settings.streamingUsingCellular,
"disableSleepTimerFadeOut": settings.disableSleepTimerFadeOut
]
}

View file

@ -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)")

View file

@ -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
}
}