Compare commits
7 Commits
0231ccc542
...
be985aead2
Author | SHA1 | Date |
---|---|---|
tzugen | be985aead2 | |
tzugen | 8f6b985dcd | |
tzugen | b7cf8a19ed | |
tzugen | 4f79ae8e9e | |
tzugen | 6b0a9b788a | |
birdbird | 3445576dc9 | |
tzugen | 8c40f662a1 |
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* ArtworkBitmapLoader.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.moire.ultrasonic.imageloader
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.media3.session.BitmapLoader
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class ArtworkBitmapLoader : BitmapLoader {
|
||||||
|
|
||||||
|
private val executorService: ListeningExecutorService by lazy {
|
||||||
|
MoreExecutors.listeningDecorator(
|
||||||
|
Executors.newSingleThreadExecutor()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> {
|
||||||
|
return executorService.submit<Bitmap> {
|
||||||
|
decode(
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadBitmap(uri: Uri): ListenableFuture<Bitmap> {
|
||||||
|
return executorService.submit<Bitmap> {
|
||||||
|
load(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decode(data: ByteArray): Bitmap {
|
||||||
|
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
|
||||||
|
return bitmap ?: throw IllegalArgumentException("Could not decode bitmap")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun load(uri: Uri): Bitmap {
|
||||||
|
return BitmapFactory.decodeFile(uri.path)
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ class BitmapUtils {
|
||||||
if (track == null) return null
|
if (track == null) return null
|
||||||
val albumArtFile = FileUtil.getAlbumArtFile(track)
|
val albumArtFile = FileUtil.getAlbumArtFile(track)
|
||||||
val bitmap: Bitmap? = null
|
val bitmap: Bitmap? = null
|
||||||
if (albumArtFile != null && File(albumArtFile).exists()) {
|
if (File(albumArtFile).exists()) {
|
||||||
return getBitmapFromDisk(albumArtFile, size, bitmap)
|
return getBitmapFromDisk(albumArtFile, size, bitmap)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@ -44,41 +44,12 @@ class BitmapUtils {
|
||||||
): Bitmap? {
|
): Bitmap? {
|
||||||
val albumArtFile = FileUtil.getAlbumArtFile(filename)
|
val albumArtFile = FileUtil.getAlbumArtFile(filename)
|
||||||
val bitmap: Bitmap? = null
|
val bitmap: Bitmap? = null
|
||||||
if (albumArtFile != null && File(albumArtFile).exists()) {
|
if (File(albumArtFile).exists()) {
|
||||||
return getBitmapFromDisk(albumArtFile, size, bitmap)
|
return getBitmapFromDisk(albumArtFile, size, bitmap)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
fun getSampledBitmap(bytes: ByteArray, size: Int): Bitmap? {
|
|
||||||
val opt = BitmapFactory.Options()
|
|
||||||
if (size > 0) {
|
|
||||||
// With this flag we only calculate the size first
|
|
||||||
opt.inJustDecodeBounds = true
|
|
||||||
|
|
||||||
// Decode the size
|
|
||||||
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opt)
|
|
||||||
|
|
||||||
// Now set the remaining flags
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
|
||||||
opt.inDither = true
|
|
||||||
opt.inPreferQualityOverSpeed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
opt.inSampleSize = Util.calculateInSampleSize(
|
|
||||||
opt,
|
|
||||||
size,
|
|
||||||
Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Enable real decoding
|
|
||||||
opt.inJustDecodeBounds = false
|
|
||||||
}
|
|
||||||
Timber.i("getSampledBitmap %s", size.toString())
|
|
||||||
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private fun getBitmapFromDisk(
|
private fun getBitmapFromDisk(
|
||||||
path: String,
|
path: String,
|
||||||
|
|
|
@ -20,11 +20,12 @@ import androidx.media3.session.SessionCommand
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.imageloader.ArtworkBitmapLoader
|
||||||
import org.moire.ultrasonic.service.MediaPlayerController
|
import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class MediaNotificationProvider(context: Context) :
|
class MediaNotificationProvider(context: Context) :
|
||||||
DefaultMediaNotificationProvider(context), KoinComponent {
|
DefaultMediaNotificationProvider(context, ArtworkBitmapLoader()), KoinComponent {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* It is currently not possible to edit a MediaItem after creation so the isRated value
|
* It is currently not possible to edit a MediaItem after creation so the isRated value
|
||||||
|
|
|
@ -23,6 +23,7 @@ import com.google.common.util.concurrent.FutureCallback
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import java.io.File
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -666,6 +667,9 @@ class MediaPlayerController(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Merge with the Builder functions in AutoMediaBrowserCallback
|
||||||
|
*/
|
||||||
fun Track.toMediaItem(): MediaItem {
|
fun Track.toMediaItem(): MediaItem {
|
||||||
|
|
||||||
val filePath = FileUtil.getSongFile(this)
|
val filePath = FileUtil.getSongFile(this)
|
||||||
|
@ -676,13 +680,18 @@ fun Track.toMediaItem(): MediaItem {
|
||||||
.setMediaUri(uri.toUri())
|
.setMediaUri(uri.toUri())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
val artworkFile = File(FileUtil.getAlbumArtFile(this))
|
||||||
|
|
||||||
val metadata = MediaMetadata.Builder()
|
val metadata = MediaMetadata.Builder()
|
||||||
metadata.setTitle(title)
|
metadata.setTitle(title)
|
||||||
.setArtist(artist)
|
.setArtist(artist)
|
||||||
.setAlbumTitle(album)
|
.setAlbumTitle(album)
|
||||||
.setAlbumArtist(artist)
|
.setAlbumArtist(artist)
|
||||||
.setUserRating(HeartRating(starred))
|
.setUserRating(HeartRating(starred))
|
||||||
.build()
|
|
||||||
|
if (artworkFile.exists()) {
|
||||||
|
metadata.setArtworkUri(artworkFile.toUri())
|
||||||
|
}
|
||||||
|
|
||||||
val mediaItem = MediaItem.Builder()
|
val mediaItem = MediaItem.Builder()
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
|
|
|
@ -114,7 +114,7 @@ object FileUtil {
|
||||||
* @param entry The album entry
|
* @param entry The album entry
|
||||||
* @return File object. Not guaranteed that it exists
|
* @return File object. Not guaranteed that it exists
|
||||||
*/
|
*/
|
||||||
fun getAlbumArtFile(entry: MusicDirectory.Child): String? {
|
fun getAlbumArtFile(entry: MusicDirectory.Child): String {
|
||||||
val albumDir = getAlbumDirectory(entry)
|
val albumDir = getAlbumDirectory(entry)
|
||||||
return getAlbumArtFileForAlbumDir(albumDir)
|
return getAlbumArtFileForAlbumDir(albumDir)
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ object FileUtil {
|
||||||
* @return File object. Not guaranteed that it exists
|
* @return File object. Not guaranteed that it exists
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getAlbumArtFileForAlbumDir(albumDir: String): String? {
|
fun getAlbumArtFileForAlbumDir(albumDir: String): String {
|
||||||
val key = getAlbumArtKey(albumDir, true)
|
val key = getAlbumArtKey(albumDir, true)
|
||||||
return getAlbumArtFile(key)
|
return getAlbumArtFile(key)
|
||||||
}
|
}
|
||||||
|
@ -180,11 +180,9 @@ object FileUtil {
|
||||||
* @return File object. Not guaranteed that it exists
|
* @return File object. Not guaranteed that it exists
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getAlbumArtFile(cacheKey: String?): String? {
|
fun getAlbumArtFile(cacheKey: String): String {
|
||||||
val albumArtDir = albumArtDirectory.absolutePath
|
val albumArtDir = albumArtDirectory.absolutePath
|
||||||
return if (cacheKey == null) {
|
return "$albumArtDir/$cacheKey"
|
||||||
null
|
|
||||||
} else "$albumArtDir/$cacheKey"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val albumArtDirectory: File
|
val albumArtDirectory: File
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
<item>3</item>
|
<item>3</item>
|
||||||
<item>5</item>
|
<item>5</item>
|
||||||
<item>10</item>
|
<item>10</item>
|
||||||
|
<item>50</item>
|
||||||
|
<item>100</item>
|
||||||
|
<item>500</item>
|
||||||
|
<item>1000</item>
|
||||||
<item>-1</item>
|
<item>-1</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="preloadCountNames" translatable="false">
|
<string-array name="preloadCountNames" translatable="false">
|
||||||
|
|
Loading…
Reference in New Issue