Compare commits

...

7 Commits

Author SHA1 Message Date
tzugen be985aead2
Simplify 2022-07-07 19:27:08 +02:00
tzugen 8f6b985dcd
Implement basic bitmap loader 2022-07-07 19:27:08 +02:00
tzugen b7cf8a19ed
Formatting 2022-07-07 19:27:08 +02:00
tzugen 4f79ae8e9e
Make some methods non-nullable 2022-07-07 19:27:08 +02:00
tzugen 6b0a9b788a
Add artwork URI to metadata 2022-07-07 19:27:08 +02:00
birdbird 3445576dc9
Merge pull request #775 from ultrasonic/seekBar
Add more values to preload count
2022-07-06 10:46:56 +02:00
tzugen 8c40f662a1
Add more values to preload count 2022-07-06 08:49:29 +02:00
6 changed files with 73 additions and 39 deletions

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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

View 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">