mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-28 06:14:00 +02:00
Fix:Android internal file paths using file provider
This commit is contained in:
@@ -92,6 +92,16 @@
|
|||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="com.audiobookshelf.app.fileprovider"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManage
|
|||||||
|
|
||||||
views.setOnClickPendingIntent(R.id.widgetBackground, wholeWidgetClickPI)
|
views.setOnClickPendingIntent(R.id.widgetBackground, wholeWidgetClickPI)
|
||||||
|
|
||||||
val imageUri = playbackSession?.getCoverUri() ?: Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
val imageUri = playbackSession?.getCoverUri(context) ?: Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
||||||
val awt: AppWidgetTarget = object : AppWidgetTarget(context.applicationContext, R.id.widgetAlbumArt, views, appWidgetId) {
|
val awt: AppWidgetTarget = object : AppWidgetTarget(context.applicationContext, R.id.widgetAlbumArt, views, appWidgetId) {
|
||||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||||
super.onResourceReady(resource, transition)
|
super.onResourceReady(resource, transition)
|
||||||
|
|||||||
@@ -258,9 +258,9 @@ data class PodcastEpisode(
|
|||||||
var localEpisodeId:String? // For Android Auto server episodes with local copy
|
var localEpisodeId:String? // For Android Auto server episodes with local copy
|
||||||
) {
|
) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaDescription(libraryItem:LibraryItemWrapper, progress:MediaProgressWrapper?, ctx: Context?): MediaDescriptionCompat {
|
fun getMediaDescription(libraryItem:LibraryItemWrapper, progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||||
val coverUri = if (libraryItem is LocalLibraryItem) {
|
val coverUri = if (libraryItem is LocalLibraryItem) {
|
||||||
libraryItem.getCoverUri()
|
libraryItem.getCoverUri(ctx)
|
||||||
} else {
|
} else {
|
||||||
(libraryItem as LibraryItem).getCoverUri()
|
(libraryItem as LibraryItem).getCoverUri()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ data class LocalFolder(
|
|||||||
)
|
)
|
||||||
open class LibraryItemWrapper(var id:String) {
|
open class LibraryItemWrapper(var id:String) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
open fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context?): MediaDescriptionCompat { return MediaDescriptionCompat.Builder().build() }
|
open fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat { return MediaDescriptionCompat.Builder().build() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class LibraryItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context?): MediaDescriptionCompat {
|
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
|
|
||||||
if (localLibraryItemId != null) {
|
if (localLibraryItemId != null) {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import android.os.Bundle
|
|||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.support.v4.media.MediaDescriptionCompat
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.net.toFile
|
||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
import com.audiobookshelf.app.BuildConfig
|
import com.audiobookshelf.app.BuildConfig
|
||||||
import com.audiobookshelf.app.R
|
import com.audiobookshelf.app.R
|
||||||
@@ -46,7 +48,10 @@ class LocalLibraryItem(
|
|||||||
val isPodcast get() = mediaType == "podcast"
|
val isPodcast get() = mediaType == "podcast"
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getCoverUri(): Uri {
|
fun getCoverUri(ctx:Context): Uri {
|
||||||
|
if (coverContentUrl?.startsWith("file:") == true) {
|
||||||
|
return FileProvider.getUriForFile(ctx, "com.audiobookshelf.app.fileprovider", Uri.parse(coverContentUrl).toFile())
|
||||||
|
}
|
||||||
return if (coverContentUrl != null) Uri.parse(coverContentUrl) else Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
return if (coverContentUrl != null) Uri.parse(coverContentUrl) else Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,18 +112,16 @@ class LocalLibraryItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx:Context?): MediaDescriptionCompat {
|
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx:Context): MediaDescriptionCompat {
|
||||||
val coverUri = getCoverUri()
|
val coverUri = getCoverUri(ctx)
|
||||||
|
|
||||||
var bitmap:Bitmap? = null
|
var bitmap:Bitmap? = null
|
||||||
if (coverContentUrl != null) {
|
if (coverContentUrl != null) {
|
||||||
ctx?.let {
|
bitmap = if (Build.VERSION.SDK_INT < 28) {
|
||||||
bitmap = if (Build.VERSION.SDK_INT < 28) {
|
MediaStore.Images.Media.getBitmap(ctx.contentResolver, coverUri)
|
||||||
MediaStore.Images.Media.getBitmap(it.contentResolver, coverUri)
|
} else {
|
||||||
} else {
|
val source: ImageDecoder.Source = ImageDecoder.createSource(ctx.contentResolver, coverUri)
|
||||||
val source: ImageDecoder.Source = ImageDecoder.createSource(it.contentResolver, coverUri)
|
ImageDecoder.decodeBitmap(source)
|
||||||
ImageDecoder.decodeBitmap(source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.net.toFile
|
||||||
import com.audiobookshelf.app.BuildConfig
|
import com.audiobookshelf.app.BuildConfig
|
||||||
import com.audiobookshelf.app.R
|
import com.audiobookshelf.app.R
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
@@ -138,8 +140,15 @@ class PlaybackSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getCoverUri(): Uri {
|
fun getCoverUri(ctx:Context): Uri {
|
||||||
if (localLibraryItem?.coverContentUrl != null) return Uri.parse(localLibraryItem?.coverContentUrl) ?: Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
if (localLibraryItem?.coverContentUrl != null) {
|
||||||
|
var coverUri = Uri.parse(localLibraryItem?.coverContentUrl.toString())
|
||||||
|
if (coverUri.toString().startsWith("file:")) {
|
||||||
|
coverUri = FileProvider.getUriForFile(ctx, "com.audiobookshelf.app.fileprovider", coverUri.toFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
return coverUri ?: Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
||||||
|
}
|
||||||
|
|
||||||
if (coverPath == null) return Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
if (coverPath == null) return Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon)
|
||||||
return Uri.parse("$serverAddress/api/items/$libraryItemId/cover?token=${DeviceManager.token}")
|
return Uri.parse("$serverAddress/api/items/$libraryItemId/cover?token=${DeviceManager.token}")
|
||||||
@@ -153,6 +162,8 @@ class PlaybackSession(
|
|||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaMetadataCompat(ctx: Context): MediaMetadataCompat {
|
fun getMediaMetadataCompat(ctx: Context): MediaMetadataCompat {
|
||||||
|
val coverUri = getCoverUri(ctx)
|
||||||
|
|
||||||
val metadataBuilder = MediaMetadataCompat.Builder()
|
val metadataBuilder = MediaMetadataCompat.Builder()
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, displayTitle)
|
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, displayTitle)
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, displayTitle)
|
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, displayTitle)
|
||||||
@@ -163,16 +174,16 @@ class PlaybackSession(
|
|||||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, displayAuthor)
|
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, displayAuthor)
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, displayAuthor)
|
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, displayAuthor)
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getCoverUri().toString())
|
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, coverUri.toString())
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, getCoverUri().toString())
|
.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, coverUri.toString())
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, getCoverUri().toString())
|
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, coverUri.toString())
|
||||||
|
|
||||||
// Local covers get bitmap
|
// Local covers get bitmap
|
||||||
if (localLibraryItem?.coverContentUrl != null) {
|
if (localLibraryItem?.coverContentUrl != null) {
|
||||||
val bitmap = if (Build.VERSION.SDK_INT < 28) {
|
val bitmap = if (Build.VERSION.SDK_INT < 28) {
|
||||||
MediaStore.Images.Media.getBitmap(ctx.contentResolver, getCoverUri())
|
MediaStore.Images.Media.getBitmap(ctx.contentResolver, coverUri)
|
||||||
} else {
|
} else {
|
||||||
val source: ImageDecoder.Source = ImageDecoder.createSource(ctx.contentResolver, getCoverUri())
|
val source: ImageDecoder.Source = ImageDecoder.createSource(ctx.contentResolver, coverUri)
|
||||||
ImageDecoder.decodeBitmap(source)
|
ImageDecoder.decodeBitmap(source)
|
||||||
}
|
}
|
||||||
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
|
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
|
||||||
@@ -183,7 +194,9 @@ class PlaybackSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getExoMediaMetadata(): MediaMetadata {
|
fun getExoMediaMetadata(ctx:Context): MediaMetadata {
|
||||||
|
val coverUri = getCoverUri(ctx)
|
||||||
|
|
||||||
val metadataBuilder = MediaMetadata.Builder()
|
val metadataBuilder = MediaMetadata.Builder()
|
||||||
.setTitle(displayTitle)
|
.setTitle(displayTitle)
|
||||||
.setDisplayTitle(displayTitle)
|
.setDisplayTitle(displayTitle)
|
||||||
@@ -192,18 +205,18 @@ class PlaybackSession(
|
|||||||
.setSubtitle(displayAuthor)
|
.setSubtitle(displayAuthor)
|
||||||
.setAlbumTitle(displayAuthor)
|
.setAlbumTitle(displayAuthor)
|
||||||
.setDescription(displayAuthor)
|
.setDescription(displayAuthor)
|
||||||
.setArtworkUri(getCoverUri())
|
.setArtworkUri(coverUri)
|
||||||
.setMediaType(MediaMetadata.MEDIA_TYPE_AUDIO_BOOK)
|
.setMediaType(MediaMetadata.MEDIA_TYPE_AUDIO_BOOK)
|
||||||
|
|
||||||
return metadataBuilder.build()
|
return metadataBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaItems():List<MediaItem> {
|
fun getMediaItems(ctx:Context):List<MediaItem> {
|
||||||
val mediaItems:MutableList<MediaItem> = mutableListOf()
|
val mediaItems:MutableList<MediaItem> = mutableListOf()
|
||||||
|
|
||||||
for (audioTrack in audioTracks) {
|
for (audioTrack in audioTracks) {
|
||||||
val mediaMetadata = this.getExoMediaMetadata()
|
val mediaMetadata = this.getExoMediaMetadata(ctx)
|
||||||
val mediaUri = this.getContentUri(audioTrack)
|
val mediaUri = this.getContentUri(audioTrack)
|
||||||
val mimeType = audioTrack.mimeType
|
val mimeType = audioTrack.mimeType
|
||||||
|
|
||||||
|
|||||||
+4
-7
@@ -9,10 +9,7 @@ import android.graphics.Color
|
|||||||
import android.graphics.ImageDecoder
|
import android.graphics.ImageDecoder
|
||||||
import android.hardware.Sensor
|
import android.hardware.Sensor
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.net.ConnectivityManager
|
import android.net.*
|
||||||
import android.net.Network
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.net.NetworkRequest
|
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
@@ -292,7 +289,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
return MediaDescriptionCompat.Builder().build()
|
return MediaDescriptionCompat.Builder().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
val coverUri = currentPlaybackSession!!.getCoverUri()
|
val coverUri = currentPlaybackSession!!.getCoverUri(ctx)
|
||||||
|
|
||||||
var bitmap: Bitmap? = null
|
var bitmap: Bitmap? = null
|
||||||
// Local covers get bitmap
|
// Local covers get bitmap
|
||||||
@@ -398,7 +395,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
|
|
||||||
val metadata = playbackSession.getMediaMetadataCompat(ctx)
|
val metadata = playbackSession.getMediaMetadataCompat(ctx)
|
||||||
mediaSession.setMetadata(metadata)
|
mediaSession.setMetadata(metadata)
|
||||||
val mediaItems = playbackSession.getMediaItems()
|
val mediaItems = playbackSession.getMediaItems(ctx)
|
||||||
val playbackRateToUse = playbackRate ?: initialPlaybackRate ?: 1f
|
val playbackRateToUse = playbackRate ?: initialPlaybackRate ?: 1f
|
||||||
initialPlaybackRate = playbackRate
|
initialPlaybackRate = playbackRate
|
||||||
|
|
||||||
@@ -501,7 +498,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setMediaSessionConnectorCustomActions(playbackSession:PlaybackSession) {
|
private fun setMediaSessionConnectorCustomActions(playbackSession:PlaybackSession) {
|
||||||
val mediaItems = playbackSession.getMediaItems()
|
val mediaItems = playbackSession.getMediaItems(ctx)
|
||||||
val customActionProviders = mutableListOf(
|
val customActionProviders = mutableListOf(
|
||||||
JumpBackwardCustomActionProvider(),
|
JumpBackwardCustomActionProvider(),
|
||||||
JumpForwardCustomActionProvider(),
|
JumpForwardCustomActionProvider(),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
<paths>
|
||||||
<external-path name="my_images" path="." />
|
<files-path name="downloads" path="downloads/" />
|
||||||
<cache-path name="my_cache_images" path="." />
|
|
||||||
</paths>
|
</paths>
|
||||||
|
|||||||
Reference in New Issue
Block a user