使用media3库时添加MediaItem报错

Adding MediaItem when using the media3 library caused an error

我正在使用最新的 Android Media3 库,但我在使用它时发现了一个问题...

我创建了一个MediaSessionService,然后在Activity中获取了MediaController,然后当我尝试调用媒体控制器并添加一些 MediaItems,发生错误:

 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(MediaSessionStub.java:1052)
        at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda8.run(Unknown Source:2)
        at androidx.media3.session.MediaSessionStub.lambda$getSessionTaskWithPlayerCommandRunnable$androidx-media3-session-MediaSessionStub(MediaSessionStub.java:234)
        at androidx.media3.session.MediaSessionStub$$ExternalSyntheticLambda52.run(Unknown Source:14)
        at androidx.media3.session.MediaSessionStub.lambda$flushCommandQueue(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)

于是查看了DefaultMediaSourceFactory的createMediaSource函数,发现是在检查MediaItem的localConfiguration是否为null:

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

这是本地配置:

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

我很确定我创建 MediaItem 的方式没有问题,并且它在 Service 中运行良好,但是当我尝试将 MediaItem 插入 Activity 时,出现错误。根据评论,我猜这可能是一个跨进程通信问题,但我对此没有任何线索。有人使用过 Media3 吗?

我遇到了和你一样的问题,在服务中创建的 MediaItem 可以正常播放,但是从我的 Activity 创建的任何 MediaItem 都会抛出 NullPointerException .这是我在与 media3 github 演示打架后发现的。

当您在 MediaLibraryService 中创建 MediaLibrarySession 时,您可以添加一个 MediaItemFiller。这个 MediaItemFiller 有一个 fillInLocalConfiguration 方法,它将是 "Called to fill in the MediaItem.localConfiguration of the media item from controllers."

了解这一点,您需要:

将 MediaItemFiller 添加到您服务中的 MediaLibrarySession 构建器。

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

创建自定义 MediaSession.MediaItemFiller。任何时候你从控制器使用 setMediaItem/addMediaItem 这将被调用,这里返回的 MediaItem 将是播放的。

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

最后,从 activity.

创建并播放您的 MediaItem
// 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()

我不知道为什么要这么尴尬,但如果你看看 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

此外,据我所知,你在 fillInLocalConfiguration 中所做的任何事情都必须阻塞主线程(我相信 setMediaItem 必须从主线程调用)所以,如果可以的话,试试将任何繁重的工作(即从您的数据库中获取媒体信息)转移到您有更多控制权的 Activity/ViewModel,在那里填写您需要的所有元数据,然后使用您的 MediaSession.MediaItemFiller 进行简单的转换。或者把所有的东西都转移到你的服务上,然后忘记一切。

我希望流程被理解。我对 media3 没有太多经验,也许我遗漏了一些东西,但由于 MediaItemFiller 的限制,我发现它有点没用,我真的很想知道更多关于它的用途。