mirror of
https://github.com/sudoxnym/audiobookshelf-atv.git
synced 2026-04-14 19:46:30 +00:00
Change: laying the groundwork for android auto
This commit is contained in:
parent
44f535020d
commit
52e3ea0a99
11 changed files with 423 additions and 175 deletions
|
|
@ -39,16 +39,18 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation "com.anggrayudi:storage:0.13.0"
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation project(':capacitor-android')
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation project(':capacitor-android')
|
||||
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
|
||||
implementation "androidx.core:core-ktx:1.6.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@
|
|||
android:resource="@drawable/icon" />
|
||||
|
||||
<!-- Android auto rejected the update, removing this so it can be published on the store -->
|
||||
<!-- <meta-data-->
|
||||
<!-- android:name="com.google.android.gms.car.application"-->
|
||||
<!-- android:resource="@xml/automotive_app_desc"/>-->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc"/>
|
||||
|
||||
<!-- TODO: Can remove in future -->
|
||||
<!-- <provider-->
|
||||
|
|
|
|||
|
|
@ -4,66 +4,42 @@ import android.net.Uri
|
|||
import com.getcapacitor.JSObject
|
||||
|
||||
class Audiobook {
|
||||
var id:String = "audiobook"
|
||||
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 id:String
|
||||
var ino:String
|
||||
var libraryId:String
|
||||
var folderId:String
|
||||
var book:Book
|
||||
var duration:Float
|
||||
var size:Long
|
||||
var numTracks:Int
|
||||
var isMissing:Boolean
|
||||
var isInvalid:Boolean
|
||||
var path:String
|
||||
|
||||
var isLocal:Boolean = false
|
||||
var contentUrl:String = ""
|
||||
var fallbackCover:Uri
|
||||
var fallbackUri:Uri
|
||||
|
||||
var hasPlayerLoaded:Boolean = false
|
||||
constructor(jsobj: JSObject) {
|
||||
id = jsobj.getString("id", "").toString()
|
||||
ino = jsobj.getString("ino", "").toString()
|
||||
libraryId = jsobj.getString("libraryId", "").toString()
|
||||
folderId = jsobj.getString("folderId", "").toString()
|
||||
|
||||
var playlistUri:Uri = Uri.EMPTY
|
||||
var coverUri:Uri = Uri.EMPTY
|
||||
var contentUri:Uri = Uri.EMPTY // For Local only
|
||||
var bookJsObj = jsobj.getJSObject("book")
|
||||
book = bookJsObj?.let { Book(it) }!!
|
||||
|
||||
constructor(jsondata:JSObject) {
|
||||
id = jsondata.getString("id", "audiobook").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
|
||||
duration = jsobj.getDouble("duration").toFloat()
|
||||
size = jsobj.getLong("size")
|
||||
numTracks = jsobj.getInteger("numTracks")!!
|
||||
isMissing = jsobj.getBoolean("isMissing")
|
||||
isInvalid = jsobj.getBoolean("isInvalid")
|
||||
path = jsobj.getString("path", "").toString()
|
||||
|
||||
if (jsondata.has("startTime")) {
|
||||
startTime = jsondata.getString("startTime", "0")!!.toLong()
|
||||
}
|
||||
fallbackUri = Uri.parse("http://fallback.com/run.mp3")
|
||||
fallbackCover = Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
||||
}
|
||||
|
||||
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 != "") {
|
||||
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)
|
||||
}
|
||||
fun getCover(serverUrl:String, token:String):Uri {
|
||||
return Uri.parse("$serverUrl/${book.cover}?token=$token")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
package com.audiobookshelf.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
|
||||
import android.util.Log
|
||||
import androidx.media.MediaBrowserServiceCompat
|
||||
import com.getcapacitor.JSArray
|
||||
import com.getcapacitor.JSObject
|
||||
import com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlite
|
||||
import okhttp3.*
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
class AudiobookManager {
|
||||
var tag = "AudiobookManager"
|
||||
|
||||
var hasLoaded = false
|
||||
var ctx: Context
|
||||
var serverUrl = ""
|
||||
var token = ""
|
||||
private var client:OkHttpClient
|
||||
|
||||
var audiobooks:MutableList<Audiobook> = mutableListOf()
|
||||
|
||||
constructor(_ctx:Context, _client:OkHttpClient) {
|
||||
ctx = _ctx
|
||||
client = _client
|
||||
}
|
||||
|
||||
fun init() {
|
||||
var sharedPreferences = ctx.getSharedPreferences("CapacitorStorage", Activity.MODE_PRIVATE)
|
||||
serverUrl = sharedPreferences.getString("serverUrl", null).toString()
|
||||
Log.d(tag, "SHARED PREF SERVERURL $serverUrl")
|
||||
token = sharedPreferences.getString("token", null).toString()
|
||||
Log.d(tag, "SHARED PREF TOKEN $token")
|
||||
}
|
||||
|
||||
fun fetchAudiobooks(result: MediaBrowserServiceCompat.Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||
var url = "$serverUrl/api/library/main/audiobooks"
|
||||
Log.d(tag, "RUNNING SAMPLER $url")
|
||||
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")
|
||||
// for ((name, value) in response.headers) {
|
||||
// Log.d(tag, "HEADER $name: $value")
|
||||
// }
|
||||
|
||||
var bodyString = response.body!!.string()
|
||||
var json = JSArray(bodyString)
|
||||
var totalBooks = json.length() - 1
|
||||
for (i in 0..totalBooks) {
|
||||
var abobj = json.get(i)
|
||||
var jsobj = JSObject(abobj.toString())
|
||||
var audiobook = Audiobook(jsobj)
|
||||
audiobooks.add(audiobook)
|
||||
Log.d(tag, "Audiobook: ${audiobook.toString()}")
|
||||
}
|
||||
Log.d(tag, "Audiobooks Loaded")
|
||||
|
||||
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||
audiobooks.forEach {
|
||||
var builder = MediaDescriptionCompat.Builder()
|
||||
.setMediaId(it.id)
|
||||
.setTitle(it.book.title)
|
||||
.setSubtitle(it.book.authorFL)
|
||||
.setMediaUri(it.fallbackUri)
|
||||
.setIconUri(it.fallbackCover)
|
||||
|
||||
var mediaDescription = builder.build()
|
||||
var newMediaItem = MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
mediaItems.add(newMediaItem)
|
||||
|
||||
}
|
||||
Log.d(tag, "AudiobookManager: Sending ${mediaItems.size} Aduiobooks")
|
||||
result.sendResult(mediaItems)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun load() {
|
||||
hasLoaded = true
|
||||
|
||||
var db = CapacitorDataStorageSqlite(ctx)
|
||||
db.openStore("storage", "downloads", false, "no-encryption", 1)
|
||||
Log.d(tag, "CHECK IF DB IS OPEN ${db.isStoreOpen("storage")}")
|
||||
var keyvalues = db.keysvalues()
|
||||
Log.d(tag, "KEY VALUES $keyvalues")
|
||||
keyvalues.forEach { Log.d(tag, "keyvalue ${it.getString("key")} | ${it.getString("value")}") }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.audiobookshelf.app
|
||||
|
||||
import android.net.Uri
|
||||
import com.getcapacitor.JSObject
|
||||
|
||||
class AudiobookStreamData {
|
||||
var id:String = "audiobook"
|
||||
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 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", "audiobook").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 != "") {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
39
android/app/src/main/java/com/audiobookshelf/app/Book.kt
Normal file
39
android/app/src/main/java/com/audiobookshelf/app/Book.kt
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package com.audiobookshelf.app
|
||||
|
||||
import com.getcapacitor.JSObject
|
||||
|
||||
class Book {
|
||||
var title:String
|
||||
var subtitle:String
|
||||
var author:String
|
||||
var authorFL:String
|
||||
var narrator:String
|
||||
var series:String
|
||||
var volumeNumber:String
|
||||
var publisher:String
|
||||
var description:String
|
||||
var publishYear:String
|
||||
var language:String
|
||||
var cover:String
|
||||
var coverFullPath:String
|
||||
var genres:String
|
||||
var lastUpdate:Long
|
||||
|
||||
constructor(jsobj: JSObject) {
|
||||
title = jsobj.getString("title", "").toString()
|
||||
subtitle = jsobj.getString("subtitle", "").toString()
|
||||
author = jsobj.getString("author", "").toString()
|
||||
authorFL = jsobj.getString("authorFL", "").toString()
|
||||
narrator = jsobj.getString("narrator", "").toString()
|
||||
series = jsobj.getString("series", "").toString()
|
||||
volumeNumber = jsobj.getString("volumeNumber", "").toString()
|
||||
publisher = jsobj.getString("publisher", "").toString()
|
||||
description = jsobj.getString("description", "").toString()
|
||||
publishYear = jsobj.getString("publishYear", "").toString()
|
||||
language = jsobj.getString("language", "").toString()
|
||||
cover = jsobj.getString("cover", "").toString()
|
||||
coverFullPath = jsobj.getString("coverFullPath", "").toString()
|
||||
genres = jsobj.getString("genres", "").toString()
|
||||
lastUpdate = jsobj.getLong("lastUpdate")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.audiobookshelf.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.DownloadManager
|
||||
import android.content.*
|
||||
import android.os.*
|
||||
|
|
@ -7,6 +8,11 @@ import android.util.Log
|
|||
import com.anggrayudi.storage.SimpleStorage
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.getcapacitor.BridgeActivity
|
||||
import com.getcapacitor.JSObject
|
||||
import com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlite
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.net.URL
|
||||
|
||||
|
||||
class MainActivity : BridgeActivity() {
|
||||
|
|
@ -40,6 +46,13 @@ class MainActivity : BridgeActivity() {
|
|||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// REMOVE FOR TESTING
|
||||
Log.d(tag, "STARTING UP APP")
|
||||
// var client: OkHttpClient = OkHttpClient()
|
||||
// var abManager = AudiobookManager(this, client)
|
||||
// abManager.init()
|
||||
// abManager.fetchAudiobooks()
|
||||
|
||||
Log.d(tag, "onCreate")
|
||||
registerPlugin(MyNativeAudio::class.java)
|
||||
registerPlugin(AudioDownloader::class.java)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import androidx.core.content.ContextCompat
|
|||
import com.getcapacitor.*
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
@CapacitorPlugin(name = "MyNativeAudio")
|
||||
class MyNativeAudio : Plugin() {
|
||||
|
|
@ -64,8 +62,8 @@ class MyNativeAudio : Plugin() {
|
|||
}
|
||||
var jsobj = JSObject()
|
||||
|
||||
var audiobook:Audiobook = Audiobook(call.data)
|
||||
if (audiobook.playlistUrl == "" && audiobook.contentUrl == "") {
|
||||
var audiobookStreamData:AudiobookStreamData = AudiobookStreamData(call.data)
|
||||
if (audiobookStreamData.playlistUrl == "" && audiobookStreamData.contentUrl == "") {
|
||||
Log.e(tag, "Invalid URL for init audio player")
|
||||
|
||||
jsobj.put("success", false)
|
||||
|
|
@ -73,7 +71,7 @@ class MyNativeAudio : Plugin() {
|
|||
}
|
||||
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
playerNotificationService.initPlayer(audiobook)
|
||||
playerNotificationService.initPlayer(audiobookStreamData)
|
||||
jsobj.put("success", true)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
|
@ -174,38 +172,38 @@ class MyNativeAudio : Plugin() {
|
|||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun setAudiobooks(call: PluginCall) {
|
||||
var audiobooks = call.getArray("audiobooks", JSArray())
|
||||
if (audiobooks == null) {
|
||||
Log.w(tag, "setAudiobooks IS NULL")
|
||||
call.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
var audiobookObjs = mutableListOf<Audiobook>()
|
||||
|
||||
var len = audiobooks.length()
|
||||
(0 until len).forEach { _it ->
|
||||
var jsonobj = audiobooks.get(_it) as JSONObject
|
||||
|
||||
var _names = Array(jsonobj.names().length()) {
|
||||
jsonobj.names().getString(it)
|
||||
}
|
||||
var jsobj = JSObject(jsonobj, _names)
|
||||
|
||||
if (jsobj.has("duration")) {
|
||||
var dur = jsobj.getDouble("duration")
|
||||
var duration = Math.floor(dur * 1000L).toLong()
|
||||
jsobj.put("duration", duration)
|
||||
}
|
||||
|
||||
var audiobook = Audiobook(jsobj)
|
||||
audiobookObjs.add(audiobook)
|
||||
}
|
||||
Log.d(tag, "Setting Audiobooks ${audiobookObjs.size}")
|
||||
playerNotificationService.setAudiobooks(audiobookObjs)
|
||||
}
|
||||
// @PluginMethod
|
||||
// fun setAudiobooks(call: PluginCall) {
|
||||
// var audiobooks = call.getArray("audiobooks", JSArray())
|
||||
// if (audiobooks == null) {
|
||||
// Log.w(tag, "setAudiobooks IS NULL")
|
||||
// call.resolve()
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var audiobookObjs = mutableListOf<AudiobookStreamData>()
|
||||
//
|
||||
// var len = audiobooks.length()
|
||||
// (0 until len).forEach { _it ->
|
||||
// var jsonobj = audiobooks.get(_it) as JSONObject
|
||||
//
|
||||
// var _names = Array(jsonobj.names().length()) {
|
||||
// jsonobj.names().getString(it)
|
||||
// }
|
||||
// var jsobj = JSObject(jsonobj, _names)
|
||||
//
|
||||
// if (jsobj.has("duration")) {
|
||||
// var dur = jsobj.getDouble("duration")
|
||||
// var duration = Math.floor(dur * 1000L).toLong()
|
||||
// jsobj.put("duration", duration)
|
||||
// }
|
||||
//
|
||||
// var audiobook = AudiobookStreamData(jsobj)
|
||||
// audiobookObjs.add(audiobook)
|
||||
// }
|
||||
// Log.d(tag, "Setting Audiobooks ${audiobookObjs.size}")
|
||||
// playerNotificationService.setAudiobooks(audiobookObjs)
|
||||
// }
|
||||
|
||||
@PluginMethod
|
||||
fun setSleepTimer(call: PluginCall) {
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
|||
import com.google.android.exoplayer2.upstream.*
|
||||
import kotlinx.coroutines.*
|
||||
import android.view.KeyEvent
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import android.annotation.SuppressLint
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
|
||||
const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px
|
||||
|
|
@ -74,9 +74,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
private var channelId = "audiobookshelf_channel"
|
||||
private var channelName = "Audiobookshelf Channel"
|
||||
|
||||
private var currentAudiobook:Audiobook? = null
|
||||
private var currentAudiobookStreamData:AudiobookStreamData? = null
|
||||
|
||||
private var audiobooks = mutableListOf<Audiobook>()
|
||||
// private var audiobooks = mutableListOf<AudiobookStreamData>()
|
||||
|
||||
private var mediaButtonClickCount: Int = 0
|
||||
var mediaButtonClickTimeout: Long = 1000 //ms
|
||||
|
|
@ -88,6 +88,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
private var sleepTimerTask:TimerTask? = null
|
||||
private var sleepChapterTime:Long = 0L
|
||||
|
||||
private lateinit var audiobookManager:AudiobookManager
|
||||
|
||||
fun setCustomObjectListener(mylistener: MyCustomObjectListener) {
|
||||
listener = mylistener
|
||||
}
|
||||
|
|
@ -160,6 +162,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ctx = this
|
||||
var client: OkHttpClient = OkHttpClient()
|
||||
audiobookManager = AudiobookManager(ctx, client)
|
||||
audiobookManager.init()
|
||||
|
||||
channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel(channelId, channelName)
|
||||
|
|
@ -252,11 +257,11 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
val queueNavigator: TimelineQueueNavigator = object : TimelineQueueNavigator(mediaSession) {
|
||||
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat {
|
||||
var builder = MediaDescriptionCompat.Builder()
|
||||
.setMediaId(currentAudiobook!!.id)
|
||||
.setTitle(currentAudiobook!!.title)
|
||||
.setSubtitle(currentAudiobook!!.author)
|
||||
.setMediaUri(currentAudiobook!!.playlistUri)
|
||||
.setIconUri(currentAudiobook!!.coverUri)
|
||||
.setMediaId(currentAudiobookStreamData!!.id)
|
||||
.setTitle(currentAudiobookStreamData!!.title)
|
||||
.setSubtitle(currentAudiobookStreamData!!.author)
|
||||
.setMediaUri(currentAudiobookStreamData!!.playlistUri)
|
||||
.setIconUri(currentAudiobookStreamData!!.coverUri)
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -277,7 +282,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
override fun onPrepare(playWhenReady: Boolean) {
|
||||
Log.d(tag, "ON PREPARE $playWhenReady")
|
||||
|
||||
var audiobook = audiobooks[0]
|
||||
var audiobook = audiobookManager.audiobooks[0]
|
||||
if (audiobook == null) {
|
||||
Log.e(tag, "Audiobook NOT FOUND")
|
||||
return
|
||||
|
|
@ -287,7 +292,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
|
||||
override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
|
||||
Log.d(tag, "ON PREPARE FROM MEDIA ID $mediaId $playWhenReady")
|
||||
var audiobook = audiobooks.find { it.id == mediaId }
|
||||
var audiobook = audiobookManager.audiobooks.find { it.id == mediaId }
|
||||
if (audiobook == null) {
|
||||
Log.e(tag, "Audiobook NOT FOUND")
|
||||
return
|
||||
|
|
@ -391,7 +396,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
mPlayer.seekTo(currentAudiobook!!.startTime)
|
||||
}*/
|
||||
|
||||
currentAudiobook!!.hasPlayerLoaded = true
|
||||
currentAudiobookStreamData!!.hasPlayerLoaded = true
|
||||
if (lastPauseTime == 0L) {
|
||||
sendClientMetadata("ready_no_sync")
|
||||
lastPauseTime = -1;
|
||||
|
|
@ -448,27 +453,27 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
*/
|
||||
|
||||
// fun initPlayer(token: String, playlistUri: String, playWhenReady: Boolean, currentTime: Long, title: String, artist: String, albumArt: String) {
|
||||
fun initPlayer(audiobook: Audiobook) {
|
||||
currentAudiobook = audiobook
|
||||
fun initPlayer(audiobookStreamData: AudiobookStreamData) {
|
||||
currentAudiobookStreamData = audiobookStreamData
|
||||
|
||||
Log.d(tag, "Init Player Audiobook ${currentAudiobook!!.playlistUrl} | ${currentAudiobook!!.title} | ${currentAudiobook!!.author}")
|
||||
Log.d(tag, "Init Player Audiobook ${currentAudiobookStreamData!!.playlistUrl} | ${currentAudiobookStreamData!!.title} | ${currentAudiobookStreamData!!.author}")
|
||||
|
||||
if (mPlayer.isPlaying) {
|
||||
Log.d(tag, "Init Player audiobook already playing")
|
||||
}
|
||||
|
||||
var metadataBuilder = MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, currentAudiobook!!.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, currentAudiobook!!.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, currentAudiobook!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, currentAudiobook!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, currentAudiobook!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, currentAudiobook!!.series)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, currentAudiobook!!.id)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, currentAudiobookStreamData!!.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, currentAudiobookStreamData!!.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, currentAudiobookStreamData!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, currentAudiobookStreamData!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, currentAudiobookStreamData!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, currentAudiobookStreamData!!.series)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, currentAudiobookStreamData!!.id)
|
||||
|
||||
if (currentAudiobook!!.cover != "") {
|
||||
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, currentAudiobook!!.cover)
|
||||
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, currentAudiobook!!.cover)
|
||||
if (currentAudiobookStreamData!!.cover != "") {
|
||||
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, currentAudiobookStreamData!!.cover)
|
||||
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, currentAudiobookStreamData!!.cover)
|
||||
}
|
||||
|
||||
var metadata = metadataBuilder.build()
|
||||
|
|
@ -478,27 +483,27 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
|
||||
|
||||
var mediaSource:MediaSource
|
||||
if (currentAudiobook!!.isLocal) {
|
||||
if (currentAudiobookStreamData!!.isLocal) {
|
||||
Log.d(tag, "Playing Local File")
|
||||
var mediaItem = MediaItem.Builder().setUri(currentAudiobook!!.contentUri).setMediaMetadata(mediaMetadata).build()
|
||||
var mediaItem = MediaItem.Builder().setUri(currentAudiobookStreamData!!.contentUri).setMediaMetadata(mediaMetadata).build()
|
||||
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
||||
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
} else {
|
||||
Log.d(tag, "Playing HLS File")
|
||||
var mediaItem = MediaItem.Builder().setUri(currentAudiobook!!.playlistUri).setMediaMetadata(mediaMetadata).build()
|
||||
var mediaItem = MediaItem.Builder().setUri(currentAudiobookStreamData!!.playlistUri).setMediaMetadata(mediaMetadata).build()
|
||||
var dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||
dataSourceFactory.setUserAgent(channelId)
|
||||
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobook!!.token}"))
|
||||
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobookStreamData!!.token}"))
|
||||
|
||||
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
}
|
||||
|
||||
|
||||
//mPlayer.setMediaSource(mediaSource, true)
|
||||
mPlayer.setMediaSource(mediaSource, currentAudiobook!!.startTime)
|
||||
mPlayer.setMediaSource(mediaSource, currentAudiobookStreamData!!.startTime)
|
||||
mPlayer.prepare()
|
||||
mPlayer.playWhenReady = currentAudiobook!!.playWhenReady
|
||||
mPlayer.setPlaybackSpeed(audiobook.playbackSpeed)
|
||||
mPlayer.playWhenReady = currentAudiobookStreamData!!.playWhenReady
|
||||
mPlayer.setPlaybackSpeed(audiobookStreamData.playbackSpeed)
|
||||
|
||||
lastPauseTime = 0
|
||||
}
|
||||
|
|
@ -534,7 +539,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
}
|
||||
|
||||
fun getCurrentAudiobookId() : String {
|
||||
return currentAudiobook?.id.toString()
|
||||
return currentAudiobookStreamData?.id.toString()
|
||||
}
|
||||
|
||||
fun play() {
|
||||
|
|
@ -569,7 +574,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
if (mPlayer.playbackState == Player.STATE_READY) {
|
||||
mPlayer.clearMediaItems()
|
||||
}
|
||||
currentAudiobook?.id = ""
|
||||
currentAudiobookStreamData?.id = ""
|
||||
lastPauseTime = 0
|
||||
}
|
||||
|
||||
|
|
@ -589,9 +594,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
//
|
||||
private val MY_MEDIA_ROOT_ID = "audiobookshelf"
|
||||
|
||||
fun setAudiobooks(_audiobooks:MutableList<Audiobook>) {
|
||||
audiobooks = _audiobooks
|
||||
}
|
||||
|
||||
private fun isValid(packageName:String, uid:Int) : Boolean {
|
||||
Log.d(tag, "Check package $packageName is valid with uid $uid")
|
||||
|
|
@ -620,25 +622,27 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
override fun onLoadChildren(parentMediaId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||
|
||||
if (audiobooks.size == 0) {
|
||||
if (!audiobookManager.hasLoaded) {
|
||||
Log.d(tag, "audiobook manager loading")
|
||||
result.detach()
|
||||
audiobookManager.load()
|
||||
audiobookManager.fetchAudiobooks(result)
|
||||
return
|
||||
}
|
||||
|
||||
if (audiobookManager.audiobooks.size == 0) {
|
||||
Log.d(tag, "AudiobookManager: Sending no items")
|
||||
result.sendResult(mediaItems)
|
||||
return
|
||||
}
|
||||
|
||||
audiobooks.forEach {
|
||||
audiobookManager.audiobooks.forEach {
|
||||
var builder = MediaDescriptionCompat.Builder()
|
||||
.setMediaId(it.id)
|
||||
.setTitle(it.title)
|
||||
.setSubtitle(it.author)
|
||||
.setMediaUri(it.playlistUri)
|
||||
.setIconUri(it.coverUri)
|
||||
|
||||
// val extras = Bundle()
|
||||
// var startsWithA = it.title.toLowerCase().startsWith("a")
|
||||
// var groupTitle = "test group
|
||||
// extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, groupTitle)
|
||||
// builder.setExtras(extras)\
|
||||
// Log.d(tag, "Load Media Item for AUTO ${it.title} - ${it.author}")
|
||||
.setTitle(it.book.title)
|
||||
.setSubtitle(it.book.authorFL)
|
||||
.setMediaUri(it.fallbackUri)
|
||||
.setIconUri(it.getCover(audiobookManager.serverUrl, audiobookManager.token))
|
||||
|
||||
var mediaDescription = builder.build()
|
||||
var newMediaItem = MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
|
|
@ -654,6 +658,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
// examine the passed parentMediaId to see which submenu we're at,
|
||||
// and put the children of that menu in the mediaItems list
|
||||
}
|
||||
Log.d(tag, "AudiobookManager: Sending ${mediaItems.size} Aduiobooks")
|
||||
result.sendResult(mediaItems)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ class StoreService {
|
|||
isService = false
|
||||
platform
|
||||
isOpen = false
|
||||
tableName = 'downloads'
|
||||
|
||||
constructor() {
|
||||
this.init()
|
||||
|
|
@ -323,31 +322,75 @@ class StoreService {
|
|||
}
|
||||
}
|
||||
|
||||
async getDownload(id) {
|
||||
async setServerConfig(config) {
|
||||
if (!this.isOpen) {
|
||||
var success = await this.openStore('storage', this.tableName)
|
||||
var success = await this.openStore('storage', 'serverConfig')
|
||||
if (!success) {
|
||||
console.error('Store failed to open')
|
||||
return null
|
||||
return false
|
||||
}
|
||||
}
|
||||
try {
|
||||
var value = await this.getItem(id)
|
||||
return JSON.parse(value)
|
||||
await this.setTable('serverConfig')
|
||||
} catch (error) {
|
||||
console.error('Failed to get download from store', error)
|
||||
console.error('Failed to set table', error)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.setItem('config', JSON.stringify(config))
|
||||
console.log(`[STORE] Set Server Config`)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to set server config in store', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async getServerConfig() {
|
||||
if (!this.isOpen) {
|
||||
var success = await this.openStore('storage', 'serverConfig')
|
||||
if (!success) {
|
||||
console.error('Store failed to open')
|
||||
return false
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.setTable('serverConfig')
|
||||
} catch (error) {
|
||||
console.error('Failed to set table', error)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
var configVal = await this.getItem('config')
|
||||
if (!configVal) {
|
||||
console.log(`[STORE] server config not available`)
|
||||
return null
|
||||
}
|
||||
var config = JSON.parse(configVal)
|
||||
console.log(`[STORE] Got Server Config`, JSON.stringify(config))
|
||||
return config
|
||||
} catch (error) {
|
||||
console.error('Failed to set server config in store', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async setDownload(download) {
|
||||
if (!this.isOpen) {
|
||||
var success = await this.openStore('storage', this.tableName)
|
||||
var success = await this.openStore('storage', 'downloads')
|
||||
if (!success) {
|
||||
console.error('Store failed to open')
|
||||
return false
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.setTable('downloads')
|
||||
} catch (error) {
|
||||
console.error('Failed to set table', error)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.setItem(download.id, JSON.stringify(download))
|
||||
|
|
@ -361,12 +404,18 @@ class StoreService {
|
|||
|
||||
async removeDownload(id) {
|
||||
if (!this.isOpen) {
|
||||
var success = await this.openStore('storage', this.tableName)
|
||||
var success = await this.openStore('storage', 'downloads')
|
||||
if (!success) {
|
||||
console.error('Store failed to open')
|
||||
return false
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.setTable('downloads')
|
||||
} catch (error) {
|
||||
console.error('Failed to set table', error)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.removeItem(id)
|
||||
|
|
@ -380,12 +429,19 @@ class StoreService {
|
|||
|
||||
async getAllDownloads() {
|
||||
if (!this.isOpen) {
|
||||
var success = await this.openStore('storage', this.tableName)
|
||||
var success = await this.openStore('storage', 'downloads')
|
||||
if (!success) {
|
||||
console.error('Store failed to open')
|
||||
return []
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.setTable('downloads')
|
||||
} catch (error) {
|
||||
console.error('Failed to set table', error)
|
||||
return
|
||||
}
|
||||
|
||||
var keysvalues = await this.getAllKeysValues()
|
||||
var downloads = []
|
||||
|
||||
|
|
|
|||
|
|
@ -145,21 +145,6 @@ export const actions = {
|
|||
},
|
||||
useDownloaded({ commit, rootGetters }) {
|
||||
commit('set', rootGetters['downloads/getAudiobooks'])
|
||||
},
|
||||
setNativeAudiobooks({ state }) {
|
||||
var audiobooks = state.audiobooks.map(ab => {
|
||||
var _book = ab.book
|
||||
return {
|
||||
id: ab.id,
|
||||
title: _book.title,
|
||||
author: _book.author,
|
||||
duration: ab.duration,
|
||||
size: ab.size,
|
||||
cover: _book.cover || '',
|
||||
series: _book.series || ''
|
||||
}
|
||||
})
|
||||
MyNativeAudio.setAudiobooks({ audiobooks: audiobooks })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue