Refactor music repository

This commit is contained in:
MM20 2022-05-26 14:04:25 +02:00
parent 0165ac5dc8
commit c3257846c1
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
5 changed files with 346 additions and 244 deletions

View File

@ -89,7 +89,6 @@ dependencies {
implementation(libs.androidx.fragment) implementation(libs.androidx.fragment)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.exifinterface) implementation(libs.androidx.exifinterface)
implementation(libs.androidx.media2)
implementation(libs.materialcomponents.core) implementation(libs.materialcomponents.core)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout)

View File

@ -37,10 +37,9 @@ android {
dependencies { dependencies {
implementation(libs.bundles.kotlin) implementation(libs.bundles.kotlin)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.media2)
implementation(libs.koin.android) implementation(libs.koin.android)
implementation(libs.coil.core)
implementation(project(":ktx")) implementation(project(":ktx"))
implementation(project(":preferences")) implementation(project(":preferences"))

View File

@ -1,32 +1,34 @@
package de.mm20.launcher2.music package de.mm20.launcher2.music
import android.app.Notification
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.AudioManager import android.media.AudioManager
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession import android.media.session.MediaSession
import android.support.v4.media.session.MediaSessionCompat import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.service.notification.StatusBarNotification
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.graphics.scale import androidx.core.graphics.drawable.toBitmap
import androidx.media2.common.MediaItem import coil.imageLoader
import androidx.media2.common.MediaMetadata import coil.request.ImageRequest
import androidx.media2.common.SessionPlayer import coil.size.Scale
import androidx.media2.session.MediaController
import androidx.media2.session.SessionCommandGroup
import de.mm20.launcher2.notifications.NotificationRepository import de.mm20.launcher2.notifications.NotificationRepository
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Semaphore
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 java.io.File import java.io.IOException
import java.util.concurrent.Executors
interface MusicRepository { interface MusicRepository {
val playbackState: Flow<PlaybackState> val playbackState: Flow<PlaybackState>
@ -55,218 +57,361 @@ internal class MusicRepositoryImpl(
private val scope = CoroutineScope(Job() + Dispatchers.Default) private val scope = CoroutineScope(Job() + Dispatchers.Default)
private val dataStore: LauncherDataStore by inject() private val dataStore: LauncherDataStore by inject()
override val playbackState = MutableStateFlow(PlaybackState.Stopped) private val preferences: SharedPreferences by lazy {
override val title = MutableStateFlow<String?>(null) context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
override val artist = MutableStateFlow<String?>(null)
override val album = MutableStateFlow<String?>(null)
override val albumArt = MutableStateFlow<Bitmap?>(null)
private var lastPlayer: String? = null
private var lastToken: String? = null
private val semaphore = Semaphore(permits = 1)
init {
scope.launch {
notificationRepository.notifications
.mapNotNull {
it
.sortedByDescending { it.postTime }
.find {
it.notification.category == Notification.CATEGORY_TRANSPORT || it.notification.category == Notification.CATEGORY_SERVICE
}
}
.collectLatest {
val token =
it.notification.extras[NotificationCompat.EXTRA_MEDIA_SESSION] as? MediaSession.Token
?: return@collectLatest
setMediaSession(
MediaSessionCompat.Token.fromToken(token),
it.packageName
)
}
}
} }
private fun setMediaSession(token: MediaSessionCompat.Token, packageName: String) { private var lastPlayerPackage: String? = null
if (token.toString() == lastToken.toString()) return get() {
if (field == null) {
scope.launch { field = preferences.getString(PREFS_KEY_LAST_PLAYER, null)
val filterMusicApps = dataStore.data.map { it.musicWidget.filterSources }.first()
if (filterMusicApps && !isMusicApp(packageName)) {
return@launch
}
try {
semaphore.acquire()
mediaController?.close()
val appName = context.packageManager.getPackageInfo(
packageName,
0
).applicationInfo.loadLabel(context.packageManager)
mediaController = MediaController.Builder(context)
.setSessionCompatToken(token)
.setControllerCallback(
Executors.newSingleThreadExecutor(),
mediaSessionCallback
)
.build()
setMetadata(
title = context.getString(R.string.music_widget_default_title, appName),
artist = null,
album = null,
albumArt = null,
packageName
)
lastToken = token.toString()
} finally {
semaphore.release()
} }
return field
} }
}
private var mediaController: MediaController? = null
set(value) { set(value) {
if (value == null) { preferences.edit {
playbackState.value = PlaybackState.Stopped putString(PREFS_KEY_LAST_PLAYER, value)
} }
field = value field = value
} }
private val mediaSessionCallback = object : MediaController.ControllerCallback() { private val currentMediaController: SharedFlow<MediaController?> =
override fun onConnected( combine(
controller: MediaController, notificationRepository.notifications,
allowedCommands: SessionCommandGroup dataStore.data.map { it.musicWidget.filterSources }
) { ) { notifications, filter ->
super.onConnected(controller, allowedCommands) withContext(Dispatchers.Default) {
if (controller != mediaController) return val musicApps = if (filter) getMusicApps() else null
updateMetadata(controller.currentMediaItem, controller.connectedToken?.packageName) val sbn: StatusBarNotification? = notifications.filter {
updateState(controller.playerState) it.notification.extras.getParcelable(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token != null &&
} (musicApps?.contains(it.packageName) != false)
}.maxByOrNull { it.postTime }
override fun onCurrentMediaItemChanged(controller: MediaController, item: MediaItem?) { return@withContext (sbn?.notification?.extras?.get(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token)
super.onCurrentMediaItemChanged(controller, item)
if (controller != mediaController) return
updateMetadata(item, controller.connectedToken?.packageName)
}
override fun onPlayerStateChanged(controller: MediaController, state: Int) {
super.onPlayerStateChanged(controller, state)
if (controller != mediaController) return
updateState(state)
}
override fun onDisconnected(controller: MediaController) {
super.onDisconnected(controller)
if (controller != mediaController) return
mediaController = null
}
}
private fun updateMetadata(mediaItem: MediaItem?, playerPackage: String?) {
val metadata = mediaItem?.metadata ?: return
val title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
?: metadata.getString(MediaMetadata.METADATA_KEY_TITLE) ?: return
val artist = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE)
?: metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
?: metadata.getString(MediaMetadata.METADATA_KEY_COMPOSER)
?: metadata.getString(MediaMetadata.METADATA_KEY_AUTHOR)
?: metadata.getString(MediaMetadata.METADATA_KEY_WRITER)
?: return
val album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)
val albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
// Hack for Spotify sending inconsistent metadata updates
if (playerPackage == "com.spotify.music" && album == null) return
scope.launch {
setMetadata(title, artist, album, albumArt, playerPackage)
}
}
private fun updateState(playerState: Int) {
val playbackState = when (playerState) {
SessionPlayer.PLAYER_STATE_PLAYING -> PlaybackState.Playing
SessionPlayer.PLAYER_STATE_PAUSED -> PlaybackState.Paused
else -> PlaybackState.Stopped
}
this.playbackState.value = playbackState
}
init {
loadLastPlaybackMetadata()
}
private fun loadLastPlaybackMetadata() {
val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
lastPlayer = prefs.getString(PREFS_KEY_LAST_PLAYER, null)
title.value = prefs.getString(PREFS_KEY_TITLE, null)
artist.value = prefs.getString(PREFS_KEY_ARTIST, null)
album.value = prefs.getString(PREFS_KEY_ALBUM, null)
if (prefs.getString(PREFS_KEY_ALBUM_ART, "null") == "null") {
albumArt.value = null
} else scope.launch {
val albumArt = withContext(Dispatchers.IO) {
BitmapFactory.decodeFile(File(context.cacheDir, "album_art").absolutePath)
} }
this@MusicRepositoryImpl.albumArt.value = albumArt
} }
playbackState.value = PlaybackState.Stopped .distinctUntilChanged()
.map { token ->
if (token == null) return@map null
else {
return@map MediaController(context, token).also {
lastPlayerPackage = it.packageName
}
}
}
.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private val currentMetadata: SharedFlow<MediaMetadata?> = channelFlow {
currentMediaController.collectLatest { controller ->
if (controller == null) {
send(null)
return@collectLatest
}
send(controller.metadata)
val callback = object : MediaController.Callback() {
override fun onMetadataChanged(metadata: MediaMetadata?) {
super.onMetadataChanged(metadata)
trySend(metadata)
}
}
try {
controller.registerCallback(callback, Handler(Looper.getMainLooper()))
awaitCancellation()
} finally {
controller.unregisterCallback(callback)
}
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
override val playbackState: SharedFlow<PlaybackState> = channelFlow {
currentMediaController.collectLatest { controller ->
if (controller == null) return@collectLatest send(PlaybackState.Stopped)
send(
when (controller.playbackState?.state) {
android.media.session.PlaybackState.STATE_PLAYING -> PlaybackState.Playing
android.media.session.PlaybackState.STATE_PAUSED -> PlaybackState.Paused
else -> PlaybackState.Stopped
}
)
val callback = object : MediaController.Callback() {
override fun onPlaybackStateChanged(state: android.media.session.PlaybackState?) {
super.onPlaybackStateChanged(state)
trySend(
when (state?.state) {
android.media.session.PlaybackState.STATE_PLAYING -> PlaybackState.Playing
android.media.session.PlaybackState.STATE_PAUSED -> PlaybackState.Paused
else -> PlaybackState.Stopped
}
)
}
}
try {
controller.registerCallback(callback, Handler(Looper.getMainLooper()))
awaitCancellation()
} finally {
controller.unregisterCallback(callback)
}
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private var lastTitle: String? = null
get() {
if (field == null) {
field = preferences.getString(PREFS_KEY_TITLE, null)
}
return field
}
set(value) {
preferences.edit {
putString(PREFS_KEY_TITLE, value)
}
field = value
}
override val title: Flow<String?> = channelFlow {
currentMetadata.collectLatest { metadata ->
if (metadata == null) {
send(lastTitle)
return@collectLatest
}
val title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
?: metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
?: currentMediaController.firstOrNull()?.packageName?.let { pkg ->
getAppLabel(pkg)?.let {
context.getString(
R.string.music_widget_default_title,
it
)
}
}
lastTitle = title
send(title)
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private var lastArtist: String? = null
get() {
if (field == null) {
field = preferences.getString(PREFS_KEY_ARTIST, null)
}
return field
}
set(value) {
preferences.edit {
putString(PREFS_KEY_ARTIST, value)
}
field = value
}
override val artist: Flow<String?> = channelFlow {
currentMetadata.collectLatest { metadata ->
if (metadata == null) {
send(lastArtist)
return@collectLatest
}
val artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
?: metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE)
?: currentMediaController.firstOrNull()?.packageName?.let { pkg ->
getAppLabel(pkg)
}
lastArtist = artist
send(artist)
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private var lastAlbum: String? = null
get() {
if (field == null) {
field = preferences.getString(PREFS_KEY_ALBUM, null)
}
return field
}
set(value) {
preferences.edit {
putString(PREFS_KEY_ALBUM, value)
}
field = value
}
override val album = channelFlow {
currentMetadata.collectLatest { metadata ->
if (metadata == null) {
send(lastAlbum)
return@collectLatest
}
val album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)
lastAlbum = album
send(album)
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
override val albumArt: Flow<Bitmap?> = channelFlow {
val size = context.resources.getDimensionPixelSize(R.dimen.album_art_size)
currentMetadata.collectLatest { metadata ->
if (metadata == null) {
val isNull = preferences.getString(PREFS_KEY_ALBUM_ART, "null") == "null"
if (isNull) {
send(null)
} else {
val bmp: Bitmap? = withContext(Dispatchers.IO) {
val file = java.io.File(context.filesDir, "album_art")
val request = ImageRequest.Builder(context)
.data(file)
.size(size)
.build()
context.imageLoader.execute(request).drawable?.toBitmap()
}
send(bmp)
}
return@collectLatest
}
val bitmap =
metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)?.let { resize(it, size) }
?: metadata.getBitmap(MediaMetadata.METADATA_KEY_ART)?.let { resize(it, size) }
?: metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
?.let { loadBitmapFromUri(Uri.parse(it), size) }
?: metadata.getString(MediaMetadata.METADATA_KEY_ART_URI)
?.let { loadBitmapFromUri(Uri.parse(it), size) }
withContext(Dispatchers.IO) {
if (bitmap == null) {
preferences.edit {
putString(PREFS_KEY_ALBUM_ART, "null")
}
} else {
val file = java.io.File(context.filesDir, "album_art")
bitmap.compress(Bitmap.CompressFormat.PNG, 100, file.outputStream())
preferences.edit {
putString(PREFS_KEY_ALBUM_ART, "notnull")
}
}
}
send(bitmap)
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private suspend fun loadBitmapFromUri(uri: Uri, size: Int): Bitmap? {
try {
val request = ImageRequest.Builder(context)
.data(uri)
.size(size)
.scale(Scale.FILL)
.build()
context.imageLoader.execute(request).drawable?.toBitmap()
} catch (e: IOException) {
} catch (e: SecurityException) {
}
return null
} }
private suspend fun resize(bitmap: Bitmap, size: Int): Bitmap? {
return withContext(Dispatchers.IO) {
val request = ImageRequest.Builder(context).data(bitmap)
.size(size)
.scale(Scale.FILL)
.build()
context.imageLoader.execute(request).drawable?.toBitmap()
}
}
private fun getAppLabel(packageName: String): String? {
return try {
context
.packageManager
.getPackageInfo(packageName, 0).applicationInfo
.loadLabel(context.packageManager).toString()
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
override fun previous() { override fun previous() {
if (mediaController?.skipToPreviousPlaylistItem()?.get() == null) { scope.launch {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager val controller = currentMediaController.firstOrNull()
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS) if (controller != null) {
audioManager.dispatchMediaKeyEvent(downEvent) controller.transportControls.skipToPrevious()
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS) } else {
audioManager.dispatchMediaKeyEvent(upEvent) val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS)
audioManager.dispatchMediaKeyEvent(downEvent)
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)
audioManager.dispatchMediaKeyEvent(upEvent)
}
} }
} }
override fun next() { override fun next() {
if (mediaController?.skipToNextPlaylistItem()?.get() == null) { scope.launch {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager val controller = currentMediaController.firstOrNull()
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT) if (controller != null) {
audioManager.dispatchMediaKeyEvent(downEvent) controller.transportControls.skipToNext()
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT) } else {
audioManager.dispatchMediaKeyEvent(upEvent) val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT)
audioManager.dispatchMediaKeyEvent(downEvent)
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT)
audioManager.dispatchMediaKeyEvent(upEvent)
}
} }
} }
override fun play() { override fun play() {
if (mediaController?.play()?.get() == null) { scope.launch {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager val controller = currentMediaController.firstOrNull()
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY) if (controller != null) {
audioManager.dispatchMediaKeyEvent(downEvent) controller.transportControls.play()
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY) } else {
audioManager.dispatchMediaKeyEvent(upEvent) val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)
audioManager.dispatchMediaKeyEvent(downEvent)
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)
audioManager.dispatchMediaKeyEvent(upEvent)
}
} }
} }
override fun pause() { override fun pause() {
if (mediaController?.pause()?.get() == null) { scope.launch {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager val controller = currentMediaController.firstOrNull()
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE) if (controller != null) {
audioManager.dispatchMediaKeyEvent(downEvent) controller.transportControls.pause()
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE) } else {
audioManager.dispatchMediaKeyEvent(upEvent) val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val downEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE)
audioManager.dispatchMediaKeyEvent(downEvent)
val upEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE)
audioManager.dispatchMediaKeyEvent(upEvent)
}
} }
} }
override fun togglePause() { override fun togglePause() {
if (playbackState.value != PlaybackState.Playing) play() else pause() scope.launch {
val controller = currentMediaController.firstOrNull()
if (controller != null && controller.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING) {
pause()
} else {
play()
}
}
} }
override fun openPlayer(): PendingIntent? { override fun openPlayer(): PendingIntent? {
mediaController?.sessionActivity?.let {
val controller = currentMediaController.replayCache.firstOrNull()
controller?.sessionActivity?.let {
return it return it
} }
val intent = lastPlayer?.let { val packageName = controller?.packageName ?: lastPlayerPackage
val intent = packageName?.let {
context.packageManager.getLaunchIntentForPackage(it)?.apply { context.packageManager.getLaunchIntentForPackage(it)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
} }
@ -296,61 +441,23 @@ internal class MusicRepositoryImpl(
) )
} }
private suspend fun isMusicApp(packageName: String): Boolean { private fun getMusicApps(): Set<String> {
val intent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_APP_MUSIC) } val apps = mutableSetOf<String>()
return !withContext(Dispatchers.IO) { var intent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_APP_MUSIC) }
context.packageManager.queryIntentActivities(intent, 0) apps.addAll(context.packageManager.queryIntentActivities(intent, 0)
.none { it.activityInfo.packageName == packageName } .map { it.activityInfo.packageName })
} intent = Intent("android.intent.action.MUSIC_PLAYER")
} apps.addAll(context.packageManager.queryIntentActivities(intent, 0)
.map { it.activityInfo.packageName })
private suspend fun setMetadata( Log.d("MM20", apps.joinToString())
title: String?, return apps
artist: String?,
album: String?,
albumArt: Bitmap?,
playerPackage: String?
) {
withContext(Dispatchers.IO) {
if (albumArt == null) {
this@MusicRepositoryImpl.albumArt.value = null
} else {
val size = context.resources.getDimension(R.dimen.album_art_size)
val (scaledW, scaledH) = if (albumArt.width > albumArt.height) {
size * albumArt.width / albumArt.height to size
} else {
size to size * albumArt.height / albumArt.width
}
val scaledBitmap = albumArt.scale(scaledW.toInt(), scaledH.toInt())
val file = File(context.cacheDir, "album_art")
val outStream = file.outputStream()
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream)
outStream.close()
this@MusicRepositoryImpl.albumArt.value = scaledBitmap
}
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit {
putString(PREFS_KEY_TITLE, title)
putString(PREFS_KEY_ARTIST, artist)
putString(PREFS_KEY_ALBUM, album)
putString(PREFS_KEY_LAST_PLAYER, playerPackage)
putString(PREFS_KEY_ALBUM_ART, if (albumArt == null) "null" else "notnull")
}
lastPlayer = playerPackage ?: lastPlayer
this@MusicRepositoryImpl.title.value = title
this@MusicRepositoryImpl.artist.value = artist
this@MusicRepositoryImpl.album.value = album
}
} }
override fun resetPlayer() { override fun resetPlayer() {
scope.launch { scope.launch {
mediaController?.close() preferences.edit {
mediaController = null clear()
setMetadata(null, null, null, null, null) }
} }
} }

View File

@ -38,8 +38,6 @@ dependencies {
implementation(libs.bundles.kotlin) implementation(libs.bundles.kotlin)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.media2)
implementation(libs.bundles.androidx.lifecycle) implementation(libs.bundles.androidx.lifecycle)
implementation(libs.koin.android) implementation(libs.koin.android)

View File

@ -38,7 +38,6 @@ dependencies {
implementation(libs.bundles.kotlin) implementation(libs.bundles.kotlin)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.media2)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout)
implementation(libs.bundles.androidx.lifecycle) implementation(libs.bundles.androidx.lifecycle)