diff --git a/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt b/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt index 492d7a8c..5c8ae5b3 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt @@ -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.* diff --git a/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt b/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt index 2ba2c0c9..1d3ba0ef 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt @@ -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) diff --git a/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt b/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt index a81ef99f..31f1e4dc 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt @@ -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 = mutableListOf() var audiobooksInProgress:MutableList = 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 { var mediaMetadata:MutableList = 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 { var mediaMetadata:MutableList = mutableListOf() if (audiobooks.isEmpty()) { - localMediaManager.localAudioFiles.forEach { mediaMetadata.add(it.toMediaMetadata()) } + } else { audiobooks.forEach { if (it.isDownloaded) { mediaMetadata.add(it.toMediaMetadata()) } } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/AudiobookStreamData.kt b/android/app/src/main/java/com/audiobookshelf/app/AudiobookStreamData.kt deleted file mode 100644 index 1e2977e7..00000000 --- a/android/app/src/main/java/com/audiobookshelf/app/AudiobookStreamData.kt +++ /dev/null @@ -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 = 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 { - var mediaQueue: java.util.ArrayList = java.util.ArrayList() - - 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 - } -} diff --git a/android/app/src/main/java/com/audiobookshelf/app/LocalMediaManager.kt b/android/app/src/main/java/com/audiobookshelf/app/LocalMediaManager.kt deleted file mode 100644 index 5d234125..00000000 --- a/android/app/src/main/java/com/audiobookshelf/app/LocalMediaManager.kt +++ /dev/null @@ -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() - - /** - * 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") - } -} diff --git a/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt b/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt index 189fda47..d6eb3824 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt @@ -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 diff --git a/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt b/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt index 90024faf..4b523446 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt @@ -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() { diff --git a/android/app/src/main/java/com/audiobookshelf/app/AbMediaDescriptionAdapter.kt b/android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt similarity index 97% rename from android/app/src/main/java/com/audiobookshelf/app/AbMediaDescriptionAdapter.kt rename to android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt index 69b5f19c..2fa70e92 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/AbMediaDescriptionAdapter.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt @@ -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 diff --git a/android/app/src/main/java/com/audiobookshelf/app/AudiobookProgressSyncer.kt b/android/app/src/main/java/com/audiobookshelf/app/player/AudiobookProgressSyncer.kt similarity index 91% rename from android/app/src/main/java/com/audiobookshelf/app/AudiobookProgressSyncer.kt rename to android/app/src/main/java/com/audiobookshelf/app/player/AudiobookProgressSyncer.kt index ff6b19d1..cff536f3 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/AudiobookProgressSyncer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/AudiobookProgressSyncer.kt @@ -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 diff --git a/android/app/src/main/java/com/audiobookshelf/app/BrowseTree.kt b/android/app/src/main/java/com/audiobookshelf/app/player/BrowseTree.kt similarity index 97% rename from android/app/src/main/java/com/audiobookshelf/app/BrowseTree.kt rename to android/app/src/main/java/com/audiobookshelf/app/player/BrowseTree.kt index 1bcece9c..4053fe11 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/BrowseTree.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/BrowseTree.kt @@ -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( diff --git a/android/app/src/main/java/com/audiobookshelf/app/CastManager.kt b/android/app/src/main/java/com/audiobookshelf/app/player/CastManager.kt similarity index 99% rename from android/app/src/main/java/com/audiobookshelf/app/CastManager.kt rename to android/app/src/main/java/com/audiobookshelf/app/player/CastManager.kt index 854eeaf6..36458548 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/CastManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/CastManager.kt @@ -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) { diff --git a/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt similarity index 85% rename from android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt rename to android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index bc066ec2..643e19ba 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -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") diff --git a/android/app/src/main/java/com/audiobookshelf/app/ShakeDetector.kt b/android/app/src/main/java/com/audiobookshelf/app/player/ShakeDetector.kt similarity index 97% rename from android/app/src/main/java/com/audiobookshelf/app/ShakeDetector.kt rename to android/app/src/main/java/com/audiobookshelf/app/player/ShakeDetector.kt index c61974a5..c6126e31 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/ShakeDetector.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/ShakeDetector.kt @@ -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 { diff --git a/android/app/src/main/java/com/audiobookshelf/app/SleepTimerManager.kt b/android/app/src/main/java/com/audiobookshelf/app/player/SleepTimerManager.kt similarity index 99% rename from android/app/src/main/java/com/audiobookshelf/app/SleepTimerManager.kt rename to android/app/src/main/java/com/audiobookshelf/app/player/SleepTimerManager.kt index e0d0ec60..39d2a389 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/SleepTimerManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/SleepTimerManager.kt @@ -1,4 +1,4 @@ -package com.audiobookshelf.app +package com.audiobookshelf.app.player import android.os.Handler import android.os.Looper