mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-30 07:07:22 +02:00
Initial android auto support, fix bug saving user settings
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId "com.audiobookshelf.app"
|
applicationId "com.audiobookshelf.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 15
|
versionCode 16
|
||||||
versionName "0.9.0-beta"
|
versionName "0.9.1-beta"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />-->
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -31,18 +30,25 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<provider
|
<!--Used by Android Auto-->
|
||||||
android:name="androidx.core.content.FileProvider"
|
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:resource="@drawable/icon" />
|
||||||
android:exported="false"
|
<meta-data
|
||||||
android:grantUriPermissions="true">
|
android:name="com.google.android.gms.car.application"
|
||||||
<meta-data
|
android:resource="@xml/automotive_app_desc"/>
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths" />
|
<!-- TODO: Can remove in future -->
|
||||||
</provider>
|
<!-- <provider-->
|
||||||
|
<!-- android:name="androidx.core.content.FileProvider"-->
|
||||||
|
<!-- android:authorities="${applicationId}.fileprovider"-->
|
||||||
|
<!-- android:exported="false"-->
|
||||||
|
<!-- android:grantUriPermissions="true">-->
|
||||||
|
<!-- <meta-data-->
|
||||||
|
<!-- android:name="android.support.FILE_PROVIDER_PATHS"-->
|
||||||
|
<!-- android:resource="@xml/file_paths" />-->
|
||||||
|
<!-- </provider>-->
|
||||||
|
|
||||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -51,8 +57,12 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
android:exported="true"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:name=".PlayerNotificationService">
|
android:name=".PlayerNotificationService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||||
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -34,9 +34,19 @@ class Audiobook {
|
|||||||
cover = jsondata.getString("cover", "").toString()
|
cover = jsondata.getString("cover", "").toString()
|
||||||
playlistUrl = jsondata.getString("playlistUrl", "").toString()
|
playlistUrl = jsondata.getString("playlistUrl", "").toString()
|
||||||
playWhenReady = jsondata.getBoolean("playWhenReady", false) == true
|
playWhenReady = jsondata.getBoolean("playWhenReady", false) == true
|
||||||
startTime = jsondata.getString("startTime", "0")!!.toLong()
|
|
||||||
playbackSpeed = jsondata.getDouble("playbackSpeed")!!.toFloat()
|
if (jsondata.has("startTime")) {
|
||||||
duration = jsondata.getString("duration", "0")!!.toLong()
|
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
|
// Local data
|
||||||
isLocal = jsondata.getBoolean("isLocal", false) == true
|
isLocal = jsondata.getBoolean("isLocal", false) == true
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ class MainActivity : BridgeActivity() {
|
|||||||
|
|
||||||
mConnection = object : ServiceConnection {
|
mConnection = object : ServiceConnection {
|
||||||
override fun onServiceDisconnected(name: ComponentName) {
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
Log.w(tag, "Service Disconnected")
|
Log.w(tag, "Service Disconnected $name")
|
||||||
mBounded = false
|
mBounded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
Log.d(tag, "Service Connected")
|
Log.d(tag, "Service Connected $name")
|
||||||
|
|
||||||
|
|
||||||
mBounded = true
|
mBounded = true
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ 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 androidx.core.content.ContextCompat
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.*
|
||||||
import com.getcapacitor.Plugin
|
|
||||||
import com.getcapacitor.PluginCall
|
|
||||||
import com.getcapacitor.PluginMethod
|
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
@CapacitorPlugin(name = "MyNativeAudio")
|
@CapacitorPlugin(name = "MyNativeAudio")
|
||||||
class MyNativeAudio : Plugin() {
|
class MyNativeAudio : Plugin() {
|
||||||
@@ -31,6 +29,13 @@ class MyNativeAudio : Plugin() {
|
|||||||
override fun onMetadata(metadata:JSObject) {
|
override fun onMetadata(metadata:JSObject) {
|
||||||
notifyListeners("onMetadata", metadata)
|
notifyListeners("onMetadata", metadata)
|
||||||
}
|
}
|
||||||
|
override fun onPrepare(audiobookId:String, playWhenReady:Boolean) {
|
||||||
|
var jsobj = JSObject()
|
||||||
|
jsobj.put("audiobookId", audiobookId)
|
||||||
|
jsobj.put("playWhenReady", playWhenReady)
|
||||||
|
notifyListeners("onPrepareMedia", jsobj)
|
||||||
|
}
|
||||||
|
override fun onCar() {}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
mainActivity.pluginCallback = foregroundServiceReady
|
mainActivity.pluginCallback = foregroundServiceReady
|
||||||
@@ -141,4 +146,37 @@ class MyNativeAudio : Plugin() {
|
|||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ import android.content.Intent
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Binder
|
import android.os.*
|
||||||
import android.os.Build
|
import android.support.v4.media.MediaBrowserCompat
|
||||||
import android.os.IBinder
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.support.v4.media.MediaDescriptionCompat
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import android.support.v4.media.session.MediaControllerCompat
|
import android.support.v4.media.session.MediaControllerCompat
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
import android.support.v4.media.session.PlaybackStateCompat
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
|
import androidx.media.utils.MediaConstants
|
||||||
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
|
||||||
@@ -25,20 +26,17 @@ import com.google.android.exoplayer2.*
|
|||||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||||
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
|
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
|
||||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource
|
import com.google.android.exoplayer2.source.MediaSource
|
||||||
import com.google.android.exoplayer2.source.ProgressiveMediaExtractor
|
|
||||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
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 kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px
|
const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px
|
||||||
|
|
||||||
class PlayerNotificationService : Service() {
|
class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var isStarted = false
|
var isStarted = false
|
||||||
@@ -47,6 +45,8 @@ class PlayerNotificationService : Service() {
|
|||||||
interface MyCustomObjectListener {
|
interface MyCustomObjectListener {
|
||||||
fun onPlayingUpdate(isPlaying: Boolean)
|
fun onPlayingUpdate(isPlaying: Boolean)
|
||||||
fun onMetadata(metadata: JSObject)
|
fun onMetadata(metadata: JSObject)
|
||||||
|
fun onPrepare(audiobookId:String, playWhenReady:Boolean)
|
||||||
|
fun onCar()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val tag = "PlayerService"
|
private val tag = "PlayerService"
|
||||||
@@ -71,6 +71,8 @@ class PlayerNotificationService : Service() {
|
|||||||
|
|
||||||
private var currentAudiobook:Audiobook? = null
|
private var currentAudiobook:Audiobook? = null
|
||||||
|
|
||||||
|
private var audiobooks = mutableListOf<Audiobook>()
|
||||||
|
|
||||||
fun setCustomObjectListener(mylistener: MyCustomObjectListener) {
|
fun setCustomObjectListener(mylistener: MyCustomObjectListener) {
|
||||||
listener = mylistener
|
listener = mylistener
|
||||||
}
|
}
|
||||||
@@ -78,8 +80,14 @@ class PlayerNotificationService : Service() {
|
|||||||
/*
|
/*
|
||||||
Service related stuff
|
Service related stuff
|
||||||
*/
|
*/
|
||||||
override fun onBind(intent: Intent?): IBinder? {
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
Log.d(tag, "onBind")
|
Log.d(tag, "onBind")
|
||||||
|
|
||||||
|
// Android Auto Media Browser Service
|
||||||
|
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||||
|
Log.d(tag, "Is Media Browser Service")
|
||||||
|
return super.onBind(intent);
|
||||||
|
}
|
||||||
return binder
|
return binder
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,11 +105,13 @@ class PlayerNotificationService : Service() {
|
|||||||
Log.d(tag, "onStartCommand $startId")
|
Log.d(tag, "onStartCommand $startId")
|
||||||
isStarted = true
|
isStarted = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStart(intent: Intent?, startId: Int) {
|
||||||
|
Log.d(tag, "onStart $startId" )
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun createNotificationChannel(channelId: String, channelName: String): String{
|
private fun createNotificationChannel(channelId: String, channelName: String): String{
|
||||||
val chan = NotificationChannel(channelId,
|
val chan = NotificationChannel(channelId,
|
||||||
@@ -166,6 +176,9 @@ class PlayerNotificationService : Service() {
|
|||||||
|
|
||||||
val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken)
|
val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken)
|
||||||
|
|
||||||
|
// This is for Media Browser
|
||||||
|
sessionToken = mediaSession.sessionToken
|
||||||
|
|
||||||
val builder = PlayerNotificationManager.Builder(
|
val builder = PlayerNotificationManager.Builder(
|
||||||
ctx,
|
ctx,
|
||||||
notificationId,
|
notificationId,
|
||||||
@@ -232,7 +245,53 @@ class PlayerNotificationService : Service() {
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val myPlaybackPreparer:MediaSessionConnector.PlaybackPreparer = object :MediaSessionConnector.PlaybackPreparer {
|
||||||
|
override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?): Boolean {
|
||||||
|
Log.d(tag, "ON COMMAND $command")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedPrepareActions(): Long {
|
||||||
|
Log.d(tag, "GET SUPORTED ACITONS")
|
||||||
|
return PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
|
||||||
|
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or
|
||||||
|
PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH or
|
||||||
|
PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepare(playWhenReady: Boolean) {
|
||||||
|
Log.d(tag, "ON PREPARE $playWhenReady")
|
||||||
|
|
||||||
|
var audiobook = audiobooks[0]
|
||||||
|
if (audiobook == null) {
|
||||||
|
Log.e(tag, "Audiobook NOT FOUND")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listener.onPrepare(audiobook.id, playWhenReady)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
if (audiobook == null) {
|
||||||
|
Log.e(tag, "Audiobook NOT FOUND")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listener.onPrepare(audiobook.id, playWhenReady)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) {
|
||||||
|
Log.d(tag, "ON PREPARE FROM SEARCH $query")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) {
|
||||||
|
Log.d(tag, "ON PREPARE FROM URI $uri")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
mediaSessionConnector.setQueueNavigator(queueNavigator)
|
mediaSessionConnector.setQueueNavigator(queueNavigator)
|
||||||
|
mediaSessionConnector.setPlaybackPreparer(myPlaybackPreparer)
|
||||||
mediaSessionConnector.setPlayer(mPlayer)
|
mediaSessionConnector.setPlayer(mPlayer)
|
||||||
|
|
||||||
//attach player to playerNotificationManager
|
//attach player to playerNotificationManager
|
||||||
@@ -286,9 +345,6 @@ class PlayerNotificationService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun setPlayerListeners() {
|
private fun setPlayerListeners() {
|
||||||
mPlayer.addListener(object : Player.Listener {
|
mPlayer.addListener(object : Player.Listener {
|
||||||
override fun onPlayerError(error: PlaybackException) {
|
override fun onPlayerError(error: PlaybackException) {
|
||||||
@@ -451,4 +507,81 @@ class PlayerNotificationService : Service() {
|
|||||||
metadata.put("stateName", stateName)
|
metadata.put("stateName", stateName)
|
||||||
if (listener != null) listener.onMetadata(metadata)
|
if (listener != null) listener.onMetadata(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// MEDIA BROWSER STUFF (ANDROID AUTO)
|
||||||
|
//
|
||||||
|
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")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
|
||||||
|
// Verify that the specified package is allowed to access your
|
||||||
|
// content! You'll need to write your own logic to do this.
|
||||||
|
return if (!isValid(clientPackageName, clientUid)) {
|
||||||
|
// If the request comes from an untrusted package, return null.
|
||||||
|
// No further calls will be made to other media browsing methods.
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
listener.onCar()
|
||||||
|
|
||||||
|
val extras = Bundle()
|
||||||
|
extras.putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
|
||||||
|
extras.putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
|
||||||
|
MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, extras)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadChildren(parentMediaId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||||
|
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||||
|
|
||||||
|
if (audiobooks.size == 0) {
|
||||||
|
result.sendResult(mediaItems)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
var mediaDescription = builder.build()
|
||||||
|
var newMediaItem = MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
|
mediaItems.add(newMediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the root menu:
|
||||||
|
if (MY_MEDIA_ROOT_ID == parentMediaId) {
|
||||||
|
// build the MediaItem objects for the top level,
|
||||||
|
// and put them in the mediaItems list
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// examine the passed parentMediaId to see which submenu we're at,
|
||||||
|
// and put the children of that menu in the mediaItems list
|
||||||
|
}
|
||||||
|
result.sendResult(mediaItems)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<automotiveApp>
|
||||||
|
<uses name="media" />
|
||||||
|
</automotiveApp>
|
||||||
+21
-1
@@ -10,11 +10,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Path from 'path'
|
|
||||||
import { Capacitor } from '@capacitor/core'
|
import { Capacitor } from '@capacitor/core'
|
||||||
import { Network } from '@capacitor/network'
|
import { Network } from '@capacitor/network'
|
||||||
import { AppUpdate } from '@robingenz/capacitor-app-update'
|
import { AppUpdate } from '@robingenz/capacitor-app-update'
|
||||||
import AudioDownloader from '@/plugins/audio-downloader'
|
import AudioDownloader from '@/plugins/audio-downloader'
|
||||||
|
import MyNativeAudio from '@/plugins/my-native-audio'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -248,6 +248,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.checkLoadCurrent()
|
this.checkLoadCurrent()
|
||||||
|
this.$store.dispatch('audiobooks/setNativeAudiobooks')
|
||||||
},
|
},
|
||||||
selectDownload(download) {
|
selectDownload(download) {
|
||||||
this.$store.commit('setPlayOnLoad', true)
|
this.$store.commit('setPlayOnLoad', true)
|
||||||
@@ -373,6 +374,25 @@ export default {
|
|||||||
this.checkForUpdate()
|
this.checkForUpdate()
|
||||||
this.initMediaStore()
|
this.initMediaStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For Testing Android Auto
|
||||||
|
MyNativeAudio.addListener('onPrepareMedia', (data) => {
|
||||||
|
var audiobookId = data.audiobookId
|
||||||
|
var playWhenReady = data.playWhenReady
|
||||||
|
|
||||||
|
var audiobook = this.$store.getters['audiobooks/getAudiobook'](audiobookId)
|
||||||
|
|
||||||
|
var download = this.$store.getters['downloads/getDownloadIfReady'](audiobookId)
|
||||||
|
this.$store.commit('setPlayOnLoad', playWhenReady)
|
||||||
|
if (!download) {
|
||||||
|
// Stream
|
||||||
|
this.$store.commit('setStreamAudiobook', audiobook)
|
||||||
|
this.$server.socket.emit('open_stream', audiobook.id)
|
||||||
|
} else {
|
||||||
|
// Local
|
||||||
|
this.$store.commit('setPlayingDownload', download)
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (!this.$server) {
|
if (!this.$server) {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "v0.9.0-beta",
|
"version": "v0.9.1-beta",
|
||||||
"author": "advplyr",
|
"author": "advplyr",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt --hostname localhost --port 1337",
|
"dev": "nuxt --hostname localhost --port 1337",
|
||||||
|
|||||||
@@ -46,7 +46,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Path from 'path'
|
import Path from 'path'
|
||||||
import { Dialog } from '@capacitor/dialog'
|
import { Dialog } from '@capacitor/dialog'
|
||||||
import { Capacitor } from '@capacitor/core'
|
|
||||||
import AudioDownloader from '@/plugins/audio-downloader'
|
import AudioDownloader from '@/plugins/audio-downloader'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
+18
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import MyNativeAudio from '@/plugins/my-native-audio'
|
||||||
import { sort } from '@/assets/fastSort'
|
import { sort } from '@/assets/fastSort'
|
||||||
import { decode } from '@/plugins/init.client'
|
import { decode } from '@/plugins/init.client'
|
||||||
|
|
||||||
@@ -60,12 +61,13 @@ export const getters = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
load({ commit }) {
|
load({ commit, dispatch }) {
|
||||||
return this.$axios
|
return this.$axios
|
||||||
.$get(`/api/audiobooks`)
|
.$get(`/api/audiobooks`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('Audiobooks request data', data)
|
console.log('Audiobooks request data', data)
|
||||||
commit('set', data)
|
commit('set', data)
|
||||||
|
dispatch('setNativeAudiobooks')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
@@ -73,6 +75,21 @@ export const actions = {
|
|||||||
},
|
},
|
||||||
useDownloaded({ commit, rootGetters }) {
|
useDownloaded({ commit, rootGetters }) {
|
||||||
commit('set', rootGetters['downloads/getAudiobooks'])
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-7
@@ -1,5 +1,3 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
user: null,
|
user: null,
|
||||||
localUserAudiobooks: {},
|
localUserAudiobooks: {},
|
||||||
@@ -31,15 +29,13 @@ export const getters = {
|
|||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
async updateUserSettings({ commit }, payload) {
|
async updateUserSettings({ commit }, payload) {
|
||||||
|
if (this.$server.connected) {
|
||||||
if (Vue.prototype.$server.connected) {
|
|
||||||
var updatePayload = {
|
var updatePayload = {
|
||||||
...payload
|
...payload
|
||||||
}
|
}
|
||||||
return this.$axios.$patch('/api/user/settings', updatePayload).then((result) => {
|
return this.$axios.$patch('/api/user/settings', updatePayload).then((result) => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
commit('setSettings', result.settings)
|
commit('setSettings', result.settings)
|
||||||
console.log('Settings updated', result.settings)
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@@ -80,9 +76,7 @@ export const mutations = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
console.log('Update settings in local storage')
|
|
||||||
this.$localStore.setUserSettings({ ...state.settings })
|
this.$localStore.setUserSettings({ ...state.settings })
|
||||||
|
|
||||||
state.settingsListeners.forEach((listener) => {
|
state.settingsListeners.forEach((listener) => {
|
||||||
listener.meth(state.settings)
|
listener.meth(state.settings)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user