Add: play local audio files in android auto to demonstrate to the google app testers that the android auto player works

This commit is contained in:
advplyr 2021-11-18 18:22:38 -06:00
parent 0abefbd9bc
commit 2965ccb513
11 changed files with 176 additions and 23 deletions

View file

@ -13,8 +13,8 @@ android {
applicationId "com.audiobookshelf.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 39
versionName "0.9.20-beta"
versionCode 40
versionName "0.9.21-beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View file

@ -26,11 +26,15 @@ class AudiobookManager {
var token = ""
private var client:OkHttpClient
lateinit var localMediaManager:LocalMediaManager
var audiobooks:MutableList<Audiobook> = mutableListOf()
constructor(_ctx:Context, _client:OkHttpClient) {
ctx = _ctx
client = _client
localMediaManager = LocalMediaManager(ctx)
}
fun init() {
@ -102,6 +106,8 @@ class AudiobookManager {
isLoading = true
hasLoaded = true
localMediaManager.loadLocalAudio()
var db = CapacitorDataStorageSqlite(ctx)
db.openStore("storage", "downloads", false, "no-encryption", 1)
var keyvalues = db.keysvalues()
@ -171,8 +177,7 @@ class AudiobookManager {
})
}
fun initLocalPlay(audiobook:Audiobook):AudiobookStreamData {
fun initDownloadPlay(audiobook:Audiobook):AudiobookStreamData {
var abStreamDataObj = JSObject()
abStreamDataObj.put("id", audiobook.id)
abStreamDataObj.put("contentUrl", audiobook.contentUrl)
@ -190,6 +195,24 @@ class AudiobookManager {
return audiobookStreamData
}
fun initLocalPlay(local: LocalMediaManager.LocalAudio):AudiobookStreamData {
var abStreamDataObj = JSObject()
abStreamDataObj.put("id", local.id)
abStreamDataObj.put("contentUrl", local.uri.toString())
abStreamDataObj.put("title", local.name)
abStreamDataObj.put("author", "")
abStreamDataObj.put("token", null)
abStreamDataObj.put("cover", "")
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
}
fun levenshtein(lhs : CharSequence, rhs : CharSequence) : Int {
val lhsLength = lhs.length + 1
val rhsLength = rhs.length + 1
@ -243,4 +266,9 @@ class AudiobookManager {
if (audiobooks.isEmpty()) return null
return audiobooks[0]
}
fun getFirstLocal(): LocalMediaManager.LocalAudio? {
if (localMediaManager.localAudioFiles.isEmpty()) return null
return localMediaManager.localAudioFiles[0]
}
}

View file

@ -11,6 +11,7 @@ import androidx.annotation.AnyRes
class BrowseTree(
val context: Context,
val audiobooks: List<Audiobook>,
val localAudio: List<LocalMediaManager.LocalAudio>,
val recentMediaId: String? = null
) {
private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaMetadataCompat>>()
@ -41,14 +42,21 @@ class BrowseTree(
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, resource)
}.build()
val albumsMetadata = MediaMetadataCompat.Builder().apply {
val downloadsMetadata = MediaMetadataCompat.Builder().apply {
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, DOWNLOADS_ROOT)
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Downloads")
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_downloaddone).toString())
}.build()
val localsMetadata = MediaMetadataCompat.Builder().apply {
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, LOCAL_ROOT)
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Local Audio")
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_localaudio).toString())
}.build()
rootList += allMetadata
rootList += albumsMetadata
rootList += downloadsMetadata
rootList += localsMetadata
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
audiobooks.forEach { audiobook ->
@ -61,6 +69,13 @@ class BrowseTree(
allChildren += audiobook.toMediaMetadata()
mediaIdToChildren[ALL_ROOT] = allChildren
}
localAudio.forEach { local ->
val localChildren = mediaIdToChildren[LOCAL_ROOT] ?: mutableListOf()
localChildren += local.toMediaMetadata()
mediaIdToChildren[LOCAL_ROOT] = localChildren
}
Log.d("BrowseTree", "Set LOCAL AUDIO ${localAudio.size}")
}
operator fun get(mediaId: String) = mediaIdToChildren[mediaId]
@ -69,3 +84,4 @@ class BrowseTree(
const val AUTO_BROWSE_ROOT = "/"
const val ALL_ROOT = "__ALL__"
const val DOWNLOADS_ROOT = "__DOWNLOADS__"
const val LOCAL_ROOT = "__LOCAL__"

View file

@ -0,0 +1,73 @@
package com.audiobookshelf.app
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import android.support.v4.media.MediaMetadataCompat
import android.util.Log
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
) {
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)
// putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, book.authorFL)
// putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, getCover().toString())
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, "android.resource://com.audiobookshelf.app/" + R.drawable.icon)
// putString(MediaMetadataCompat.METADATA_KEY_ART_URI, getCover().toString())
// putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, book.authorFL)
}.build()
}
}
val localAudioFiles = mutableListOf<LocalAudio>()
fun loadLocalAudio() {
Log.d(tag, "Media store looking for local audio files")
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(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 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)
}
}
Log.d(tag, "${localAudioFiles.size} Local Audio Files found")
}
}

View file

@ -141,6 +141,19 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
return channelId
}
private fun playLocal(local: LocalMediaManager.LocalAudio, playWhenReady: Boolean) {
var asd = audiobookManager.initLocalPlay(local)
asd.playWhenReady = playWhenReady
initPlayer(asd)
}
private fun playFirstLocal(playWhenReady: Boolean) {
var localAudio = audiobookManager.getFirstLocal()
if (localAudio != null) {
playLocal(localAudio, playWhenReady)
}
}
private fun playAudiobookFromMediaBrowser(audiobook: Audiobook, playWhenReady: Boolean) {
if (!audiobook.isDownloaded) {
var streamListener = object : AudiobookManager.OnStreamData {
@ -152,7 +165,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
}
audiobookManager.openStream(audiobook, streamListener)
} else {
var asd = audiobookManager.initLocalPlay(audiobook)
var asd = audiobookManager.initDownloadPlay(audiobook)
asd.playWhenReady = playWhenReady
initPlayer(asd)
}
@ -162,12 +175,20 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
var firstAudiobook = audiobookManager.getFirstAudiobook()
if (firstAudiobook != null) {
playAudiobookFromMediaBrowser(firstAudiobook, playWhenReady)
} else {
playFirstLocal(playWhenReady)
}
}
private fun openFromMediaId(mediaId: String, playWhenReady: Boolean) {
var audiobook = audiobookManager.audiobooks.find { it.id == mediaId }
if (audiobook == null) {
var localAudio = audiobookManager.localMediaManager.localAudioFiles.find { it.id == mediaId }
if (localAudio != null) {
playLocal(localAudio, playWhenReady)
return
}
Log.e(tag, "Audiobook NOT FOUND")
return
}
@ -470,15 +491,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
}
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
Log.d(tag, "PLAY PAUSE TEST")
transportControls.playFromSearch("Brave New World", Bundle())
// transportControls.playFromSearch("Brave New World", Bundle())
// if (mPlayer.isPlaying) {
// if (0 == mediaButtonClickCount) pause()
// handleMediaButtonClickCount()
// } else {
// if (0 == mediaButtonClickCount) play()
// handleMediaButtonClickCount()
// }
if (mPlayer.isPlaying) {
if (0 == mediaButtonClickCount) pause()
handleMediaButtonClickCount()
} else {
if (0 == mediaButtonClickCount) play()
handleMediaButtonClickCount()
}
}
else -> {
Log.d(tag, "KeyCode:${keyEvent?.getKeyCode()}")
@ -834,7 +855,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
audiobookManager.isLoading = false
Log.d(tag, "LOADED AUDIOBOOKS")
browseTree = BrowseTree(this, audiobookManager.audiobooks, null)
browseTree = BrowseTree(this, audiobookManager.audiobooks, audiobookManager.localMediaManager.localAudioFiles, null)
val children = browseTree[parentMediaId]?.map { item ->
MediaBrowserCompat.MediaItem(item.description, flag)
}
@ -850,11 +871,11 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
return
}
if (audiobookManager.audiobooks.size == 0) {
Log.d(tag, "AudiobookManager: Sending no items")
result.sendResult(mediaItems)
return
}
// if (audiobookManager.audiobooks.size == 0) {
// Log.d(tag, "AudiobookManager: Sending no items")
// result.sendResult(mediaItems)
// return
// }
val children = browseTree[parentMediaId]?.map { item ->
MediaBrowserCompat.MediaItem(item.description, flag)
@ -887,7 +908,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
audiobookManager.isLoading = false
Log.d(tag, "LOADED AUDIOBOOKS")
browseTree = BrowseTree(this, audiobookManager.audiobooks, null)
browseTree = BrowseTree(this, audiobookManager.audiobooks, audiobookManager.localMediaManager.localAudioFiles, null)
val children = browseTree[ALL_ROOT]?.map { item ->
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
}

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="1.127451"
android:scaleY="1.127451"
android:translateX="-1.5294118"
android:translateY="-1.5294118">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

View file

@ -1,6 +1,6 @@
{
"name": "audiobookshelf-app",
"version": "v0.9.20-beta",
"version": "v0.9.21-beta",
"author": "advplyr",
"scripts": {
"dev": "nuxt --hostname localhost --port 1337",