Skip to content

MediaController generates wrong onMediaItemTransition() callback on seek inside same media item #3248

Open

Description

@nift4

Version

Media3 1.10.1

More version details

No response

Devices that reproduce the issue

API 33 emulator

Devices that do not reproduce the issue

N/A

Reproducible in the demo app?

Yes

Reproduction steps

  1. Apply attached patch to session-demo app
  2. Play any song from an artist with more than one song, choose a song that is not the first in the list
  3. Seek inside this same song to some other position using seekbar
  4. onMediaItemTransition() is called on controller at this point

Note: to reproduce this, controller must be attached before setMediaItems happens, which is why I added a new controller in the patch. Also, index of song passed to setMediaItems must be >0.

diff --git a/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt b/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt
index 81502f5ee8..f49cc17e60 100644
--- a/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt
+++ b/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt
@@ -23,6 +23,7 @@ import android.content.Context
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.os.Build
+import android.util.Log
 import androidx.annotation.OptIn
 import androidx.core.app.NotificationCompat
 import androidx.core.app.NotificationManagerCompat
@@ -32,6 +33,7 @@ import androidx.datastore.dataStore
 import androidx.lifecycle.lifecycleScope
 import androidx.media3.cast.CastPlayer
 import androidx.media3.common.AudioAttributes
+import androidx.media3.common.MediaItem
 import androidx.media3.common.Player
 import androidx.media3.common.listenTo
 import androidx.media3.common.util.UnstableApi
@@ -39,6 +41,7 @@ import androidx.media3.demo.session.service.R
 import androidx.media3.exoplayer.ExoPlayer
 import androidx.media3.exoplayer.util.EventLogger
 import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController
 import androidx.media3.session.MediaLibraryService
 import androidx.media3.session.MediaSession
 import androidx.media3.session.MediaSession.ControllerInfo
@@ -54,6 +57,7 @@ import kotlinx.coroutines.launch
 open class DemoPlaybackService : MediaLibraryService() {
 
   private lateinit var mediaLibrarySession: MediaLibrarySession
+  private lateinit var controller: MediaController
 
   private val turnShuffleOnButton by
     lazy @OptIn(UnstableApi::class) {
@@ -146,6 +150,7 @@ open class DemoPlaybackService : MediaLibraryService() {
   @OptIn(UnstableApi::class)
   override fun onDestroy() {
     getBackStackedActivity()?.let { mediaLibrarySession.setSessionActivity(it) }
+    controller.release()
     mediaLibrarySession.release()
     mediaLibrarySession.player.release()
     clearListener()
@@ -165,6 +170,17 @@ open class DemoPlaybackService : MediaLibraryService() {
       MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
         .also { builder -> getSingleTopActivity()?.let { builder.setSessionActivity(it) } }
         .build()
+    controller = MediaController.Builder(this, mediaLibrarySession.token)
+      .buildAsync().get()
+    controller.addListener(object : Player.Listener {
+
+          override fun onMediaItemTransition(
+            mediaItem: MediaItem?,
+            reason: @Player.MediaItemTransitionReason Int
+          ) {
+            androidx.media3.common.util.Log.e("hi","[CONTROLLER] media item changed due to $reason")
+          }
+      })
 
     mediaLibrarySession.setCustomShuffleModeButton()
     lifecycleScope.launch {
@@ -181,6 +197,14 @@ open class DemoPlaybackService : MediaLibraryService() {
         .setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true)
         .build()
     exoPlayer.addAnalyticsListener(EventLogger())
+    exoPlayer.addListener(object : Player.Listener {
+      override fun onMediaItemTransition(
+        mediaItem: MediaItem?,
+        reason: @Player.MediaItemTransitionReason Int
+      ) {
+        Log.e("hi", "[PLAYER] media item transition due to $reason")
+      }
+    })
     return CastPlayer.Builder(/* context= */ this).setLocalPlayer(exoPlayer).build()
   }
 
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java
index f1361dedec..7ad9006381 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java
@@ -2530,6 +2530,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
     }
     MediaItem currentMediaItem = newPlayerInfo.getCurrentMediaItem();
     if (mediaItemTransitionReason != null) {
+      Log.i("hi","dispatch mit");
       listeners.queueEvent(
           /* eventFlag= */ Player.EVENT_MEDIA_ITEM_TRANSITION,
           listener -> listener.onMediaItemTransition(currentMediaItem, mediaItemTransitionReason));
@@ -2991,6 +2992,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
             || positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK)) {
       if (oldPlayerInfo.newPositionInfo.mediaItemIndex
           != finalPlayerInfo.newPositionInfo.mediaItemIndex) {
+        Log.e("hi", "move from "+oldPlayerInfo.newPositionInfo.mediaItemIndex+ " to "+finalPlayerInfo.newPositionInfo.mediaItemIndex);
         mediaItemTransitionReason =
             positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION
                 ? MEDIA_ITEM_TRANSITION_REASON_AUTO

Expected result

No onMediaItemTransition() callback

Actual result

2026-05-29 18:54:24.382 12519-12519 hi                      androidx.media3.demo.session         E  move from 0 to 1
2026-05-29 18:54:24.382 12519-12519 hi                      androidx.media3.demo.session         I  dispatch mit
2026-05-29 18:54:24.382 12519-12519 hi                      androidx.media3.demo.session         E  [CONTROLLER] media item changed due to 2

Media

Anything works

Bug Report

Activity

  1. marcbaechinger commented on Jun 16, 2026

    @marcbaechinger
    Contributor

    Thanks for your excellent report!

    I can repro this. When the controller is connected before media items are set, the initial window index that is sent from the session to the controller is 0. When then setMediaItems(items, windowIndexNotEqualZero) is called this produces the media item transition event.

    We provide a fix for this.

    Can you confirm that only the very first seek for this controller triggers this. Once it is in sync and the incorrect event is emitted it does not emit any further events?

  2. nift4 commented on Jun 17, 2026

    @nift4
    ContributorAuthor

    Thanks for your excellent report!

    Glad to hear it was useful :)

    Can you confirm that only the very first seek for this controller triggers this. Once it is in sync and the incorrect event is emitted it does not emit any further events?

    No, I was able to generate further events by getting it out of sync again.

    1. use same patch and initial reproduction steps provided above, then
    2. remove song with index 0 (we are currently playing song with some index >0, so its index changes by one because the playlist changed but of course media item transition did not happen). (This already generated some logs but not for the controller I specifically added)
    3. Now seek inside same song again and this time it will print:
    2026-06-17 18:14:05.889 26554-26554 hi                      androidx.media3.demo.session         E  move from 1 to 0
    2026-06-17 18:14:05.889 26554-26554 hi                      androidx.media3.demo.session         I  dispatch mit
    2026-06-17 18:14:05.889 26554-26554 hi                      androidx.media3.demo.session         E  [CONTROLLER] media item changed due to 2
    

    So old index from before the previous song was removed got stuck there somehow and it again only noticed on unrelated seek.

  3. marcbaechinger commented on Jun 17, 2026

    @marcbaechinger
    Contributor

    Thanks! Ok, I see. I think the fix I have would address this as well. It's basically when a timeline change happens with reason TIMELINE_CHANGED_REASON_PLAYLIST_CHANGED we don't update PlayerInfo.newPosition and PlayerInfo.oldPosition. Then when a seek arrives it compares oldPlayerInfo.newPosition with newPlayerinfo.newPosition and still has the old mediaItemIndex of oldPlayerInfo because of the inconsistent update when the timeline changed.

    I will validate the case you describe above as well. Many thanks!

  4. nift4 commented on Jul 20, 2026

    @nift4
    ContributorAuthor

    Hi @marcbaechinger, thanks for the fix! I just tried it and it while it fixed the initial issue, it didn't fix the issue with song removal. Try the following steps to reproduce:

    1. Take media3 tree with your fix applied
    2. Apply the patch from top post for logging again
    3. Play any song from an artist with more than one song, choose a song that is not the first in the list
    4. Remove the first song in the list
    5. Seek inside this same song to some other position using seekbar
    6. onMediaItemTransition() is called on controller at this point
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions