Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to androidx.media3 #1118

Closed
wants to merge 2 commits into from
Closed

Migrate to androidx.media3 #1118

wants to merge 2 commits into from

Conversation

nielsvanvelzen
Copy link
Member

Changes

  • Migrate ExoPlayer to androidx.media3

To do

  • Migrate exoplayer-ffmpeg-extension to media3
  • Fix commented code (MediaSessionConnector)
  • Look into dropping androidx.media
  • Fix UI issues
    • Play/pause icon doesn't work
    • Seekbar show/hide animation is incorrect

Issues

@jellyfin-bot jellyfin-bot added this to the v2.6.0 milestone Jun 26, 2023
@nielsvanvelzen
Copy link
Member Author

Closing for now because we prioritized some other stuff. Will re-create later.

@satmandu
Copy link

Any chance of this being resurrected?

4k HEVC HDR playback might start working on supported hardware if we start using media3 as per androidx/media#1311

@nielsvanvelzen
Copy link
Member Author

@Maxr1998 wants to migrate the existing code to a new UI first so I closed this PR.

@satmandu
Copy link

Ah. @Maxr1998 would you be opposed to having a separate branch with the media3 stuff in it for now until the new UI is setup? #1404 depends upon exoplayer to play downloaded files, so ideally having the most functionality in the exoplayer/media3 player would help with that functionality, since 4k HEVC HDR files otherwise display a black screen when playing with exoplayer.

@Maxr1998
Copy link
Member

Migrating to media3 would also be quite a lot of work on its own. And rebasing the UI changes afterwards would be really painful. I know it's far from ideal, but I fear we'll have to keep things as-is for now until I finish my thesis and have more time to work on Jellyfin again, which should be towards the end of this year.

@satmandu
Copy link

Migrating to media3 would also be quite a lot of work on its own. And rebasing the UI changes afterwards would be really painful. I know it's far from ideal, but I fear we'll have to keep things as-is for now until I finish my thesis and have more time to work on Jellyfin again, which should be towards the end of this year.

Ah academia. I wish you the best of luck, and thanks for your feedback.

@satmandu
Copy link

Just an FYI for anyone wanting to play around with media3. I minimally updated this PR/patch series to work with #1404 and the newest tagged release of media3 (1.40-rc0), and the following was the patch needed.

Aside from the issues reported above, which still exist, DTS-HD audio streams also do not work in the built-in player.

diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/android-lint.xml c/android-lint.xml
--- a/android-lint.xml	2024-07-15 17:01:30.294096820 -0400
+++ c/android-lint.xml	2024-07-17 14:21:19.935573194 -0400
@@ -11,4 +11,8 @@
         <!-- Weblate doesn't use ellipsis characters -->
         <ignore regexp="app/src/main/res/values-.*" />
     </issue>
+    <issue id="UnsafeOptInUsageError">
+        <!-- We use a lot of "unstable" media3 apis -->
+        <ignore regexp="\(markerClass = androidx\.media3\.common\.util\.UnstableApi\.class\)" />
+    </issue>
 </lint>
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/build.gradle.kts c/app/build.gradle.kts
--- a/app/build.gradle.kts	2024-07-15 17:01:30.294096820 -0400
+++ c/app/build.gradle.kts	2024-07-17 14:21:19.935573194 -0400
@@ -150,12 +150,12 @@ dependencies {
     // Media
     implementation(libs.androidx.media)
     implementation(libs.androidx.mediarouter)
-    implementation(libs.bundles.exoplayer) {
+    implementation(libs.bundles.androidx.media3) {
         // Exclude Play Services cronet provider library
         exclude("com.google.android.gms", "play-services-cronet")
     }
     implementation(libs.jellyfin.exoplayer.ffmpegextension)
-    proprietaryImplementation(libs.exoplayer.cast)
+    proprietaryImplementation(libs.androidx.media3.cast)
     proprietaryImplementation(libs.bundles.playservices)
 
     // Room
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/proguard-rules.pro c/app/proguard-rules.pro
--- a/app/proguard-rules.pro	2024-07-15 17:01:30.298096775 -0400
+++ c/app/proguard-rules.pro	2024-07-17 14:22:21.346553255 -0400
@@ -32,3 +32,7 @@
 -assumevalues class android.os.Build$VERSION {
   int SDK_INT return 21..2147483647;
 }
+
+# Please add these rules to your existing keep rules in order to suppress warnings.
+# This is generated automatically by the Android Gradle plugin.
+-dontwarn com.google.auto.service.AutoService
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/libre/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt c/app/src/libre/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt
--- a/app/src/libre/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt	2024-07-15 17:01:30.298096775 -0400
+++ c/app/src/libre/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt	2024-07-17 14:21:19.935573194 -0400
@@ -1,6 +1,6 @@
 package org.jellyfin.mobile.player.cast
 
-import com.google.android.exoplayer2.Player
+import androidx.media3.common.Player
 import org.jellyfin.mobile.player.audio.MediaService
 
 class CastPlayerProvider(@Suppress("UNUSED_PARAMETER") mediaService: MediaService) : ICastPlayerProvider {
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/app/AppModule.kt c/app/src/main/java/org/jellyfin/mobile/app/AppModule.kt
--- a/app/src/main/java/org/jellyfin/mobile/app/AppModule.kt	2024-07-15 17:01:30.298096775 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/app/AppModule.kt	2024-07-17 14:54:10.013902547 -0400
@@ -1,24 +1,24 @@
 package org.jellyfin.mobile.app
-
 import android.content.Context
+import androidx.media3.common.util.Util
+import androidx.media3.database.DatabaseProvider
+import androidx.media3.database.StandaloneDatabaseProvider
+import androidx.media3.datasource.cache.Cache
+import androidx.media3.datasource.cache.CacheDataSource
+import androidx.media3.datasource.cache.NoOpCacheEvictor
+import androidx.media3.datasource.cache.SimpleCache
+import androidx.media3.datasource.cronet.CronetDataSource
+import androidx.media3.datasource.DataSource
+import androidx.media3.datasource.DefaultDataSource
+import androidx.media3.datasource.DefaultHttpDataSource
+import androidx.media3.exoplayer.hls.HlsMediaSource
+import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
+import androidx.media3.exoplayer.source.MediaSource
+import androidx.media3.exoplayer.source.ProgressiveMediaSource
+import androidx.media3.exoplayer.source.SingleSampleMediaSource
+import androidx.media3.extractor.DefaultExtractorsFactory
+import androidx.media3.extractor.ts.TsExtractor
 import coil.ImageLoader
-import com.google.android.exoplayer2.database.DatabaseProvider
-import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
-import com.google.android.exoplayer2.ext.cronet.CronetDataSource
-import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
-import com.google.android.exoplayer2.extractor.ts.TsExtractor
-import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
-import com.google.android.exoplayer2.source.MediaSource
-import com.google.android.exoplayer2.source.ProgressiveMediaSource
-import com.google.android.exoplayer2.source.SingleSampleMediaSource
-import com.google.android.exoplayer2.source.hls.HlsMediaSource
-import com.google.android.exoplayer2.upstream.DefaultDataSource
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
-import com.google.android.exoplayer2.upstream.cache.Cache
-import com.google.android.exoplayer2.upstream.cache.CacheDataSource
-import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor
-import com.google.android.exoplayer2.upstream.cache.SimpleCache
-import com.google.android.exoplayer2.util.Util
 import kotlinx.coroutines.channels.Channel
 import okhttp3.OkHttpClient
 import org.chromium.net.CronetEngine
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/downloads/DownloadServiceUtil.kt c/app/src/main/java/org/jellyfin/mobile/downloads/DownloadServiceUtil.kt
--- a/app/src/main/java/org/jellyfin/mobile/downloads/DownloadServiceUtil.kt	2024-07-15 17:01:30.298096775 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/downloads/DownloadServiceUtil.kt	2024-07-17 14:25:06.006420449 -0400
@@ -1,12 +1,12 @@
 package org.jellyfin.mobile.downloads
 
 import android.content.Context
-import com.google.android.exoplayer2.database.DatabaseProvider
-import com.google.android.exoplayer2.offline.DefaultDownloadIndex
-import com.google.android.exoplayer2.offline.DefaultDownloaderFactory
-import com.google.android.exoplayer2.offline.DownloadManager
-import com.google.android.exoplayer2.ui.DownloadNotificationHelper
-import com.google.android.exoplayer2.upstream.cache.CacheDataSource
+import androidx.media3.database.DatabaseProvider
+import androidx.media3.exoplayer.offline.DefaultDownloadIndex
+import androidx.media3.exoplayer.offline.DefaultDownloaderFactory
+import androidx.media3.exoplayer.offline.DownloadManager
+import androidx.media3.exoplayer.offline.DownloadNotificationHelper
+import androidx.media3.datasource.cache.CacheDataSource
 import org.jellyfin.mobile.utils.Constants.DOWNLOAD_NOTIFICATION_CHANNEL_ID
 import org.koin.core.component.KoinComponent
 import org.koin.core.component.inject
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt c/app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt
--- a/app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt	2024-07-15 17:01:30.298096775 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt	2024-07-17 14:25:23.934855321 -0400
@@ -1,9 +1,9 @@
 package org.jellyfin.mobile.downloads
 
 import android.net.Uri
-import com.google.android.exoplayer2.offline.Download
-import com.google.android.exoplayer2.offline.DownloadIndex
-import com.google.android.exoplayer2.offline.DownloadManager
+import androidx.media3.exoplayer.offline.Download
+import androidx.media3.exoplayer.offline.DownloadIndex
+import androidx.media3.exoplayer.offline.DownloadManager
 import com.google.common.base.Preconditions
 import timber.log.Timber
 import java.io.IOException
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/downloads/DownloadUtils.kt c/app/src/main/java/org/jellyfin/mobile/downloads/DownloadUtils.kt
--- a/app/src/main/java/org/jellyfin/mobile/downloads/DownloadUtils.kt	2024-07-15 17:01:30.298096775 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/downloads/DownloadUtils.kt	2024-07-17 14:25:39.919225049 -0400
@@ -21,9 +21,9 @@ import androidx.core.graphics.drawable.t
 import androidx.core.net.toUri
 import coil.ImageLoader
 import coil.request.ImageRequest
-import com.google.android.exoplayer2.offline.DownloadRequest
-import com.google.android.exoplayer2.offline.DownloadService
-import com.google.android.exoplayer2.scheduler.Requirements
+import androidx.media3.exoplayer.offline.DownloadRequest
+import androidx.media3.exoplayer.offline.DownloadService
+import androidx.media3.exoplayer.scheduler.Requirements
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/downloads/JellyfinDownloadService.kt c/app/src/main/java/org/jellyfin/mobile/downloads/JellyfinDownloadService.kt
--- a/app/src/main/java/org/jellyfin/mobile/downloads/JellyfinDownloadService.kt	2024-07-15 17:01:30.298096775 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/downloads/JellyfinDownloadService.kt	2024-07-17 14:25:58.039624351 -0400
@@ -3,14 +3,14 @@ package org.jellyfin.mobile.downloads
 import android.app.Notification
 import android.content.Context
 import android.os.Build
-import com.google.android.exoplayer2.offline.Download
-import com.google.android.exoplayer2.offline.DownloadManager
-import com.google.android.exoplayer2.offline.DownloadService
-import com.google.android.exoplayer2.scheduler.PlatformScheduler
-import com.google.android.exoplayer2.scheduler.Scheduler
-import com.google.android.exoplayer2.ui.DownloadNotificationHelper
-import com.google.android.exoplayer2.util.NotificationUtil
-import com.google.android.exoplayer2.util.Util
+import androidx.media3.exoplayer.offline.Download
+import androidx.media3.exoplayer.offline.DownloadManager
+import androidx.media3.exoplayer.offline.DownloadService
+import androidx.media3.exoplayer.scheduler.PlatformScheduler
+import androidx.media3.exoplayer.scheduler.Scheduler
+import androidx.media3.exoplayer.offline.DownloadNotificationHelper
+import androidx.media3.common.util.NotificationUtil
+import androidx.media3.common.util.Util
 import org.jellyfin.mobile.R
 import org.jellyfin.mobile.utils.Constants
 
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/audio/AudioNotificationManager.kt c/app/src/main/java/org/jellyfin/mobile/player/audio/AudioNotificationManager.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/audio/AudioNotificationManager.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/audio/AudioNotificationManager.kt	2024-07-17 14:21:19.939573127 -0400
@@ -25,11 +25,11 @@ import android.net.Uri
 import android.support.v4.media.session.MediaControllerCompat
 import android.support.v4.media.session.MediaSessionCompat
 import androidx.core.graphics.drawable.toBitmap
+import androidx.media3.common.ForwardingPlayer
+import androidx.media3.common.Player
+import androidx.media3.ui.PlayerNotificationManager
 import coil.ImageLoader
 import coil.request.ImageRequest
-import com.google.android.exoplayer2.ForwardingPlayer
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.ui.PlayerNotificationManager
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.SupervisorJob
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/audio/MediaService.kt c/app/src/main/java/org/jellyfin/mobile/player/audio/MediaService.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/audio/MediaService.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/audio/MediaService.kt	2024-07-17 14:21:19.939573127 -0400
@@ -5,31 +5,25 @@ package org.jellyfin.mobile.player.audio
 import android.app.Notification
 import android.app.PendingIntent
 import android.content.Intent
-import android.net.Uri
 import android.os.Bundle
-import android.os.ResultReceiver
 import android.support.v4.media.MediaBrowserCompat.MediaItem
-import android.support.v4.media.MediaDescriptionCompat
 import android.support.v4.media.MediaMetadataCompat
 import android.support.v4.media.session.MediaControllerCompat
 import android.support.v4.media.session.MediaSessionCompat
-import android.support.v4.media.session.PlaybackStateCompat
 import android.widget.Toast
 import androidx.core.content.ContextCompat
 import androidx.media.MediaBrowserServiceCompat
+import androidx.media3.common.AudioAttributes
+import androidx.media3.common.C
+import androidx.media3.common.PlaybackException
+import androidx.media3.common.Player
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.exoplayer.source.MediaSource
+import androidx.media3.ui.PlayerNotificationManager
 import androidx.mediarouter.media.MediaControlIntent
 import androidx.mediarouter.media.MediaRouteSelector
 import androidx.mediarouter.media.MediaRouter
 import androidx.mediarouter.media.MediaRouterParams
-import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.ExoPlayer
-import com.google.android.exoplayer2.PlaybackException
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.audio.AudioAttributes
-import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
-import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
-import com.google.android.exoplayer2.source.MediaSource
-import com.google.android.exoplayer2.ui.PlayerNotificationManager
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.MainScope
@@ -38,7 +32,6 @@ import kotlinx.coroutines.launch
 import org.jellyfin.mobile.R
 import org.jellyfin.mobile.app.ApiClientController
 import org.jellyfin.mobile.player.audio.car.LibraryBrowser
-import org.jellyfin.mobile.player.audio.car.LibraryPage
 import org.jellyfin.mobile.player.cast.CastPlayerProvider
 import org.jellyfin.mobile.player.cast.ICastPlayerProvider
 import org.jellyfin.mobile.utils.Constants
@@ -49,7 +42,7 @@ import org.jellyfin.sdk.api.client.excep
 import org.koin.android.ext.android.get
 import org.koin.android.ext.android.inject
 import timber.log.Timber
-import com.google.android.exoplayer2.MediaItem as ExoPlayerMediaItem
+import androidx.media3.common.MediaItem as Media3Item
 
 class MediaService : MediaBrowserServiceCompat() {
     private val apiClientController: ApiClientController by inject()
@@ -68,7 +61,7 @@ class MediaService : MediaBrowserService
     private lateinit var notificationManager: AudioNotificationManager
     private lateinit var mediaController: MediaControllerCompat
     private lateinit var mediaSession: MediaSessionCompat
-    private lateinit var mediaSessionConnector: MediaSessionConnector
+    // private lateinit var mediaSessionConnector: MediaSessionConnector
     private lateinit var mediaRouteSelector: MediaRouteSelector
     private lateinit var mediaRouter: MediaRouter
     private val mediaRouterCallback = MediaRouterCallback()
@@ -123,11 +116,11 @@ class MediaService : MediaBrowserService
 
         mediaController = MediaControllerCompat(this, mediaSession)
 
-        mediaSessionConnector = MediaSessionConnector(mediaSession).apply {
-            setPlayer(exoPlayer)
-            setPlaybackPreparer(MediaPlaybackPreparer())
-            setQueueNavigator(MediaQueueNavigator(mediaSession))
-        }
+//        mediaSessionConnector = MediaSessionConnector(mediaSession).apply {
+//            setPlayer(exoPlayer)
+//            setPlaybackPreparer(MediaPlaybackPreparer())
+//            setQueueNavigator(MediaQueueNavigator(mediaSession))
+//        }
 
         mediaRouter = MediaRouter.getInstance(this)
         mediaRouter.setMediaSessionCompat(mediaSession)
@@ -203,7 +196,7 @@ class MediaService : MediaBrowserService
         currentPlaylistItems = metadataList
 
         val mediaItems = metadataList.map { metadata ->
-            ExoPlayerMediaItem.Builder().apply {
+            Media3Item.Builder().apply {
                 setUri(metadata.mediaUri)
                 setTag(metadata)
             }.build()
@@ -255,23 +248,23 @@ class MediaService : MediaBrowserService
                 )
             }
         }
-        mediaSessionConnector.setPlayer(newPlayer)
+//        mediaSessionConnector.setPlayer(newPlayer)
         previousPlayer?.run {
             stop()
             clearMediaItems()
         }
     }
 
-    private fun setPlaybackError() {
-        val errorState = PlaybackStateCompat.Builder()
-            .setState(PlaybackStateCompat.STATE_ERROR, 0, 1f)
-            .setErrorMessage(
-                PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED,
-                getString(R.string.media_service_item_not_found),
-            )
-            .build()
-        mediaSession.setPlaybackState(errorState)
-    }
+//    private fun setPlaybackError() {
+//        val errorState = PlaybackStateCompat.Builder()
+//            .setState(PlaybackStateCompat.STATE_ERROR, 0, 1f)
+//            .setErrorMessage(
+//                PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED,
+//                getString(R.string.media_service_item_not_found),
+//            )
+//            .build()
+//        mediaSession.setPlaybackState(errorState)
+//    }
 
     @Suppress("unused")
     fun onCastSessionAvailable() {
@@ -284,83 +277,83 @@ class MediaService : MediaBrowserService
         switchToPlayer(currentPlayer, exoPlayer)
     }
 
-    private inner class MediaQueueNavigator(mediaSession: MediaSessionCompat) : TimelineQueueNavigator(mediaSession) {
-        override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat =
-            currentPlaylistItems[windowIndex].description
-    }
-
-    private inner class MediaPlaybackPreparer : MediaSessionConnector.PlaybackPreparer {
-        override fun getSupportedPrepareActions(): Long = 0L or
-            PlaybackStateCompat.ACTION_PREPARE or
-            PlaybackStateCompat.ACTION_PLAY or
-            PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
-            PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or
-            PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH or
-            PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
-
-        override fun onPrepare(playWhenReady: Boolean) {
-            serviceScope.launch {
-                val recents = try {
-                    libraryBrowser.getDefaultRecents()
-                } catch (e: ApiClientException) {
-                    Timber.e(e)
-                    null
-                }
-                if (recents != null) {
-                    preparePlaylist(recents, 0, playWhenReady)
-                } else {
-                    setPlaybackError()
-                }
-            }
-        }
-
-        override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
-            if (mediaId == LibraryPage.RESUME) {
-                // Requested recents
-                onPrepare(playWhenReady)
-            } else {
-                serviceScope.launch {
-                    val result = libraryBrowser.buildPlayQueue(mediaId)
-                    if (result != null) {
-                        val (playbackQueue, initialPlaybackIndex) = result
-                        preparePlaylist(playbackQueue, initialPlaybackIndex, playWhenReady)
-                    } else {
-                        setPlaybackError()
-                    }
-                }
-            }
-        }
-
-        override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) {
-            if (query.isEmpty()) {
-                // No search provided, fallback to recents
-                onPrepare(playWhenReady)
-            } else {
-                serviceScope.launch {
-                    val results = try {
-                        libraryBrowser.getSearchResults(query, extras)
-                    } catch (e: ApiClientException) {
-                        Timber.e(e)
-                        null
-                    }
-                    if (results != null) {
-                        preparePlaylist(results, 0, playWhenReady)
-                    } else {
-                        setPlaybackError()
-                    }
-                }
-            }
-        }
-
-        override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) = Unit
-
-        override fun onCommand(
-            player: Player,
-            command: String,
-            extras: Bundle?,
-            cb: ResultReceiver?,
-        ): Boolean = false
-    }
+//    private inner class MediaQueueNavigator(mediaSession: MediaSessionCompat) : TimelineQueueNavigator(mediaSession) {
+//        override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat =
+//            currentPlaylistItems[windowIndex].description
+//    }
+
+//    private inner class MediaPlaybackPreparer : MediaSessionConnector.PlaybackPreparer {
+//        override fun getSupportedPrepareActions(): Long = 0L or
+//            PlaybackStateCompat.ACTION_PREPARE or
+//            PlaybackStateCompat.ACTION_PLAY or
+//            PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
+//            PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or
+//            PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH or
+//            PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
+//
+//        override fun onPrepare(playWhenReady: Boolean) {
+//            serviceScope.launch {
+//                val recents = try {
+//                    libraryBrowser.getDefaultRecents()
+//                } catch (e: ApiClientException) {
+//                    Timber.e(e)
+//                    null
+//                }
+//                if (recents != null) {
+//                    preparePlaylist(recents, 0, playWhenReady)
+//                } else {
+//                    setPlaybackError()
+//                }
+//            }
+//        }
+//
+//        override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
+//            if (mediaId == LibraryPage.RESUME) {
+//                // Requested recents
+//                onPrepare(playWhenReady)
+//            } else {
+//                serviceScope.launch {
+//                    val result = libraryBrowser.buildPlayQueue(mediaId)
+//                    if (result != null) {
+//                        val (playbackQueue, initialPlaybackIndex) = result
+//                        preparePlaylist(playbackQueue, initialPlaybackIndex, playWhenReady)
+//                    } else {
+//                        setPlaybackError()
+//                    }
+//                }
+//            }
+//        }
+//
+//        override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) {
+//            if (query.isEmpty()) {
+//                // No search provided, fallback to recents
+//                onPrepare(playWhenReady)
+//            } else {
+//                serviceScope.launch {
+//                    val results = try {
+//                        libraryBrowser.getSearchResults(query, extras)
+//                    } catch (e: ApiClientException) {
+//                        Timber.e(e)
+//                        null
+//                    }
+//                    if (results != null) {
+//                        preparePlaylist(results, 0, playWhenReady)
+//                    } else {
+//                        setPlaybackError()
+//                    }
+//                }
+//            }
+//        }
+//
+//        override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) = Unit
+//
+//        override fun onCommand(
+//            player: Player,
+//            command: String,
+//            extras: Bundle?,
+//            cb: ResultReceiver?,
+//        ): Boolean = false
+//    }
 
     /**
      * Listen for notification events.
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/cast/ICastPlayerProvider.kt c/app/src/main/java/org/jellyfin/mobile/player/cast/ICastPlayerProvider.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/cast/ICastPlayerProvider.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/cast/ICastPlayerProvider.kt	2024-07-17 14:21:19.939573127 -0400
@@ -1,6 +1,6 @@
 package org.jellyfin.mobile.player.cast
 
-import com.google.android.exoplayer2.Player
+import androidx.media3.common.Player
 
 interface ICastPlayerProvider {
     val isCastSessionAvailable: Boolean
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/CodecHelpers.kt c/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/CodecHelpers.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/CodecHelpers.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/CodecHelpers.kt	2024-07-17 14:21:19.939573127 -0400
@@ -2,7 +2,7 @@ package org.jellyfin.mobile.player.devic
 
 import android.media.MediaCodecInfo.CodecProfileLevel
 import android.media.MediaFormat
-import com.google.android.exoplayer2.util.MimeTypes
+import androidx.media3.common.MimeTypes
 
 @Suppress("TooManyFunctions", "CyclomaticComplexMethod")
 object CodecHelpers {
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/interaction/PlayerNotificationHelper.kt c/app/src/main/java/org/jellyfin/mobile/player/interaction/PlayerNotificationHelper.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/interaction/PlayerNotificationHelper.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/interaction/PlayerNotificationHelper.kt	2024-07-17 14:21:19.939573127 -0400
@@ -13,9 +13,9 @@ import androidx.core.content.ContextComp
 import androidx.core.content.getSystemService
 import androidx.core.graphics.drawable.toBitmap
 import androidx.lifecycle.viewModelScope
+import androidx.media3.common.Player
 import coil.ImageLoader
 import coil.request.ImageRequest
-import com.google.android.exoplayer2.Player
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt c/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt	2024-07-17 14:21:19.935573194 -0400
@@ -12,20 +12,20 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ProcessLifecycleOwner
 import androidx.lifecycle.viewModelScope
-import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.DefaultRenderersFactory
-import com.google.android.exoplayer2.ExoPlayer
-import com.google.android.exoplayer2.PlaybackException
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector
-import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException
-import com.google.android.exoplayer2.mediacodec.MediaCodecInfo
-import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
-import com.google.android.exoplayer2.source.MediaSource
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
-import com.google.android.exoplayer2.util.Clock
-import com.google.android.exoplayer2.util.EventLogger
-import com.google.android.exoplayer2.util.MimeTypes
+import androidx.media3.common.C
+import androidx.media3.common.MimeTypes
+import androidx.media3.common.PlaybackException
+import androidx.media3.common.Player
+import androidx.media3.common.util.Clock
+import androidx.media3.exoplayer.DefaultRenderersFactory
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector
+import androidx.media3.exoplayer.mediacodec.MediaCodecDecoderException
+import androidx.media3.exoplayer.mediacodec.MediaCodecInfo
+import androidx.media3.exoplayer.mediacodec.MediaCodecSelector
+import androidx.media3.exoplayer.source.MediaSource
+import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
+import androidx.media3.exoplayer.util.EventLogger
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt c/app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt	2024-07-17 14:21:19.939573127 -0400
@@ -5,12 +5,12 @@ import androidx.annotation.CheckResult
 import androidx.core.net.toUri
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import com.google.android.exoplayer2.MediaItem
-import com.google.android.exoplayer2.source.MediaSource
-import com.google.android.exoplayer2.source.MergingMediaSource
-import com.google.android.exoplayer2.source.ProgressiveMediaSource
-import com.google.android.exoplayer2.source.SingleSampleMediaSource
-import com.google.android.exoplayer2.source.hls.HlsMediaSource
+import androidx.media3.common.MediaItem
+import androidx.media3.exoplayer.hls.HlsMediaSource
+import androidx.media3.exoplayer.source.MediaSource
+import androidx.media3.exoplayer.source.MergingMediaSource
+import androidx.media3.exoplayer.source.ProgressiveMediaSource
+import androidx.media3.exoplayer.source.SingleSampleMediaSource
 import org.jellyfin.mobile.data.dao.DownloadDao
 import org.jellyfin.mobile.player.PlayerException
 import org.jellyfin.mobile.player.PlayerViewModel
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/TrackSelectionHelper.kt c/app/src/main/java/org/jellyfin/mobile/player/TrackSelectionHelper.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/TrackSelectionHelper.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/TrackSelectionHelper.kt	2024-07-17 14:21:19.939573127 -0400
@@ -1,7 +1,7 @@
 package org.jellyfin.mobile.player
 
-import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
+import androidx.media3.common.C
+import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
 import org.jellyfin.mobile.player.source.ExternalSubtitleStream
 import org.jellyfin.mobile.player.source.JellyfinMediaSource
 import org.jellyfin.mobile.utils.clearSelectionAndDisableRendererByType
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt c/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt	2024-07-17 14:21:19.939573127 -0400
@@ -24,8 +24,8 @@ import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.viewModels
 import androidx.lifecycle.lifecycleScope
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.ui.PlayerView
+import androidx.media3.common.Player
+import androidx.media3.ui.PlayerView
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 import org.jellyfin.mobile.R
@@ -50,7 +50,7 @@ import org.jellyfin.mobile.utils.extensi
 import org.jellyfin.mobile.utils.toast
 import org.jellyfin.sdk.model.api.MediaStream
 import org.koin.android.ext.android.inject
-import com.google.android.exoplayer2.ui.R as ExoplayerR
+import androidx.media3.ui.R as Media3R
 
 class PlayerFragment : Fragment(), BackPressInterceptor {
     private val appPreferences: AppPreferences by inject()
@@ -353,7 +353,7 @@ class PlayerFragment : Fragment(), BackP
                     }
                 }
                 setAspectRatio(aspectRational)
-                val contentFrame: View = playerView.findViewById(ExoplayerR.id.exo_content_frame)
+                val contentFrame: View = playerView.findViewById(Media3R.id.exo_content_frame)
                 val contentRect = with(contentFrame) {
                     val (x, y) = intArrayOf(0, 0).also(::getLocationInWindow)
                     Rect(x, y, x + width, y + height)
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerGestureHelper.kt c/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerGestureHelper.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerGestureHelper.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerGestureHelper.kt	2024-07-17 14:21:19.939573127 -0400
@@ -14,8 +14,8 @@ import android.widget.ProgressBar
 import androidx.core.content.getSystemService
 import androidx.core.view.isVisible
 import androidx.core.view.postDelayed
-import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
-import com.google.android.exoplayer2.ui.PlayerView
+import androidx.media3.ui.AspectRatioFrameLayout
+import androidx.media3.ui.PlayerView
 import org.jellyfin.mobile.R
 import org.jellyfin.mobile.app.AppPreferences
 import org.jellyfin.mobile.databinding.FragmentPlayerBinding
@@ -122,7 +122,7 @@ class PlayerGestureHelper(
 
             override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
                 playerView.apply {
-                    if (!isControllerVisible) showController() else hideController()
+                    if (!isControllerFullyVisible) showController() else hideController()
                 }
                 return true
             }
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerLockScreenHelper.kt c/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerLockScreenHelper.kt
--- a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerLockScreenHelper.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerLockScreenHelper.kt	2024-07-17 14:21:19.939573127 -0400
@@ -4,7 +4,7 @@ import android.content.pm.ActivityInfo
 import android.view.OrientationEventListener
 import android.widget.ImageButton
 import androidx.core.view.isVisible
-import com.google.android.exoplayer2.ui.PlayerView
+import androidx.media3.ui.PlayerView
 import org.jellyfin.mobile.databinding.FragmentPlayerBinding
 import org.jellyfin.mobile.utils.AndroidVersion
 import org.jellyfin.mobile.utils.Constants
@@ -50,7 +50,7 @@ class PlayerLockScreenHelper(
         if (!AndroidVersion.isAtLeastN || !activity.isInPictureInPictureMode) {
             playerView.useController = true
             playerView.apply {
-                if (!isControllerVisible) showController()
+                if (!isControllerFullyVisible) showController()
             }
         }
     }
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/ui/screens/connect/ServerSelection.kt c/app/src/main/java/org/jellyfin/mobile/ui/screens/connect/ServerSelection.kt
--- a/app/src/main/java/org/jellyfin/mobile/ui/screens/connect/ServerSelection.kt	2024-07-15 17:01:30.302096728 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/ui/screens/connect/ServerSelection.kt	2024-07-17 14:32:32.720301927 -0400
@@ -28,6 +28,7 @@ import androidx.compose.material.Outline
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.ArrowBack
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
@@ -278,7 +279,7 @@ private fun ServerDiscoveryList(
             verticalAlignment = Alignment.CenterVertically,
         ) {
             IconButton(onClick = onGoBack) {
-                Icon(imageVector = Icons.Outlined.ArrowBack, contentDescription = null)
+                Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)
             }
             Text(
                 modifier = Modifier
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/utils/MediaExtensions.kt c/app/src/main/java/org/jellyfin/mobile/utils/MediaExtensions.kt
--- a/app/src/main/java/org/jellyfin/mobile/utils/MediaExtensions.kt	2024-07-15 17:01:30.306096682 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/utils/MediaExtensions.kt	2024-07-17 14:21:19.939573127 -0400
@@ -7,13 +7,13 @@ import android.media.AudioManager
 import android.media.MediaMetadata
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
-import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.ExoPlayer
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.analytics.AnalyticsCollector
+import androidx.media3.common.C
+import androidx.media3.common.Player
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.exoplayer.analytics.AnalyticsCollector
 import org.jellyfin.mobile.player.source.JellyfinMediaSource
 import org.jellyfin.mobile.utils.extensions.width
-import com.google.android.exoplayer2.audio.AudioAttributes as ExoPlayerAudioAttributes
+import androidx.media3.common.AudioAttributes as Media3AudioAttributes
 
 inline fun MediaSession.applyDefaultLocalAudioAttributes(contentType: Int) {
     val audioAttributes = AudioAttributes.Builder().apply {
@@ -72,10 +72,10 @@ fun AudioManager.getVolumeLevelPercent()
 }
 
 /**
- * Set ExoPlayer [ExoPlayerAudioAttributes], make ExoPlayer handle audio focus
+ * Set ExoPlayer [Media3AudioAttributes], make ExoPlayer handle audio focus
  */
 inline fun ExoPlayer.applyDefaultAudioAttributes(@C.AudioContentType contentType: Int) {
-    val audioAttributes = ExoPlayerAudioAttributes.Builder()
+    val audioAttributes = Media3AudioAttributes.Builder()
         .setUsage(C.USAGE_MEDIA)
         .setContentType(contentType)
         .build()
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt c/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt
--- a/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt	2024-07-15 17:01:30.306096682 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt	2024-07-17 14:57:56.439455485 -0400
@@ -17,7 +17,7 @@ import android.provider.Settings
 import android.provider.Settings.System.ACCELEROMETER_ROTATION
 import androidx.coordinatorlayout.widget.CoordinatorLayout
 import androidx.core.content.getSystemService
-import com.google.android.exoplayer2.offline.DownloadService
+import androidx.media3.exoplayer.offline.DownloadService
 import com.google.android.material.snackbar.Snackbar
 import kotlinx.coroutines.suspendCancellableCoroutine
 import org.jellyfin.mobile.BuildConfig
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/java/org/jellyfin/mobile/utils/TrackSelectionUtils.kt c/app/src/main/java/org/jellyfin/mobile/utils/TrackSelectionUtils.kt
--- a/app/src/main/java/org/jellyfin/mobile/utils/TrackSelectionUtils.kt	2024-07-15 17:01:30.306096682 -0400
+++ c/app/src/main/java/org/jellyfin/mobile/utils/TrackSelectionUtils.kt	2024-07-17 14:21:19.939573127 -0400
@@ -1,9 +1,8 @@
 package org.jellyfin.mobile.utils
 
-import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.source.TrackGroup
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
-import com.google.android.exoplayer2.trackselection.TrackSelectionOverride
+import androidx.media3.common.TrackGroup
+import androidx.media3.common.TrackSelectionOverride
+import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
 
 /**
  * Select the [trackGroup] of the specified [type] and ensure the type is enabled.
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/res/layout/exo_player_control_view.xml c/app/src/main/res/layout/exo_player_control_view.xml
--- a/app/src/main/res/layout/exo_player_control_view.xml	2024-07-15 17:01:30.306096682 -0400
+++ c/app/src/main/res/layout/exo_player_control_view.xml	2024-07-17 14:21:19.939573127 -0400
@@ -85,7 +85,7 @@
         app:layout_constraintTop_toTopOf="@id/exo_progress"
         tools:text="33:01" />
 
-    <com.google.android.exoplayer2.ui.DefaultTimeBar
+    <androidx.media3.ui.DefaultTimeBar
         android:id="@+id/exo_progress"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/main/res/layout/fragment_player.xml c/app/src/main/res/layout/fragment_player.xml
--- a/app/src/main/res/layout/fragment_player.xml	2024-07-15 17:01:30.306096682 -0400
+++ c/app/src/main/res/layout/fragment_player.xml	2024-07-17 14:21:19.939573127 -0400
@@ -5,7 +5,7 @@
     android:layout_height="match_parent"
     android:background="@android:color/black">
 
-    <com.google.android.exoplayer2.ui.PlayerView
+    <androidx.media3.ui.PlayerView
         android:id="@+id/player_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/app/src/proprietary/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt c/app/src/proprietary/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt
--- a/app/src/proprietary/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt	2024-07-15 17:01:30.318096543 -0400
+++ c/app/src/proprietary/java/org/jellyfin/mobile/player/cast/CastPlayerProvider.kt	2024-07-17 14:21:19.939573127 -0400
@@ -1,8 +1,8 @@
 package org.jellyfin.mobile.player.cast
 
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.ext.cast.CastPlayer
-import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
+import androidx.media3.cast.CastPlayer
+import androidx.media3.cast.SessionAvailabilityListener
+import androidx.media3.common.Player
 import com.google.android.gms.cast.framework.CastContext
 import org.jellyfin.mobile.player.audio.MediaService
 
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/build.gradle.kts c/build.gradle.kts
--- a/build.gradle.kts	2024-07-15 17:01:30.318096543 -0400
+++ c/build.gradle.kts	2024-07-17 14:34:31.520638047 -0400
@@ -22,5 +22,5 @@ tasks.wrapper {
 }
 
 tasks.create<Delete>("clean") {
-    delete(rootProject.buildDir)
+    delete(rootProject.layout.buildDirectory)
 }
diff '--exclude=.git' '--exclude=.gradle' '--exclude=build' -Npaur a/gradle/libs.versions.toml c/gradle/libs.versions.toml
--- a/gradle/libs.versions.toml	2024-07-15 17:01:30.334096358 -0400
+++ c/gradle/libs.versions.toml	2024-07-17 14:36:37.200644401 -0400
@@ -1,6 +1,6 @@
 [versions]
 # Plugins
-android-plugin = "8.5.0"
+android-plugin = "8.5.1"
 kotlin = "2.0.0"
 kotlin-ksp = "2.0.0-1.0.22"
 detekt = "1.22.0"
@@ -42,6 +42,7 @@ coil = "2.6.0"
 cronet-embedded = "119.6045.31"
 
 # Media
+androidx-media3 = "1.4.0-rc01"
 androidx-media = "1.7.0"
 androidx-mediarouter = "1.7.0"
 exoplayer = "2.19.1"
@@ -120,16 +121,16 @@ cronet-embedded = { group = "org.chromiu
 
 # Media
 androidx-media = { group = "androidx.media", name = "media", version.ref = "androidx-media" }
+androidx-media3-cast = { group = "androidx.media3", name = "media3-cast", version.ref = "androidx-media3" }
+androidx-media3-datasource-cronet = { group = "androidx.media3", name = "media3-datasource-cronet", version.ref = "androidx-media3" }
+androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "androidx-media3" }
+androidx-media3-exoplayer-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "androidx-media3" }
+androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "androidx-media3" }
+androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "androidx-media3" }
 androidx-mediarouter = { group = "androidx.mediarouter", name = "mediarouter", version.ref = "androidx-mediarouter" }
-exoplayer-core = { group = "com.google.android.exoplayer", name = "exoplayer-core", version.ref = "exoplayer" }
-exoplayer-ui = { group = "com.google.android.exoplayer", name = "exoplayer-ui", version.ref = "exoplayer" }
-exoplayer-mediaSession = { group = "com.google.android.exoplayer", name = "extension-mediasession", version.ref = "exoplayer" }
-exoplayer-hls = { group = "com.google.android.exoplayer", name = "exoplayer-hls", version.ref = "exoplayer" }
-exoplayer-cast = { group = "com.google.android.exoplayer", name = "extension-cast", version.ref = "exoplayer" }
-exoplayer-cronet = { group = "com.google.android.exoplayer", name = "extension-cronet", version.ref = "exoplayer" }
+jellyfin-exoplayer-ffmpegextension = { group = "org.jellyfin.exoplayer", name = "exoplayer-ffmpeg-extension", version.ref = "jellyfin-exoplayer-ffmpegextension" }
 playservices-cast = { group = "com.google.android.gms", name = "play-services-cast", version.ref = "playservices" }
 playservices-castframework = { group = "com.google.android.gms", name = "play-services-cast-framework", version.ref = "playservices" }
-jellyfin-exoplayer-ffmpegextension = { group = "org.jellyfin.exoplayer", name = "exoplayer-ffmpeg-extension", version.ref = "jellyfin-exoplayer-ffmpegextension" }
 
 # Room
 androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "androidx-room" }
@@ -172,12 +173,12 @@ compose = [
     "compose-material-icons",
     "compose-material-icons-extended",
 ]
-exoplayer = [
-    "exoplayer-core",
-    "exoplayer-ui",
-    "exoplayer-mediaSession",
-    "exoplayer-hls",
-    "exoplayer-cronet",
+androidx-media3 = [
+    "androidx-media3-datasource-cronet",
+    "androidx-media3-exoplayer",
+    "androidx-media3-exoplayer-hls",
+    "androidx-media3-session",
+    "androidx-media3-ui"
 ]
 playservices = [
     "playservices-cast",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants