Android cleaning up and refactoring

This commit is contained in:
advplyr
2022-04-03 20:45:19 -05:00
parent 4b834cb5c1
commit cc744bb975
14 changed files with 107 additions and 689 deletions
@@ -4,13 +4,7 @@ import android.app.DownloadManager
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment
import android.util.Log import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.SimpleStorage
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.file.*
import com.anggrayudi.storage.media.FileDescription
import com.audiobookshelf.app.data.LibraryItem import com.audiobookshelf.app.data.LibraryItem
import com.audiobookshelf.app.data.LocalFolder import com.audiobookshelf.app.data.LocalFolder
import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.device.DeviceManager
@@ -28,7 +22,6 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.util.* import java.util.*
@@ -1,9 +1,6 @@
package com.audiobookshelf.app package com.audiobookshelf.app
import android.net.Uri import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.MediaMetadataCompat
import com.getcapacitor.JSObject import com.getcapacitor.JSObject
@@ -70,10 +67,6 @@ class Audiobook {
return Uri.parse("$serverUrl${book.cover}?token=$token&ts=${book.lastUpdate}") return Uri.parse("$serverUrl${book.cover}?token=$token&ts=${book.lastUpdate}")
} }
fun getDurationLong():Long {
return duration.toLong() * 1000L
}
fun toMediaMetadata():MediaMetadataCompat { fun toMediaMetadata():MediaMetadataCompat {
return MediaMetadataCompat.Builder().apply { return MediaMetadataCompat.Builder().apply {
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id) putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
@@ -8,6 +8,7 @@ import android.os.Looper
import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.MediaMetadataCompat
import android.util.Log import android.util.Log
import com.audiobookshelf.app.device.DeviceManager
import com.getcapacitor.JSObject import com.getcapacitor.JSObject
import okhttp3.* import okhttp3.*
import org.json.JSONArray import org.json.JSONArray
@@ -16,58 +17,23 @@ import java.io.IOException
class AudiobookManager { class AudiobookManager {
var tag = "AudiobookManager" var tag = "AudiobookManager"
interface OnStreamData {
fun onStreamReady(asd:AudiobookStreamData)
}
var hasLoaded = false var hasLoaded = false
var isLoading = false var isLoading = false
var ctx: Context var ctx: Context
var serverUrl = ""
var token = ""
private var client:OkHttpClient private var client:OkHttpClient
var localMediaManager:LocalMediaManager
var audiobooks:MutableList<Audiobook> = mutableListOf() var audiobooks:MutableList<Audiobook> = mutableListOf()
var audiobooksInProgress:MutableList<Audiobook> = mutableListOf() var audiobooksInProgress:MutableList<Audiobook> = mutableListOf()
var storageSharedPreferences: SharedPreferences? = null
constructor(_ctx:Context, _client:OkHttpClient) { constructor(_ctx:Context, _client:OkHttpClient) {
ctx = _ctx ctx = _ctx
client = _client client = _client
localMediaManager = LocalMediaManager(ctx)
}
fun init() {
storageSharedPreferences = ctx.getSharedPreferences("CapacitorStorage", Activity.MODE_PRIVATE)
serverUrl = storageSharedPreferences?.getString("serverUrl", "").toString()
Log.d(tag, "SHARED PREF SERVERURL $serverUrl")
token = storageSharedPreferences?.getString("token", "").toString()
Log.d(tag, "SHARED PREF TOKEN $token")
}
fun getPlaybackRate() : Float {
if (storageSharedPreferences != null) {
var userSettings = storageSharedPreferences?.getString("userSettings", "").toString()
if (userSettings != "") {
var json = JSObject(userSettings)
var playbackRate = json.getString("playbackRate", "1")
if (playbackRate != null) {
return playbackRate.toFloat()
}
}
}
return 1f
} }
fun loadCategories(cb: (() -> Unit)) { fun loadCategories(cb: (() -> Unit)) {
Log.d(tag, "LOAD Categories $serverUrl | $token") var url = "${DeviceManager.serverAddress}/api/libraries/main/categories"
var url = "$serverUrl/api/libraries/main/categories"
val request = Request.Builder() val request = Request.Builder()
.url(url).addHeader("Authorization", "Bearer $token") .url(url).addHeader("Authorization", "Bearer ${DeviceManager.token}")
.build() .build()
client.newCall(request).enqueue(object : Callback { client.newCall(request).enqueue(object : Callback {
@@ -101,7 +67,7 @@ class AudiobookManager {
Log.d(tag, "Shelf category ab id $y = ${abobj.toString()}") Log.d(tag, "Shelf category ab id $y = ${abobj.toString()}")
var abjsobj = JSObject(abobj.toString()) var abjsobj = JSObject(abobj.toString())
abjsobj.put("isDownloaded", false) abjsobj.put("isDownloaded", false)
var audiobook = Audiobook(abjsobj, serverUrl, token) var audiobook = Audiobook(abjsobj, DeviceManager.serverAddress, DeviceManager.token)
if (audiobook.isMissing || audiobook.isInvalid || audiobook.numTracks <= 0) { if (audiobook.isMissing || audiobook.isInvalid || audiobook.numTracks <= 0) {
Log.d(tag, "Not an audiobook or invalid/missing") Log.d(tag, "Not an audiobook or invalid/missing")
} else { } else {
@@ -121,13 +87,12 @@ class AudiobookManager {
} }
fun loadAudiobooks(cb: (() -> Unit)) { fun loadAudiobooks(cb: (() -> Unit)) {
Log.d(tag, "Load Audiobooks: $serverUrl | $token") if (DeviceManager.serverAddress == "" || DeviceManager.token == "") {
if (serverUrl == "" || token == "") {
Log.d(tag, "Load Audiobooks: No Server or Token set") Log.d(tag, "Load Audiobooks: No Server or Token set")
cb() cb()
return return
} else if (!serverUrl.startsWith("http")) { } else if (!DeviceManager.serverAddress.startsWith("http")) {
Log.e(tag, "Load Audiobooks: Invalid server url $serverUrl") Log.e(tag, "Load Audiobooks: Invalid server url ${DeviceManager.serverAddress}")
cb() cb()
return return
} }
@@ -135,9 +100,9 @@ class AudiobookManager {
// First load currently reading // First load currently reading
loadCategories() { loadCategories() {
// Then load all // Then load all
var url = "$serverUrl/api/libraries/main/books/all?sort=book.title" var url = "${DeviceManager.serverAddress}/api/libraries/main/books/all?sort=book.title"
val request = Request.Builder() val request = Request.Builder()
.url(url).addHeader("Authorization", "Bearer $token") .url(url).addHeader("Authorization", "Bearer ${DeviceManager.token}")
.build() .build()
client.newCall(request).enqueue(object : Callback { client.newCall(request).enqueue(object : Callback {
@@ -161,7 +126,7 @@ class AudiobookManager {
var jsobj = JSObject(abobj.toString()) var jsobj = JSObject(abobj.toString())
jsobj.put("isDownloaded", false) jsobj.put("isDownloaded", false)
var audiobook = Audiobook(jsobj, serverUrl, token) var audiobook = Audiobook(jsobj, DeviceManager.serverAddress, DeviceManager.token)
if (audiobook.isMissing || audiobook.isInvalid) { if (audiobook.isMissing || audiobook.isInvalid) {
Log.d(tag, "Audiobook ${audiobook.book.title} is missing or invalid") Log.d(tag, "Audiobook ${audiobook.book.title} is missing or invalid")
@@ -187,100 +152,6 @@ class AudiobookManager {
fun load() { fun load() {
isLoading = true isLoading = true
hasLoaded = true hasLoaded = true
localMediaManager.loadLocalAudio()
}
fun openStream(audiobook:Audiobook, streamListener:OnStreamData) {
var url = "$serverUrl/api/books/${audiobook.id}/stream"
val request = Request.Builder()
.url(url).addHeader("Authorization", "Bearer $token")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
var playbackRate = getPlaybackRate()
var bodyString = response.body!!.string()
var stream = JSObject(bodyString)
var streamId = stream.getString("streamId", "").toString()
var startTime = stream.getDouble("startTime")
var streamUrl = stream.getString("streamUrl", "").toString()
var startTimeLong = (startTime * 1000).toLong()
var abStreamDataObj = JSObject()
abStreamDataObj.put("id", streamId)
abStreamDataObj.put("audiobookId", audiobook.id)
abStreamDataObj.put("playlistUrl", "$serverUrl$streamUrl")
abStreamDataObj.put("title", audiobook.book.title)
abStreamDataObj.put("author", audiobook.book.authorFL)
abStreamDataObj.put("token", token)
abStreamDataObj.put("cover", audiobook.getCover())
abStreamDataObj.put("duration", audiobook.getDurationLong())
abStreamDataObj.put("startTime", startTimeLong)
abStreamDataObj.put("playbackSpeed", playbackRate)
abStreamDataObj.put("playWhenReady", true)
abStreamDataObj.put("isLocal", false)
var audiobookStreamData = AudiobookStreamData(abStreamDataObj)
Handler(Looper.getMainLooper()).post() {
Log.d(tag, "Stream Ready on Main Looper")
streamListener.onStreamReady(audiobookStreamData)
}
Log.d(tag, "Init Player Stream")
}
}
})
}
fun initDownloadPlay(audiobook:Audiobook):AudiobookStreamData {
var playbackRate = getPlaybackRate()
var abStreamDataObj = JSObject()
abStreamDataObj.put("id", "download")
abStreamDataObj.put("audiobookId", audiobook.id)
abStreamDataObj.put("contentUrl", audiobook.contentUrl)
abStreamDataObj.put("title", audiobook.book.title)
abStreamDataObj.put("author", audiobook.book.authorFL)
abStreamDataObj.put("token", null)
abStreamDataObj.put("cover", audiobook.getCover())
abStreamDataObj.put("duration", audiobook.getDurationLong())
abStreamDataObj.put("startTime", 0)
abStreamDataObj.put("playbackSpeed", playbackRate)
abStreamDataObj.put("playWhenReady", true)
abStreamDataObj.put("isLocal", true)
var audiobookStreamData = AudiobookStreamData(abStreamDataObj)
return audiobookStreamData
}
fun initLocalPlay(local: LocalMediaManager.LocalAudio):AudiobookStreamData {
var abStreamDataObj = JSObject()
abStreamDataObj.put("id", "local")
abStreamDataObj.put("audiobookId", local.id)
abStreamDataObj.put("contentUrl", local.uri.toString())
abStreamDataObj.put("title", local.name)
abStreamDataObj.put("author", "")
abStreamDataObj.put("token", null)
abStreamDataObj.put("cover", local.coverUri)
abStreamDataObj.put("duration", local.duration)
abStreamDataObj.put("startTime", 0)
abStreamDataObj.put("playbackSpeed", 1)
abStreamDataObj.put("playWhenReady", true)
abStreamDataObj.put("isLocal", true)
var audiobookStreamData = AudiobookStreamData(abStreamDataObj)
return audiobookStreamData
} }
private fun levenshtein(lhs : CharSequence, rhs : CharSequence) : Int { private fun levenshtein(lhs : CharSequence, rhs : CharSequence) : Int {
@@ -333,20 +204,14 @@ class AudiobookManager {
} }
fun getFirstAudiobook():Audiobook? { fun getFirstAudiobook():Audiobook? {
if (audiobooks.isEmpty()) return null return null
return audiobooks[0]
}
fun getFirstLocal(): LocalMediaManager.LocalAudio? {
if (localMediaManager.localAudioFiles.isEmpty()) return null
return localMediaManager.localAudioFiles[0]
} }
// Used for media browser loadChildren, fallback to using the samples if no audiobooks are there // Used for media browser loadChildren, fallback to using the samples if no audiobooks are there
fun getAudiobooksMediaMetadata() : List<MediaMetadataCompat> { fun getAudiobooksMediaMetadata() : List<MediaMetadataCompat> {
var mediaMetadata:MutableList<MediaMetadataCompat> = mutableListOf() var mediaMetadata:MutableList<MediaMetadataCompat> = mutableListOf()
if (audiobooks.isEmpty()) { if (audiobooks.isEmpty()) {
localMediaManager.localAudioFiles.forEach { mediaMetadata.add(it.toMediaMetadata()) }
} else { } else {
audiobooks.forEach { mediaMetadata.add(it.toMediaMetadata()) } audiobooks.forEach { mediaMetadata.add(it.toMediaMetadata()) }
} }
@@ -356,7 +221,7 @@ class AudiobookManager {
fun getDownloadedAudiobooksMediaMetadata() : List<MediaMetadataCompat> { fun getDownloadedAudiobooksMediaMetadata() : List<MediaMetadataCompat> {
var mediaMetadata:MutableList<MediaMetadataCompat> = mutableListOf() var mediaMetadata:MutableList<MediaMetadataCompat> = mutableListOf()
if (audiobooks.isEmpty()) { if (audiobooks.isEmpty()) {
localMediaManager.localAudioFiles.forEach { mediaMetadata.add(it.toMediaMetadata()) }
} else { } else {
audiobooks.forEach { if (it.isDownloaded) { mediaMetadata.add(it.toMediaMetadata()) } } audiobooks.forEach { if (it.isDownloaded) { mediaMetadata.add(it.toMediaMetadata()) } }
} }
@@ -1,176 +0,0 @@
package com.audiobookshelf.app
import android.net.Uri
import android.support.v4.media.MediaMetadataCompat
import android.util.Log
import com.getcapacitor.JSObject
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.MediaMetadata
import com.google.android.exoplayer2.util.MimeTypes
import java.lang.Exception
class AudiobookStreamData {
var id:String = "unset"
var audiobookId:String = ""
var token:String = ""
var playlistUrl:String = ""
var title:String = "No Title"
var author:String = "Unknown"
var series:String = ""
var cover:String = ""
var playWhenReady:Boolean = false
var startTime:Long = 0
var playbackSpeed:Float = 1f
var duration:Long = 0
var tracks:MutableList<String> = mutableListOf()
var isLocal:Boolean = false
var contentUrl:String = ""
var hasPlayerLoaded:Boolean = false
var playlistUri:Uri = Uri.EMPTY
var coverUri:Uri = Uri.EMPTY
var contentUri:Uri = Uri.EMPTY // For Local only
constructor(jsondata:JSObject) {
id = jsondata.getString("id", "unset").toString()
audiobookId = jsondata.getString("audiobookId", "").toString()
title = jsondata.getString("title", "No Title").toString()
token = jsondata.getString("token", "").toString()
author = jsondata.getString("author", "Unknown").toString()
series = jsondata.getString("series", "").toString()
cover = jsondata.getString("cover", "").toString()
playlistUrl = jsondata.getString("playlistUrl", "").toString()
playWhenReady = jsondata.getBoolean("playWhenReady", false) == true
if (jsondata.has("startTime")) {
startTime = jsondata.getString("startTime", "0")!!.toLong()
}
if (jsondata.has("duration")) {
duration = jsondata.getString("duration", "0")!!.toLong()
}
if (jsondata.has("playbackSpeed")) {
playbackSpeed = jsondata.getDouble("playbackSpeed")!!.toFloat()
}
// Local data
isLocal = jsondata.getBoolean("isLocal", false) == true
contentUrl = jsondata.getString("contentUrl", "").toString()
if (playlistUrl != "") {
playlistUri = Uri.parse(playlistUrl)
}
if (cover != "" && cover != null) {
coverUri = Uri.parse(cover)
} else {
coverUri = Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
cover = coverUri.toString()
}
if (contentUrl != "") {
contentUri = Uri.parse(contentUrl)
}
// Tracks for cast
try {
var tracksTest = jsondata.getJSONArray("tracks")
Log.d("AudiobookStreamData", "Load tracks from json array ${tracksTest.length()}")
for (i in 0 until tracksTest.length()) {
var track = tracksTest.get(i)
Log.d("AudiobookStreamData", "Extracting track $track")
tracks.add(track as String)
}
} catch(e:Exception) {
Log.d("AudiobookStreamData", "No tracks found $e")
}
}
fun clearCover() {
coverUri = Uri.EMPTY
cover = ""
}
fun getMediaMetadataCompat():MediaMetadataCompat {
var metadataBuilder = MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, author)
.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, author)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, author)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, series)
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
// if (cover != "") {
// metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, cover)
// metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, cover)
// }
return metadataBuilder.build()
}
fun getMediaMetadata():MediaMetadata {
var metadataBuilder = MediaMetadata.Builder()
.setTitle(title)
.setDisplayTitle(title)
.setArtist(author)
.setAlbumArtist(author)
.setSubtitle(author)
// if (coverUri != Uri.EMPTY) {
// metadataBuilder.setArtworkUri(coverUri)
// }
if (playlistUri != Uri.EMPTY) {
metadataBuilder.setMediaUri(playlistUri)
}
if (contentUri != Uri.EMPTY) {
metadataBuilder.setMediaUri(contentUri)
}
return metadataBuilder.build()
}
fun getMimeType():String {
return if (isLocal) {
MimeTypes.BASE_TYPE_AUDIO
} else {
MimeTypes.APPLICATION_M3U8
}
}
fun getMediaUri():Uri {
return if (isLocal) {
contentUri
} else {
Uri.parse("$playlistUrl?token=$token")
}
}
fun getCastQueue():ArrayList<MediaItem> {
var mediaQueue: java.util.ArrayList<MediaItem> = java.util.ArrayList<MediaItem>()
for (i in 0 until tracks.size) {
var track = tracks[i]
var metadataBuilder = MediaMetadata.Builder()
.setTitle(title)
.setDisplayTitle(title)
.setArtist(author)
.setAlbumArtist(author)
.setSubtitle(author)
.setTrackNumber(i + 1)
if (coverUri != Uri.EMPTY) {
metadataBuilder.setArtworkUri(coverUri)
}
var mimeType = MimeTypes.BASE_TYPE_AUDIO
var mediaMetadata = metadataBuilder.build()
var mediaItem = MediaItem.Builder().setUri(Uri.parse(track)).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
mediaQueue.add(mediaItem)
}
return mediaQueue
}
}
@@ -1,120 +0,0 @@
package com.audiobookshelf.app
import android.Manifest
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.database.Cursor
import android.media.MediaPlayer
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.support.v4.media.MediaMetadataCompat
import android.util.Log
import androidx.annotation.AnyRes
import androidx.core.content.ContextCompat
import androidx.core.net.toFile
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import java.io.File
import java.io.IOException
class LocalMediaManager {
private var ctx: Context
val tag = "LocalAudioManager"
constructor(ctx: Context) {
this.ctx = ctx
}
data class LocalAudio(val uri: Uri,
val id: String,
val name: String,
val duration: Int,
val size: Int,
val coverUri: Uri?
) {
fun toMediaMetadata(): MediaMetadataCompat {
return MediaMetadataCompat.Builder().apply {
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, name)
putString(MediaMetadataCompat.METADATA_KEY_TITLE, name)
if (coverUri != null) {
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, coverUri.toString())
}
}.build()
}
}
val localAudioFiles = mutableListOf<LocalAudio>()
/**
* get uri to drawable or any other resource type if u wish
* @param context - context
* @param drawableId - drawable res id
* @return - uri
*/
fun getUriToDrawable(context: Context,
@AnyRes drawableId: Int): Uri {
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
+ "://" + context.resources.getResourcePackageName(drawableId)
+ '/' + context.resources.getResourceTypeName(drawableId)
+ '/' + context.resources.getResourceEntryName(drawableId))
}
fun loadLocalAudio() {
localAudioFiles.clear()
localAudioFiles += LocalAudio(Uri.parse("asset:///public/samples/Anthem/AnthemSample.m4b"), "anthem_sample", "Anthem", 60000, 10000, getUriToDrawable(ctx, R.drawable.exo_icon_localaudio))
localAudioFiles += LocalAudio(Uri.parse("asset:///public/samples/Legend of Sleepy Hollow/LegendOfSleepyHollowSample.m4b"), "sleepy_hollow", "Legend of Sleepy Hollow", 60000, 10000, getUriToDrawable(ctx, R.drawable.exo_icon_localaudio))
// TODO: No longer reading in local audio files - just use samples
// if (ContextCompat.checkSelfPermission(ctx, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// Log.e(tag, "Permission not granted to read from external storage")
// return
// }
//
// val collection =
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// MediaStore.Audio.Media.getContentUri(
// MediaStore.VOLUME_EXTERNAL
// )
// } else {
// MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
// }
//
// val proj = arrayOf(MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.SIZE)
// val audioCursor: Cursor? = ctx.contentResolver.query(collection, proj, null, null, null)
//
// audioCursor?.use { cursor ->
// // Cache column indices.
// val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
// val nameColumn =
// cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
// val durationColumn =
// cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
// val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
//
// while (cursor.moveToNext()) {
// // Get values of columns for a given video.
// val id = cursor.getLong(idColumn)
// val name = cursor.getString(nameColumn)
// val duration = cursor.getInt(durationColumn)
// val size = cursor.getInt(sizeColumn)
//
// val contentUri: Uri = ContentUris.withAppendedId(
// MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
// id
// )
// Log.d(tag, "Found local audio file $name")
// localAudioFiles += LocalAudio(contentUri, id.toString(), name, duration, size, null)
// }
// }
//
// Log.d(tag, "${localAudioFiles.size} Local Audio Files found")
}
}
@@ -10,6 +10,7 @@ import androidx.core.app.ActivityCompat
import com.anggrayudi.storage.SimpleStorage import com.anggrayudi.storage.SimpleStorage
import com.anggrayudi.storage.SimpleStorageHelper import com.anggrayudi.storage.SimpleStorageHelper
import com.audiobookshelf.app.data.DbManager import com.audiobookshelf.app.data.DbManager
import com.audiobookshelf.app.player.PlayerNotificationService
import com.getcapacitor.BridgeActivity import com.getcapacitor.BridgeActivity
import io.paperdb.Paper import io.paperdb.Paper
@@ -1,14 +1,13 @@
package com.audiobookshelf.app package com.audiobookshelf.app
import android.content.Intent
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat
import com.audiobookshelf.app.data.PlaybackSession import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.player.CastManager
import com.audiobookshelf.app.player.PlayerNotificationService
import com.audiobookshelf.app.server.ApiHandler import com.audiobookshelf.app.server.ApiHandler
import com.capacitorjs.plugins.app.AppPlugin
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.getcapacitor.* import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin import com.getcapacitor.annotation.CapacitorPlugin
@@ -100,46 +99,6 @@ class MyNativeAudio : Plugin() {
} }
} }
@PluginMethod
fun getLibraryItems(call: PluginCall) {
var libraryId = call.getString("libraryId", "").toString()
apiHandler.getLibraryItems(libraryId) {
val mapper = jacksonObjectMapper()
var jsobj = JSObject()
var libarray = JSArray()
it.map {
libarray.put(JSObject(mapper.writeValueAsString(it)))
}
jsobj.put("value", libarray)
call.resolve(jsobj)
}
}
@PluginMethod
fun initPlayer(call: PluginCall) {
if (!PlayerNotificationService.isStarted) {
Log.w(tag, "Starting foreground service --")
Intent(mainActivity, PlayerNotificationService::class.java).also { intent ->
ContextCompat.startForegroundService(mainActivity, intent)
}
}
var jsobj = JSObject()
var audiobookStreamData:AudiobookStreamData = AudiobookStreamData(call.data)
if (audiobookStreamData.playlistUrl == "" && audiobookStreamData.contentUrl == "") {
Log.e(tag, "Invalid URL for init audio player")
jsobj.put("success", false)
return call.resolve(jsobj)
}
Handler(Looper.getMainLooper()).post() {
playerNotificationService.initPlayer(audiobookStreamData)
jsobj.put("success", true)
call.resolve(jsobj)
}
}
@PluginMethod @PluginMethod
fun getCurrentTime(call: PluginCall) { fun getCurrentTime(call: PluginCall) {
Handler(Looper.getMainLooper()).post() { Handler(Looper.getMainLooper()).post() {
@@ -152,28 +111,6 @@ class MyNativeAudio : Plugin() {
} }
} }
@PluginMethod
fun getStreamSyncData(call: PluginCall) {
Handler(Looper.getMainLooper()).post() {
var isPlaying = playerNotificationService.getPlayStatus()
var lastPauseTime = playerNotificationService.getTheLastPauseTime()
Log.d(tag, "Get Last Pause Time $lastPauseTime")
var currentTime = playerNotificationService.getCurrentTime()
//if (!isPlaying) currentTime -= playerNotificationService.calcPauseSeekBackTime()
var id = playerNotificationService.getCurrentAudiobookId()
Log.d(tag, "Get Current id $id")
var duration = playerNotificationService.getDuration()
Log.d(tag, "Get duration $duration")
val ret = JSObject()
ret.put("lastPauseTime", lastPauseTime)
ret.put("currentTime", currentTime)
ret.put("isPlaying", isPlaying)
ret.put("id", id)
ret.put("duration", duration)
call.resolve(ret)
}
}
@PluginMethod @PluginMethod
fun pausePlayer(call: PluginCall) { fun pausePlayer(call: PluginCall) {
Handler(Looper.getMainLooper()).post() { Handler(Looper.getMainLooper()).post() {
@@ -1,10 +1,11 @@
package com.audiobookshelf.app package com.audiobookshelf.app.player
import android.app.PendingIntent import android.app.PendingIntent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.support.v4.media.session.MediaControllerCompat import android.support.v4.media.session.MediaControllerCompat
import android.util.Log import android.util.Log
import com.audiobookshelf.app.R
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
@@ -1,8 +1,9 @@
package com.audiobookshelf.app package com.audiobookshelf.app.player
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import com.audiobookshelf.app.device.DeviceManager
import com.getcapacitor.JSObject import com.getcapacitor.JSObject
import okhttp3.* import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@@ -45,9 +46,9 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
webviewOpenOnStart = playerNotificationService.getIsWebviewOpen() webviewOpenOnStart = playerNotificationService.getIsWebviewOpen()
listeningBookTitle = playerNotificationService.getCurrentBookTitle() listeningBookTitle = playerNotificationService.getCurrentBookTitle()
listeningBookIsLocal = playerNotificationService.getCurrentBookIsLocal() listeningBookIsLocal = false
listeningBookId = playerNotificationService.getCurrentBookId() listeningBookId = "empty"
listeningStreamId = playerNotificationService.getCurrentStreamId() listeningStreamId = "empty"
lastPlaybackTime = playerNotificationService.getCurrentTime() lastPlaybackTime = playerNotificationService.getCurrentTime()
lastUpdateTime = System.currentTimeMillis() / 1000L lastUpdateTime = System.currentTimeMillis() / 1000L
@@ -117,7 +118,8 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
// Send sync data only for local books // Send sync data only for local books
var syncData: JSObject = JSObject() var syncData: JSObject = JSObject()
var duration = playerNotificationService.getAudiobookDuration() / 1000 // var duration = playerNotificationService.getAudiobookDuration() / 1000
var duration = 1000
var currentTime = playerNotificationService.getCurrentTime() / 1000 var currentTime = playerNotificationService.getCurrentTime() / 1000
syncData.put("totalDuration", duration) syncData.put("totalDuration", duration)
syncData.put("currentTime", currentTime) syncData.put("currentTime", currentTime)
@@ -132,8 +134,8 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
} }
fun sendLocalSyncData(payload:JSObject, cb: (() -> Unit)) { fun sendLocalSyncData(payload:JSObject, cb: (() -> Unit)) {
var serverUrl = playerNotificationService.getServerUrl() var serverUrl = DeviceManager.serverAddress
var token = playerNotificationService.getUserToken() var token = DeviceManager.token
if (serverUrl == "" || token == "") { if (serverUrl == "" || token == "") {
return return
@@ -145,8 +147,8 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
} }
fun sendStreamSyncData(payload:JSObject, cb: (() -> Unit)) { fun sendStreamSyncData(payload:JSObject, cb: (() -> Unit)) {
var serverUrl = playerNotificationService.getServerUrl() var serverUrl = DeviceManager.serverAddress
var token = playerNotificationService.getUserToken() var token = DeviceManager.token
if (serverUrl == "" || token == "") { if (serverUrl == "" || token == "") {
return return
@@ -1,4 +1,4 @@
package com.audiobookshelf.app package com.audiobookshelf.app.player
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
@@ -6,6 +6,8 @@ import android.net.Uri
import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.MediaMetadataCompat
import android.util.Log import android.util.Log
import androidx.annotation.AnyRes import androidx.annotation.AnyRes
import com.audiobookshelf.app.Audiobook
import com.audiobookshelf.app.R
class BrowseTree( class BrowseTree(
@@ -1,4 +1,4 @@
package com.audiobookshelf.app package com.audiobookshelf.app.player
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
@@ -22,7 +22,7 @@ class CastManager constructor(playerNotificationService:PlayerNotificationServic
private val tag = "SleepTimerManager" private val tag = "SleepTimerManager"
private val playerNotificationService:PlayerNotificationService = playerNotificationService private val playerNotificationService:PlayerNotificationService = playerNotificationService
private var newConnectionListener:SessionListener? = null private var newConnectionListener: SessionListener? = null
private var mainActivity:Activity? = null private var mainActivity:Activity? = null
private fun switchToPlayer(useCastPlayer:Boolean) { private fun switchToPlayer(useCastPlayer:Boolean) {
@@ -1,4 +1,4 @@
package com.audiobookshelf.app package com.audiobookshelf.app.player
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.* import android.app.*
@@ -19,12 +19,10 @@ import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.documentfile.provider.DocumentFile
import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants import androidx.media.utils.MediaConstants
import com.anggrayudi.storage.file.isExternalStorageDocument import com.audiobookshelf.app.Audiobook
import com.audiobookshelf.app.data.DbManager import com.audiobookshelf.app.AudiobookManager
import com.audiobookshelf.app.data.LocalMediaItem
import com.audiobookshelf.app.data.PlaybackSession import com.audiobookshelf.app.data.PlaybackSession
import com.getcapacitor.Bridge import com.getcapacitor.Bridge
import com.getcapacitor.JSObject import com.getcapacitor.JSObject
@@ -38,11 +36,7 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.* import com.google.android.exoplayer2.upstream.*
import com.google.android.gms.cast.*
import com.google.android.gms.cast.framework.*
import kotlinx.coroutines.*
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.json.JSONObject
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
@@ -74,7 +68,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
private lateinit var playerNotificationManager: PlayerNotificationManager private lateinit var playerNotificationManager: PlayerNotificationManager
private lateinit var mediaSession: MediaSessionCompat private lateinit var mediaSession: MediaSessionCompat
private lateinit var transportControls:MediaControllerCompat.TransportControls private lateinit var transportControls:MediaControllerCompat.TransportControls
private lateinit var audiobookManager:AudiobookManager private lateinit var audiobookManager: AudiobookManager
lateinit var mPlayer: SimpleExoPlayer lateinit var mPlayer: SimpleExoPlayer
lateinit var currentPlayer:Player lateinit var currentPlayer:Player
@@ -88,7 +82,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
private var channelId = "audiobookshelf_channel" private var channelId = "audiobookshelf_channel"
private var channelName = "Audiobookshelf Channel" private var channelName = "Audiobookshelf Channel"
private var currentAudiobookStreamData:AudiobookStreamData? = null
private var currentPlaybackSession:PlaybackSession? = null private var currentPlaybackSession:PlaybackSession? = null
private var mediaButtonClickCount: Int = 0 private var mediaButtonClickCount: Int = 0
@@ -162,54 +155,17 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
return channelId return channelId
} }
private fun playLocal(local: LocalMediaManager.LocalAudio, playWhenReady: Boolean) {
var asd = audiobookManager.initLocalPlay(local)
asd.playWhenReady = playWhenReady
initPlayer(asd)
}
private fun playFirstLocal(playWhenReady: Boolean) {
var localAudio = audiobookManager.getFirstLocal()
if (localAudio != null) {
playLocal(localAudio, playWhenReady)
}
}
private fun playAudiobookFromMediaBrowser(audiobook: Audiobook, playWhenReady: Boolean) { private fun playAudiobookFromMediaBrowser(audiobook: Audiobook, playWhenReady: Boolean) {
if (!audiobook.isDownloaded) {
var streamListener = object : AudiobookManager.OnStreamData {
override fun onStreamReady(asd: AudiobookStreamData) {
Log.d(tag, "Stream Ready ${asd.playlistUrl}")
asd.playWhenReady = playWhenReady
initPlayer(asd)
}
}
audiobookManager.openStream(audiobook, streamListener)
} else {
var asd = audiobookManager.initDownloadPlay(audiobook)
asd.playWhenReady = playWhenReady
initPlayer(asd)
}
} }
private fun playFirstAudiobook(playWhenReady: Boolean) { private fun playFirstAudiobook(playWhenReady: Boolean) {
var firstAudiobook = audiobookManager.getFirstAudiobook()
if (firstAudiobook != null) {
playAudiobookFromMediaBrowser(firstAudiobook, playWhenReady)
} else {
playFirstLocal(playWhenReady)
}
} }
private fun openFromMediaId(mediaId: String, playWhenReady: Boolean) { private fun openFromMediaId(mediaId: String, playWhenReady: Boolean) {
var audiobook = audiobookManager.audiobooks.find { it.id == mediaId } var audiobook = audiobookManager.audiobooks.find { it.id == mediaId }
if (audiobook == null) { if (audiobook == null) {
var localAudio = audiobookManager.localMediaManager.localAudioFiles.find { it.id == mediaId }
if (localAudio != null) {
playLocal(localAudio, playWhenReady)
return
}
Log.e(tag, "Audiobook NOT FOUND") Log.e(tag, "Audiobook NOT FOUND")
return return
} }
@@ -295,7 +251,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
// Initialize audiobook manager // Initialize audiobook manager
audiobookManager = AudiobookManager(ctx, client) audiobookManager = AudiobookManager(ctx, client)
audiobookManager.init()
channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(channelId, channelName) createNotificationChannel(channelId, channelName)
@@ -719,68 +674,68 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
currentPlayer.setPlaybackSpeed(1f) // TODO: Playback speed should come from settings currentPlayer.setPlaybackSpeed(1f) // TODO: Playback speed should come from settings
currentPlayer.prepare() currentPlayer.prepare()
} }
//
fun initPlayer(audiobookStreamData: AudiobookStreamData) { // fun initPlayer(audiobookStreamData: AudiobookStreamData) {
currentAudiobookStreamData = audiobookStreamData // currentAudiobookStreamData = audiobookStreamData
//
Log.d(tag, "Init Player Audiobook ${currentAudiobookStreamData!!.playlistUrl} | ${currentAudiobookStreamData!!.title} | ${currentAudiobookStreamData!!.author}") // Log.d(tag, "Init Player Audiobook ${currentAudiobookStreamData!!.playlistUrl} | ${currentAudiobookStreamData!!.title} | ${currentAudiobookStreamData!!.author}")
//
if (mPlayer.isPlaying) { // if (mPlayer.isPlaying) {
Log.d(tag, "Init Player audiobook already playing") // Log.d(tag, "Init Player audiobook already playing")
} // }
//
// Issue with onenote plus crashing when using local cover art. https://github.com/advplyr/audiobookshelf-app/issues/35 // // Issue with onenote plus crashing when using local cover art. https://github.com/advplyr/audiobookshelf-app/issues/35
// Same issue with sony xperia https://github.com/advplyr/audiobookshelf-app/issues/94 // // Same issue with sony xperia https://github.com/advplyr/audiobookshelf-app/issues/94
if (currentAudiobookStreamData?.coverUri != null && currentAudiobookStreamData?.isLocal == true) { // if (currentAudiobookStreamData?.coverUri != null && currentAudiobookStreamData?.isLocal == true) {
var deviceName = Build.DEVICE // var deviceName = Build.DEVICE
var deviceMan = Build.MANUFACTURER // var deviceMan = Build.MANUFACTURER
var deviceModel = Build.MODEL // var deviceModel = Build.MODEL
Log.d(tag, "Checking device $deviceName | Model $deviceModel | Manufacturer $deviceMan") // Log.d(tag, "Checking device $deviceName | Model $deviceModel | Manufacturer $deviceMan")
if (deviceMan.lowercase(Locale.getDefault()).contains("oneplus") || deviceName.lowercase(Locale.getDefault()).contains("oneplus")) { // if (deviceMan.lowercase(Locale.getDefault()).contains("oneplus") || deviceName.lowercase(Locale.getDefault()).contains("oneplus")) {
Log.d(tag, "Detected OnePlus device - removing local cover") // Log.d(tag, "Detected OnePlus device - removing local cover")
currentAudiobookStreamData?.clearCover() // currentAudiobookStreamData?.clearCover()
} else if (deviceName.lowercase(Locale.getDefault()).contains("xperia") || deviceModel.lowercase(Locale.getDefault()).contains("xperia")) { // } else if (deviceName.lowercase(Locale.getDefault()).contains("xperia") || deviceModel.lowercase(Locale.getDefault()).contains("xperia")) {
Log.d(tag, "Detected Sony Xperia device - removing local cover") // Log.d(tag, "Detected Sony Xperia device - removing local cover")
currentAudiobookStreamData?.clearCover() // currentAudiobookStreamData?.clearCover()
} // }
} // }
//
var metadata = currentAudiobookStreamData!!.getMediaMetadataCompat() // var metadata = currentAudiobookStreamData!!.getMediaMetadataCompat()
mediaSession.setMetadata(metadata) // mediaSession.setMetadata(metadata)
//
var mediaUri:Uri = currentAudiobookStreamData!!.getMediaUri() // var mediaUri:Uri = currentAudiobookStreamData!!.getMediaUri()
var mimeType:String = currentAudiobookStreamData!!.getMimeType() // var mimeType:String = currentAudiobookStreamData!!.getMimeType()
//
var mediaMetadata = currentAudiobookStreamData!!.getMediaMetadata() // var mediaMetadata = currentAudiobookStreamData!!.getMediaMetadata()
var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build() // var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
//
if (mPlayer == currentPlayer) { // if (mPlayer == currentPlayer) {
var mediaSource:MediaSource // var mediaSource:MediaSource
//
if (currentAudiobookStreamData!!.isLocal) { // if (currentAudiobookStreamData!!.isLocal) {
Log.d(tag, "Playing Local File") // Log.d(tag, "Playing Local File")
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId) // var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem) // mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
} else { // } else {
Log.d(tag, "Playing HLS File") // Log.d(tag, "Playing HLS File")
var dataSourceFactory = DefaultHttpDataSource.Factory() // var dataSourceFactory = DefaultHttpDataSource.Factory()
dataSourceFactory.setUserAgent(channelId) // dataSourceFactory.setUserAgent(channelId)
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobookStreamData!!.token}")) // dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobookStreamData!!.token}"))
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem) // mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
} // }
mPlayer.setMediaSource(mediaSource, currentAudiobookStreamData!!.startTime) // mPlayer.setMediaSource(mediaSource, currentAudiobookStreamData!!.startTime)
} else if (castPlayer != null) { // } else if (castPlayer != null) {
var mediaQueue = currentAudiobookStreamData!!.getCastQueue() // var mediaQueue = currentAudiobookStreamData!!.getCastQueue()
// TODO: Start position will need to be adjusted if using multi-track queue // // TODO: Start position will need to be adjusted if using multi-track queue
castPlayer?.setMediaItems(mediaQueue, 0, 0) // castPlayer?.setMediaItems(mediaQueue, 0, 0)
} // }
//
currentPlayer.prepare() // currentPlayer.prepare()
currentPlayer.playWhenReady = currentAudiobookStreamData!!.playWhenReady // currentPlayer.playWhenReady = currentAudiobookStreamData!!.playWhenReady
currentPlayer.setPlaybackSpeed(audiobookStreamData.playbackSpeed) // currentPlayer.setPlaybackSpeed(audiobookStreamData.playbackSpeed)
//
lastPauseTime = 0 // lastPauseTime = 0
} // }
fun switchToPlayer(useCastPlayer: Boolean) { fun switchToPlayer(useCastPlayer: Boolean) {
currentPlayer = if (useCastPlayer) { currentPlayer = if (useCastPlayer) {
@@ -792,9 +747,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
mediaSessionConnector.setPlayer(mPlayer) mediaSessionConnector.setPlayer(mPlayer)
mPlayer mPlayer
} }
if (currentAudiobookStreamData != null) { if (currentPlaybackSession != null) {
Log.d(tag, "switchToPlayer: Initing current ab stream data") Log.d(tag, "switchToPlayer: Initing current ab stream data")
initPlayer(currentAudiobookStreamData!!) preparePlayer(currentPlaybackSession!!, false)
} }
} }
@@ -827,33 +782,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
} }
fun getCurrentBookTitle() : String? { fun getCurrentBookTitle() : String? {
return currentAudiobookStreamData?.title return currentPlaybackSession?.displayTitle
}
fun getCurrentBookIsLocal() : Boolean {
return currentAudiobookStreamData?.isLocal == true
}
fun getCurrentBookId() : String? {
return currentAudiobookStreamData?.audiobookId
}
fun getCurrentStreamId() : String? {
return currentAudiobookStreamData?.id
}
// The duration stored on the audiobook
fun getAudiobookDuration() : Long {
if (currentAudiobookStreamData == null) return 0L
return currentAudiobookStreamData!!.duration
}
fun getServerUrl(): String {
return audiobookManager.serverUrl
}
fun getUserToken() : String {
return audiobookManager.token
} }
fun calcPauseSeekBackTime() : Long { fun calcPauseSeekBackTime() : Long {
@@ -869,14 +798,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
return seekback return seekback
} }
fun getPlayStatus() : Boolean {
return mPlayer.isPlaying
}
fun getCurrentAudiobookId() : String {
return currentAudiobookStreamData?.id.toString()
}
fun play() { fun play() {
if (currentPlayer.isPlaying) { if (currentPlayer.isPlaying) {
Log.d(tag, "Already playing") Log.d(tag, "Already playing")
@@ -1,10 +1,9 @@
package com.audiobookshelf.app package com.audiobookshelf.app.player
import android.hardware.Sensor import android.hardware.Sensor
import android.hardware.SensorEvent import android.hardware.SensorEvent
import android.hardware.SensorEventListener import android.hardware.SensorEventListener
import android.hardware.SensorManager import android.hardware.SensorManager
import java.lang.Math.sqrt
import kotlin.math.sqrt import kotlin.math.sqrt
class ShakeDetector : SensorEventListener { class ShakeDetector : SensorEventListener {
@@ -1,4 +1,4 @@
package com.audiobookshelf.app package com.audiobookshelf.app.player
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper