diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt b/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt
index 2d4e065a..ffd4a9ac 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt
@@ -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
diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt b/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt
index 28e1d21c..5ffe332f 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt
@@ -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)
diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt
index 3722a0ec..7e6b3a82 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt
@@ -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()]
diff --git a/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt b/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt
index 94f3756a..0948d02b 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt
@@ -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 {
diff --git a/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt b/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt
index fb5da1b4..6b8cd783 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt
@@ -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()
+ }
}
diff --git a/android/app/src/main/java/com/audiobookshelf/app/media/MediaProgressSyncer.kt b/android/app/src/main/java/com/audiobookshelf/app/media/MediaProgressSyncer.kt
index aaa7c09c..98be44df 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/media/MediaProgressSyncer.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/media/MediaProgressSyncer.kt
@@ -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
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
index 7fa00026..74f2e1ed 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
@@ -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()
}
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
index 6504b060..d7d42f7a 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
@@ -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) }
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
index 9b3672af..a0ee353c 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
@@ -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 {
diff --git a/components/modals/SleepTimerLengthModal.vue b/components/modals/SleepTimerLengthModal.vue
new file mode 100644
index 00000000..9785e10e
--- /dev/null
+++ b/components/modals/SleepTimerLengthModal.vue
@@ -0,0 +1,100 @@
+
+ Sleep Timer {{ manualTimeoutMin }} min
+
+
+
User Interface Settings
Lock orientation
Haptic feedback
-Haptic feedback
+Sleep Timer Settings
-Sleep Timer Settings
+Disable shake to reset
+Disable shake to reset
- +Shake Sensitivity
+Auto Sleep Timer
+ +Start Time
+Shake Sensitivity
-End Time
+Sleep Timer
+