mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-09 05:18:40 +02:00
Change:Sleep timer shake to extend time 15m and fade out audio #44 #10, Change:Audio player show time remaining accounting for current playback speed #40, Fix:highlighted current chapter, Update api endpoints
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "com.audiobookshelf.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 46
|
||||
versionName "0.9.27-beta"
|
||||
versionCode 47
|
||||
versionName "0.9.28-beta"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -132,7 +132,7 @@ class AudiobookManager {
|
||||
}
|
||||
|
||||
fun openStream(audiobook:Audiobook, streamListener:OnStreamData) {
|
||||
var url = "$serverUrl/api/audiobook/${audiobook.id}/stream"
|
||||
var url = "$serverUrl/api/books/${audiobook.id}/stream"
|
||||
val request = Request.Builder()
|
||||
.url(url).addHeader("Authorization", "Bearer $token")
|
||||
.build()
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.app.DownloadManager
|
||||
import android.app.SearchManager
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorManager
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -27,6 +29,11 @@ class MainActivity : BridgeActivity() {
|
||||
val storageHelper = SimpleStorageHelper(this)
|
||||
val storage = SimpleStorage(this)
|
||||
|
||||
// The following are used for the shake detection
|
||||
private var mSensorManager: SensorManager? = null
|
||||
private var mAccelerometer: Sensor? = null
|
||||
private var mShakeDetector: ShakeDetector? = null
|
||||
|
||||
val broadcastReceiver = object: BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
@@ -54,6 +61,24 @@ class MainActivity : BridgeActivity() {
|
||||
addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED)
|
||||
}
|
||||
registerReceiver(broadcastReceiver, filter)
|
||||
|
||||
initSensor()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
Log.d(tag, "onResume Register sensor listener")
|
||||
mSensorManager!!.registerListener(
|
||||
mShakeDetector,
|
||||
mAccelerometer,
|
||||
SensorManager.SENSOR_DELAY_UI
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mSensorManager!!.unregisterListener(mShakeDetector)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -128,6 +153,19 @@ class MainActivity : BridgeActivity() {
|
||||
storageHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun initSensor() {
|
||||
// ShakeDetector initialization
|
||||
mSensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
|
||||
mAccelerometer = mSensorManager!!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
||||
mShakeDetector = ShakeDetector()
|
||||
mShakeDetector!!.setOnShakeListener(object : ShakeDetector.OnShakeListener {
|
||||
override fun onShake(count: Int) {
|
||||
Log.d(tag, "PHONE SHAKE! $count")
|
||||
foregroundService.handleShake()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// override fun onUserInteraction() {
|
||||
// super.onUserInteraction()
|
||||
// Log.d(tag, "USER INTERACTION")
|
||||
|
||||
@@ -38,6 +38,9 @@ class MyNativeAudio : Plugin() {
|
||||
override fun onSleepTimerEnded(currentPosition:Long) {
|
||||
emit("onSleepTimerEnded", currentPosition)
|
||||
}
|
||||
override fun onSleepTimerSet(sleepTimerEndTime:Long) {
|
||||
emit("onSleepTimerSet", sleepTimerEndTime)
|
||||
}
|
||||
})
|
||||
}
|
||||
mainActivity.pluginCallback = foregroundServiceReady
|
||||
@@ -81,7 +84,6 @@ class MyNativeAudio : Plugin() {
|
||||
fun getCurrentTime(call: PluginCall) {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
var currentTime = playerNotificationService.getCurrentTime()
|
||||
Log.d(tag, "Get Current Time $currentTime")
|
||||
val ret = JSObject()
|
||||
ret.put("value", currentTime)
|
||||
call.resolve(ret)
|
||||
@@ -95,7 +97,6 @@ class MyNativeAudio : Plugin() {
|
||||
var lastPauseTime = playerNotificationService.getTheLastPauseTime()
|
||||
Log.d(tag, "Get Last Pause Time $lastPauseTime")
|
||||
var currentTime = playerNotificationService.getCurrentTime()
|
||||
Log.d(tag, "Get Current Time $currentTime")
|
||||
//if (!isPlaying) currentTime -= playerNotificationService.calcPauseSeekBackTime()
|
||||
var id = playerNotificationService.getCurrentAudiobookId()
|
||||
Log.d(tag, "Get Current id $id")
|
||||
|
||||
@@ -49,9 +49,11 @@ import okhttp3.OkHttpClient
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px
|
||||
const val SLEEP_EXTENSION_TIME = 900000L // 15m
|
||||
|
||||
class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
|
||||
@@ -64,6 +66,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
fun onMetadata(metadata: JSObject)
|
||||
fun onPrepare(audiobookId: String, playWhenReady: Boolean)
|
||||
fun onSleepTimerEnded(currentPosition: Long)
|
||||
fun onSleepTimerSet(sleepTimerEndTime:Long)
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +104,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
private var onSeekBack: Boolean = false
|
||||
|
||||
private var sleepTimerTask:TimerTask? = null
|
||||
private var sleepChapterTime:Long = 0L
|
||||
private var sleepTimerRunning:Boolean = false
|
||||
private var sleepTimerEndTime:Long = 0L
|
||||
|
||||
private lateinit var audiobookManager:AudiobookManager
|
||||
private var newConnectionListener:SessionListener? = null
|
||||
@@ -490,7 +494,12 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
handleMediaButtonClickCount()
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY -> {
|
||||
if (0 == mediaButtonClickCount) play()
|
||||
if (0 == mediaButtonClickCount) {
|
||||
play()
|
||||
if (sleepTimerRunning) {
|
||||
extendSleepTime()
|
||||
}
|
||||
}
|
||||
handleMediaButtonClickCount()
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
|
||||
@@ -511,7 +520,12 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
if (0 == mediaButtonClickCount) pause()
|
||||
handleMediaButtonClickCount()
|
||||
} else {
|
||||
if (0 == mediaButtonClickCount) play()
|
||||
if (0 == mediaButtonClickCount) {
|
||||
play()
|
||||
if (sleepTimerRunning) {
|
||||
extendSleepTime()
|
||||
}
|
||||
}
|
||||
handleMediaButtonClickCount()
|
||||
}
|
||||
}
|
||||
@@ -759,7 +773,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
|
||||
|
||||
fun getCurrentTime() : Long {
|
||||
return mPlayer.currentPosition
|
||||
return currentPlayer.currentPosition
|
||||
}
|
||||
|
||||
fun getTheLastPauseTime() : Long {
|
||||
@@ -767,7 +781,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
}
|
||||
|
||||
fun getDuration() : Long {
|
||||
return mPlayer.duration
|
||||
return currentPlayer.duration
|
||||
}
|
||||
|
||||
fun calcPauseSeekBackTime() : Long {
|
||||
@@ -989,57 +1003,89 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
fun setSleepTimer(time: Long, isChapterTime: Boolean) : Boolean {
|
||||
Log.d(tag, "Setting Sleep Timer for $time is chapter time $isChapterTime")
|
||||
sleepTimerTask?.cancel()
|
||||
sleepChapterTime = 0L
|
||||
sleepTimerRunning = false
|
||||
|
||||
var currentTime = getCurrentTime()
|
||||
if (isChapterTime) {
|
||||
// Validate time
|
||||
if (currentPlayer.isPlaying) {
|
||||
if (currentPlayer.currentPosition >= time) {
|
||||
Log.d(tag, "Invalid setSleepTimer chapter time is already passed")
|
||||
return false
|
||||
}
|
||||
if (currentTime > time) {
|
||||
Log.d(tag, "Invalid sleep timer - current time is already passed chapter time $time")
|
||||
return false
|
||||
}
|
||||
sleepTimerEndTime = time
|
||||
} else {
|
||||
sleepTimerEndTime = currentTime + time
|
||||
}
|
||||
|
||||
sleepChapterTime = time
|
||||
sleepTimerTask = Timer("SleepTimer", false).schedule(0L, 1000L) {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
if (currentPlayer.isPlaying && currentPlayer.currentPosition > sleepChapterTime) {
|
||||
if (sleepTimerEndTime > getDuration()) {
|
||||
sleepTimerEndTime = getDuration()
|
||||
}
|
||||
|
||||
Log.d(tag, "SLEEP VOLUME ${currentPlayer.volume} | ${currentPlayer.deviceVolume}")
|
||||
// if (isChapterTime) {
|
||||
// sleepChapterTime = time
|
||||
listener?.onSleepTimerSet(sleepTimerEndTime)
|
||||
|
||||
sleepTimerRunning = true
|
||||
sleepTimerTask = Timer("SleepTimer", false).schedule(0L, 1000L) {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
if (currentPlayer.isPlaying) {
|
||||
var sleepTimeRemaining = sleepTimerEndTime - getCurrentTime()
|
||||
var sleepTimeSecondsRemaining = ((sleepTimeRemaining / 1000).toDouble()).roundToInt()
|
||||
Log.d(tag, "Sleep TIMER time remaining $sleepTimeSecondsRemaining s")
|
||||
|
||||
if (sleepTimeRemaining <= 0) {
|
||||
Log.d(tag, "Sleep Timer Pausing Player on Chapter")
|
||||
currentPlayer.pause()
|
||||
|
||||
listener?.onSleepTimerEnded(currentPlayer.currentPosition)
|
||||
sleepTimerTask?.cancel()
|
||||
sleepTimerRunning = false
|
||||
} else if (sleepTimeSecondsRemaining <= 30) {
|
||||
// Start fading out audio
|
||||
var volume = sleepTimeSecondsRemaining / 30F
|
||||
Log.d(tag, "SLEEP VOLUME FADE $volume | ${sleepTimeSecondsRemaining}s remaining")
|
||||
currentPlayer.volume = volume
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sleepTimerTask = Timer("SleepTimer", false).schedule(time) {
|
||||
Log.d(tag, "Sleep Timer Done")
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
if (currentPlayer.isPlaying) {
|
||||
Log.d(tag, "Sleep Timer Pausing Player")
|
||||
currentPlayer.pause()
|
||||
}
|
||||
listener?.onSleepTimerEnded(currentPlayer.currentPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun getSleepTimerTime():Long? {
|
||||
var time = sleepTimerTask?.scheduledExecutionTime()
|
||||
Log.d(tag, "Sleep Timer execution time $time")
|
||||
return time
|
||||
return sleepTimerEndTime
|
||||
}
|
||||
|
||||
fun cancelSleepTimer() {
|
||||
Log.d(tag, "Canceling Sleep Timer")
|
||||
sleepTimerTask?.cancel()
|
||||
sleepTimerTask = null
|
||||
sleepChapterTime = 0L
|
||||
sleepTimerEndTime = 0
|
||||
sleepTimerRunning = false
|
||||
listener?.onSleepTimerSet(0)
|
||||
}
|
||||
|
||||
private fun extendSleepTime() {
|
||||
if (!sleepTimerRunning) return
|
||||
currentPlayer.volume = 1F
|
||||
sleepTimerEndTime += SLEEP_EXTENSION_TIME
|
||||
if (sleepTimerEndTime > getDuration()) sleepTimerEndTime = getDuration()
|
||||
listener?.onSleepTimerSet(sleepTimerEndTime)
|
||||
}
|
||||
|
||||
fun handleShake() {
|
||||
Log.d(tag, "HANDLE SHAKE HERE")
|
||||
if (sleepTimerRunning) {
|
||||
Log.d(tag, "Sleep Timer is Running, EXTEND TIME")
|
||||
extendSleepTime()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
CAST STUFF
|
||||
*/
|
||||
|
||||
private inner class CastSessionAvailabilityListener : SessionAvailabilityListener {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import java.lang.Math.sqrt
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class ShakeDetector : SensorEventListener {
|
||||
private var mListener: OnShakeListener? = null
|
||||
private var mShakeTimestamp: Long = 0
|
||||
private var mShakeCount = 0
|
||||
fun setOnShakeListener(listener: OnShakeListener?) {
|
||||
mListener = listener
|
||||
}
|
||||
|
||||
interface OnShakeListener {
|
||||
fun onShake(count: Int)
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(
|
||||
sensor: Sensor,
|
||||
accuracy: Int
|
||||
) { // ignore
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
if (mListener != null) {
|
||||
val x = event.values[0]
|
||||
val y = event.values[1]
|
||||
val z = event.values[2]
|
||||
val gX = x / SensorManager.GRAVITY_EARTH
|
||||
val gY = y / SensorManager.GRAVITY_EARTH
|
||||
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 now = System.currentTimeMillis()
|
||||
// ignore shake events too close to each other (500ms)
|
||||
if (mShakeTimestamp + SHAKE_SLOP_TIME_MS > now) {
|
||||
return
|
||||
}
|
||||
// reset the shake count after 3 seconds of no shakes
|
||||
if (mShakeTimestamp + SHAKE_COUNT_RESET_TIME_MS < now) {
|
||||
mShakeCount = 0
|
||||
}
|
||||
mShakeTimestamp = now
|
||||
mShakeCount++
|
||||
mListener!!.onShake(mShakeCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/*
|
||||
* The gForce that is necessary to register as shake.
|
||||
* Must be greater than 1G (one earth gravity unit).
|
||||
* You can install "G-Force", by Blake La Pierre
|
||||
* from the Google Play Store and run it to see how
|
||||
* many G's it takes to register a shake
|
||||
*/
|
||||
private const val SHAKE_THRESHOLD_GRAVITY = 2.7f
|
||||
private const val SHAKE_SLOP_TIME_MS = 500
|
||||
private const val SHAKE_COUNT_RESET_TIME_MS = 3000
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user