mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-29 06:37:16 +02:00
Android cleaning up and refactoring
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
+2
-1
@@ -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
|
||||||
+11
-9
@@ -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
|
||||||
+3
-1
@@ -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(
|
||||||
+2
-2
@@ -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) {
|
||||||
+71
-150
@@ -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
-2
@@ -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
-1
@@ -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
|
||||||
Reference in New Issue
Block a user