RE_*_*OVO 10 android android-mediaplayer android-mediasession
我正在使用最新的Android Media3库,但是我在使用它时发现了一个问题......
我创建了一个MediaSessionService,然后在Activity中获取了 MediaController ,然后当我尝试调用媒体控制器并添加一些 MediaItem 时,发生了错误:
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)
Run Code Online (Sandbox Code Playgroud)
于是我检查了DefaultMediaSourceFactory的createMediaSource函数,发现它正在检查localConfiguration
MediaItem的是否为null:
@Override
public MediaSource createMediaSource(MediaItem mediaItem) {
checkNotNull(mediaItem.localConfiguration);
...
}
Run Code Online (Sandbox Code Playgroud)
这是本地配置:
/**
* Optional configuration for local playback. May be {@code null} if shared over process
* boundaries.
*/
@Nullable public final LocalConfiguration localConfiguration;
Run Code Online (Sandbox Code Playgroud)
我非常确定我创建 MediaItem 的方式没有问题,并且它在 Service 中运行良好,但是当我尝试在 Activity 中插入 MediaItem 时,会发生错误。根据评论,我猜这可能是跨进程通信问题,但我对此没有任何线索。有人有 Media3 的经验吗?
spu*_*pun 29
当您从控制器添加/设置 MediaItems 时,出于安全/隐私原因,localConfiguration
MediaItem 的(uri、mimeType、drm 配置等)将被删除。没有播放器就无法播放媒体项目。我们需要将缺失的信息添加回 MediaItem。localConfiguration
打开您在服务中创建 MediaLibrarySession 时定义的回调。
// 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.
Run Code Online (Sandbox Code Playgroud)
在onAddMediaItems
你的MediaLibrarySession.Callback
. 每次您从控制器使用setMediaItem
/时,您的onAddMediaItems 都会被调用,并且返回的 MediaItems 就是将要播放的 MediaItems。addMediaItem
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)
}
}
Run Code Online (Sandbox Code Playgroud)
MediaItem
从活动/片段中创建并播放您的内容。
// 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()
Run Code Online (Sandbox Code Playgroud)
MediaLibrarySession
当您在您的内部创建时MediaLibraryService
,您可以添加一个MediaItemFiller
. 它MediaItemFiller
有一个fillInLocalConfiguration
方法,将被“调用以从控制器填充媒体项的 MediaItem.localConfiguration”。
知道这一点后,您需要:
将 MediaItemFiller 添加到服务内的 MediaLibrarySession 构建器。
// My MediaLibraryService
// onCreate()
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setMediaItemFiller(CustomMediaItemFiller()) // <--
.setSessionActivity(pendingIntent)
.build()
Run Code Online (Sandbox Code Playgroud)
创建自定义MediaSession.MediaItemFiller
. 每当您使用控制器中的setMediaItem
/时,都会调用此函数,并且此处返回的 MediaItem 将是播放的 MediaItem。addMediaItem
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()
}
}
Run Code Online (Sandbox Code Playgroud)
最后,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()
Run Code Online (Sandbox Code Playgroud)
我不知道为什么会这么尴尬,但是如果您查看他们在官方存储库中使用的 CustomMediaItemFiller,您会发现他们使用mediaItem.mediaId
从媒体目录中获取有效的 MediaItem。这就是为什么他们的演示在使用Activity 中的 setMediaItem时有效。
另外,据我所知,你在里面做的任何事情都fillInLocalConfiguration
必须阻塞主线程(我相信setMediaItem
必须从主线程调用),所以,如果可以的话,尝试移动任何繁重的工作(即从数据库获取媒体信息)到您有更多控制权的 Activity/ViewModel,填充您需要的所有元数据,并使用您的MediaSession.MediaItemFiller
进行简单的转换。或者将所有内容移至您的服务中并忘记所有内容。
我希望流程能够被理解。我对 media3 没有太多经验,也许我错过了一些东西,但由于它的局限性,MediaItemFiller
我发现它有点无用,我真的很想更多地了解它的用途。