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

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

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

開始する

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

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

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

1.3.1 は、必要なバージョンのライブラリに置き換えてください。最新バージョンを確認するには、リリースノートをご覧ください。

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

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

ExoPlayer の作成

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

Kotlin

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

Java

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

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

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.3.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() ライフサイクル メソッドなど、不要になったら必ず MediaController.releaseFuture() を呼び出して MediaController を解放してください。

通知の公開

フォアグラウンド サービスは、アクティブなときに通知を公開する必要があります。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 と単一レベルの子 MediaItem ノードのみが含まれるのに対し、別のクライアント アプリに戻ったツリーは、より完全なコンテンツのライブラリを表す場合があります。

MediaLibrarySession の作成

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

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

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