Skip to content

Commit

Permalink
Merge pull request PaulWoitaschek#2012 from PaulWoitaschek/media3_fix
Browse files Browse the repository at this point in the history
Update media3 and implement onPlaybackResumption
  • Loading branch information
PaulWoitaschek authored Jul 1, 2023
2 parents 87a4356 + 43bc5fc commit 652d855
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import voice.common.pref.CurrentBook
import voice.data.Book
import voice.data.repo.BookRepository
import voice.playback.playstate.PlayStateManager
import voice.playback.receiver.MediaButtonReceiver
import voice.playback.receiver.WidgetButtonReceiver
import javax.inject.Inject
import voice.common.R as CommonR

Expand Down Expand Up @@ -123,13 +123,13 @@ class WidgetUpdater
}

private suspend fun initElements(remoteViews: RemoteViews, book: Book, coverSize: Int) {
val playPausePI = MediaButtonReceiver.pendingIntent(context, MediaButtonReceiver.Action.PlayPause)
val playPausePI = WidgetButtonReceiver.pendingIntent(context, WidgetButtonReceiver.Action.PlayPause)
remoteViews.setOnClickPendingIntent(R.id.playPause, playPausePI)

val fastForwardPI = MediaButtonReceiver.pendingIntent(context, MediaButtonReceiver.Action.FastForward)
val fastForwardPI = WidgetButtonReceiver.pendingIntent(context, WidgetButtonReceiver.Action.FastForward)
remoteViews.setOnClickPendingIntent(R.id.fastForward, fastForwardPI)

val rewindPI = MediaButtonReceiver.pendingIntent(context, MediaButtonReceiver.Action.Rewind)
val rewindPI = WidgetButtonReceiver.pendingIntent(context, WidgetButtonReceiver.Action.Rewind)
remoteViews.setOnClickPendingIntent(R.id.rewind, rewindPI)

val playIcon = if (playStateManager.playState == PlayStateManager.PlayState.Playing) {
Expand Down
4 changes: 0 additions & 4 deletions app/src/main/kotlin/voice/app/injection/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import de.paulwoitaschek.flowpref.Pref
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import voice.app.BuildConfig
import voice.app.features.widget.TriggerWidgetOnChange
import voice.app.misc.StrictModeInit
import voice.app.scanner.MediaScanTrigger
import voice.common.DARK_THEME_SETTABLE
import voice.common.pref.PrefKeys
Expand All @@ -37,8 +35,6 @@ class App : Application() {
override fun onCreate() {
super.onCreate()

if (BuildConfig.DEBUG) StrictModeInit.init()

Coil.setImageLoader(
ImageLoader.Builder(this)
.addLastModifiedToFileCacheKey(false)
Expand Down
37 changes: 0 additions & 37 deletions app/src/main/kotlin/voice/app/misc/StrictModeInit.kt

This file was deleted.

2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ materialDialog = "3.3.0"
dagger = "2.46.1"
kotlin = "1.8.22"
compose-compiler = "1.4.8"
media3 = "1.0.2"
media3 = "1.1.0-rc01"
agp = "8.0.2"
ktlint = "0.49.1"
retrofit = "2.9.0"
Expand Down
11 changes: 9 additions & 2 deletions playback/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
<application>

<receiver
android:name="voice.playback.receiver.MediaButtonReceiver"
android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">

<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>

<receiver
android:name="voice.playback.receiver.WidgetButtonReceiver"
android:exported="false">

<intent-filter>
<action android:name="voice.WidgetAction" />
</intent-filter>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.view.KeyEvent
import com.squareup.anvil.annotations.ContributesTo
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
Expand All @@ -18,7 +16,7 @@ import voice.playback.PlayerController
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds

class MediaButtonReceiver : BroadcastReceiver() {
class WidgetButtonReceiver : BroadcastReceiver() {

private val scope = MainScope()

Expand Down Expand Up @@ -58,7 +56,7 @@ class MediaButtonReceiver : BroadcastReceiver() {

@ContributesTo(AppScope::class)
interface Component {
fun inject(target: MediaButtonReceiver)
fun inject(target: WidgetButtonReceiver)
}

companion object {
Expand All @@ -71,7 +69,7 @@ class MediaButtonReceiver : BroadcastReceiver() {
action: Action,
): PendingIntent? {
val intent = Intent(WIDGET_ACTION)
.setComponent(ComponentName(context, MediaButtonReceiver::class.java))
.setComponent(ComponentName(context, WidgetButtonReceiver::class.java))
.putExtra(ACTION_KEY, action.name)
return PendingIntent.getBroadcast(
context,
Expand All @@ -88,25 +86,6 @@ class MediaButtonReceiver : BroadcastReceiver() {
companion object {
fun parse(intent: Intent?): Action? {
return when (intent?.action) {
Intent.ACTION_MEDIA_BUTTON -> {
val key: KeyEvent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)
}
Logger.d("key=$key")
if (key?.action == KeyEvent.ACTION_UP) {
when (key.keyCode) {
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY -> PlayPause
KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD -> FastForward
KeyEvent.KEYCODE_MEDIA_REWIND, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD -> Rewind
else -> null
}
} else {
null
}
}
WIDGET_ACTION -> {
entries.find { it.name == intent.getStringExtra(ACTION_KEY) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.guava.future
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import voice.common.BookId
import voice.common.pref.CurrentBook
import voice.data.Book
import voice.data.repo.BookRepository
import voice.logging.core.Logger
import voice.playback.player.VoicePlayer
import voice.playback.session.search.BookSearchHandler
Expand All @@ -42,6 +43,7 @@ class LibrarySessionCallback
private val currentBookId: DataStore<BookId?>,
private val sleepTimerCommandUpdater: SleepTimerCommandUpdater,
private val sleepTimer: SleepTimer,
private val bookRepository: BookRepository,
) : MediaLibrarySession.Callback {

override fun onAddMediaItems(
Expand Down Expand Up @@ -136,19 +138,33 @@ class LibrarySessionCallback
}
}

override fun onPlaybackResumption(mediaSession: MediaSession, controller: ControllerInfo): ListenableFuture<MediaItemsWithStartPosition> {
Logger.d("onPlaybackResumption")
return scope.future {
val currentBook = currentBook()
if (currentBook != null) {
mediaItemProvider.mediaItemsWithStartPosition(currentBook)
} else {
throw UnsupportedOperationException()
}
}
}

private suspend fun currentBook(): Book? {
val bookId = currentBookId.data.first() ?: return null
return bookRepository.get(bookId)
}

override fun onConnect(session: MediaSession, controller: ControllerInfo): ConnectionResult {
Logger.d("onConnect to ${controller.packageName}")
if (player.playbackState == Player.STATE_IDLE) {
Logger.d("onConnect and player is idle. Preparing current book so it shows up as recently played.")

if (player.playbackState == Player.STATE_IDLE &&
controller.packageName == "com.google.android.projection.gearhead"
) {
Logger.d("onConnect to ${controller.packageName} and player is idle.")
Logger.d("Preparing current book so it shows up as recently played")
scope.launch {
val bookId = currentBookId.data.first()
if (bookId != null) {
val item = mediaItemProvider.item(Json.encodeToString(MediaId.serializer(), MediaId.Book(bookId)))
if (item != null) {
player.setMediaItem(item)
player.prepare()
}
}
prepareCurrentBook()
}
}

Expand All @@ -166,6 +182,14 @@ class LibrarySessionCallback
)
}

private suspend fun prepareCurrentBook() {
val bookId = currentBookId.data.first() ?: return
val book = bookRepository.get(bookId) ?: return
val item = mediaItemProvider.mediaItem(book)
player.setMediaItem(item)
player.prepare()
}

override fun onCustomCommand(
session: MediaSession,
controller: ControllerInfo,
Expand Down
31 changes: 14 additions & 17 deletions playback/src/main/kotlin/voice/playback/session/PlaybackService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,27 @@ class PlaybackService : MediaLibraryService() {
setMediaNotificationProvider(voiceNotificationProvider)
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return try {
super.onStartCommand(intent, flags, startId)
} catch (e: Exception) {
Logger.e(e, "onStartCommand crashed")
START_STICKY
}
}

override fun onDestroy() {
super.onDestroy()
release()
}

override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
release()
stopSelf()
if (!player.playWhenReady) {
// If the player isn't set to play when ready, the service is stopped and resources released.
// This is done because if the app is swiped away from recent apps without this check,
// the notification would remain in an unresponsive state.
// Further explanation can be found at: https://github.com/androidx/media/issues/167#issuecomment-1615184728
release()
stopSelf()
}
}

private fun release() {
scope.cancel()
player.release()
session.release()
scope.cancel()
}

override fun onDestroy() {
super.onDestroy()
release()
}

override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
Expand Down
8 changes: 1 addition & 7 deletions scanner/src/main/kotlin/voice/app/scanner/MediaScanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import voice.documentfile.CachedDocumentFile
import voice.documentfile.walk
import voice.logging.core.Logger
import javax.inject.Inject
import kotlin.time.measureTime

class MediaScanner
@Inject constructor(
Expand Down Expand Up @@ -59,12 +58,7 @@ class MediaScanner
files
.sortedBy { it.audioFileCount() }
.forEach { file ->
Logger.d("scanning $file")
measureTime {
scan(file)
}.also {
Logger.i("scan took $it for ${file.uri}")
}
scan(file)
}
}

Expand Down

0 comments on commit 652d855

Please sign in to comment.