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

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

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

スタートガイド

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

Kotlin

implementation("androidx.media3:media3-exoplayer:1.11.1")
implementation("androidx.media3:media3-ui:1.11.1")
implementation("androidx.media3:media3-common:1.11.1")

Groovy

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

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

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

メディア プレーヤーの作成

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

ExoPlayer を作成する

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

Kotlin

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

Java

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

メディア プレーヤーは、それが存在する Activity、Fragment、Service の onCreate() ライフサイクル メソッドで作成できます。

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

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

プレーヤーを準備する

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

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

プレーヤーを操作する

プレーヤーの準備が完了したら、プレーヤーのメソッドを呼び出して再生を制御できます。

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

プレーヤーを解放する

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

プレーヤーが Activity または Fragment にある場合は、API レベル 24 以降では onStop() ライフサイクル メソッド、API レベル 23 以前では onPause() メソッドでプレーヤーを解放します。Service のプレーヤーは、onDestroy() でリリースできます。ライフサイクル メソッドの実装例については、Exoplayer Codelab をご覧ください。

メディア セッションによる再生の管理

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

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

Kotlin

implementation("androidx.media3:media3-session:1.11.1")

Groovy

implementation "androidx.media3:media3-session:1.11.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 の状態と自動的に同期します。これは、ExoPlayer、CastPlayer、カスタム実装など、あらゆる Player 実装で機能します。

他のクライアントに制御を付与する

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

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

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

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

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

アプリがフォアグラウンドにないときにメディアの再生を継続するには(たとえば、ユーザーがアプリを開いていないときでも音楽、オーディオブック、ポッドキャストを再生するには)、Player と MediaSession をフォアグラウンド サービスにカプセル化する必要があります。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 または Fragment の onStart() メソッドで、MediaSession の SessionToken を作成し、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())
}

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

通知の公開

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

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

MediaLibraryService は、クライアント アプリがアプリによって提供されるメディア コンテンツを閲覧できるようにすることで、MediaSessionService を基盤として構築されます。クライアント アプリは、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 を使用すると、単一のルート MediaItem を持つツリー構造でコンテンツ ライブラリを提供できます。ツリー内の各 MediaItem には、任意の数の子 MediaItem ノードを含めることができます。クライアント アプリのリクエストに基づいて、異なるルートまたは異なるツリーを提供できます。たとえば、おすすめのメディア アイテムのリストを探しているクライアントに返すツリーには、ルート MediaItem と 1 レベルの子 MediaItem ノードのみが含まれる場合があります。一方、別のクライアント アプリに返すツリーは、より完全なコンテンツ ライブラリを表す場合があります。

MediaLibrarySession の作成

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

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

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