Media3 ExoPlayer を使用して基本的なメディア プレーヤー アプリを作成する

Jetpack Media3 は、動画ファイルと音声ファイルの再生の基本機能を概説する Player インターフェースを定義します。ExoPlayer は、Media3 でこのインターフェースのデフォルト実装です。ExoPlayer の使用をおすすめします。ExoPlayer には、ほとんどの再生ユースケースに対応する包括的な機能セットが用意されており、追加のユースケースに対応するようにカスタマイズできます。また、ExoPlayer はデバイスと OS の分散を抽象化するため、Android エコシステム全体でコードが一貫して動作します。ExoPlayer には以下が含まれます。

このページでは、再生アプリの作成に関する主な手順について説明します。詳細については、Media3 ExoPlayer の完全なガイドをご覧ください。

はじめに

始めに、Jetpack Media3 の ExoPlayer、UI、Common モジュールの依存関係を追加します。

implementation "androidx.media3:media3-exoplayer:1.5.1"
implementation "androidx.media3:media3-ui:1.5.1"
implementation "androidx.media3:media3-common:1.5.1"

ユースケースによっては、DASH 形式でストリームを再生する exoplayer-dash など、Media3 の追加モジュールが必要になる場合があります。

1.5.1 は、使用するライブラリのバージョンに置き換えてください。最新バージョンについては、リリースノートをご覧ください。

メディア プレーヤーを作成する

Media3 では、Player インターフェースの付属の実装である ExoPlayer を使用するか、独自のカスタム実装を構築できます。

ExoPlayer の作成

ExoPlayer インスタンスを作成する最も簡単な方法は次のとおりです。

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

メディア プレーヤーは、存在する ActivityFragment、または ServiceonCreate() ライフサイクル メソッドで作成できます。

Builder には、次のようなさまざまなカスタマイズ オプションが含まれています。

Media3 には、アプリのレイアウト ファイルに含めることができる PlayerView UI コンポーネントが用意されています。このコンポーネントは、再生コントロール用の PlayerControlView、字幕の表示用の SubtitleView、動画のレンダリング用の Surface をカプセル化します。

プレーヤーの準備

setMediaItem()addMediaItem() などのメソッドを使用して再生するプレイリストにメディア アイテムを追加します。次に、prepare() を呼び出してメディアの読み込みを開始し、必要なリソースを取得します。

アプリがフォアグラウンドになる前に、これらの手順を実行しないでください。プレーヤーが Activity または Fragment にある場合、これは API レベル 24 以降では onStart() ライフサイクル メソッドで、API レベル 23 以前では onResume() ライフサイクル メソッドでプレーヤーを準備することを意味します。Service にいるプレーヤーの場合は、onCreate() で準備できます。

プレーヤーを操作する

プレーヤーの準備が整ったら、次のようなプレーヤーのメソッドを呼び出して再生を制御できます。

PlayerViewPlayerControlView などの UI コンポーネントは、プレーヤーにバインドされると、それに応じて更新されます。

プレーヤーをリリースする

再生には、動画デコーダなど、供給量が限られているリソースが必要になる場合があります。そのため、プレーヤーが不要になったときにプレーヤーで release() を呼び出してリソースを解放することが重要です。

プレーヤーが Activity または Fragment にある場合は、API レベル 24 以降の onStop() ライフサイクル メソッド、または API レベル 23 以前の onPause() メソッドでプレーヤーを解放します。Service にいるプレーヤーは、onDestroy() で解放できます。

メディア セッションを使用した再生の管理

Android では、メディア セッションにより、プロセスの境界を越えてメディア プレーヤーを操作するための標準化された方法が提供されます。メディア セッションをプレーヤーに接続すると、メディアの再生を外部に宣伝したり、外部ソースから再生コマンドを受け取ったりできます。たとえば、モバイル デバイスや大画面デバイスのシステム メディア コントロールと統合できます。

メディア セッションを使用するには、Media3 Session モジュールへの依存関係を追加します。

implementation "androidx.media3:media3-session:1.5.1"

メディア セッションを作成する

プレーヤーを初期化した後、次のように MediaSession を作成できます。

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

Media3 は、Player の状態と MediaSession の状態を自動的に同期します。これは、ExoPlayerCastPlayer、カスタム実装など、任意の Player 実装で機能します。

他のクライアントに管理権限を付与する

クライアント アプリは、メディア コントローラを実装して、メディア セッションの再生を制御できます。これらのリクエストを受信するには、MediaSession を作成するときにコールバック オブジェクトを設定します。

コントローラがメディア セッションに接続しようとすると、onConnect() メソッドが呼び出されます。提供されている ControllerInfo を使用して、リクエストを承認するか拒否するかを決定できます。例については、Media3 Session デモアプリをご覧ください。

接続すると、コントローラはセッションに再生コマンドを送信できます。セッションは、これらのコマンドをプレーヤーに委任します。Player インターフェースで定義された再生コマンドと再生リスト コマンドは、セッションによって自動的に処理されます。

他のコールバック メソッドを使用すると、カスタム再生コマンドのリクエストやプレイリストの変更などを処理できます。これらのコールバックにも同様に ControllerInfo オブジェクトが含まれるため、リクエストごとにアクセス制御を決定できます。

バックグラウンドでメディアを再生する

アプリがフォアグラウンドにない場合でもメディアの再生を続けるには(たとえば、ユーザーがアプリを開いていないときに音楽、オーディオブック、ポッドキャストを再生する場合)、PlayerMediaSessionフォアグラウンド サービスにカプセル化する必要があります。Media3 には、この目的のための MediaSessionService インターフェースが用意されています。

MediaSessionService の実装

MediaSessionService を拡張するクラスを作成し、onCreate() ライフサイクル メソッドで MediaSession をインスタンス化します。

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

マニフェストで、MediaSessionService インテント フィルタを持つ Service クラスを作成し、フォアグラウンド サービスを実行するための FOREGROUND_SERVICE 権限をリクエストします。

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

最後に、作成したクラスで onGetSession() メソッドをオーバーライドして、メディア セッションへのクライアント アクセスを制御します。接続リクエストを承認するには MediaSession を返します。リクエストを拒否するには null を返します。

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

UI への接続

メディア セッションが、プレーヤー UI が存在する Activity または Fragment とは別の Service にあるため、MediaController を使用してそれらをリンクできます。UI の Activity または FragmentonStart() メソッドで、MediaSessionSessionToken を作成し、SessionToken を使用して MediaController を作成します。MediaController のビルドは非同期で行われます。

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaControllerPlayer インターフェースを実装しているため、play()pause() などの同じメソッドを使用して再生を制御できます。他のコンポーネントと同様に、ActivityonStop() ライフサイクル メソッドなど、不要になった MediaControllerMediaController.releaseFuture() を呼び出して解放してください。

通知の公開

フォアグラウンド サービスは、アクティブなときに通知を公開する必要があります。MediaSessionService は、MediaNotification の形式でMediaStyle 通知を自動的に作成します。カスタム通知を提供する場合は、DefaultMediaNotificationProvider.Builder を使用して MediaNotification.Provider を作成するか、プロバイダ インターフェースのカスタム実装を作成します。setMediaNotificationProvider を使用して、プロバイダを MediaSession に追加します。

コンテンツ ライブラリの宣伝

MediaLibraryServiceMediaSessionService を基盤としており、クライアント アプリがアプリが提供するメディア コンテンツをブラウジングできるようにします。クライアント アプリは、MediaLibraryService とやり取りするために MediaBrowser を実装します。

MediaLibraryService の実装は MediaSessionService の実装に似ていますが、onGetSession() では MediaSession ではなく MediaLibrarySession を返す必要があります。MediaSession.Callback と比較して、MediaLibrarySession.Callback には、ブラウザ クライアントがライブラリ サービスが提供するコンテンツを操作できる追加メソッドが含まれています。

MediaSessionService と同様に、マニフェストで MediaLibraryService を宣言し、フォアグラウンド サービスを実行するための FOREGROUND_SERVICE 権限をリクエストします。

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

上記の例には、MediaLibraryService と、下位互換性のために以前の MediaBrowserService の両方のインテント フィルタが含まれています。追加されたインテント フィルタにより、MediaBrowserCompat API を使用するクライアント アプリが Service を認識できるようになります。

MediaLibrarySession を使用すると、単一のルート MediaItem を使用してコンテンツ ライブラリをツリー構造で提供できます。ツリーの各 MediaItem には、任意の数の子 MediaItem ノードを設定できます。クライアント アプリのリクエストに基づいて、別のルートまたは別のツリーを提供できます。たとえば、おすすめのメディア アイテムのリストを検索しているクライアントに返すツリーには、ルート MediaItem と 1 つのレベルの子 MediaItem ノードしか含まれていない場合があります。一方、別のクライアント アプリに返すツリーには、より完全なコンテンツ ライブラリが含まれている場合があります。

MediaLibrarySession の作成

MediaLibrarySessionMediaSession API を拡張して、コンテンツ ブラウジング API を追加します。MediaSession コールバックと比較して、MediaLibrarySession コールバックには次のようなメソッドが追加されています。

  • onGetLibraryRoot() - クライアントがコンテンツ ツリーのルート MediaItem をリクエストする場合
  • onGetChildren() クライアントがコンテンツ ツリー内の MediaItem の子孫をリクエストする場合
  • onGetSearchResult(): クライアントが特定のクエリに対してコンテンツ ツリーから検索結果をリクエストする場合

関連するコールバック メソッドには、クライアント アプリが関心を持っているコンテンツ ツリーのタイプに関する追加のシグナルを含む LibraryParams オブジェクトが含まれます。