From 28e50dd4618552be24333abfed09b742a4d64d3b Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 28 Jan 2023 17:31:52 -0600 Subject: [PATCH] Add:Android device setting to adjust shake sensitivity for sleep timer #260 --- .../audiobookshelf/app/data/DeviceClasses.kt | 110 +++++++++++------- .../app/device/DeviceManager.kt | 2 +- .../app/device/FolderScanner.kt | 1 - .../app/managers/SleepTimerManager.kt | 14 +-- .../app/player/PlayerNotificationService.kt | 6 +- .../app/player/ShakeDetector.kt | 6 +- .../audiobookshelf/app/plugins/AbsDatabase.kt | 2 +- pages/bookshelf/index.vue | 10 +- pages/settings.vue | 34 +++++- 9 files changed, 115 insertions(+), 70 deletions(-) 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 0d872100..2d4e065a 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 @@ -2,6 +2,7 @@ package com.audiobookshelf.app.data import android.content.Context import android.support.v4.media.MediaDescriptionCompat +import android.util.Log import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonSubTypes @@ -15,6 +16,10 @@ enum class HapticFeedbackSetting { OFF, LIGHT, MEDIUM, HEAVY } +enum class ShakeSensitivitySetting { + VERY_LOW, LOW, MEDIUM, HIGH, VERY_HIGH +} + data class ServerConnectionConfig( var id:String, var index:Int, @@ -26,50 +31,6 @@ data class ServerConnectionConfig( var customHeaders:Map? ) -data class DeviceSettings( - var disableAutoRewind:Boolean, - var enableAltView:Boolean, - var jumpBackwardsTime:Int, - var jumpForwardTime:Int, - var disableShakeToResetSleepTimer:Boolean, - var lockOrientation:LockOrientationSetting, - var hapticFeedback: HapticFeedbackSetting -) { - companion object { - // Static method to get default device settings - fun default():DeviceSettings { - return DeviceSettings( - disableAutoRewind = false, - enableAltView = false, - jumpBackwardsTime = 10, - jumpForwardTime = 10, - disableShakeToResetSleepTimer = false, - lockOrientation = LockOrientationSetting.NONE, - hapticFeedback = HapticFeedbackSetting.LIGHT - ) - } - } - - @get:JsonIgnore - val jumpBackwardsTimeMs get() = (jumpBackwardsTime ?: default().jumpBackwardsTime) * 1000L - @get:JsonIgnore - val jumpForwardTimeMs get() = (jumpForwardTime ?: default().jumpForwardTime) * 1000L -} - -data class DeviceData( - var serverConnectionConfigs:MutableList, - var lastServerConnectionConfigId:String?, - var currentLocalPlaybackSession:PlaybackSession?, // Stored to open up where left off for local media - var deviceSettings:DeviceSettings? -) { - @JsonIgnore - fun getLastServerConnectionConfig():ServerConnectionConfig? { - return lastServerConnectionConfigId?.let { lsccid -> - return serverConnectionConfigs.find { it.id == lsccid } - } - } -} - @JsonIgnoreProperties(ignoreUnknown = true) data class LocalFile( var id:String, @@ -127,3 +88,64 @@ data class PlayItemRequestPayload( var forceTranscode:Boolean, var deviceInfo:DeviceInfo ) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class DeviceSettings( + var disableAutoRewind:Boolean, + var enableAltView:Boolean, + var jumpBackwardsTime:Int, + var jumpForwardTime:Int, + var disableShakeToResetSleepTimer:Boolean, + var shakeSensitivity: ShakeSensitivitySetting, + var lockOrientation: LockOrientationSetting, + var hapticFeedback: HapticFeedbackSetting +) { + companion object { + // Static method to get default device settings + fun default():DeviceSettings { + return DeviceSettings( + disableAutoRewind = false, + enableAltView = false, + jumpBackwardsTime = 10, + jumpForwardTime = 10, + disableShakeToResetSleepTimer = false, + shakeSensitivity = ShakeSensitivitySetting.MEDIUM, + lockOrientation = LockOrientationSetting.NONE, + hapticFeedback = HapticFeedbackSetting.LIGHT + ) + } + } + + @get:JsonIgnore + val jumpBackwardsTimeMs get() = jumpBackwardsTime * 1000L + @get:JsonIgnore + val jumpForwardTimeMs get() = jumpForwardTime * 1000L + + @JsonIgnore + fun getShakeThresholdGravity() : Float { // Used in ShakeDetector + return if (shakeSensitivity == ShakeSensitivitySetting.VERY_HIGH) 1.2f + else if (shakeSensitivity == ShakeSensitivitySetting.HIGH) 1.4f + else if (shakeSensitivity == ShakeSensitivitySetting.MEDIUM) 1.6f + else if (shakeSensitivity == ShakeSensitivitySetting.LOW) 2f + else if (shakeSensitivity == ShakeSensitivitySetting.VERY_LOW) 2.7f + else { + Log.e("DeviceSetting", "Invalid ShakeSensitivitySetting $shakeSensitivity") + 1.6f + } + } +} + +data class DeviceData( + var serverConnectionConfigs:MutableList, + var lastServerConnectionConfigId:String?, + var currentLocalPlaybackSession: PlaybackSession?, // Stored to open up where left off for local media + var deviceSettings: DeviceSettings? +) { + @JsonIgnore + fun getLastServerConnectionConfig(): ServerConnectionConfig? { + return lastServerConnectionConfigId?.let { lsccid -> + return serverConnectionConfigs.find { it.id == lsccid } + } + } +} + 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 84a5c6a5..58a5293c 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 @@ -13,7 +13,7 @@ object DeviceManager { const val tag = "DeviceManager" val dbManager: DbManager = DbManager() - var deviceData:DeviceData = dbManager.getDeviceData() + var deviceData: DeviceData = dbManager.getDeviceData() var serverConnectionConfig: ServerConnectionConfig? = null val serverConnectionConfigId get() = serverConnectionConfig?.id ?: "" diff --git a/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt b/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt index cb68eb6a..2cca1a9d 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt @@ -10,7 +10,6 @@ import com.arthenica.ffmpegkit.FFprobeKit import com.arthenica.ffmpegkit.Level import com.audiobookshelf.app.data.* import com.audiobookshelf.app.models.DownloadItem -import com.audiobookshelf.app.plugins.AbsDownloader import com.fasterxml.jackson.core.json.JsonReadFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue 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 067034fe..313c0a43 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 @@ -12,16 +12,6 @@ import kotlin.math.roundToInt const val SLEEP_EXTENSION_TIME = 900000L // 15m -class ShakeSensitivity { - companion object { - val veryHigh = 1f - val high = 1.3f - val medium = 1.6f - val low = 2f - val veryLow = 2.7f - } -} - class SleepTimerManager constructor(val playerNotificationService: PlayerNotificationService) { private val tag = "SleepTimerManager" @@ -194,8 +184,8 @@ class SleepTimerManager constructor(val playerNotificationService: PlayerNotific return } - // Does not apply to chapter sleep timers - if (sleepTimerEndTime == 0L) { + // Does not apply to chapter sleep timers and timer must be running for at least 3 seconds + if (sleepTimerEndTime == 0L && sleepTimerElapsed > 3000L) { vibrate() setSleepTimer(sleepTimerExtensionTime, false) } 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 9137d7fc..0d6f435f 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 @@ -35,7 +35,6 @@ import com.audiobookshelf.app.managers.DbManager import com.audiobookshelf.app.managers.SleepTimerManager import com.audiobookshelf.app.media.MediaManager import com.audiobookshelf.app.server.ApiHandler -import com.fasterxml.jackson.annotation.JsonIgnore import com.google.android.exoplayer2.* import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector @@ -849,8 +848,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { return DeviceInfo(Build.MANUFACTURER, Build.MODEL, Build.BRAND, Build.VERSION.SDK_INT, BuildConfig.VERSION_NAME) } - @get:JsonIgnore - val deviceSettings get() = DeviceManager.deviceData.deviceSettings ?: DeviceSettings.default() + private val deviceSettings get() = DeviceManager.deviceData.deviceSettings ?: DeviceSettings.default() fun getPlayItemRequestPayload(forceTranscode:Boolean):PlayItemRequestPayload { return PlayItemRequestPayload(getMediaPlayer(), !forceTranscode, forceTranscode, getDeviceInfo()) @@ -1046,7 +1044,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { // Shake sensor used for sleep timer fun registerSensor() { if (isShakeSensorRegistered) { - Log.w(tag, "Shake sensor already registered") + Log.i(tag, "Shake sensor already registered") return } shakeSensorUnregisterTask?.cancel() diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/ShakeDetector.kt b/android/app/src/main/java/com/audiobookshelf/app/player/ShakeDetector.kt index 2118fdcd..331543db 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/ShakeDetector.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/ShakeDetector.kt @@ -4,6 +4,7 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import com.audiobookshelf.app.device.DeviceManager import kotlin.math.sqrt class ShakeDetector : SensorEventListener { @@ -34,7 +35,8 @@ class ShakeDetector : SensorEventListener { val gZ = z / SensorManager.GRAVITY_EARTH // gForce will be close to 1 when there is no movement. val gForce: Float = sqrt(gX * gX + gY * gY + gZ * gZ) - if (gForce > SHAKE_THRESHOLD_GRAVITY) { + val shakeThreshold = DeviceManager.deviceData.deviceSettings?.getShakeThresholdGravity() ?: 1.6f + if (gForce > shakeThreshold) { val now = System.currentTimeMillis() // ignore shake events too close to each other (500ms) if (mShakeTimestamp + SHAKE_SLOP_TIME_MS > now) { @@ -59,7 +61,7 @@ class ShakeDetector : SensorEventListener { * from the Google Play Store and run it to see how * many G's it takes to register a shake */ - var SHAKE_THRESHOLD_GRAVITY = 1.5f // orig 2.7f +// var SHAKE_THRESHOLD_GRAVITY = 1.5f // orig 2.7f private const val SHAKE_SLOP_TIME_MS = 500 private const val SHAKE_COUNT_RESET_TIME_MS = 3000 } diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt index da9d5dd9..f133d33f 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt @@ -17,7 +17,7 @@ import kotlinx.coroutines.launch @CapacitorPlugin(name = "AbsDatabase") class AbsDatabase : Plugin() { val tag = "AbsDatabase" - var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) + private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) lateinit var mainActivity: MainActivity lateinit var apiHandler: ApiHandler diff --git a/pages/bookshelf/index.vue b/pages/bookshelf/index.vue index 1e66c8ab..ee812b7a 100644 --- a/pages/bookshelf/index.vue +++ b/pages/bookshelf/index.vue @@ -38,6 +38,7 @@ export default { return { shelves: [], loading: false, + lastServerFetch: 0, localLibraryItems: [] } }, @@ -47,9 +48,11 @@ export default { console.log(`Network changed to ${newVal} - fetch categories`) if (newVal) { - setTimeout(() => { - this.fetchCategories() - }, 4000) + if (!this.lastServerFetch || Date.now() - this.lastServerFetch < 4000) { + setTimeout(() => { + this.fetchCategories() + }, 4000) + } } else { this.fetchCategories() } @@ -131,6 +134,7 @@ export default { const localCategories = await this.getLocalMediaItemCategories() if (this.user && this.currentLibraryId && this.networkConnected) { + this.lastServerFetch = Date.now() const categories = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/personalized?minified=1`).catch((error) => { console.error('Failed to fetch categories', error) return [] diff --git a/pages/settings.vue b/pages/settings.vue index a563846d..47d4df70 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -1,5 +1,5 @@ @@ -65,6 +69,7 @@ export default { jumpForwardTime: 10, jumpBackwardsTime: 10, disableShakeToResetSleepTimer: false, + shakeSensitivity: 'MEDIUM', lockOrientation: 0, hapticFeedback: 'LIGHT' }, @@ -92,6 +97,28 @@ export default { text: 'Heavy', value: 'HEAVY' } + ], + shakeSensitivityItems: [ + { + text: 'Very Low', + value: 'VERY_LOW' + }, + { + text: 'Low', + value: 'LOW' + }, + { + text: 'Medium', + value: 'MEDIUM' + }, + { + text: 'High', + value: 'HIGH' + }, + { + text: 'Very High', + value: 'VERY_HIGH' + } ] } }, @@ -121,6 +148,9 @@ export default { } }, methods: { + sensitivityUpdated(val) { + this.saveSettings() + }, hapticFeedbackUpdated(val) { this.$store.commit('globals/setHapticFeedback', val) this.saveSettings() @@ -176,7 +206,6 @@ export default { async saveSettings() { await this.$hapticsImpact() const updatedDeviceData = await this.$db.updateDeviceSettings({ ...this.settings }) - console.log('Saved device data', updatedDeviceData) if (updatedDeviceData) { this.$store.commit('setDeviceData', updatedDeviceData) this.init() @@ -192,6 +221,7 @@ export default { this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10 this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10 this.settings.disableShakeToResetSleepTimer = !!deviceSettings.disableShakeToResetSleepTimer + this.settings.shakeSensitivity = deviceSettings.shakeSensitivity || 'MEDIUM' this.settings.lockOrientation = deviceSettings.lockOrientation || 'NONE' this.lockCurrentOrientation = this.settings.lockOrientation !== 'NONE' this.settings.hapticFeedback = deviceSettings.hapticFeedback || 'LIGHT'