diff --git a/app/src/main/java/audio/funkwhale/ffa/koin/Modules.kt b/app/src/main/java/audio/funkwhale/ffa/koin/Modules.kt
index 028f771167b48e5ea5476ccedba4130bc2251ce1..c4ee334b7e5d83fb6488055593823e720d196f31 100644
--- a/app/src/main/java/audio/funkwhale/ffa/koin/Modules.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/koin/Modules.kt
@@ -1,16 +1,14 @@
 package audio.funkwhale.ffa.koin
 
 import android.content.Context
+import audio.funkwhale.ffa.R
 import audio.funkwhale.ffa.playback.CacheDataSourceFactoryProvider
 import audio.funkwhale.ffa.playback.MediaSession
 import audio.funkwhale.ffa.utils.AuthorizationServiceFactory
 import audio.funkwhale.ffa.utils.OAuth
 import com.google.android.exoplayer2.database.DatabaseProvider
 import com.google.android.exoplayer2.database.ExoDatabaseProvider
-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.offline.DownloaderConstructorHelper
 import com.google.android.exoplayer2.upstream.cache.Cache
 import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
 import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor
@@ -27,15 +25,12 @@ fun exoplayerModule(context: Context) = module {
 
   single {
     val cacheDataSourceFactoryProvider = get<CacheDataSourceFactoryProvider>()
-    DownloaderConstructorHelper(
-      get(named("exoDownloadCache")), cacheDataSourceFactoryProvider.create(context)
-    ).run {
-      DownloadManager(
-        context,
-        DefaultDownloadIndex(get(named("exoDatabase"))),
-        DefaultDownloaderFactory(this)
-      )
-    }
+
+    val exoDownloadCache = get<Cache>(named("exoDownloadCache"))
+    val exoDatabase = get<DatabaseProvider>(named("exoDatabase"))
+    val cacheDataSourceFactory = cacheDataSourceFactoryProvider.create(context)
+
+    DownloadManager(context, exoDatabase, exoDownloadCache, cacheDataSourceFactory, Runnable::run)
   }
 
   single {
diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/CacheDataSourceFactoryProvider.kt b/app/src/main/java/audio/funkwhale/ffa/playback/CacheDataSourceFactoryProvider.kt
index 74132d2588bceae1e29b48e84bb7a9e684394195..8dd33ec15855aa43b31568a25c10957e97e7609d 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/CacheDataSourceFactoryProvider.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/CacheDataSourceFactoryProvider.kt
@@ -9,7 +9,6 @@ import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
 import com.google.android.exoplayer2.upstream.FileDataSource
 import com.google.android.exoplayer2.upstream.cache.Cache
 import com.google.android.exoplayer2.upstream.cache.CacheDataSource
-import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory
 import com.google.android.exoplayer2.util.Util
 
 class CacheDataSourceFactoryProvider(
@@ -18,19 +17,19 @@ class CacheDataSourceFactoryProvider(
   private val exoDownloadCache: Cache
 ) {
 
-  fun create(context: Context): CacheDataSourceFactory {
+  fun create(context: Context): CacheDataSource.Factory {
 
-    val playbackCache =
-      CacheDataSourceFactory(exoCache, createDatasourceFactory(context, oAuth))
+    val playbackCache = CacheDataSource.Factory().apply {
+      setCache(exoCache)
+      setUpstreamDataSourceFactory(createDatasourceFactory(context, oAuth))
+    }
 
-    return CacheDataSourceFactory(
-      exoDownloadCache,
-      playbackCache,
-      FileDataSource.Factory(),
-      null,
-      CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
-      null
-    )
+    return CacheDataSource.Factory().apply {
+      setCache(exoDownloadCache)
+      setUpstreamDataSourceFactory(playbackCache)
+      setCacheReadDataSourceFactory(FileDataSource.Factory())
+      setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
+    }
   }
 
   private fun createDatasourceFactory(context: Context, oAuth: OAuth): DataSource.Factory {
diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/OAuth2Datasource.kt b/app/src/main/java/audio/funkwhale/ffa/playback/OAuth2Datasource.kt
index 64b5786b3f38c9ebd47f35626e0932191ddd004a..e53f0ddc2b66aa3e83555282edd9e0ff80a0b813 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/OAuth2Datasource.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/OAuth2Datasource.kt
@@ -3,11 +3,7 @@ package audio.funkwhale.ffa.playback
 import android.content.Context
 import android.net.Uri
 import audio.funkwhale.ffa.utils.OAuth
-import com.google.android.exoplayer2.upstream.DataSource
-import com.google.android.exoplayer2.upstream.DataSpec
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
-import com.google.android.exoplayer2.upstream.HttpDataSource
-import com.google.android.exoplayer2.upstream.TransferListener
+import com.google.android.exoplayer2.upstream.*
 
 class OAuthDatasource(
   private val context: Context,
@@ -15,11 +11,11 @@ class OAuthDatasource(
   private val oauth: OAuth
 ) : DataSource {
 
-  override fun addTransferListener(transferListener: TransferListener?) {
+  override fun addTransferListener(transferListener: TransferListener) {
     http.addTransferListener(transferListener)
   }
 
-  override fun open(dataSpec: DataSpec?): Long {
+  override fun open(dataSpec: DataSpec): Long {
     oauth.tryRefreshAccessToken(context)
     http.apply {
       setRequestProperty("Authorization", "Bearer ${oauth.state().accessToken}")
@@ -27,7 +23,7 @@ class OAuthDatasource(
     return http.open(dataSpec)
   }
 
-  override fun read(buffer: ByteArray?, offset: Int, readLength: Int): Int {
+  override fun read(buffer: ByteArray, offset: Int, readLength: Int): Int {
     return http.read(buffer, offset, readLength)
   }
 
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 0ef225c7f5d8ff354af4577a43f5ec680237b8f4..587c7c132bc9bc6a0ec43d078f78ed5ad46bd89e 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt
@@ -3,7 +3,7 @@ package audio.funkwhale.ffa.playback
 import android.app.Notification
 import android.content.Context
 import android.content.Intent
-import android.net.Uri
+import androidx.core.net.toUri
 import audio.funkwhale.ffa.R
 import audio.funkwhale.ffa.model.DownloadInfo
 import audio.funkwhale.ffa.model.Track
@@ -42,16 +42,12 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
           )
         ).toByteArray()
 
-        DownloadRequest(
-          url,
-          DownloadRequest.TYPE_PROGRESSIVE,
-          Uri.parse(url),
-          Collections.emptyList(),
-          null,
-          data
-        ).also {
-          sendAddDownload(context, PinService::class.java, it, false)
-        }
+        val request = DownloadRequest.Builder(track.id.toString(), url.toUri())
+          .setData(data)
+          .setStreamKeys(Collections.emptyList())
+          .build()
+
+        sendAddDownload(context, PinService::class.java, request, false)
       }
     }
   }
@@ -83,14 +79,19 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
     return DownloadNotificationHelper(
       this,
       AppContext.NOTIFICATION_CHANNEL_DOWNLOADS
-    ).buildProgressNotification(R.drawable.downloads, null, description, downloads)
+    ).buildProgressNotification(this, R.drawable.downloads, null, description, downloads)
   }
 
   private fun getDownloads() = downloadManager.downloadIndex.getDownloads()
 
   inner class DownloadListener : DownloadManager.Listener {
-    override fun onDownloadChanged(downloadManager: DownloadManager, download: Download) {
-      super.onDownloadChanged(downloadManager, download)
+
+    override fun onDownloadChanged(
+      downloadManager: DownloadManager,
+      download: Download,
+      finalException: Exception?
+    ) {
+      super.onDownloadChanged(downloadManager, download, finalException)
 
       EventBus.send(Event.DownloadChanged(download))
     }
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 5032195c19faf4f64b77ff923f4dba83db8e9bba..3731c1b45628cde243e495798c296a2fe8e96bea 100644
--- a/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt
@@ -476,10 +476,13 @@ class PlayerService : Service() {
       CommandBus.send(Command.RefreshTrack(queue.current()))
     }
 
-    override fun onPositionDiscontinuity(reason: Int) {
-      super.onPositionDiscontinuity(reason)
-
-      if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) {
+    override fun onPositionDiscontinuity(
+      oldPosition: Player.PositionInfo,
+      newPosition: Player.PositionInfo,
+      reason: Int
+    ) {
+      super.onPositionDiscontinuity(oldPosition, newPosition, reason)
+      if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
         val currentTrack = queue.current().also {
           it.log("Track finished")
         }
diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt
index 5475577414d43ce4cbd5fe0d711180ba0e35f6c9..d1f702a5012edebe1fa7e890ee8b3d7fba82607f 100644
--- a/buildSrc/src/main/java/Versions.kt
+++ b/buildSrc/src/main/java/Versions.kt
@@ -4,8 +4,8 @@ object Versions {
   const val unmock = "0.7.8"
   const val gradleDependencyPlugin = "0.38.0"
 
-  const val exoPlayer = "2.11.8"
-  const val exoPlayerExtensions = "2.11.4"
+  const val exoPlayer = "2.14.2"
+  const val exoPlayerExtensions = "2.14.0"
   const val fuel = "2.3.1"
   const val gson = "2.8.7"
   const val powerPreference = "2.0.0"
diff --git a/changes/changelog.d/65.misc b/changes/changelog.d/65.misc
new file mode 100644
index 0000000000000000000000000000000000000000..dc10a30f1b009190df73cbb1335f9c921a72840d
--- /dev/null
+++ b/changes/changelog.d/65.misc
@@ -0,0 +1 @@
+Upgrade ExoPlayer version to 2.14.2 (#65)