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

View file

@ -4,13 +4,7 @@ import android.app.DownloadManager
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Environment
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.LocalFolder
import com.audiobookshelf.app.device.DeviceManager
@ -28,7 +22,6 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.util.*

View file

@ -1,9 +1,6 @@
package com.audiobookshelf.app
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 com.getcapacitor.JSObject
@ -70,10 +67,6 @@ class Audiobook {
return Uri.parse("$serverUrl${book.cover}?token=$token&ts=${book.lastUpdate}")
}
fun getDurationLong():Long {
return duration.toLong() * 1000L
}
fun toMediaMetadata():MediaMetadataCompat {
return MediaMetadataCompat.Builder().apply {
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)

View file

@ -8,6 +8,7 @@ import android.os.Looper
import android.support.v4.media.MediaMetadataCompat
import android.util.Log
import com.audiobookshelf.app.device.DeviceManager
import com.getcapacitor.JSObject
import okhttp3.*
import org.json.JSONArray
@ -16,58 +17,23 @@ import java.io.IOException
class AudiobookManager {
var tag = "AudiobookManager"
interface OnStreamData {
fun onStreamReady(asd:AudiobookStreamData)
}
var hasLoaded = false
var isLoading = false
var ctx: Context
var serverUrl = ""
var token = ""
private var client:OkHttpClient
var localMediaManager:LocalMediaManager
var audiobooks:MutableList<Audiobook> = mutableListOf()
var audiobooksInProgress:MutableList<Audiobook> = mutableListOf()
var storageSharedPreferences: SharedPreferences? = null
constructor(_ctx:Context, _client:OkHttpClient) {
ctx = _ctx
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)) {
Log.d(tag, "LOAD Categories $serverUrl | $token")
var url = "$serverUrl/api/libraries/main/categories"
var url = "${DeviceManager.serverAddress}/api/libraries/main/categories"
val request = Request.Builder()
.url(url).addHeader("Authorization", "Bearer $token")
.url(url).addHeader("Authorization", "Bearer ${DeviceManager.token}")
.build()
client.newCall(request).enqueue(object : Callback {
@ -101,7 +67,7 @@ class AudiobookManager {
Log.d(tag, "Shelf category ab id $y = ${abobj.toString()}")
var abjsobj = JSObject(abobj.toString())
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) {
Log.d(tag, "Not an audiobook or invalid/missing")
} else {
@ -121,13 +87,12 @@ class AudiobookManager {
}
fun loadAudiobooks(cb: (() -> Unit)) {
Log.d(tag, "Load Audiobooks: $serverUrl | $token")
if (serverUrl == "" || token == "") {
if (DeviceManager.serverAddress == "" || DeviceManager.token == "") {
Log.d(tag, "Load Audiobooks: No Server or Token set")
cb()
return
} else if (!serverUrl.startsWith("http")) {
Log.e(tag, "Load Audiobooks: Invalid server url $serverUrl")
} else if (!DeviceManager.serverAddress.startsWith("http")) {
Log.e(tag, "Load Audiobooks: Invalid server url ${DeviceManager.serverAddress}")
cb()
return
}
@ -135,9 +100,9 @@ class AudiobookManager {
// First load currently reading
loadCategories() {
// 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()
.url(url).addHeader("Authorization", "Bearer $token")
.url(url).addHeader("Authorization", "Bearer ${DeviceManager.token}")
.build()
client.newCall(request).enqueue(object : Callback {
@ -161,7 +126,7 @@ class AudiobookManager {
var jsobj = JSObject(abobj.toString())
jsobj.put("isDownloaded", false)
var audiobook = Audiobook(jsobj, serverUrl, token)
var audiobook = Audiobook(jsobj, DeviceManager.serverAddress, DeviceManager.token)
if (audiobook.isMissing || audiobook.isInvalid) {
Log.d(tag, "Audiobook ${audiobook.book.title} is missing or invalid")
@ -187,100 +152,6 @@ class AudiobookManager {
fun load() {
isLoading = 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 {
@ -333,20 +204,14 @@ class AudiobookManager {
}
fun getFirstAudiobook():Audiobook? {
if (audiobooks.isEmpty()) return null
return audiobooks[0]
}
fun getFirstLocal(): LocalMediaManager.LocalAudio? {
if (localMediaManager.localAudioFiles.isEmpty()) return null
return localMediaManager.localAudioFiles[0]
return null
}
// Used for media browser loadChildren, fallback to using the samples if no audiobooks are there
fun getAudiobooksMediaMetadata() : List<MediaMetadataCompat> {
var mediaMetadata:MutableList<MediaMetadataCompat> = mutableListOf()
if (audiobooks.isEmpty()) {
localMediaManager.localAudioFiles.forEach { mediaMetadata.add(it.toMediaMetadata()) }
} else {
audiobooks.forEach { mediaMetadata.add(it.toMediaMetadata()) }
}
@ -356,7 +221,7 @@ class AudiobookManager {
fun getDownloadedAudiobooksMediaMetadata() : List<MediaMetadataCompat> {
var mediaMetadata:MutableList<MediaMetadataCompat> = mutableListOf()
if (audiobooks.isEmpty()) {
localMediaManager.localAudioFiles.forEach { mediaMetadata.add(it.toMediaMetadata()) }
} else {
audiobooks.forEach { if (it.isDownloaded) { mediaMetadata.add(it.toMediaMetadata()) } }
}

View file

@ -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
}
}

View file

@ -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")
}
}

View file

@ -10,6 +10,7 @@ import androidx.core.app.ActivityCompat
import com.anggrayudi.storage.SimpleStorage
import com.anggrayudi.storage.SimpleStorageHelper
import com.audiobookshelf.app.data.DbManager
import com.audiobookshelf.app.player.PlayerNotificationService
import com.getcapacitor.BridgeActivity
import io.paperdb.Paper

View file

@ -1,14 +1,13 @@
package com.audiobookshelf.app
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.content.ContextCompat
import com.audiobookshelf.app.data.PlaybackSession
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.capacitorjs.plugins.app.AppPlugin
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.getcapacitor.*
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
fun getCurrentTime(call: PluginCall) {
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
fun pausePlayer(call: PluginCall) {
Handler(Looper.getMainLooper()).post() {

View file

@ -1,10 +1,11 @@
package com.audiobookshelf.app
package com.audiobookshelf.app.player
import android.app.PendingIntent
import android.graphics.Bitmap
import android.net.Uri
import android.support.v4.media.session.MediaControllerCompat
import android.util.Log
import com.audiobookshelf.app.R
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions

View file

@ -1,8 +1,9 @@
package com.audiobookshelf.app
package com.audiobookshelf.app.player
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.audiobookshelf.app.device.DeviceManager
import com.getcapacitor.JSObject
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
@ -45,9 +46,9 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
webviewOpenOnStart = playerNotificationService.getIsWebviewOpen()
listeningBookTitle = playerNotificationService.getCurrentBookTitle()
listeningBookIsLocal = playerNotificationService.getCurrentBookIsLocal()
listeningBookId = playerNotificationService.getCurrentBookId()
listeningStreamId = playerNotificationService.getCurrentStreamId()
listeningBookIsLocal = false
listeningBookId = "empty"
listeningStreamId = "empty"
lastPlaybackTime = playerNotificationService.getCurrentTime()
lastUpdateTime = System.currentTimeMillis() / 1000L
@ -117,7 +118,8 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
// Send sync data only for local books
var syncData: JSObject = JSObject()
var duration = playerNotificationService.getAudiobookDuration() / 1000
// var duration = playerNotificationService.getAudiobookDuration() / 1000
var duration = 1000
var currentTime = playerNotificationService.getCurrentTime() / 1000
syncData.put("totalDuration", duration)
syncData.put("currentTime", currentTime)
@ -132,8 +134,8 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
}
fun sendLocalSyncData(payload:JSObject, cb: (() -> Unit)) {
var serverUrl = playerNotificationService.getServerUrl()
var token = playerNotificationService.getUserToken()
var serverUrl = DeviceManager.serverAddress
var token = DeviceManager.token
if (serverUrl == "" || token == "") {
return
@ -145,8 +147,8 @@ class AudiobookProgressSyncer constructor(playerNotificationService:PlayerNotifi
}
fun sendStreamSyncData(payload:JSObject, cb: (() -> Unit)) {
var serverUrl = playerNotificationService.getServerUrl()
var token = playerNotificationService.getUserToken()
var serverUrl = DeviceManager.serverAddress
var token = DeviceManager.token
if (serverUrl == "" || token == "") {
return

View file

@ -1,4 +1,4 @@
package com.audiobookshelf.app
package com.audiobookshelf.app.player
import android.content.ContentResolver
import android.content.Context
@ -6,6 +6,8 @@ import android.net.Uri
import android.support.v4.media.MediaMetadataCompat
import android.util.Log
import androidx.annotation.AnyRes
import com.audiobookshelf.app.Audiobook
import com.audiobookshelf.app.R
class BrowseTree(

View file

@ -1,4 +1,4 @@
package com.audiobookshelf.app
package com.audiobookshelf.app.player
import android.app.Activity
import android.app.AlertDialog
@ -22,7 +22,7 @@ class CastManager constructor(playerNotificationService:PlayerNotificationServic
private val tag = "SleepTimerManager"
private val playerNotificationService:PlayerNotificationService = playerNotificationService
private var newConnectionListener:SessionListener? = null
private var newConnectionListener: SessionListener? = null
private var mainActivity:Activity? = null
private fun switchToPlayer(useCastPlayer:Boolean) {

View file

@ -1,4 +1,4 @@
package com.audiobookshelf.app
package com.audiobookshelf.app.player
import android.annotation.SuppressLint
import android.app.*
@ -19,12 +19,10 @@ import android.util.Log
import android.view.KeyEvent
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.documentfile.provider.DocumentFile
import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants
import com.anggrayudi.storage.file.isExternalStorageDocument
import com.audiobookshelf.app.data.DbManager
import com.audiobookshelf.app.data.LocalMediaItem
import com.audiobookshelf.app.Audiobook
import com.audiobookshelf.app.AudiobookManager
import com.audiobookshelf.app.data.PlaybackSession
import com.getcapacitor.Bridge
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.ui.PlayerNotificationManager
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 org.json.JSONObject
import java.util.*
import kotlin.concurrent.schedule
@ -74,7 +68,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
private lateinit var playerNotificationManager: PlayerNotificationManager
private lateinit var mediaSession: MediaSessionCompat
private lateinit var transportControls:MediaControllerCompat.TransportControls
private lateinit var audiobookManager:AudiobookManager
private lateinit var audiobookManager: AudiobookManager
lateinit var mPlayer: SimpleExoPlayer
lateinit var currentPlayer:Player
@ -88,7 +82,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
private var channelId = "audiobookshelf_channel"
private var channelName = "Audiobookshelf Channel"
private var currentAudiobookStreamData:AudiobookStreamData? = null
private var currentPlaybackSession:PlaybackSession? = null
private var mediaButtonClickCount: Int = 0
@ -162,54 +155,17 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
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) {
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) {
var firstAudiobook = audiobookManager.getFirstAudiobook()
if (firstAudiobook != null) {
playAudiobookFromMediaBrowser(firstAudiobook, playWhenReady)
} else {
playFirstLocal(playWhenReady)
}
}
private fun openFromMediaId(mediaId: String, playWhenReady: Boolean) {
var audiobook = audiobookManager.audiobooks.find { it.id == mediaId }
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")
return
}
@ -295,7 +251,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
// Initialize audiobook manager
audiobookManager = AudiobookManager(ctx, client)
audiobookManager.init()
channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(channelId, channelName)
@ -719,68 +674,68 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
currentPlayer.setPlaybackSpeed(1f) // TODO: Playback speed should come from settings
currentPlayer.prepare()
}
fun initPlayer(audiobookStreamData: AudiobookStreamData) {
currentAudiobookStreamData = audiobookStreamData
Log.d(tag, "Init Player Audiobook ${currentAudiobookStreamData!!.playlistUrl} | ${currentAudiobookStreamData!!.title} | ${currentAudiobookStreamData!!.author}")
if (mPlayer.isPlaying) {
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
// Same issue with sony xperia https://github.com/advplyr/audiobookshelf-app/issues/94
if (currentAudiobookStreamData?.coverUri != null && currentAudiobookStreamData?.isLocal == true) {
var deviceName = Build.DEVICE
var deviceMan = Build.MANUFACTURER
var deviceModel = Build.MODEL
Log.d(tag, "Checking device $deviceName | Model $deviceModel | Manufacturer $deviceMan")
if (deviceMan.lowercase(Locale.getDefault()).contains("oneplus") || deviceName.lowercase(Locale.getDefault()).contains("oneplus")) {
Log.d(tag, "Detected OnePlus device - removing local cover")
currentAudiobookStreamData?.clearCover()
} else if (deviceName.lowercase(Locale.getDefault()).contains("xperia") || deviceModel.lowercase(Locale.getDefault()).contains("xperia")) {
Log.d(tag, "Detected Sony Xperia device - removing local cover")
currentAudiobookStreamData?.clearCover()
}
}
var metadata = currentAudiobookStreamData!!.getMediaMetadataCompat()
mediaSession.setMetadata(metadata)
var mediaUri:Uri = currentAudiobookStreamData!!.getMediaUri()
var mimeType:String = currentAudiobookStreamData!!.getMimeType()
var mediaMetadata = currentAudiobookStreamData!!.getMediaMetadata()
var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
if (mPlayer == currentPlayer) {
var mediaSource:MediaSource
if (currentAudiobookStreamData!!.isLocal) {
Log.d(tag, "Playing Local File")
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
} else {
Log.d(tag, "Playing HLS File")
var dataSourceFactory = DefaultHttpDataSource.Factory()
dataSourceFactory.setUserAgent(channelId)
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobookStreamData!!.token}"))
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
}
mPlayer.setMediaSource(mediaSource, currentAudiobookStreamData!!.startTime)
} else if (castPlayer != null) {
var mediaQueue = currentAudiobookStreamData!!.getCastQueue()
// TODO: Start position will need to be adjusted if using multi-track queue
castPlayer?.setMediaItems(mediaQueue, 0, 0)
}
currentPlayer.prepare()
currentPlayer.playWhenReady = currentAudiobookStreamData!!.playWhenReady
currentPlayer.setPlaybackSpeed(audiobookStreamData.playbackSpeed)
lastPauseTime = 0
}
//
// fun initPlayer(audiobookStreamData: AudiobookStreamData) {
// currentAudiobookStreamData = audiobookStreamData
//
// Log.d(tag, "Init Player Audiobook ${currentAudiobookStreamData!!.playlistUrl} | ${currentAudiobookStreamData!!.title} | ${currentAudiobookStreamData!!.author}")
//
// if (mPlayer.isPlaying) {
// 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
// // Same issue with sony xperia https://github.com/advplyr/audiobookshelf-app/issues/94
// if (currentAudiobookStreamData?.coverUri != null && currentAudiobookStreamData?.isLocal == true) {
// var deviceName = Build.DEVICE
// var deviceMan = Build.MANUFACTURER
// var deviceModel = Build.MODEL
// Log.d(tag, "Checking device $deviceName | Model $deviceModel | Manufacturer $deviceMan")
// if (deviceMan.lowercase(Locale.getDefault()).contains("oneplus") || deviceName.lowercase(Locale.getDefault()).contains("oneplus")) {
// Log.d(tag, "Detected OnePlus device - removing local cover")
// currentAudiobookStreamData?.clearCover()
// } else if (deviceName.lowercase(Locale.getDefault()).contains("xperia") || deviceModel.lowercase(Locale.getDefault()).contains("xperia")) {
// Log.d(tag, "Detected Sony Xperia device - removing local cover")
// currentAudiobookStreamData?.clearCover()
// }
// }
//
// var metadata = currentAudiobookStreamData!!.getMediaMetadataCompat()
// mediaSession.setMetadata(metadata)
//
// var mediaUri:Uri = currentAudiobookStreamData!!.getMediaUri()
// var mimeType:String = currentAudiobookStreamData!!.getMimeType()
//
// var mediaMetadata = currentAudiobookStreamData!!.getMediaMetadata()
// var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
//
// if (mPlayer == currentPlayer) {
// var mediaSource:MediaSource
//
// if (currentAudiobookStreamData!!.isLocal) {
// Log.d(tag, "Playing Local File")
// var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
// mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
// } else {
// Log.d(tag, "Playing HLS File")
// var dataSourceFactory = DefaultHttpDataSource.Factory()
// dataSourceFactory.setUserAgent(channelId)
// dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobookStreamData!!.token}"))
// mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
// }
// mPlayer.setMediaSource(mediaSource, currentAudiobookStreamData!!.startTime)
// } else if (castPlayer != null) {
// var mediaQueue = currentAudiobookStreamData!!.getCastQueue()
// // TODO: Start position will need to be adjusted if using multi-track queue
// castPlayer?.setMediaItems(mediaQueue, 0, 0)
// }
//
// currentPlayer.prepare()
// currentPlayer.playWhenReady = currentAudiobookStreamData!!.playWhenReady
// currentPlayer.setPlaybackSpeed(audiobookStreamData.playbackSpeed)
//
// lastPauseTime = 0
// }
fun switchToPlayer(useCastPlayer: Boolean) {
currentPlayer = if (useCastPlayer) {
@ -792,9 +747,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
mediaSessionConnector.setPlayer(mPlayer)
mPlayer
}
if (currentAudiobookStreamData != null) {
if (currentPlaybackSession != null) {
Log.d(tag, "switchToPlayer: Initing current ab stream data")
initPlayer(currentAudiobookStreamData!!)
preparePlayer(currentPlaybackSession!!, false)
}
}
@ -827,33 +782,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
}
fun getCurrentBookTitle() : String? {
return currentAudiobookStreamData?.title
}
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
return currentPlaybackSession?.displayTitle
}
fun calcPauseSeekBackTime() : Long {
@ -869,14 +798,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
return seekback
}
fun getPlayStatus() : Boolean {
return mPlayer.isPlaying
}
fun getCurrentAudiobookId() : String {
return currentAudiobookStreamData?.id.toString()
}
fun play() {
if (currentPlayer.isPlaying) {
Log.d(tag, "Already playing")

View file

@ -1,10 +1,9 @@
package com.audiobookshelf.app
package com.audiobookshelf.app.player
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 {

View file

@ -1,4 +1,4 @@
package com.audiobookshelf.app
package com.audiobookshelf.app.player
import android.os.Handler
import android.os.Looper