diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f93b57f160be0174a321f028afdf128361d18257..bf4cabbbe75ff3d3408ebd1beee7b9c42df17551 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -52,7 +52,7 @@ android {
     disable += listOf("MissingTranslation", "ExtraTranslation")
   }
 
-  compileSdk = 31
+  compileSdk = 33
 
   defaultConfig {
 
@@ -62,7 +62,7 @@ android {
     versionName = androidGitVersion.name()
 
     minSdk = 24
-    targetSdk = 30
+    targetSdk = 33
 
     testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 
@@ -167,21 +167,14 @@ dependencies {
   implementation("com.google.android.material:material:1.6.1")
   implementation("com.android.support.constraint:constraint-layout:2.0.4")
 
-  implementation("com.google.android.exoplayer:exoplayer-core:2.14.2")
-  implementation("com.google.android.exoplayer:exoplayer-ui:2.14.2")
-  implementation("com.google.android.exoplayer:extension-mediasession:2.14.2")
+  implementation("com.google.android.exoplayer:exoplayer-core:2.18.1")
+  implementation("com.google.android.exoplayer:exoplayer-ui:2.18.1")
+  implementation("com.google.android.exoplayer:extension-mediasession:2.18.1")
 
   implementation("io.insert-koin:koin-core:3.1.2")
   implementation("io.insert-koin:koin-android:3.1.2")
   testImplementation("io.insert-koin:koin-test:3.1.2")
 
-  implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:2.14.0") {
-    isTransitive = false
-  }
-  implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-flac:2.14.0") {
-    isTransitive = false
-  }
-
   implementation("com.aliassadi:power-preference-lib:2.0.0")
   implementation("com.github.kittinunf.fuel:fuel:2.3.1")
   implementation("com.github.kittinunf.fuel:fuel-coroutines:2.3.1")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b6e4126339ab3cfa1457089aef571ae38ecb75e2..a417e9e8d1e8e1f628fb8cd45e7f243a204c2f4f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -22,7 +22,8 @@
             android:name=".activities.SplashActivity"
             android:launchMode="singleInstance"
             android:noHistory="true"
-            android:screenOrientation="portrait">
+            android:screenOrientation="portrait"
+            android:exported="true">
 
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -61,7 +62,8 @@
 
         <service
             android:name=".playback.PlayerService"
-            android:foregroundServiceType="mediaPlayback">
+            android:foregroundServiceType="mediaPlayback"
+            android:exported="false">
 
             <intent-filter>
                 <action android:name="android.intent.action.MEDIA_BUTTON" />
@@ -80,7 +82,8 @@
 
         </service>
 
-        <receiver android:name="androidx.media.session.MediaButtonReceiver">
+        <receiver android:name="androidx.media.session.MediaButtonReceiver"
+            android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.MEDIA_BUTTON" />
             </intent-filter>
diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt
index 14d8939ec2e77282246eeff022dcb21dd5a1af8e..ee77457cdea44e65466906a15917a5ac21d7ae1b 100644
--- a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt
@@ -228,8 +228,8 @@ class MainActivity : AppCompatActivity() {
           item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW)
           item.actionView = View(this)
           item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
-            override fun onMenuItemActionExpand(item: MenuItem?) = false
-            override fun onMenuItemActionCollapse(item: MenuItem?) = false
+            override fun onMenuItemActionExpand(item: MenuItem) = false
+            override fun onMenuItemActionCollapse(item: MenuItem) = false
           })
 
           item.isChecked = !item.isChecked
@@ -359,7 +359,7 @@ class MainActivity : AppCompatActivity() {
               .alpha(0.0f)
               .setDuration(400)
               .setListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animator: Animator?) {
+                override fun onAnimationEnd(animator: Animator) {
                   binding.nowPlaying.visibility = View.GONE
                 }
               })
diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt b/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt
index 36b5302f6834fd7c82fd36865fe46ef649b697b0..73ee5b5a02409c49b80a316c805291bb61cd3ed5 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt
@@ -2,6 +2,7 @@ package audio.funkwhale.ffa.playback
 
 import android.app.Notification
 import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_MUTABLE
 import android.app.Service
 import android.content.Intent
 import android.support.v4.media.session.MediaSessionCompat
@@ -42,7 +43,7 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
 
       scope.launch(Default) {
         val openIntent = Intent(context, MainActivity::class.java).apply { action = NOTIFICATION_ACTION_OPEN_QUEUE.toString() }
-        val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, 0)
+        val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, FLAG_MUTABLE)
 
         val coverUrl = maybeNormalizeUrl(track.album?.cover())
 
diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt b/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt
index 8898309b548551dc242f4c1ebf2c9a842b43aaec..5e0a5d5175ddfb9800bc2072f4d349ffc1a0c18a 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt
@@ -9,7 +9,6 @@ import android.support.v4.media.session.MediaSessionCompat
 import android.support.v4.media.session.PlaybackStateCompat
 import audio.funkwhale.ffa.utils.Command
 import audio.funkwhale.ffa.utils.CommandBus
-import com.google.android.exoplayer2.ControlDispatcher
 import com.google.android.exoplayer2.Player
 import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
 
@@ -43,7 +42,7 @@ class MediaSession(private val context: Context) {
     MediaSessionConnector(session).also {
       it.setQueueNavigator(FFAQueueNavigator())
 
-      it.setMediaButtonEventHandler { _, _, intent ->
+      it.setMediaButtonEventHandler { _, intent ->
         if (!active) {
           Intent(context, PlayerService::class.java).let { player ->
             player.action = intent.action
@@ -67,13 +66,14 @@ class MediaSession(private val context: Context) {
 }
 
 class FFAQueueNavigator : MediaSessionConnector.QueueNavigator {
-  override fun onSkipToQueueItem(player: Player, controlDispatcher: ControlDispatcher, id: Long) {
+  override fun onSkipToQueueItem(player: Player, id: Long) {
     CommandBus.send(Command.PlayTrack(id.toInt()))
   }
 
-  override fun onCurrentWindowIndexChanged(player: Player) {}
+  override fun onCurrentMediaItemIndexChanged(player: Player) {}
 
-  override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?) = true
+  override fun onCommand(player: Player, command: String, extras: Bundle?, cb: ResultReceiver?) =
+    true
 
   override fun getSupportedQueueNavigatorActions(player: Player): Long {
     return PlaybackStateCompat.ACTION_PLAY_PAUSE or
@@ -82,13 +82,13 @@ class FFAQueueNavigator : MediaSessionConnector.QueueNavigator {
       PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
   }
 
-  override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) {
+  override fun onSkipToNext(player: Player) {
     CommandBus.send(Command.NextTrack)
   }
 
   override fun getActiveQueueItemId(player: Player?) = player?.currentWindowIndex?.toLong() ?: 0
 
-  override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) {
+  override fun onSkipToPrevious(player: Player) {
     CommandBus.send(Command.PreviousTrack)
   }
 
diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt b/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt
index f3bd1cea02b99b1a3abbc9974091a5531328cbdc..5993800a9e444e47cfaf2d4ee4a6dbea3c128b1b 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt
@@ -80,7 +80,10 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
 
   override fun getScheduler(): Scheduler? = null
 
-  override fun getForegroundNotification(downloads: MutableList<Download>): Notification {
+  override fun getForegroundNotification(
+    downloads: MutableList<Download>,
+    notMetRequirements: Int
+  ): Notification {
     val description =
       resources.getQuantityString(R.plurals.downloads_description, downloads.size, downloads.size)
 
diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt b/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt
index 9c74da5cc4110b1d6dc94d07668579d8c1bf6e2d..f0bf33678b15e8a983640a16c703d6dafb453a78 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt
@@ -31,11 +31,10 @@ import audio.funkwhale.ffa.utils.log
 import audio.funkwhale.ffa.utils.maybeNormalizeUrl
 import audio.funkwhale.ffa.utils.onApi
 import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.ExoPlaybackException
+import com.google.android.exoplayer2.PlaybackException
 import com.google.android.exoplayer2.Player
 import com.google.android.exoplayer2.SimpleExoPlayer
-import com.google.android.exoplayer2.source.TrackGroupArray
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray
+import com.google.android.exoplayer2.Tracks
 import com.squareup.picasso.Picasso
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers.IO
@@ -43,7 +42,6 @@ import kotlinx.coroutines.Dispatchers.Main
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.koin.java.KoinJavaComponent.inject
@@ -136,6 +134,7 @@ class PlayerService : Service() {
       playWhenReady = false
 
       playerEventListener = PlayerEventListener().also {
+
         addListener(it)
       }
     }
@@ -419,9 +418,9 @@ class PlayerService : Service() {
   }
 
   @SuppressLint("NewApi")
-  inner class PlayerEventListener : Player.EventListener {
+  inner class PlayerEventListener : Player.Listener {
     override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
-      super.onPlayerStateChanged(playWhenReady, playbackState)
+      super.onPlayWhenReadyChanged(playWhenReady, playbackState)
 
       EventBus.send(Event.StateChanged(playWhenReady))
 
@@ -469,14 +468,11 @@ class PlayerService : Service() {
       }
     }
 
-    override fun onTracksChanged(
-      trackGroups: TrackGroupArray,
-      trackSelections: TrackSelectionArray
-    ) {
-      super.onTracksChanged(trackGroups, trackSelections)
+    override fun onTracksChanged(tracks: Tracks) {
+      super.onTracksChanged(tracks)
 
-      if (queue.current != player.currentWindowIndex) {
-        queue.current = player.currentWindowIndex
+      if (queue.current != player.currentMediaItemIndex) {
+        queue.current = player.currentMediaItemIndex
         mediaControlsManager.updateNotification(queue.current(), player.playWhenReady)
       }
 
@@ -510,7 +506,7 @@ class PlayerService : Service() {
       }
     }
 
-    override fun onPlayerError(error: ExoPlaybackException) {
+    override fun onPlayerError(error: PlaybackException) {
       EventBus.send(Event.PlaybackError(getString(R.string.error_playback)))
 
       if (player.playWhenReady) {
diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt b/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt
index fe9bf315eb3882f2373c0bba3eaaac0da6849f85..c883390a1750f250b2480c5f1af54968a4d0d517 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt
@@ -12,6 +12,7 @@ import audio.funkwhale.ffa.utils.FFACache
 import audio.funkwhale.ffa.utils.log
 import audio.funkwhale.ffa.utils.mustNormalizeUrl
 import com.github.kittinunf.fuel.gson.gsonDeserializerOf
+import com.google.android.exoplayer2.MediaItem
 import com.google.android.exoplayer2.source.ConcatenatingMediaSource
 import com.google.android.exoplayer2.source.ProgressiveMediaSource
 import com.google.gson.Gson
@@ -37,9 +38,12 @@ class QueueManager(val context: Context) {
         dataSources.addMediaSources(
           metadata.map { track ->
             val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
-
-            ProgressiveMediaSource.Factory(factory).setTag(track.title)
-              .createMediaSource(Uri.parse(url))
+            ProgressiveMediaSource.Factory(factory).createMediaSource(
+              MediaItem.Builder()
+                .setTag(track.title)
+                .setUri(Uri.parse(url))
+                .build()
+            )
           }
         )
       }
@@ -63,8 +67,12 @@ class QueueManager(val context: Context) {
     val factory = cacheDataSourceFactoryProvider.create(context)
     val sources = tracks.map { track ->
       val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
-
-      ProgressiveMediaSource.Factory(factory).setTag(track.title).createMediaSource(Uri.parse(url))
+      ProgressiveMediaSource.Factory(factory).createMediaSource(
+        MediaItem.Builder()
+          .setTag(track.title)
+          .setUri(Uri.parse(url))
+          .build()
+      )
     }
 
     metadata = tracks.toMutableList()
@@ -84,7 +92,12 @@ class QueueManager(val context: Context) {
     val sources = missingTracks.map { track ->
       val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
 
-      ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url))
+      ProgressiveMediaSource.Factory(factory).createMediaSource(
+        MediaItem.Builder()
+          .setTag(track.title) // was this missing on purpose?!
+          .setUri(Uri.parse(url))
+          .build()
+      )
     }
 
     metadata.addAll(tracks)
@@ -101,7 +114,12 @@ class QueueManager(val context: Context) {
     val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
 
     if (metadata.indexOf(track) == -1) {
-      ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)).let {
+      ProgressiveMediaSource.Factory(factory).createMediaSource(
+        MediaItem.Builder()
+          .setTag(track.title) // was this missing on purpose?!
+          .setUri(Uri.parse(url))
+          .build()
+      ).let {
         dataSources.addMediaSource(current + 1, it)
         metadata.add(current + 1, track)
       }
diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt
index ee32d642298ecdf36af5b7eed25b779d95ed994d..60021e16429e5e7afca9bf27f76b2a38aea6a66f 100644
--- a/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt
@@ -11,8 +11,8 @@ import audio.funkwhale.ffa.model.Track
 import audio.funkwhale.ffa.model.TracksCache
 import audio.funkwhale.ffa.model.TracksResponse
 import audio.funkwhale.ffa.utils.OAuth
-import com.github.kittinunf.fuel.gson.gsonDeserializerOf
 import audio.funkwhale.ffa.utils.mustNormalizeUrl
+import com.github.kittinunf.fuel.gson.gsonDeserializerOf
 import com.google.android.exoplayer2.offline.DownloadManager
 import com.google.android.exoplayer2.upstream.cache.Cache
 import com.google.gson.reflect.TypeToken
diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt
index 7858bf0089ad6a0e4b6a46556f9baf945464f72c..7a224c61b721fe920186c04620703d0c16054503 100644
--- a/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt
@@ -7,8 +7,8 @@ import audio.funkwhale.ffa.model.TracksCache
 import audio.funkwhale.ffa.model.TracksResponse
 import audio.funkwhale.ffa.utils.OAuth
 import audio.funkwhale.ffa.utils.getMetadata
-import com.github.kittinunf.fuel.gson.gsonDeserializerOf
 import audio.funkwhale.ffa.utils.mustNormalizeUrl
+import com.github.kittinunf.fuel.gson.gsonDeserializerOf
 import com.google.android.exoplayer2.offline.Download
 import com.google.android.exoplayer2.offline.DownloadManager
 import com.google.android.exoplayer2.upstream.cache.Cache
diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt
index 7fb81232671ae317632d47589178eb48f0d86a54..fbd5e81cc068392f485cd489a821ab35ac7f8613 100644
--- a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt
@@ -10,10 +10,8 @@ import audio.funkwhale.ffa.model.DownloadInfo
 import audio.funkwhale.ffa.repositories.Repository
 import com.github.kittinunf.fuel.core.FuelError
 import com.github.kittinunf.fuel.core.Request
-import com.github.kittinunf.fuel.core.ResponseDeserializable
 import com.google.android.exoplayer2.offline.Download
 import com.google.gson.Gson
-import com.google.gson.reflect.TypeToken
 import com.squareup.picasso.Picasso
 import com.squareup.picasso.RequestCreator
 import kotlinx.coroutines.CompletableDeferred
@@ -23,7 +21,6 @@ import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import net.openid.appauth.ClientSecretPost
-import java.io.Reader
 import java.text.SimpleDateFormat
 import java.util.Date
 import kotlin.coroutines.CoroutineContext
diff --git a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt
index d4133b0aab2a824f3b305727baf5375db93bf53d..7ad73746cc1bf461c4776b206cebdac4283b8b64 100644
--- a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt
@@ -52,7 +52,7 @@ class NowPlayingView : MaterialCardView {
       viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
         override fun onGlobalLayout() {
           gestureDetectorCallback = OnGestureDetection()
-          gestureDetector = GestureDetector(context, gestureDetectorCallback)
+          gestureDetector = GestureDetector(context, gestureDetectorCallback!!)
 
           setOnTouchListener { _, motionEvent ->
             val ret = gestureDetector?.onTouchEvent(motionEvent) ?: false
@@ -128,8 +128,8 @@ class NowPlayingView : MaterialCardView {
     }
 
     override fun onFling(
-      firstMotionEvent: MotionEvent?,
-      secondMotionEvent: MotionEvent?,
+      firstMotionEvent: MotionEvent,
+      secondMotionEvent: MotionEvent,
       velocityX: Float,
       velocityY: Float
     ): Boolean {
@@ -195,7 +195,7 @@ class NowPlayingView : MaterialCardView {
       return true
     }
 
-    override fun onSingleTapUp(e: MotionEvent?): Boolean {
+    override fun onSingleTapUp(e: MotionEvent): Boolean {
       layoutParams.let {
         if (height != minHeight) return true