mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-28 05:53:59 +02:00
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:
parent
0abefbd9bc
commit
2965ccb513
11 changed files with 176 additions and 23 deletions
|
@ -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 39
|
versionCode 40
|
||||||
versionName "0.9.20-beta"
|
versionName "0.9.21-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.
|
||||||
|
|
|
@ -26,11 +26,15 @@ class AudiobookManager {
|
||||||
var token = ""
|
var token = ""
|
||||||
private var client:OkHttpClient
|
private var client:OkHttpClient
|
||||||
|
|
||||||
|
lateinit var localMediaManager:LocalMediaManager
|
||||||
|
|
||||||
var audiobooks:MutableList<Audiobook> = mutableListOf()
|
var audiobooks:MutableList<Audiobook> = mutableListOf()
|
||||||
|
|
||||||
constructor(_ctx:Context, _client:OkHttpClient) {
|
constructor(_ctx:Context, _client:OkHttpClient) {
|
||||||
ctx = _ctx
|
ctx = _ctx
|
||||||
client = _client
|
client = _client
|
||||||
|
|
||||||
|
localMediaManager = LocalMediaManager(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
|
@ -102,6 +106,8 @@ class AudiobookManager {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
hasLoaded = true
|
hasLoaded = true
|
||||||
|
|
||||||
|
localMediaManager.loadLocalAudio()
|
||||||
|
|
||||||
var db = CapacitorDataStorageSqlite(ctx)
|
var db = CapacitorDataStorageSqlite(ctx)
|
||||||
db.openStore("storage", "downloads", false, "no-encryption", 1)
|
db.openStore("storage", "downloads", false, "no-encryption", 1)
|
||||||
var keyvalues = db.keysvalues()
|
var keyvalues = db.keysvalues()
|
||||||
|
@ -171,8 +177,7 @@ class AudiobookManager {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initLocalPlay(audiobook:Audiobook):AudiobookStreamData {
|
fun initDownloadPlay(audiobook:Audiobook):AudiobookStreamData {
|
||||||
|
|
||||||
var abStreamDataObj = JSObject()
|
var abStreamDataObj = JSObject()
|
||||||
abStreamDataObj.put("id", audiobook.id)
|
abStreamDataObj.put("id", audiobook.id)
|
||||||
abStreamDataObj.put("contentUrl", audiobook.contentUrl)
|
abStreamDataObj.put("contentUrl", audiobook.contentUrl)
|
||||||
|
@ -190,6 +195,24 @@ class AudiobookManager {
|
||||||
return audiobookStreamData
|
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 {
|
fun levenshtein(lhs : CharSequence, rhs : CharSequence) : Int {
|
||||||
val lhsLength = lhs.length + 1
|
val lhsLength = lhs.length + 1
|
||||||
val rhsLength = rhs.length + 1
|
val rhsLength = rhs.length + 1
|
||||||
|
@ -243,4 +266,9 @@ class AudiobookManager {
|
||||||
if (audiobooks.isEmpty()) return null
|
if (audiobooks.isEmpty()) return null
|
||||||
return audiobooks[0]
|
return audiobooks[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFirstLocal(): LocalMediaManager.LocalAudio? {
|
||||||
|
if (localMediaManager.localAudioFiles.isEmpty()) return null
|
||||||
|
return localMediaManager.localAudioFiles[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.annotation.AnyRes
|
||||||
class BrowseTree(
|
class BrowseTree(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val audiobooks: List<Audiobook>,
|
val audiobooks: List<Audiobook>,
|
||||||
|
val localAudio: List<LocalMediaManager.LocalAudio>,
|
||||||
val recentMediaId: String? = null
|
val recentMediaId: String? = null
|
||||||
) {
|
) {
|
||||||
private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaMetadataCompat>>()
|
private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaMetadataCompat>>()
|
||||||
|
@ -41,14 +42,21 @@ class BrowseTree(
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, resource)
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, resource)
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
val albumsMetadata = MediaMetadataCompat.Builder().apply {
|
val downloadsMetadata = MediaMetadataCompat.Builder().apply {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, DOWNLOADS_ROOT)
|
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, DOWNLOADS_ROOT)
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Downloads")
|
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Downloads")
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_downloaddone).toString())
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_downloaddone).toString())
|
||||||
}.build()
|
}.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 += allMetadata
|
||||||
rootList += albumsMetadata
|
rootList += downloadsMetadata
|
||||||
|
rootList += localsMetadata
|
||||||
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
|
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
|
||||||
|
|
||||||
audiobooks.forEach { audiobook ->
|
audiobooks.forEach { audiobook ->
|
||||||
|
@ -61,6 +69,13 @@ class BrowseTree(
|
||||||
allChildren += audiobook.toMediaMetadata()
|
allChildren += audiobook.toMediaMetadata()
|
||||||
mediaIdToChildren[ALL_ROOT] = allChildren
|
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]
|
operator fun get(mediaId: String) = mediaIdToChildren[mediaId]
|
||||||
|
@ -69,3 +84,4 @@ class BrowseTree(
|
||||||
const val AUTO_BROWSE_ROOT = "/"
|
const val AUTO_BROWSE_ROOT = "/"
|
||||||
const val ALL_ROOT = "__ALL__"
|
const val ALL_ROOT = "__ALL__"
|
||||||
const val DOWNLOADS_ROOT = "__DOWNLOADS__"
|
const val DOWNLOADS_ROOT = "__DOWNLOADS__"
|
||||||
|
const val LOCAL_ROOT = "__LOCAL__"
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,6 +141,19 @@ 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) {
|
if (!audiobook.isDownloaded) {
|
||||||
var streamListener = object : AudiobookManager.OnStreamData {
|
var streamListener = object : AudiobookManager.OnStreamData {
|
||||||
|
@ -152,7 +165,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
audiobookManager.openStream(audiobook, streamListener)
|
audiobookManager.openStream(audiobook, streamListener)
|
||||||
} else {
|
} else {
|
||||||
var asd = audiobookManager.initLocalPlay(audiobook)
|
var asd = audiobookManager.initDownloadPlay(audiobook)
|
||||||
asd.playWhenReady = playWhenReady
|
asd.playWhenReady = playWhenReady
|
||||||
initPlayer(asd)
|
initPlayer(asd)
|
||||||
}
|
}
|
||||||
|
@ -162,12 +175,20 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
var firstAudiobook = audiobookManager.getFirstAudiobook()
|
var firstAudiobook = audiobookManager.getFirstAudiobook()
|
||||||
if (firstAudiobook != null) {
|
if (firstAudiobook != null) {
|
||||||
playAudiobookFromMediaBrowser(firstAudiobook, playWhenReady)
|
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
|
||||||
}
|
}
|
||||||
|
@ -470,15 +491,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
||||||
Log.d(tag, "PLAY PAUSE TEST")
|
Log.d(tag, "PLAY PAUSE TEST")
|
||||||
transportControls.playFromSearch("Brave New World", Bundle())
|
// transportControls.playFromSearch("Brave New World", Bundle())
|
||||||
|
|
||||||
// if (mPlayer.isPlaying) {
|
if (mPlayer.isPlaying) {
|
||||||
// if (0 == mediaButtonClickCount) pause()
|
if (0 == mediaButtonClickCount) pause()
|
||||||
// handleMediaButtonClickCount()
|
handleMediaButtonClickCount()
|
||||||
// } else {
|
} else {
|
||||||
// if (0 == mediaButtonClickCount) play()
|
if (0 == mediaButtonClickCount) play()
|
||||||
// handleMediaButtonClickCount()
|
handleMediaButtonClickCount()
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Log.d(tag, "KeyCode:${keyEvent?.getKeyCode()}")
|
Log.d(tag, "KeyCode:${keyEvent?.getKeyCode()}")
|
||||||
|
@ -834,7 +855,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
audiobookManager.isLoading = false
|
audiobookManager.isLoading = false
|
||||||
|
|
||||||
Log.d(tag, "LOADED AUDIOBOOKS")
|
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 ->
|
val children = browseTree[parentMediaId]?.map { item ->
|
||||||
MediaBrowserCompat.MediaItem(item.description, flag)
|
MediaBrowserCompat.MediaItem(item.description, flag)
|
||||||
}
|
}
|
||||||
|
@ -850,11 +871,11 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audiobookManager.audiobooks.size == 0) {
|
// if (audiobookManager.audiobooks.size == 0) {
|
||||||
Log.d(tag, "AudiobookManager: Sending no items")
|
// Log.d(tag, "AudiobookManager: Sending no items")
|
||||||
result.sendResult(mediaItems)
|
// result.sendResult(mediaItems)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
val children = browseTree[parentMediaId]?.map { item ->
|
val children = browseTree[parentMediaId]?.map { item ->
|
||||||
MediaBrowserCompat.MediaItem(item.description, flag)
|
MediaBrowserCompat.MediaItem(item.description, flag)
|
||||||
|
@ -887,7 +908,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
audiobookManager.isLoading = false
|
audiobookManager.isLoading = false
|
||||||
|
|
||||||
Log.d(tag, "LOADED AUDIOBOOKS")
|
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 ->
|
val children = browseTree[ALL_ROOT]?.map { item ->
|
||||||
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
BIN
android/app/src/main/res/drawable-hdpi/exo_icon_localaudio.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/exo_icon_localaudio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 B |
BIN
android/app/src/main/res/drawable-mdpi/exo_icon_localaudio.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/exo_icon_localaudio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 186 B |
BIN
android/app/src/main/res/drawable-xhdpi/exo_icon_localaudio.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/exo_icon_localaudio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 289 B |
BIN
android/app/src/main/res/drawable-xxhdpi/exo_icon_localaudio.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/exo_icon_localaudio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 477 B |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "v0.9.20-beta",
|
"version": "v0.9.21-beta",
|
||||||
"author": "advplyr",
|
"author": "advplyr",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt --hostname localhost --port 1337",
|
"dev": "nuxt --hostname localhost --port 1337",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue