Add:Android auto sleep timer #260

This commit is contained in:
advplyr
2023-01-29 13:19:06 -06:00
parent 479de5f067
commit 31531197a2
13 changed files with 346 additions and 43 deletions
@@ -98,7 +98,11 @@ data class DeviceSettings(
var disableShakeToResetSleepTimer:Boolean,
var shakeSensitivity: ShakeSensitivitySetting,
var lockOrientation: LockOrientationSetting,
var hapticFeedback: HapticFeedbackSetting
var hapticFeedback: HapticFeedbackSetting,
var autoSleepTimer: Boolean,
var autoSleepTimerStartTime: String,
var autoSleepTimerEndTime: String,
var sleepTimerLength: Long // Time in milliseconds
) {
companion object {
// Static method to get default device settings
@@ -111,7 +115,11 @@ data class DeviceSettings(
disableShakeToResetSleepTimer = false,
shakeSensitivity = ShakeSensitivitySetting.MEDIUM,
lockOrientation = LockOrientationSetting.NONE,
hapticFeedback = HapticFeedbackSetting.LIGHT
hapticFeedback = HapticFeedbackSetting.LIGHT,
autoSleepTimer = false,
autoSleepTimerStartTime = "22:00",
autoSleepTimerEndTime = "06:00",
sleepTimerLength = 900000L // 15 minutes
)
}
}
@@ -120,6 +128,14 @@ data class DeviceSettings(
val jumpBackwardsTimeMs get() = jumpBackwardsTime * 1000L
@get:JsonIgnore
val jumpForwardTimeMs get() = jumpForwardTime * 1000L
@get:JsonIgnore
val autoSleepTimerStartHour get() = autoSleepTimerStartTime.split(":")[0].toInt()
@get:JsonIgnore
val autoSleepTimerStartMinute get() = autoSleepTimerStartTime.split(":")[1].toInt()
@get:JsonIgnore
val autoSleepTimerEndHour get() = autoSleepTimerEndTime.split(":")[0].toInt()
@get:JsonIgnore
val autoSleepTimerEndMinute get() = autoSleepTimerEndTime.split(":")[1].toInt()
@JsonIgnore
fun getShakeThresholdGravity() : Float { // Used in ShakeDetector
@@ -14,6 +14,7 @@ import com.audiobookshelf.app.R
import com.audiobookshelf.app.device.DeviceManager
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.audiobookshelf.app.player.PLAYMETHOD_LOCAL
import java.util.*
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -84,6 +84,12 @@ class PlaybackSession(
return chapters.find { time >= it.startMs && it.endMs > time}
}
@JsonIgnore
fun getCurrentTrackEndTime():Long? {
val currentTrack = audioTracks[this.getCurrentTrackIndex()]
return currentTrack.startOffsetMs + currentTrack.durationMs
}
@JsonIgnore
fun getCurrentTrackTimeMs():Long {
val currentTrack = audioTracks[this.getCurrentTrackIndex()]
@@ -26,6 +26,16 @@ object DeviceManager {
init {
Log.d(tag, "Device Manager Singleton invoked")
// Initialize new sleep timer settings and shake sensitivity added in v0.9.61
if (deviceData.deviceSettings?.autoSleepTimerStartTime == null || deviceData.deviceSettings?.autoSleepTimerEndTime == null) {
deviceData.deviceSettings?.autoSleepTimerStartTime = "22:00"
deviceData.deviceSettings?.autoSleepTimerStartTime = "06:00"
deviceData.deviceSettings?.sleepTimerLength = 900000L
}
if (deviceData.deviceSettings?.shakeSensitivity == null) {
deviceData.deviceSettings?.shakeSensitivity = ShakeSensitivitySetting.MEDIUM
}
}
fun getBase64Id(id:String):String {
@@ -6,12 +6,11 @@ import android.util.Log
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.player.PlayerNotificationService
import com.audiobookshelf.app.player.SLEEP_TIMER_WAKE_UP_EXPIRATION
import java.text.SimpleDateFormat
import java.util.*
import kotlin.concurrent.schedule
import kotlin.math.roundToInt
const val SLEEP_EXTENSION_TIME = 900000L // 15m
class SleepTimerManager constructor(private val playerNotificationService: PlayerNotificationService) {
private val tag = "SleepTimerManager"
@@ -58,7 +57,7 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
fun setSleepTimer(time: Long, isChapterTime: Boolean) : Boolean {
Log.d(tag, "Setting Sleep Timer for $time is chapter time $isChapterTime")
sleepTimerTask?.cancel()
sleepTimerRunning = false
sleepTimerRunning = true
sleepTimerFinishedAt = 0L
sleepTimerElapsed = 0L
@@ -88,7 +87,6 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds())
sleepTimerRunning = true
sleepTimerTask = Timer("SleepTimer", false).schedule(0L, 1000L) {
Handler(Looper.getMainLooper()).post {
if (getIsPlaying()) {
@@ -120,12 +118,6 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
return true
}
// Called when playing audio and only applies to regular timer
fun resetSleepTimer() {
if (!sleepTimerRunning || sleepTimerLength <= 0L) return
setSleepTimer(sleepTimerLength, false)
}
private fun clearSleepTimer() {
sleepTimerTask?.cancel()
sleepTimerTask = null
@@ -168,7 +160,7 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
}
}
fun checkShouldExtendSleepTimer() {
fun checkShouldResetSleepTimer() {
if (!sleepTimerRunning) {
if (sleepTimerFinishedAt <= 0L) return
@@ -180,15 +172,27 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
return
}
val newSleepTime = if (sleepTimerLength >= 0) sleepTimerLength else SLEEP_EXTENSION_TIME
vibrate()
setSleepTimer(newSleepTime, false)
play()
// Set sleep timer
// When sleepTimerLength is 0 then use end of chapter/track time
if (sleepTimerLength == 0L) {
val currentChapterEndTimeMs = playerNotificationService.getEndTimeOfChapterOrTrack()
if (currentChapterEndTimeMs == null) {
Log.e(tag, "Checking reset sleep timer to end of chapter/track but there is no current session")
} else {
vibrate()
setSleepTimer(currentChapterEndTimeMs, true)
play()
}
} else {
vibrate()
setSleepTimer(sleepTimerLength, false)
play()
}
return
}
// Does not apply to chapter sleep timers and timer must be running for at least 3 seconds
if (sleepTimerEndTime == 0L && sleepTimerElapsed > 3000L) {
if (sleepTimerLength > 0L && sleepTimerElapsed > 3000L) {
vibrate()
setSleepTimer(sleepTimerLength, false)
}
@@ -200,7 +204,7 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
Log.d(tag, "Shake to reset sleep timer is disabled")
return
}
checkShouldExtendSleepTimer()
checkShouldResetSleepTimer()
}
}
@@ -245,4 +249,53 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
setVolume(1F)
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds())
}
fun checkAutoSleepTimer() {
if (sleepTimerRunning) { // Sleep timer already running
return
}
DeviceManager.deviceData.deviceSettings?.let { deviceSettings ->
if (!deviceSettings.autoSleepTimer) return // Check auto sleep timer is enabled
val startCalendar = Calendar.getInstance()
startCalendar.set(Calendar.HOUR_OF_DAY, deviceSettings.autoSleepTimerStartHour)
startCalendar.set(Calendar.MINUTE, deviceSettings.autoSleepTimerStartMinute)
val endCalendar = Calendar.getInstance()
endCalendar.set(Calendar.HOUR_OF_DAY, deviceSettings.autoSleepTimerEndHour)
endCalendar.set(Calendar.MINUTE, deviceSettings.autoSleepTimerEndMinute)
// In cases where end time is earlier then start time then we add a day to end time
// e.g. start time 22:00 and end time 06:00. End time will be treated as 6am the next day.
// e.g. start time 08:00 and end time 22:00. Start and end time will be the same day.
if (endCalendar.before(startCalendar)) {
endCalendar.add(Calendar.DAY_OF_MONTH, 1)
}
val currentCalendar = Calendar.getInstance()
val currentHour = SimpleDateFormat("HH:mm", Locale.getDefault()).format(currentCalendar.time)
if (currentCalendar.after(startCalendar) && currentCalendar.before(endCalendar)) {
Log.i(tag, "Current hour $currentHour is between ${deviceSettings.autoSleepTimerStartTime} and ${deviceSettings.autoSleepTimerEndTime} - starting sleep timer")
// Set sleep timer
// When sleepTimerLength is 0 then use end of chapter/track time
if (deviceSettings.sleepTimerLength == 0L) {
val currentChapterEndTimeMs = playerNotificationService.getEndTimeOfChapterOrTrack()
if (currentChapterEndTimeMs == null) {
Log.e(tag, "Setting auto sleep timer to end of chapter/track but there is no current session")
} else {
setSleepTimer(currentChapterEndTimeMs, true)
}
} else {
setSleepTimer(deviceSettings.sleepTimerLength, false)
}
} else {
Log.d(tag, "Current hour $currentHour is NOT between ${deviceSettings.autoSleepTimerStartTime} and ${deviceSettings.autoSleepTimerEndTime}")
}
}
}
fun handleMediaPlayEvent() {
checkShouldResetSleepTimer()
checkAutoSleepTimer()
}
}
@@ -68,6 +68,9 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
listeningTimerTask = Timer("ListeningTimer", false).schedule(15000L, 15000L) {
Handler(Looper.getMainLooper()).post() {
if (playerNotificationService.currentPlayer.isPlaying) {
// Set auto sleep timer if enabled and within start/end time
playerNotificationService.sleepTimerManager.checkAutoSleepTimer()
// Only sync with server on unmetered connection every 15s OR sync with server if last sync time is >= 60s
val shouldSyncServer = PlayerNotificationService.isUnmeteredNetwork || System.currentTimeMillis() - lastSyncTime >= METERED_CONNECTION_SYNC_INTERVAL
@@ -169,7 +169,6 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
Log.d(tag, "handleCallMediaButton: Media Play")
if (0 == mediaButtonClickCount) {
playerNotificationService.play()
playerNotificationService.sleepTimerManager.checkShouldExtendSleepTimer()
}
handleMediaButtonClickCount()
}
@@ -201,7 +200,6 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
} else {
if (0 == mediaButtonClickCount) {
playerNotificationService.play()
playerNotificationService.sleepTimerManager.checkShouldExtendSleepTimer()
}
handleMediaButtonClickCount()
}
@@ -92,7 +92,9 @@ class PlayerListener(var playerNotificationService:PlayerNotificationService) :
// Start/stop progress sync interval
if (isPlaying) {
playerNotificationService.sleepTimerManager.resetSleepTimer() // Reset sleep timer if running and not a chapter timer
// Handles auto-starting sleep timer and resetting sleep timer
playerNotificationService.sleepTimerManager.handleMediaPlayEvent()
player.volume = 1F // Volume on sleep timer might have decreased this
val playbackSession: PlaybackSession? = playerNotificationService.mediaProgressSyncer.currentPlaybackSession ?: playerNotificationService.currentPlaybackSession
playbackSession?.let { playerNotificationService.mediaProgressSyncer.play(it) }
@@ -644,6 +644,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
return currentPlaybackSession?.getChapterForTime(this.getCurrentTime())
}
fun getEndTimeOfChapterOrTrack():Long? {
return getCurrentBookChapter()?.endMs ?: currentPlaybackSession?.getCurrentTrackEndTime()
}
// Called from PlayerListener play event
// check with server if progress has updated since last play and sync progress update
fun checkCurrentSessionProgress(seekBackTime:Long):Boolean {