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

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

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

はじめに

まず、Jetpack Media3 の ExoPlayer、UI、共通モジュールへの依存関係を追加します。

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

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

1.2.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.2.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() などの同じメソッドを使用して再生を制御できます。他のコンポーネントと同様に、MediaController.releaseFuture() を呼び出して、ActivityonStop() ライフサイクル メソッドなど、不要になったら忘れずに MediaController を解放してください。

通知の公開

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

コンテンツ ライブラリの広告

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

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 を使用すると、1 つのルート MediaItem を持つツリー構造でコンテンツ ライブラリを提供できます。ツリー内の各 MediaItem には任意の数の子 MediaItem ノードを設定できます。クライアント アプリのリクエストに基づいて、異なるルートまたは別のツリーを表示できます。たとえば、クライアントに戻ったときに推奨されるメディア アイテムのリストを探すツリーには、ルート MediaItem と単一レベルの子 MediaItem ノードしか含まれていないのに対し、別のクライアント アプリに戻ったツリーは、より完全なコンテンツ ライブラリを表す可能性があります。

MediaLibrarySession の作成

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

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

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