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:
advplyr
2021-11-26 18:27:18 -06:00
parent d3343d722f
commit edc45addc9
17 changed files with 272 additions and 117 deletions
@@ -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
}
}