[再生しています] カードを表示する

TV アプリでは、ランチャーの背後またはバックグラウンドでメディアを再生するときに [再生しています] カードを表示する必要があります。このカードにより、ユーザーはメディアを現在再生中のアプリに戻ることができます。

Android のフレームワークでは、アクティブな MediaSession があるときにホーム画面に [再生しています] カードが表示されます。カードにはアルバムアート、タイトル、アプリのアイコンといったメディアのメタデータが含まれ、ユーザーがカードを選択するとアプリが開きます。

このレッスンでは、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());
    }
    

ユーザー アクションを処理する

ユーザーが [再生しています] カードを選択すると、セッション中のアプリが開きます。アプリが PendingIntentsetSessionActivity() に提供すると、次に示すように、指定したアクティビティが開始されます。それ以外の場合は、デフォルトのシステム インテントが開きます。指定したアクティビティは、ユーザーが再生を一時停止または停止できるように再生コントロールを提供する必要があります。

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);