显示“正在播放”卡片

在启动器后面或在后台播放媒体时,TV 应用必须显示“正在播放”卡片。通过此卡片,用户可返回当前正在播放媒体的应用。

当存在活动的 MediaSession 时,Android 框架会在主屏幕上显示“正在播放”卡片。该卡片包含媒体元数据,如专辑封面、名称和应用图标。当用户选择该卡片时,系统会打开相应的应用。

本课程介绍如何利用 MediaSession 类实现“正在播放”卡片。

图 1. 在后台播放媒体时显示“正在播放”卡片。

启动媒体会话

当您的应用准备播放媒体时,创建一个 MediaSession。以下代码段举例说明了如何设置适当的回调和标记:

Kotlin

    session = MediaSession(this, "MusicService").apply {
        setCallback(MediaSessionCallback())
        setFlags(
                MediaSession.FLAG_HANDLES_MEDIA_BUTTONS or MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
        )
    }
    

Java

    session = new MediaSession(this, "MusicService");
    session.setCallback(new MediaSessionCallback());
    session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
            MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
    

注意:系统将仅为设置有 FLAG_HANDLES_TRANSPORT_CONTROLS 标记的媒体会话显示“正在播放”卡片。

显示“正在播放”卡片

系统仅为活动会话显示“正在播放”卡片。播放开始时,您必须调用 setActive(true)。此外,您的应用还必须请求音频焦点,如管理音频焦点中所述。

Kotlin

    private fun handlePlayRequest() {

        tryToGetAudioFocus()

        if (!session.isActive) {
            session.isActive = true
        }
        ...
    }
    

Java

    private void handlePlayRequest() {

        tryToGetAudioFocus();

        if (!session.isActive()) {
            session.setActive(true);
        }
        ...
    }
    

通过调用 setActive(false) 停用媒体会话或者另一个应用启动媒体播放时,系统会从启动器屏幕中移除该卡片。如果播放完全停止且没有活动的媒体,您的应用应立即停用媒体会话。如果播放暂停,您的应用应在延迟一段时间(通常为 5 到 30 分钟)后停用媒体会话。

更新播放状态

更新 MediaSession 中的播放状态,以便该卡片可以显示当前媒体的状态。

Kotlin

    private fun updatePlaybackState() {
        val position: Long =
                mediaPlayer
                        ?.takeIf { it.isPlaying }
                        ?.currentPosition?.toLong()
                        ?: PlaybackState.PLAYBACK_POSITION_UNKNOWN

        val stateBuilder = PlaybackState.Builder()
                .setActions(getAvailableActions()).apply {
                    setState(mState, position, 1.0f)
                }
        session.setPlaybackState(stateBuilder.build())
    }

    private fun getAvailableActions(): Long {
        var actions = (PlaybackState.ACTION_PLAY_PAUSE
                or PlaybackState.ACTION_PLAY_FROM_MEDIA_ID
                or PlaybackState.ACTION_PLAY_FROM_SEARCH)

        playingQueue?.takeIf { it.isNotEmpty() }?.apply {
            actions = if (mState == PlaybackState.STATE_PLAYING) {
                actions or PlaybackState.ACTION_PAUSE
            } else {
                actions or PlaybackState.ACTION_PLAY
            }
            if (currentIndexOnQueue > 0) {
                actions = actions or PlaybackState.ACTION_SKIP_TO_PREVIOUS
            }
            if (currentIndexOnQueue < size - 1) {
                actions = actions or PlaybackState.ACTION_SKIP_TO_NEXT
            }
        }
        return actions
    }
    

Java

    private void updatePlaybackState() {
        long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            position = mediaPlayer.getCurrentPosition();
        }
        PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
                .setActions(getAvailableActions());
        stateBuilder.setState(mState, position, 1.0f);
        session.setPlaybackState(stateBuilder.build());
    }

    private long getAvailableActions() {
        long actions = PlaybackState.ACTION_PLAY_PAUSE |
                PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
                PlaybackState.ACTION_PLAY_FROM_SEARCH;
        if (playingQueue == null || playingQueue.isEmpty()) {
            return actions;
        }
        if (mState == PlaybackState.STATE_PLAYING) {
            actions |= PlaybackState.ACTION_PAUSE;
        } else {
            actions |= PlaybackState.ACTION_PLAY;
        }
        if (currentIndexOnQueue > 0) {
            actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
        }
        if (currentIndexOnQueue < playingQueue.size() - 1) {
            actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
        }
        return actions;
    }
    

显示媒体元数据

使用 setMetadata() 方法设置 MediaMetadata。使用媒体会话对象的这一方法,您可以向“正在播放”卡片提供有关曲目的信息,如名称、字幕和各种图标。以下示例假定您的曲目数据存储在自定义数据类 MediaData 中。

Kotlin

    private fun updateMetadata(myData: MediaData) {
        val metadataBuilder = MediaMetadata.Builder().apply {
            // To provide most control over how an item is displayed set the
            // display fields in the metadata
            putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, myData.displayTitle)
            putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, myData.displaySubtitle)
            putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, myData.artUri)
            // And at minimum the title and artist for legacy support
            putString(MediaMetadata.METADATA_KEY_TITLE, myData.title)
            putString(MediaMetadata.METADATA_KEY_ARTIST, myData.artist)
            // A small bitmap for the artwork is also recommended
            putBitmap(MediaMetadata.METADATA_KEY_ART, myData.artBitmap)
            // Add any other fields you have for your data as well
        }
        session.setMetadata(metadataBuilder.build())
    }
    

Java

    private void updateMetadata(MediaData myData) {
        MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
        // To provide most control over how an item is displayed set the
        // display fields in the metadata
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE,
                myData.displayTitle);
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
                myData.displaySubtitle);
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
                myData.artUri);
        // And at minimum the title and artist for legacy support
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE,
                myData.title);
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST,
                myData.artist);
        // A small bitmap for the artwork is also recommended
        metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART,
                myData.artBitmap);
        // Add any other fields you have for your data as well
        session.setMetadata(metadataBuilder.build());
    }
    

响应用户操作

当用户选择“正在播放”卡片时,系统会打开拥有该会话的应用。如果您的应用向 setSessionActivity() 提供了 PendingIntent,系统会启动您指定的 Activity,如下所示。否则,系统将会打开默认系统 Intent。您指定的 Activity 必须提供允许用户暂停或停止播放的播放控件。

Kotlin

    val pi: PendingIntent = Intent(context, MyActivity::class.java).let { intent ->
        PendingIntent.getActivity(
                context, 99 /*request code*/,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT
        )
    }
    session.setSessionActivity(pi)
    

Java

    Intent intent = new Intent(context, MyActivity.class);
    PendingIntent pi = PendingIntent.getActivity(context, 99 /*request code*/,
            intent, PendingIntent.FLAG_UPDATE_CURRENT);
    session.setSessionActivity(pi);