Improve media position and duration handling

This commit is contained in:
MM20 2023-02-13 12:53:06 +01:00
parent c6608eaa90
commit 14902ba085
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389

View File

@ -13,7 +13,9 @@ import android.media.session.MediaSession
import android.net.Uri import android.net.Uri
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.SystemClock
import android.service.notification.StatusBarNotification 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
@ -28,6 +30,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@ -38,6 +41,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -50,6 +54,7 @@ interface MusicService {
val artist: Flow<String?> val artist: Flow<String?>
val album: Flow<String?> val album: Flow<String?>
val albumArt: Flow<Bitmap?> val albumArt: Flow<Bitmap?>
val position: Flow<Long?>
val duration: Flow<Long?> val duration: Flow<Long?>
val lastPlayerPackage: String? val lastPlayerPackage: String?
@ -141,26 +146,17 @@ internal class MusicServiceImpl(
} }
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1) }.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
override val playbackState: SharedFlow<PlaybackState> = channelFlow { private val currentState: SharedFlow<android.media.session.PlaybackState?> = channelFlow {
currentMediaController.collectLatest { controller -> currentMediaController.collectLatest { controller ->
if (controller == null) return@collectLatest send(PlaybackState.Stopped) if (controller == null) {
send( send(null)
when (controller.playbackState?.state) { return@collectLatest
android.media.session.PlaybackState.STATE_PLAYING -> PlaybackState.Playing }
android.media.session.PlaybackState.STATE_PAUSED -> PlaybackState.Paused send(controller.playbackState)
else -> PlaybackState.Stopped
}
)
val callback = object : MediaController.Callback() { val callback = object : MediaController.Callback() {
override fun onPlaybackStateChanged(state: android.media.session.PlaybackState?) { override fun onPlaybackStateChanged(state: android.media.session.PlaybackState?) {
super.onPlaybackStateChanged(state) super.onPlaybackStateChanged(state)
trySend( trySend(state)
when (state?.state) {
android.media.session.PlaybackState.STATE_PLAYING -> PlaybackState.Playing
android.media.session.PlaybackState.STATE_PAUSED -> PlaybackState.Paused
else -> PlaybackState.Stopped
}
)
} }
} }
try { try {
@ -172,6 +168,51 @@ internal class MusicServiceImpl(
} }
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1) }.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
override val playbackState: SharedFlow<PlaybackState> = channelFlow {
currentState.collectLatest { state ->
if (state == null) {
send(PlaybackState.Stopped)
return@collectLatest
}
when (state.state) {
android.media.session.PlaybackState.STATE_PLAYING -> send(PlaybackState.Playing)
android.media.session.PlaybackState.STATE_PAUSED -> send(PlaybackState.Paused)
android.media.session.PlaybackState.STATE_STOPPED -> send(PlaybackState.Stopped)
else -> send(PlaybackState.Stopped)
}
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private var lastPosition: Long? = null
get() {
if (field == null) {
field = preferences.getLong(PREFS_KEY_POSITION, -1).takeIf { it >= 0 }
}
return field
}
set(value) {
preferences.edit {
putLong(PREFS_KEY_POSITION, value ?: -1)
}
field = value
}
override val position: SharedFlow<Long?> = channelFlow {
currentState.collectLatest { state ->
if (state == null || state.state != android.media.session.PlaybackState.STATE_PLAYING) {
send(lastPosition)
return@collectLatest
}
while(isActive) {
val offset = SystemClock.elapsedRealtime() - state.lastPositionUpdateTime
val position = state.position + offset
lastPosition = position
send(position)
delay(1000)
}
}
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private var lastTitle: String? = null private var lastTitle: String? = null
get() { get() {
@ -312,13 +353,28 @@ internal class MusicServiceImpl(
} }
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1) }.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
private var lastDuration: Long? = null
get() {
if (field == null) {
field = preferences.getLong(PREFS_KEY_DURATION, -1).takeIf { it >= 0 }
}
return field
}
set(value) {
preferences.edit {
putLong(PREFS_KEY_DURATION, value ?: -1)
}
field = value
}
override val duration: Flow<Long?> = channelFlow { override val duration: Flow<Long?> = channelFlow {
currentMetadata.collectLatest { metadata -> currentMetadata.collectLatest { metadata ->
if (metadata == null) { if (metadata == null) {
send(null) send(lastDuration)
return@collectLatest return@collectLatest
} }
val duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION) val duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION)
lastDuration = duration
send(duration.takeIf { it > 0 }) send(duration.takeIf { it > 0 })
} }
}.shareIn(scope, SharingStarted.WhileSubscribed(), 1) }.shareIn(scope, SharingStarted.WhileSubscribed(), 1)
@ -511,6 +567,8 @@ internal class MusicServiceImpl(
private const val PREFS = "music" private const val PREFS = "music"
private const val PREFS_KEY_TITLE = "title" private const val PREFS_KEY_TITLE = "title"
private const val PREFS_KEY_DURATION = "duration"
private const val PREFS_KEY_POSITION = "position"
private const val PREFS_KEY_ARTIST = "artist" private const val PREFS_KEY_ARTIST = "artist"
private const val PREFS_KEY_ALBUM = "album" private const val PREFS_KEY_ALBUM = "album"
private const val PREFS_KEY_ALBUM_ART = "album_art" private const val PREFS_KEY_ALBUM_ART = "album_art"