mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-29 14:47:21 +02:00
Merge pull request #1453 from nichwall/android_sleep_timer_cleanup
Android sleep timer cleanup
This commit is contained in:
@@ -1,133 +1,212 @@
|
|||||||
package com.audiobookshelf.app.managers
|
package com.audiobookshelf.app.managers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.metrics.PlaybackSession
|
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||||
import com.audiobookshelf.app.player.SLEEP_TIMER_WAKE_UP_EXPIRATION
|
import com.audiobookshelf.app.player.SLEEP_TIMER_WAKE_UP_EXPIRATION
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class SleepTimerManager constructor(private val playerNotificationService: PlayerNotificationService) {
|
class SleepTimerManager
|
||||||
|
constructor(private val playerNotificationService: PlayerNotificationService) {
|
||||||
private val tag = "SleepTimerManager"
|
private val tag = "SleepTimerManager"
|
||||||
|
|
||||||
private var sleepTimerTask:TimerTask? = null
|
private var sleepTimerTask: TimerTask? = null
|
||||||
private var sleepTimerRunning:Boolean = false
|
private var sleepTimerRunning: Boolean = false
|
||||||
private var sleepTimerEndTime:Long = 0L
|
private var sleepTimerEndTime: Long = 0L
|
||||||
private var sleepTimerLength:Long = 0L
|
private var sleepTimerLength: Long = 0L
|
||||||
private var sleepTimerElapsed:Long = 0L
|
private var sleepTimerElapsed: Long = 0L
|
||||||
private var sleepTimerFinishedAt:Long = 0L
|
private var sleepTimerFinishedAt: Long = 0L
|
||||||
private var isAutoSleepTimer:Boolean = false // When timer was auto-set
|
private var isAutoSleepTimer: Boolean = false // When timer was auto-set
|
||||||
private var isFirstAutoSleepTimer: Boolean = true
|
private var autoTimerDisabled: Boolean = false // Disable until out of auto timer period
|
||||||
private var sleepTimerSessionId:String = ""
|
private var sleepTimerSessionId: String = ""
|
||||||
|
|
||||||
private fun getCurrentTime():Long {
|
/**
|
||||||
|
* Gets the current time from the player notification service.
|
||||||
|
* @return Long - the current time in milliseconds.
|
||||||
|
*/
|
||||||
|
private fun getCurrentTime(): Long {
|
||||||
return playerNotificationService.getCurrentTime()
|
return playerNotificationService.getCurrentTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDuration():Long {
|
/**
|
||||||
|
* Gets the duration of the current playback.
|
||||||
|
* @return Long - the duration in milliseconds.
|
||||||
|
*/
|
||||||
|
private fun getDuration(): Long {
|
||||||
return playerNotificationService.getDuration()
|
return playerNotificationService.getDuration()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIsPlaying():Boolean {
|
/**
|
||||||
|
* Checks if the player is currently playing.
|
||||||
|
* @return Boolean - true if the player is playing, false otherwise.
|
||||||
|
*/
|
||||||
|
private fun getIsPlaying(): Boolean {
|
||||||
return playerNotificationService.currentPlayer.isPlaying
|
return playerNotificationService.currentPlayer.isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setVolume(volume:Float) {
|
/**
|
||||||
|
* Gets the playback speed of the player.
|
||||||
|
* @return Float - the playback speed.
|
||||||
|
*/
|
||||||
|
private fun getPlaybackSpeed(): Float {
|
||||||
|
return playerNotificationService.currentPlayer.playbackParameters.speed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume of the player.
|
||||||
|
* @param volume Float - the volume level to set.
|
||||||
|
*/
|
||||||
|
private fun setVolume(volume: Float) {
|
||||||
playerNotificationService.currentPlayer.volume = volume
|
playerNotificationService.currentPlayer.volume = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pauses the player. */
|
||||||
private fun pause() {
|
private fun pause() {
|
||||||
playerNotificationService.currentPlayer.pause()
|
playerNotificationService.currentPlayer.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Plays the player. */
|
||||||
private fun play() {
|
private fun play() {
|
||||||
playerNotificationService.currentPlayer.play()
|
playerNotificationService.currentPlayer.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSleepTimerTimeRemainingSeconds():Int {
|
/**
|
||||||
|
* Gets the remaining time of the sleep timer in seconds.
|
||||||
|
* @param speed Float - the playback speed of the player, default value is 1.
|
||||||
|
* @return Int - the remaining time in seconds.
|
||||||
|
*/
|
||||||
|
private fun getSleepTimerTimeRemainingSeconds(speed: Float = 1f): Int {
|
||||||
if (sleepTimerEndTime == 0L && sleepTimerLength > 0) { // For regular timer
|
if (sleepTimerEndTime == 0L && sleepTimerLength > 0) { // For regular timer
|
||||||
return ((sleepTimerLength - sleepTimerElapsed) / 1000).toDouble().roundToInt()
|
return ((sleepTimerLength - sleepTimerElapsed) / 1000).toDouble().roundToInt()
|
||||||
}
|
}
|
||||||
// For chapter end timer
|
// For chapter end timer
|
||||||
if (sleepTimerEndTime <= 0) return 0
|
if (sleepTimerEndTime <= 0) return 0
|
||||||
return (((sleepTimerEndTime - getCurrentTime()) / 1000).toDouble()).roundToInt()
|
return (((sleepTimerEndTime - getCurrentTime()) / 1000).toDouble() / speed).roundToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSleepTimer(time: Long, isChapterTime: Boolean) : Boolean {
|
/**
|
||||||
Log.d(tag, "Setting Sleep Timer for $time is chapter time $isChapterTime")
|
* Sets the sleep timer.
|
||||||
|
* @param time Long - the time to set the sleep timer for. When 0L, use end of chapter/track time.
|
||||||
|
* @return Boolean - true if the sleep timer was set successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
private fun setSleepTimer(time: Long): Boolean {
|
||||||
|
Log.d(tag, "Setting Sleep Timer for $time")
|
||||||
sleepTimerTask?.cancel()
|
sleepTimerTask?.cancel()
|
||||||
sleepTimerRunning = true
|
sleepTimerRunning = true
|
||||||
sleepTimerFinishedAt = 0L
|
sleepTimerFinishedAt = 0L
|
||||||
sleepTimerElapsed = 0L
|
sleepTimerElapsed = 0L
|
||||||
setVolume(1f)
|
setVolume(1f)
|
||||||
|
|
||||||
// Register shake sensor
|
if (time == 0L) {
|
||||||
playerNotificationService.registerSensor()
|
// Get the current chapter time and set the sleep timer to the end of the chapter
|
||||||
|
val chapterEndTime = this.getChapterEndTime()
|
||||||
|
|
||||||
val currentTime = getCurrentTime()
|
if (chapterEndTime == null) {
|
||||||
if (isChapterTime) {
|
Log.e(tag, "Setting sleep timer to end of chapter/track but there is no current session")
|
||||||
if (currentTime > time) {
|
|
||||||
Log.d(tag, "Invalid sleep timer - current time is already passed chapter time $time")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
sleepTimerEndTime = time
|
|
||||||
sleepTimerLength = 0
|
val currentTime = getCurrentTime()
|
||||||
|
if (currentTime > chapterEndTime) {
|
||||||
|
Log.d(tag, "Invalid sleep timer - time is already past chapter time $chapterEndTime")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepTimerEndTime = chapterEndTime
|
||||||
|
|
||||||
if (sleepTimerEndTime > getDuration()) {
|
if (sleepTimerEndTime > getDuration()) {
|
||||||
sleepTimerEndTime = getDuration()
|
sleepTimerEndTime = getDuration()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sleepTimerLength = time
|
|
||||||
sleepTimerEndTime = 0L
|
sleepTimerEndTime = 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds(), isAutoSleepTimer)
|
// Set sleep timer length. Will be 0L if using chapter end time
|
||||||
|
sleepTimerLength = time
|
||||||
|
|
||||||
sleepTimerTask = Timer("SleepTimer", false).schedule(0L, 1000L) {
|
// Register shake sensor
|
||||||
Handler(Looper.getMainLooper()).post {
|
playerNotificationService.registerSensor()
|
||||||
if (getIsPlaying()) {
|
|
||||||
sleepTimerElapsed += 1000L
|
|
||||||
|
|
||||||
val sleepTimeSecondsRemaining = getSleepTimerTimeRemainingSeconds()
|
playerNotificationService.clientEventEmitter?.onSleepTimerSet(
|
||||||
Log.d(tag, "Timer Elapsed $sleepTimerElapsed | Sleep TIMER time remaining $sleepTimeSecondsRemaining s")
|
getSleepTimerTimeRemainingSeconds(getPlaybackSpeed()),
|
||||||
|
isAutoSleepTimer
|
||||||
|
)
|
||||||
|
|
||||||
if (sleepTimeSecondsRemaining > 0) {
|
sleepTimerTask =
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerSet(sleepTimeSecondsRemaining, isAutoSleepTimer)
|
Timer("SleepTimer", false).schedule(0L, 1000L) {
|
||||||
}
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
if (getIsPlaying()) {
|
||||||
|
sleepTimerElapsed += 1000L
|
||||||
|
|
||||||
if (sleepTimeSecondsRemaining <= 0) {
|
val sleepTimeSecondsRemaining =
|
||||||
Log.d(tag, "Sleep Timer Pausing Player on Chapter")
|
getSleepTimerTimeRemainingSeconds(getPlaybackSpeed())
|
||||||
pause()
|
Log.d(
|
||||||
|
tag,
|
||||||
|
"Timer Elapsed $sleepTimerElapsed | Sleep TIMER time remaining $sleepTimeSecondsRemaining s"
|
||||||
|
)
|
||||||
|
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerEnded(getCurrentTime())
|
if (sleepTimeSecondsRemaining > 0) {
|
||||||
clearSleepTimer()
|
playerNotificationService.clientEventEmitter?.onSleepTimerSet(
|
||||||
sleepTimerFinishedAt = System.currentTimeMillis()
|
sleepTimeSecondsRemaining,
|
||||||
} else if (sleepTimeSecondsRemaining <= 60 && DeviceManager.deviceData.deviceSettings?.disableSleepTimerFadeOut != true) {
|
isAutoSleepTimer
|
||||||
// Start fading out audio down to 10% volume
|
)
|
||||||
val percentToReduce = 1 - (sleepTimeSecondsRemaining / 60F)
|
}
|
||||||
val volume = 1f - (percentToReduce * 0.9f)
|
|
||||||
Log.d(tag, "SLEEP VOLUME FADE $volume | ${sleepTimeSecondsRemaining}s remaining")
|
if (sleepTimeSecondsRemaining <= 0) {
|
||||||
setVolume(volume)
|
Log.d(tag, "Sleep Timer Pausing Player on Chapter")
|
||||||
} else {
|
pause()
|
||||||
setVolume(1f)
|
|
||||||
}
|
playerNotificationService.clientEventEmitter?.onSleepTimerEnded(
|
||||||
}
|
getCurrentTime()
|
||||||
}
|
)
|
||||||
}
|
clearSleepTimer()
|
||||||
|
sleepTimerFinishedAt = System.currentTimeMillis()
|
||||||
|
} else if (sleepTimeSecondsRemaining <= 60 &&
|
||||||
|
DeviceManager.deviceData
|
||||||
|
.deviceSettings
|
||||||
|
?.disableSleepTimerFadeOut != true
|
||||||
|
) {
|
||||||
|
// Start fading out audio down to 10% volume
|
||||||
|
val percentToReduce = 1 - (sleepTimeSecondsRemaining / 60F)
|
||||||
|
val volume = 1f - (percentToReduce * 0.9f)
|
||||||
|
Log.d(
|
||||||
|
tag,
|
||||||
|
"SLEEP VOLUME FADE $volume | ${sleepTimeSecondsRemaining}s remaining"
|
||||||
|
)
|
||||||
|
setVolume(volume)
|
||||||
|
} else {
|
||||||
|
setVolume(1f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setManualSleepTimer(playbackSessionId:String, time: Long, isChapterTime:Boolean):Boolean {
|
/**
|
||||||
|
* Sets a manual sleep timer.
|
||||||
|
* @param playbackSessionId String - the playback session ID.
|
||||||
|
* @param time Long - the time to set the sleep timer for.
|
||||||
|
* @param isChapterTime Boolean - true if the time is for the end of a chapter, false otherwise.
|
||||||
|
* @return Boolean - true if the sleep timer was set successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
fun setManualSleepTimer(playbackSessionId: String, time: Long, isChapterTime: Boolean): Boolean {
|
||||||
sleepTimerSessionId = playbackSessionId
|
sleepTimerSessionId = playbackSessionId
|
||||||
isAutoSleepTimer = false
|
isAutoSleepTimer = false
|
||||||
return setSleepTimer(time, isChapterTime)
|
if (isChapterTime) {
|
||||||
|
Log.d(tag, "Setting manual sleep timer for end of chapter")
|
||||||
|
return setSleepTimer(0L)
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "Setting manual sleep timer for $time")
|
||||||
|
return setSleepTimer(time)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Clears the sleep timer. */
|
||||||
private fun clearSleepTimer() {
|
private fun clearSleepTimer() {
|
||||||
sleepTimerTask?.cancel()
|
sleepTimerTask?.cancel()
|
||||||
sleepTimerTask = null
|
sleepTimerTask = null
|
||||||
@@ -138,32 +217,36 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
setVolume(1f)
|
setVolume(1f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSleepTimerTime():Long {
|
/**
|
||||||
|
* Gets the sleep timer end time.
|
||||||
|
* @return Long - the sleep timer end time in milliseconds.
|
||||||
|
*/
|
||||||
|
fun getSleepTimerTime(): Long {
|
||||||
return sleepTimerEndTime
|
return sleepTimerEndTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Cancels the sleep timer. */
|
||||||
fun cancelSleepTimer() {
|
fun cancelSleepTimer() {
|
||||||
Log.d(tag, "Canceling Sleep Timer")
|
Log.d(tag, "Canceling Sleep Timer")
|
||||||
|
|
||||||
if (isAutoSleepTimer) {
|
if (isAutoSleepTimer) {
|
||||||
Log.i(tag, "Disabling auto sleep timer")
|
Log.i(tag, "Disabling auto sleep timer for this time period")
|
||||||
DeviceManager.deviceData.deviceSettings?.autoSleepTimer = false
|
autoTimerDisabled = true
|
||||||
DeviceManager.dbManager.saveDeviceData(DeviceManager.deviceData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSleepTimer()
|
clearSleepTimer()
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerSet(0, false)
|
playerNotificationService.clientEventEmitter?.onSleepTimerSet(0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vibrate when resetting sleep timer
|
/** Provides vibration feedback when resetting the sleep timer. */
|
||||||
private fun vibrateFeedback() {
|
private fun vibrateFeedback() {
|
||||||
if (DeviceManager.deviceData.deviceSettings?.disableSleepTimerResetFeedback == true) return
|
if (DeviceManager.deviceData.deviceSettings?.disableSleepTimerResetFeedback == true) return
|
||||||
|
|
||||||
val context = playerNotificationService.getContext()
|
val context = playerNotificationService.getContext()
|
||||||
val vibrator:Vibrator
|
val vibrator: Vibrator
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
val vibratorManager =
|
val vibratorManager =
|
||||||
context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||||
vibrator = vibratorManager.defaultVibrator
|
vibrator = vibratorManager.defaultVibrator
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@@ -172,18 +255,20 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
|
|
||||||
vibrator.let {
|
vibrator.let {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val vibrationEffect = VibrationEffect.createWaveform(longArrayOf(0, 150, 150, 150),-1)
|
val vibrationEffect = VibrationEffect.createWaveform(longArrayOf(0, 150, 150, 150), -1)
|
||||||
it.vibrate(vibrationEffect)
|
it.vibrate(vibrationEffect)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION") it.vibrate(10)
|
||||||
it.vibrate(10)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the chapter end time for use in End of Chapter timers
|
/**
|
||||||
// if less than 2s remain in chapter then use the next chapter
|
* Gets the chapter end time for use in End of Chapter timers. If less than 2 seconds remain in
|
||||||
private fun getChapterEndTime():Long? {
|
* the chapter, then use the next chapter.
|
||||||
|
* @return Long? - the chapter end time in milliseconds, or null if there is no current session.
|
||||||
|
*/
|
||||||
|
private fun getChapterEndTime(): Long? {
|
||||||
val currentChapterEndTimeMs = playerNotificationService.getEndTimeOfChapterOrTrack()
|
val currentChapterEndTimeMs = playerNotificationService.getEndTimeOfChapterOrTrack()
|
||||||
if (currentChapterEndTimeMs == null) {
|
if (currentChapterEndTimeMs == null) {
|
||||||
Log.e(tag, "Getting chapter sleep timer end of chapter/track but there is no current session")
|
Log.e(tag, "Getting chapter sleep timer end of chapter/track but there is no current session")
|
||||||
@@ -195,7 +280,10 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
Log.i(tag, "Getting chapter sleep timer time and current chapter has less than 2s remaining")
|
Log.i(tag, "Getting chapter sleep timer time and current chapter has less than 2s remaining")
|
||||||
val nextChapterEndTimeMs = playerNotificationService.getEndTimeOfNextChapterOrTrack()
|
val nextChapterEndTimeMs = playerNotificationService.getEndTimeOfNextChapterOrTrack()
|
||||||
if (nextChapterEndTimeMs == null || currentChapterEndTimeMs == nextChapterEndTimeMs) {
|
if (nextChapterEndTimeMs == null || currentChapterEndTimeMs == nextChapterEndTimeMs) {
|
||||||
Log.e(tag, "Invalid next chapter time. No current session or equal to current chapter. $nextChapterEndTimeMs")
|
Log.e(
|
||||||
|
tag,
|
||||||
|
"Invalid next chapter time. No current session or equal to current chapter. $nextChapterEndTimeMs"
|
||||||
|
)
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
nextChapterEndTimeMs
|
nextChapterEndTimeMs
|
||||||
@@ -205,17 +293,35 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetChapterTimer() {
|
/**
|
||||||
this.getChapterEndTime()?.let { chapterEndTime ->
|
* Rewind auto sleep timer if setting enabled. To ensure the first rewind of the time period does
|
||||||
Log.d(tag, "Resetting stopped sleep timer to end of chapter $chapterEndTime")
|
* not take place, make sure to set `isAutoSleepTimer` after calling this function.
|
||||||
vibrateFeedback()
|
*/
|
||||||
setSleepTimer(chapterEndTime, true)
|
private fun tryRewindAutoSleepTimer() {
|
||||||
play()
|
DeviceManager.deviceData.deviceSettings?.let { deviceSettings ->
|
||||||
|
if (isAutoSleepTimer && deviceSettings.autoSleepTimerAutoRewind) {
|
||||||
|
Log.i(
|
||||||
|
tag,
|
||||||
|
"Auto sleep timer auto rewind seeking back ${deviceSettings.autoSleepTimerAutoRewindTime}ms"
|
||||||
|
)
|
||||||
|
playerNotificationService.seekBackward(deviceSettings.autoSleepTimerAutoRewindTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks if the sleep timer should be reset. */
|
||||||
private fun checkShouldResetSleepTimer() {
|
private fun checkShouldResetSleepTimer() {
|
||||||
if (!sleepTimerRunning) {
|
if (sleepTimerRunning) {
|
||||||
|
// Reset the sleep timer if it has been running for at least 3 seconds or it is an end of
|
||||||
|
// chapter/track timer
|
||||||
|
if (sleepTimerLength == 0L || sleepTimerElapsed > 3000L) {
|
||||||
|
Log.d(tag, "Resetting running sleep timer")
|
||||||
|
vibrateFeedback()
|
||||||
|
setSleepTimer(sleepTimerLength)
|
||||||
|
play()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
if (sleepTimerFinishedAt <= 0L) return
|
if (sleepTimerFinishedAt <= 0L) return
|
||||||
|
|
||||||
val finishedAtDistance = System.currentTimeMillis() - sleepTimerFinishedAt
|
val finishedAtDistance = System.currentTimeMillis() - sleepTimerFinishedAt
|
||||||
@@ -226,49 +332,18 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically Rewind in the book if settings is enabled
|
// Automatically rewind in the book if settings are enabled
|
||||||
if (isAutoSleepTimer) {
|
tryRewindAutoSleepTimer()
|
||||||
DeviceManager.deviceData.deviceSettings?.let { deviceSettings ->
|
|
||||||
if (deviceSettings.autoSleepTimerAutoRewind && !isFirstAutoSleepTimer) {
|
|
||||||
Log.i(tag, "Auto sleep timer auto rewind seeking back ${deviceSettings.autoSleepTimerAutoRewindTime}ms")
|
|
||||||
playerNotificationService.seekBackward(deviceSettings.autoSleepTimerAutoRewindTime)
|
|
||||||
}
|
|
||||||
isFirstAutoSleepTimer = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sleep timer
|
// Set sleep timer
|
||||||
// When sleepTimerLength is 0 then use end of chapter/track time
|
Log.d(tag, "Resetting stopped sleep timer")
|
||||||
if (sleepTimerLength == 0L) {
|
|
||||||
Log.d(tag, "Resetting stopped chapter sleep timer")
|
|
||||||
resetChapterTimer()
|
|
||||||
} else {
|
|
||||||
Log.d(tag, "Resetting stopped sleep timer to length $sleepTimerLength")
|
|
||||||
vibrateFeedback()
|
|
||||||
setSleepTimer(sleepTimerLength, false)
|
|
||||||
play()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does not apply to chapter sleep timers and timer must be running for at least 3 seconds
|
|
||||||
if (sleepTimerLength > 0L && sleepTimerElapsed > 3000L) {
|
|
||||||
Log.d(tag, "Resetting running sleep timer to length $sleepTimerLength")
|
|
||||||
vibrateFeedback()
|
vibrateFeedback()
|
||||||
setSleepTimer(sleepTimerLength, false)
|
setSleepTimer(sleepTimerLength)
|
||||||
} else if (sleepTimerLength == 0L) {
|
play()
|
||||||
// When navigating to previous chapters make sure this is still the end of the current chapter
|
|
||||||
this.getChapterEndTime()?.let { chapterEndTime ->
|
|
||||||
if (chapterEndTime != sleepTimerEndTime) {
|
|
||||||
Log.d(tag, "Resetting chapter sleep timer to end of chapter $chapterEndTime from $sleepTimerEndTime")
|
|
||||||
vibrateFeedback()
|
|
||||||
setSleepTimer(chapterEndTime, true)
|
|
||||||
play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handles the shake event to reset the sleep timer. */
|
||||||
fun handleShake() {
|
fun handleShake() {
|
||||||
if (sleepTimerRunning || sleepTimerFinishedAt > 0L) {
|
if (sleepTimerRunning || sleepTimerFinishedAt > 0L) {
|
||||||
if (DeviceManager.deviceData.deviceSettings?.disableShakeToResetSleepTimer == true) {
|
if (DeviceManager.deviceData.deviceSettings?.disableShakeToResetSleepTimer == true) {
|
||||||
@@ -279,48 +354,63 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the sleep timer time.
|
||||||
|
* @param time Long - the time to increase the sleep timer by.
|
||||||
|
*/
|
||||||
fun increaseSleepTime(time: Long) {
|
fun increaseSleepTime(time: Long) {
|
||||||
Log.d(tag, "Increase Sleep time $time")
|
Log.d(tag, "Increase Sleep time $time")
|
||||||
if (!sleepTimerRunning) return
|
if (!sleepTimerRunning) return
|
||||||
|
|
||||||
|
// Increase the sleep timer time (if using fixed length) or end time (if using chapter end time)
|
||||||
|
// and ensure it doesn't go over the duration of the current playback item
|
||||||
if (sleepTimerEndTime == 0L) {
|
if (sleepTimerEndTime == 0L) {
|
||||||
|
// Fixed length
|
||||||
sleepTimerLength += time
|
sleepTimerLength += time
|
||||||
if (sleepTimerLength + getCurrentTime() > getDuration()) sleepTimerLength = getDuration() - getCurrentTime()
|
sleepTimerLength = minOf(sleepTimerLength, getDuration() - getCurrentTime())
|
||||||
} else {
|
} else {
|
||||||
val newSleepEndTime = sleepTimerEndTime + time
|
// Chapter end time
|
||||||
sleepTimerEndTime = if (newSleepEndTime >= getDuration()) {
|
sleepTimerEndTime =
|
||||||
getDuration()
|
minOf(sleepTimerEndTime + (time * getPlaybackSpeed()).roundToInt(), getDuration())
|
||||||
} else {
|
|
||||||
newSleepEndTime
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVolume(1F)
|
setVolume(1F)
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds(), isAutoSleepTimer)
|
playerNotificationService.clientEventEmitter?.onSleepTimerSet(
|
||||||
|
getSleepTimerTimeRemainingSeconds(getPlaybackSpeed()),
|
||||||
|
isAutoSleepTimer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decreases the sleep timer time.
|
||||||
|
* @param time Long - the time to decrease the sleep timer by.
|
||||||
|
*/
|
||||||
fun decreaseSleepTime(time: Long) {
|
fun decreaseSleepTime(time: Long) {
|
||||||
Log.d(tag, "Decrease Sleep time $time")
|
Log.d(tag, "Decrease Sleep time $time")
|
||||||
if (!sleepTimerRunning) return
|
if (!sleepTimerRunning) return
|
||||||
|
|
||||||
|
// Decrease the sleep timer time (if using fixed length) or end time (if using chapter end time)
|
||||||
|
// and ensure it doesn't go below 1 second
|
||||||
if (sleepTimerEndTime == 0L) {
|
if (sleepTimerEndTime == 0L) {
|
||||||
sleepTimerLength -= time
|
// Fixed length
|
||||||
if (sleepTimerLength <= 0) sleepTimerLength = 1000L
|
sleepTimerLength = maxOf(sleepTimerLength - time, 1000L)
|
||||||
} else {
|
} else {
|
||||||
val newSleepEndTime = sleepTimerEndTime - time
|
// Chapter end time
|
||||||
sleepTimerEndTime = if (newSleepEndTime <= 1000) {
|
sleepTimerEndTime =
|
||||||
// End sleep timer in 1 second
|
maxOf(
|
||||||
getCurrentTime() + 1000
|
sleepTimerEndTime - (time * getPlaybackSpeed()).roundToInt(),
|
||||||
} else {
|
getCurrentTime() + 1000
|
||||||
newSleepEndTime
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVolume(1F)
|
setVolume(1F)
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds(), isAutoSleepTimer)
|
playerNotificationService.clientEventEmitter?.onSleepTimerSet(
|
||||||
|
getSleepTimerTimeRemainingSeconds(getPlaybackSpeed()),
|
||||||
|
isAutoSleepTimer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks whether the auto sleep timer should be set, and set up auto sleep timer if so. */
|
||||||
fun checkAutoSleepTimer() {
|
fun checkAutoSleepTimer() {
|
||||||
if (sleepTimerRunning) { // Sleep timer already running
|
if (sleepTimerRunning) { // Sleep timer already running
|
||||||
return
|
return
|
||||||
@@ -337,10 +427,13 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
|
|
||||||
val currentCalendar = Calendar.getInstance()
|
val currentCalendar = Calendar.getInstance()
|
||||||
|
|
||||||
// In cases where end time is before start time then we shift the time window forward or backward based on the current time.
|
// In cases where end time is before start time then we shift the time window forward or
|
||||||
|
// backward based on the current time.
|
||||||
// e.g. start time 22:00 and end time 06:00.
|
// e.g. start time 22:00 and end time 06:00.
|
||||||
// If current time is less than start time (e.g. 00:30) then start time will be the previous day.
|
// If current time is less than start time (e.g. 00:30) then start time will be the
|
||||||
// If current time is greater than start time (e.g. 23:00) then end time will be the next day.
|
// previous day.
|
||||||
|
// If current time is greater than start time (e.g. 23:00) then end time will be the
|
||||||
|
// next day.
|
||||||
if (endCalendar.before(startCalendar)) {
|
if (endCalendar.before(startCalendar)) {
|
||||||
if (currentCalendar.before(startCalendar)) { // Shift start back a day
|
if (currentCalendar.before(startCalendar)) { // Shift start back a day
|
||||||
startCalendar.add(Calendar.DAY_OF_MONTH, -1)
|
startCalendar.add(Calendar.DAY_OF_MONTH, -1)
|
||||||
@@ -349,47 +442,52 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentHour = SimpleDateFormat("HH:mm", Locale.getDefault()).format(currentCalendar.time)
|
val isDuringAutoTime =
|
||||||
if (currentCalendar.after(startCalendar) && currentCalendar.before(endCalendar)) {
|
currentCalendar.after(startCalendar) && currentCalendar.before(endCalendar)
|
||||||
Log.i(tag, "Current hour $currentHour is between ${deviceSettings.autoSleepTimerStartTime} and ${deviceSettings.autoSleepTimerEndTime} - starting sleep timer")
|
|
||||||
|
|
||||||
// Automatically Rewind in the book if settings is enabled
|
// Determine whether to set the auto sleep timer or not
|
||||||
if (deviceSettings.autoSleepTimerAutoRewind && !isFirstAutoSleepTimer) {
|
if (autoTimerDisabled) {
|
||||||
Log.i(tag, "Auto sleep timer auto rewind seeking back ${deviceSettings.autoSleepTimerAutoRewindTime}ms")
|
if (!isDuringAutoTime) {
|
||||||
playerNotificationService.seekBackward(deviceSettings.autoSleepTimerAutoRewindTime)
|
// Check if sleep timer was disabled during the previous period and enable again
|
||||||
}
|
Log.i(tag, "Leaving disabled auto sleep time period, enabling for next time period")
|
||||||
isFirstAutoSleepTimer = false
|
autoTimerDisabled = false
|
||||||
|
|
||||||
// Set sleep timer
|
|
||||||
// When sleepTimerLength is 0 then use end of chapter/track time
|
|
||||||
if (deviceSettings.sleepTimerLength == 0L) {
|
|
||||||
val chapterEndTimeMs = this.getChapterEndTime()
|
|
||||||
if (chapterEndTimeMs == null) {
|
|
||||||
Log.e(tag, "Setting auto sleep timer to end of chapter/track but there is no current session")
|
|
||||||
} else {
|
|
||||||
isAutoSleepTimer = true
|
|
||||||
setSleepTimer(chapterEndTimeMs, true)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
isAutoSleepTimer = true
|
// Auto time is disabled, do not set sleep timer
|
||||||
setSleepTimer(deviceSettings.sleepTimerLength, false)
|
Log.i(tag, "Auto sleep timer is disabled for this time period")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
isFirstAutoSleepTimer = true
|
if (isDuringAutoTime) {
|
||||||
Log.d(tag, "Current hour $currentHour is NOT between ${deviceSettings.autoSleepTimerStartTime} and ${deviceSettings.autoSleepTimerEndTime}")
|
// Start an auto sleep timer
|
||||||
|
val currentHour = currentCalendar.get(Calendar.HOUR_OF_DAY)
|
||||||
|
val currentMin = currentCalendar.get(Calendar.MINUTE)
|
||||||
|
Log.i(tag, "Starting sleep timer at $currentHour:$currentMin")
|
||||||
|
|
||||||
|
// Automatically rewind in the book if settings is enabled
|
||||||
|
tryRewindAutoSleepTimer()
|
||||||
|
|
||||||
|
// Set `isAutoSleepTimer` to true to indicate that the timer was set automatically
|
||||||
|
// and to not cause the timer to rewind
|
||||||
|
isAutoSleepTimer = true
|
||||||
|
setSleepTimer(deviceSettings.sleepTimerLength)
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "Not in auto sleep time period")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleMediaPlayEvent(playbackSessionId:String) {
|
/**
|
||||||
|
* Handles the media play event and checks if the sleep timer should be reset or set.
|
||||||
|
* @param playbackSessionId String - the playback session ID.
|
||||||
|
*/
|
||||||
|
fun handleMediaPlayEvent(playbackSessionId: String) {
|
||||||
// Check if the playback session has changed
|
// Check if the playback session has changed
|
||||||
// If it hasn't changed OR the sleep timer is running then check reset the timer
|
// If it hasn't changed OR the sleep timer is running then check reset the timer
|
||||||
// e.g. You set a manual sleep timer for 10 mins, then decide to change books, the sleep timer will stay on and reset to 10 mins
|
// e.g. You set a manual sleep timer for 10 mins, then decide to change books, the sleep timer
|
||||||
|
// will stay on and reset to 10 mins
|
||||||
if (sleepTimerSessionId == playbackSessionId || sleepTimerRunning) {
|
if (sleepTimerSessionId == playbackSessionId || sleepTimerRunning) {
|
||||||
checkShouldResetSleepTimer()
|
checkShouldResetSleepTimer()
|
||||||
} else {
|
} else {}
|
||||||
isFirstAutoSleepTimer = true
|
|
||||||
}
|
|
||||||
sleepTimerSessionId = playbackSessionId
|
sleepTimerSessionId = playbackSessionId
|
||||||
|
|
||||||
checkAutoSleepTimer()
|
checkAutoSleepTimer()
|
||||||
|
|||||||
@@ -85,7 +85,11 @@ export default {
|
|||||||
return [5, 10, 15, 30, 45, 60, 90]
|
return [5, 10, 15, 30, 45, 60, 90]
|
||||||
},
|
},
|
||||||
timeRemainingPretty() {
|
timeRemainingPretty() {
|
||||||
|
if (this.currentTime <= 0) return '0:00'
|
||||||
return this.$secondsToTimestamp(this.currentTime)
|
return this.$secondsToTimestamp(this.currentTime)
|
||||||
|
},
|
||||||
|
isIos() {
|
||||||
|
return this.$platform === 'ios'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -105,7 +109,7 @@ export default {
|
|||||||
if (this.isAuto) {
|
if (this.isAuto) {
|
||||||
const { value } = await Dialog.confirm({
|
const { value } = await Dialog.confirm({
|
||||||
title: 'Confirm',
|
title: 'Confirm',
|
||||||
message: 'Are you sure you want to disable the auto sleep timer? You will need to enable this again in settings.'
|
message: this.$strings.MessageConfirmDisableAutoTimer
|
||||||
})
|
})
|
||||||
if (!value) return
|
if (!value) return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,6 +278,7 @@
|
|||||||
"MessageBookshelfEmpty": "Bookshelf empty",
|
"MessageBookshelfEmpty": "Bookshelf empty",
|
||||||
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
||||||
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
||||||
|
"MessageConfirmDisableAutoTimer": "Are you sure you want to disable the auto timer for the rest of today? The timer will be re-enabled at the end of this auto-sleep timer period, or if you restart the app.",
|
||||||
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
||||||
"MessageConfirmDownloadUsingCellular": "You are about to download using cellular data. This may include carrier data charges. Do you wish to continue?",
|
"MessageConfirmDownloadUsingCellular": "You are about to download using cellular data. This may include carrier data charges. Do you wish to continue?",
|
||||||
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
||||||
|
|||||||
Reference in New Issue
Block a user