Adding MediaItem when using the media3 library caused an error
Asked Answered
M

3

12

I am using the latest Android Media3 library, but I found a problem in using it...

I created a MediaSessionService, and then got the MediaController in the Activity, and then when I tried to call the media controller and add some MediaItems, an error occurred:

 java.lang.NullPointerException
        at androidx.media3.common.util.Assertions.checkNotNull(Assertions.java:155)
        at androidx.media3.exoplayer.source.DefaultMediaSourceFactory.createMediaSource(DefaultMediaSourceFactory.java:338)
        at androidx.media3.exoplayer.ExoPlayerImpl.createMediaSources(ExoPlayerImpl.java:1164)
        at androidx.media3.exoplayer.ExoPlayerImpl.addMediaItems(ExoPlayerImpl.java:463)
        at androidx.media3.exoplayer.SimpleExoPlayer.addMediaItems(SimpleExoPlayer.java:1146)
        at androidx.media3.common.BasePlayer.addMediaItems(BasePlayer.java:69)
        at androidx.media3.common.BasePlayer.addMediaItem(BasePlayer.java:64)
        at androidx.media3.common.ForwardingPlayer.addMediaItem(ForwardingPlayer.java:90)
        at androidx.media3.session.PlayerWrapper.addMediaItem(PlayerWrapper.java:346)
        at androidx.media3.session.MediaSessionStub.lambda$addMediaItem$28(MediaSessionStub.java:1052)
        at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda8.run(Unknown Source:2)
        at androidx.media3.session.MediaSessionStub.lambda$getSessionTaskWithPlayerCommandRunnable$2$androidx-media3-session-MediaSessionStub(MediaSessionStub.java:234)
        at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda52.run(Unknown Source:14)
        at androidx.media3.session.MediaSessionStub.lambda$flushCommandQueue$50(MediaSessionStub.java:1479)
        at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda58.run(Unknown Source:2)
        at androidx.media3.common.util.Util.postOrRun(Util.java:517)
        at androidx.media3.session.MediaSessionStub.flushCommandQueue(MediaSessionStub.java:1473)
        at androidx.media3.session.MediaControllerImplBase$FlushCommandQueueHandler.handleMessage(MediaControllerImplBase.java:3035)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7813)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

So I checked the createMediaSource function of DefaultMediaSourceFactory and found that it is checking whether the localConfiguration of MediaItem is null:

  @Override
  public MediaSource createMediaSource(MediaItem mediaItem) {
    checkNotNull(mediaItem.localConfiguration);
    ...
  }

And this is localConfiguration:

/**
   * Optional configuration for local playback. May be {@code null} if shared over process
   * boundaries.
   */
  @Nullable public final LocalConfiguration localConfiguration;

I am pretty sure that there is no problem with the way I created the MediaItem, and it works well inside the Service, but when I try to insert the MediaItem in the Activity, an error occurs. According to the comments, I guess this may be a cross-process communication problem, but I don't have any clue about this. Does anyone have experience with Media3?

Medellin answered 24/11, 2021 at 13:11 Comment(1)
Im currently having exactly the same issue...Veranda
F
30

When you add/set MediaItems from a controller, the localConfiguration (uri, mimeType, drm config, etc) of MediaItem is removed for security/privacy reasons. Without localConfiguration the player can't play the media item. We need to add the missing information back to the MediaItem.

Updated answer (media3 1.0.0-beta01 or higher)

Open the Callback you defined when creating the MediaLibrarySession in your Service.

// My MediaLibraryService
// onCreate()
mediaLibrarySession = MediaLibrarySession.Builder(
    this, 
    player, 
    librarySessionCallback // <--
).build()


// NOTE: If you are using MediaSessionService instead of MediaLibraryService,
// use `setCallback(librarySessionCallback)` from the MediaSession.Builder.

Override onAddMediaItems inside your MediaLibrarySession.Callback. Every time you use setMediaItem/addMediaItem from a controller, your onAddMediaItems will be called and the MediaItems returned there are the ones that will be played.

class CustomMediaLibrarySessionCallback : MediaLibraryService.MediaLibrarySession.Callback {

    // [...]

    override fun onAddMediaItems(
        mediaSession: MediaSession,
        controller: MediaSession.ControllerInfo,
        mediaItems: MutableList<MediaItem>
    ): ListenableFuture<List<MediaItem>> {
        // NOTE: You can use the id from the mediaItems to look up missing 
        // information (e.g. get URI from a database) and return a Future with 
        // a list of playable MediaItems.

        // If your use case is really simple and the security/privacy reasons 
        // mentioned earlier don't apply to you, you can add the URI to the 
        // MediaItem request metadata in your activity/fragment and use it
        // to rebuild the playable MediaItem.
        val updatedMediaItems = mediaItems.map { mediaItem ->
            mediaItem.buildUpon()
                .setUri(mediaItem.requestMetadata.mediaUri)
                .build()
        }
        return Futures.immediateFuture(updatedMediaItems)
    }
}

Create and play your MediaItem from the activity/fragment.

// My Activity

val mmd = MediaMetadata.Builder()
    .setTitle("Example")
    .setArtist("Artist name")
    .build()

// Request metadata. New in (1.0.0-beta01)
// This is optional. I'm adding a RequestMetadata to the MediaItem so I 
// can get the mediaUri from my `onAddMediaItems` simple use case (see  
// onAddMediaItems for more info).
// If you are going to get the final URI from a database, you can move your 
// query to your `MediaLibrarySession.Callback#onAddMediaItems` and skip this.
val rmd = RequestMetadata.Builder()
    .setMediaUri("...".toUri())
    .build()

val mediaItem = MediaItem.Builder()
    .setMediaId("123")
    .setMediaMetadata(mmd)
    .setRequestMetadata(rmd)
    .build()

browser.setMediaItem(mediaItem)
browser.prepare()
browser.play()

Old answer (media3 1.0.0-alpha)

When you create the MediaLibrarySession inside your MediaLibraryService, you can add a MediaItemFiller. This MediaItemFiller has a fillInLocalConfiguration method that will be "Called to fill in the MediaItem.localConfiguration of the media item from controllers."

Knowing this, you need to:

Add a MediaItemFiller to your MediaLibrarySession builder inside your service.

// My MediaLibraryService
// onCreate()
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
    .setMediaItemFiller(CustomMediaItemFiller()) // <--
    .setSessionActivity(pendingIntent)
    .build()

Create a custom MediaSession.MediaItemFiller. Any time you use a setMediaItem/addMediaItem from a controller this will be called and the MediaItem returned here will be the one played.

class CustomMediaItemFiller : MediaSession.MediaItemFiller {
  override fun fillInLocalConfiguration(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
    mediaItem: MediaItem
  ): MediaItem {
    // Return the media item to be played
    return mediaItem.buildUpon()
        // Use the metadata values to fill our media item
        .setUri(mediaItem.mediaMetadata.mediaUri)
        .build()
  }
}

And finally, create and play your MediaItem from the activity.

// My Activity

// Fill some metadata that the MediaItemFiller 
// will use to create the new MediaItem
val mmd = MediaMetadata.Builder()
    .setTitle("Example")
    .setArtist("Artist name")
    .setMediaUri("...".toUri())
    .build()

val mediaItem: MediaItem =
    MediaItem.Builder()
        .setMediaMetadata(mmd)
        .build()

browser.setMediaItem(mediaItem)
browser.prepare()
browser.play()

I don't know why it has to be this awkward, but if you have a look to the CustomMediaItemFiller they use in the official repo, you will see that they use the mediaItem.mediaId to fetch a valid MediaItem from a media catalog. That's why their demo works when they use setMediaItem from an activity.

Also, as far as I know, anything you do inside fillInLocalConfiguration has to block the main thread (I believe setMediaItem has to be called from main) so, if you can, try to move any heavy work (ie, get media info from your database) to your Activity/ViewModel where you have more control, fill all the metadata you need there, and use your MediaSession.MediaItemFiller to do a simple transformation. Or move everything to your service and forget about everything.

I hope the flow is understood. I don't have much experience with media3 and maybe I'm missing something, but with the limitations of MediaItemFiller I found it a bit useless and I would really like to know more about its purpose.

Fornax answered 24/11, 2021 at 22:0 Comment(7)
Hey @Fornax thanks for the solution. I am trying the code that you have suggested but the controller is always null and I have no idea where to set it. Would you look at my code please?Ague
Hi @ilkeraslan, I think the problem is that you are creating the MediaItem and playing it just after calling initializeController and the initialization is asynchronous so the controller is not ready. When you use controllerFuture.addListener({ setController() }, /*...*/) is like saying "call setController() when the controller is ready" so that's the only place where you can be sure that the controller can be used. Move your MediaItem creation and your controller calls from onStart to setController and try again. Good luck!Fornax
Thanks for the suggestion @spun, it was a great pointer to the solution. The only problem with moving the setController() to onStart() is that if you minimize the app and re-click it the controller is set again causing the media item to be reproduced. Thus I preferred to call the setController() in onCreate().Ague
My service class is extending from MediaSessionService. The Builder for MediaSession doesn't have setMediaItemFiller method.Esperanzaespial
Hi @Rafsanjani, you are right, in version 1.0.0-alpha01 the setMediaItemFiller in MediaSession.Builder was not public. This was fixed a couple of months ago. If you can't wait until the next release, you could try running the library locallyFornax
Thanks a lot for the trick! Media3 seems to help reducing extra boilerplate, but it doesn't seem to be well documented and not much samples available around it.Italicize
media3 1.3.0 - what they have "fixed" only works if media items are coming from custom controllers inside the app. When I tap on a playable item in Android Auto, it returns fully stripped media item, that only has mediaId assigned to it. Luckily this object requestMetadata.extras has extras where I can pass in title/artist/artworkUri.Bossism
F
1

In my case with media3 1.3.0, Not setting uri for mediaItem causes the exception, so the following makes it work:

val mediaItem: MediaItem =
    MediaItem.Builder()
        .setUri("https://mediaurl".toUri())
        .setMediaMetadata(mmd)
        .build()
Fae answered 1/5, 2024 at 11:47 Comment(0)
M
0

This was fixed in media3 1.1.0 and should work without any additional workarounds:

https://github.com/androidx/media/issues/282#issuecomment-1645323234

Mediant answered 21/7, 2023 at 12:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.