Skip to content

fix: NPE in MediaManager.scrobble()#447

Open
tinsukE wants to merge 2 commits intoeddyizm:developmentfrom
tinsukE:fix_scrobble_npe
Open

fix: NPE in MediaManager.scrobble()#447
tinsukE wants to merge 2 commits intoeddyizm:developmentfrom
tinsukE:fix_scrobble_npe

Conversation

@tinsukE
Copy link
Contributor

@tinsukE tinsukE commented Feb 18, 2026

I was watching #16 because I'm a heavy Chromecast Audio user and tried out the latest release. While that fix made the Chromecast experience better, I was still experiencing somewhat constant crashes.

I couldn't get an exact list of steps to reproduce it, sometimes doing the same thing worked, other times it crashed. But my testing routine constantly managed to reproduce a crash by randomly trying things like:

  • Start Chromecast
  • Play an album
  • Jump to track X
  • Jump to track Y (Y < X)
  • Shuffle an album

The crash was almost always a NPE on MediaManager.scrobble():

    public static void scrobble(MediaItem mediaItem, boolean submission) {
        if (mediaItem != null && Preferences.isScrobblingEnabled()) {
            getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"), submission);
        }
    }

MediaMetadata.extras is nullable, but isn't being handled properly: https://developer.android.com/reference/androidx/media3/common/MediaMetadata#extras()

Instead of making a huge "check if null" chain, I bit the bullet and converted the class to Kotlin.

Besides idiomatic Kotlin changes, the only 2 logical changes were the bugfix:

    fun scrobble(mediaItem: MediaItem?, submission: Boolean) {
        val mediaItemId = mediaItem?.mediaMetadata?.extras?.getString("id") ?: return
        if (isScrobblingEnabled()) {
            songRepository.scrobble(mediaItemId, submission)
        }
    }

And a pair of extension functions ListenableFuture<MediaBrowser>?.onComplete() to remove duplicated code/logic.

Stack trace of the NPE:

19:32:31.156  E  FATAL EXCEPTION: main
    Process: com.eddyizm.tempus.debug, PID: 8386
    java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference
    at com.cappielloantonio.tempo.service.MediaManager.scrobble(MediaManager.java:442)
    at com.cappielloantonio.tempo.service.BaseMediaService$initializePlayerListener$1.onIsPlayingChanged(BaseMediaService.kt:300)
    at androidx.media3.cast.CastPlayer.lambda$setPlayerStateAndNotifyIfChanged$20(CastPlayer.java:1450)
    at androidx.media3.cast.CastPlayer$$ExternalSyntheticLambda12.invoke(D8$$SyntheticClass:0)
    at androidx.media3.common.util.ListenerSet$ListenerHolder.invoke(ListenerSet.java:342)
    at androidx.media3.common.util.ListenerSet.lambda$queueEvent$0(ListenerSet.java:226)
    at androidx.media3.common.util.ListenerSet$$ExternalSyntheticLambda1.run(D8$$SyntheticClass:0)
    at androidx.media3.common.util.ListenerSet.flushEvents(ListenerSet.java:248)
    at androidx.media3.cast.CastPlayer.updateInternalStateAndNotifyIfChanged(CastPlayer.java:1066)
    at androidx.media3.cast.CastPlayer.access$1000(CastPlayer.java:98)
    at androidx.media3.cast.CastPlayer$StatusListener.onStatusUpdated(CastPlayer.java:1608)
    at com.google.android.gms.cast.framework.media.zzbn.zzm(com.google.android.gms:play-services-cast-framework@@21.4.0:6)
    at com.google.android.gms.cast.internal.zzaq.zzY(com.google.android.gms:play-services-cast@@21.4.0:1)
    at com.google.android.gms.cast.internal.zzaq.zzO(com.google.android.gms:play-services-cast@@21.4.0:61)
    at com.google.android.gms.cast.framework.media.RemoteMediaClient.onMessageReceived(com.google.android.gms:play-services-cast-framework@@21.4.0:1)
    at com.google.android.gms.cast.zzbp.run(com.google.android.gms:play-services-cast@@21.4.0:4)
    at android.os.Handler.handleCallback(Handler.java:1070)
    at android.os.Handler.dispatchMessage(Handler.java:125)
    at android.os.Looper.dispatchMessage(Looper.java:333)
    at android.os.Looper.loopOnce(Looper.java:263)
    at android.os.Looper.loop(Looper.java:367)
    at android.app.ActivityThread.main(ActivityThread.java:9287)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)

And a yet more elusive Chromecast crash that I'll try to track down (not fixed here), which I think is related to shuffled playlists:

19:26:57.699  E  FATAL EXCEPTION: main
    Process: com.eddyizm.tempus.debug, PID: 5025
    java.lang.ArrayIndexOutOfBoundsException: length=0; index=8
    at androidx.media3.cast.CastTimeline.getPeriod(CastTimeline.java:181)
    at androidx.media3.common.Timeline.getPeriod(Timeline.java:1275)
    at androidx.media3.cast.CastPlayer.seekTo(CastPlayer.java:526)
    at androidx.media3.common.BasePlayer.seekTo(BasePlayer.java:213)
    at androidx.media3.common.ForwardingPlayer.seekTo(ForwardingPlayer.java:322)
    at androidx.media3.session.PlayerWrapper.seekTo(PlayerWrapper.java:144)
    at androidx.media3.session.MediaSessionStub.lambda$seekToWithMediaItemIndex$23$androidx-media3-session-MediaSessionStub(MediaSessionStub.java:841)
    at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda14.run(D8$$SyntheticClass:0)
    at androidx.media3.session.MediaSessionStub.lambda$sendSessionResultSuccess$1(MediaSessionStub.java:182)
    at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda84.run(D8$$SyntheticClass:0)
    at androidx.media3.session.MediaSessionStub.lambda$queueSessionTaskWithPlayerCommandForControllerInfo$13(MediaSessionStub.java:353)
    at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda19.run(D8$$SyntheticClass:0)
    at androidx.media3.session.ConnectedControllersManager.lambda$flushCommandQueue$3$androidx-media3-session-ConnectedControllersManager(ConnectedControllersManager.java:390)
    at androidx.media3.session.ConnectedControllersManager$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
    at androidx.media3.session.MediaSessionImpl.lambda$callWithControllerForCurrentRequestSet$3$androidx-media3-session-MediaSessionImpl(MediaSessionImpl.java:347)
    at androidx.media3.session.MediaSessionImpl$$ExternalSyntheticLambda21.run(D8$$SyntheticClass:0)
    at androidx.media3.common.util.Util.postOrRun(Util.java:802)
    at androidx.media3.session.ConnectedControllersManager.flushCommandQueue(ConnectedControllersManager.java:384)
    at androidx.media3.session.ConnectedControllersManager.flushCommandQueue(ConnectedControllersManager.java:365)
    at androidx.media3.session.MediaSessionStub.lambda$flushCommandQueue$64$androidx-media3-session-MediaSessionStub(MediaSessionStub.java:1709)
    at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda40.run(D8$$SyntheticClass:0)
    at androidx.media3.common.util.Util.postOrRun(Util.java:802)
    at androidx.media3.session.MediaSessionStub.flushCommandQueue(MediaSessionStub.java:1707)
    at androidx.media3.session.MediaControllerImplBase$FlushCommandQueueHandler.flushCommandQueue(MediaControllerImplBase.java:3664)
    at androidx.media3.session.MediaControllerImplBase$FlushCommandQueueHandler.handleMessage(MediaControllerImplBase.java:3657)
    at androidx.media3.session.MediaControllerImplBase$FlushCommandQueueHandler.$r8$lambda$TVKiXTCmsW2hn-6HNXqbaigkfJc(Unknown Source:0)
    at androidx.media3.session.MediaControllerImplBase$FlushCommandQueueHandler$$ExternalSyntheticLambda0.handleMessage(D8$$SyntheticClass:0)
    at android.os.Handler.dispatchMessage(Handler.java:128)
    at android.os.Looper.dispatchMessage(Looper.java:333)
    at android.os.Looper.loopOnce(Looper.java:263)
    at android.os.Looper.loop(Looper.java:367)
    at android.app.ActivityThread.main(ActivityThread.java:9287)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)

}

@JvmStatic
fun onBrowserReleased(released: MediaBrowser?) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unused. Any clue of where should it be called from?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Off the top of my head I am not sure. But we can get rid of it if is not used.

How much testing have you done with this build, outside of the Chromecast?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not much, to be honest. Although I tried to keep the conversion as 1:1 as possible.

Is there anything you think worth it manually testing?

Or I could revert the changes and just fix the NPE on Java.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, no, I like the conversion to kotlin, just need to make sure there are no weird side effects.
I am going to figure out how to create a debug prerelease.

@tinsukE tinsukE marked this pull request as ready for review February 18, 2026 19:17
@eddyizm eddyizm changed the base branch from main to development February 21, 2026 17:57
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.

2 participants