构建一个Android视频播放器应用(3/5)

本系列的前一篇文章介绍了通过连接MediaSources创建播放列表所需的步骤,以及定制ExoPlayer播放控制UI的详细信息,本文详细介绍了如何使用MediaSession连接器扩展在应用程序中无缝使用MediaSession功能。

MediaSession介绍

MediaSession允许播放器中的信息暴露给Android系统的其他应用。

  • 例如播放器当前是暂停,播放还是加载文件等,都可以通过MediaSession广播当操作系统的其他应用程序或组件中。
  • 其他应用程序或操作系统也可以通过绑定到MediaSession的Mediacontroller.TransportControls来操作播放器。这允许蓝牙耳机上的媒体按钮,Google智能助理甚至其他应用能够播放,暂停或跳过媒体应用中的歌曲。

总之,MediaSession有两个用途,发布有关您的应用程序中正在播放的当前媒体的元数据,并允许其他组件控制播放。
元数据可以被蓝牙设备,Android可穿戴设备以及Google智能助理使用。除了操作系统,播放控制与元数据使用类似(都可以被有线耳机的按钮转换为可以被你使用的命令)。

要想了解更多的MediaSession资源,如下所示:

  • Understanding MediaSession medium article
  • Working with a MediaSession on d.android.com
  • Android sample code for working with MediaSession

使用MediaSession连接器扩展

确保添加如下配置到build.gradle文件中。

ext { ver = “2.7.0” }
dependencies {
  implementation 
    “com.google.android.exoplayer:extension-mediasession:$ver”
}

然后你可以创建session然后attach到播放器中。

override fun onCreate(savedInstanceState: Bundle?){
  ...
  playerHolder = PlayerHolder(this, exoplayerview_activity_video, state)
  mediaSession = MediaSessionCompat(this, packageName)
  connector = MediaSessionConnector(mediaSession)
}

override fun onStart(){
  ...
  playerHolder.start()
  connector.setPlayer(playerHolder.player, null)
  mediaSession.isActive = true
}

override fun onStop() {
    ...
    playerHolder.stop()
    connector.setPlayer(null, null)
    mediaSession.isActive = false
}

override fun onDestroy() {
    ...
    mediaSession.release()
    playerHolder.release()
}

关于java可变参数和Kotlin的注意事项 - ExoPlayer和MediaSession连接器扩展用Java编写,并且setPlayer()方法接受可变参数作为第三个参数。如果但你从Kotlin调用这个方法时,如果你们有传递第三个参数,那么所有的东西都可以像使用Java那样,但是如果你传入了null作为第三个参数,那么将抛出空指针异常,如果你确实需要一个参数列表,那么你必须使用spread运算符来将参数列表传递给java可变参数,如下是示例:

val uriList = mutableListOf()
val mediaSource = ConcatenatingMediaSource(*uriList.toTypedArray())

这样做将启用基本的播放操作(ACTION_PLAY_PAUSE, ACTION_PLAY, ACTION_PAUSE, ACTION_SEEK_TO, ACTION_FAST_FORWARD, ACTION_REWIND, ACTION_STOP),这意味着连机器能够处理通过MediaSession生成的这些操作,例如蓝牙耳机可以控制暂停,播放,快进和快退的操作等。

但是,如果你想要控制播放列表,则还必须通过添加QueueNavigator来告诉连接器处理**ACTION_SKIP_ ***相关的命令,下面是示例:

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    mediaSession = MediaSessionCompat(this, packageName)
    connector = MediaSessionConnector(mediaSession)
    // If QueueNavigator isn't set, then mediaSessionConnector won’t handle
    // MediaSession actions (they won't show up in the minimized PIP activity):
    // [ACTION_SKIP_PREVIOUS], [ACTION_SKIP_NEXT], [ACTION_SKIP_TO_QUEUE_ITEM]
    connector.setQueueNavigator(object : TimelineQueueNavigator(mMediaSession) {
        override fun getMediaDescription(idx: Int): MediaDescriptionCompat {
            return MediaCatalog.list.get(idx)
        }
    })
}

object MediaCatalog {
    val list = mutableListOf()
    init {
        list.add(
                with(MediaDescriptionCompat.Builder()) {
                    setDescription("MP4 loaded from assets folder")
                    setMediaId("1")
                    setMediaUri(Uri.parse("asset:///video/...video.mp4"))
                    setTitle("Stock footage")
                    setSubtitle("Local video")
                    build()
                })
        list.add(
                with(MediaDescriptionCompat.Builder()) {
                    setDescription("MP3 loaded over HTTP")
                    setMediaId("2")
                    setMediaUri(Uri.parse("http://storage.../play.mp3"))
                    setTitle("Spoken track")
                    setSubtitle("Streaming audio")
                    build()
                })
        list.add(...)
    }
}

使用此示例,您还可以更改创建ExoPlayer及其播放列表的代码,以便从该相同的MediaCatalog列表对象加载播放列表,加载播放列表的代码现在看起来这样:

player = ExoPlayerFactory.newSimpleInstance(context, DefaultTrackSelector())
    .apply {
       ...
    }

fun buildMediaSource(): MediaSource {
    val uriList = mutableListOf()
    MediaCatalog.list.forEach {
        uriList.add(createExtractorMediaSource(it.mediaUri!!))
    }
    return ConcatenatingMediaSource(*uriList.toTypedArray())
}

private fun createExtractorMediaSource(uri: Uri): MediaSource {
    return ExtractorMediaSource.Factory(
            DefaultDataSourceFactory(context, "videoapp")).createMediaSource(uri)
}

除了上面描述的之外,您还可以告诉连接器您想要处理其他MediaSession操作,你可以在这篇文章.
中找到关于以下每一项的更多信息。

  • MediaSessionConnector.PlaybackPreparer - 这允许您处理ACTION_PREPARE_FROM_SEARCH和ACTION_PREPARE_FROM_URI等操作,如果您想要与Android Auto或Google智能助理集成,则需要执行此操作。有关允许Google智能助理通过MediaSession控制媒体应用的更多信息,请参阅developers.android.com.
    上的这篇文章。
  • MediaSessionConnector.PlaybackController - 这使您可以处理基本的播放控制器操作,例如ACTION_PLAY_PAUSE和ACTION_SEEK_TO。这是可选的,如果你不用这个拦截这样的调用,就使用DefaultPlaybackController。
  • MediaSessionConnector.QueueEditor - 这允许您处理ACTION_SET_RATING和其他MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMAND命令。 TimelineQueueEditor是一个与DynamicConcatenatingMediaSource一起工作的实现。
  • MediaSessionConnector.CustomActionProvider - 处理您想要在您的应用中提供的任何自定义操作,例如提供一种方法来控制应用中的repeat mode。

你可能感兴趣的:(构建一个Android视频播放器应用(3/5))