Androidアプリ開発

ExoPlayerで動画や音楽を再生する

この記事は約61分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


Jetpack Media3のExoPlayerで動画や音楽を再生する

ExoPlayerは、Github でホストされるオープンソースプロジェクトでしたが、Jetpack Media3ライブラリとして統合され、今後はMedia3版が主流となる予定です。
基本的な使い方は同じですので、
Media3版を使用することをおすすめします。

◎オープンソース版

ExoPlayerは動画・音楽ファイルのローカル再生のほか、動的適応型 HTTP ストリーミング(DASH)、SmoothStreaming、共通暗号化など、MediaPlayerではサポートされていない機能をサポートしています。
動画を再生表示するビュー画面、動画や音楽の再生や一時停止などを操作するためのコントローラなどカスタマイズ可能なコンポーネントをもつライブラリです。
今回は
Media3版のExoPlayerを使用して、動画や音楽をローカル再生する方法を紹介いたします。

ExoPlayerを使用するための準備

ExoPlayerを使用するには、モジュールのbuild.gradleファイルに定義の追加が必要です。

◎build.gradle(モジュール)
2024年3月現在の最新バージョンは1.3.0です。

dependencies {
    …
    implementation 'androidx.media3:media3-exoplayer:1.3.0'
    implementation 'androidx.media3:media3-ui:1.3.'
    implementation 'androidx.media3:media3-session:1.3.0'
    …
}

メディアストアの音楽や動画にアクセスする

アプリで使用する動画や音楽ですが、メディアストアでハンドリングする方法が一般的です。
メディアストアの動画や音楽ファイルにアクセスする場合、
きめ細かいメディア権限を設定する必要があります。
詳細は、
Android13対応(ファイルのメディア権限)で紹介しています。

ExoPlayerで動画を再生する

ExoPlayerで動画を再生するには、動画ファイルのアクセスに必要なUriが必要です。

Android標準では、MP4形式以外の動画ファイルは再生できません(MP4形式に変換が必要です)

MP4形式以外の動画ファイルをMP4形式に変換する方法はこちらで紹介しています↓↓↓

動画ファイルのメタ情報を取得する

メディアストアはスマホ本体の画像・音楽・動画をコンテンツリゾルバを経由してアクセスができます。
下記のサンプリでは、メディアストアで管理している動画をコンテンツリゾルバにクエリを指定して、mp4形式の動画ファイルを登録の降順で取得しています。
動画ファイルのアクセスに必要な
Uriは、ContentUrisを使用して取得しています。
取得した動画ファイルのメタ情報はエンティティ(VideoItem)に格納しています。

    private Map<String, VideoItem>  videoItemMap = new LinkedHashMap<>();
    …
    try {
        ContentResolver resolver = context.getContentResolver();
        Cursor cursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, "MIME_TYPE == 'video/mp4'", null, "_ID DESC");
        while (cursor.moveToNext()) {
            Uri contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, Long.parseLong(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID))));
            videoItemMap.put(cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.DISPLAY_NAME)),
                new VideoItem(cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.TITLE)),
                    Long.parseLong(cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.DURATION))),
                    contentUri, cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.DISPLAY_NAME))));
                }
            }
        }
        cursor.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    …

◎エンティティ(VideoItem)

public class VideoItem {
    public String   title;      // タイトル(xxx)
    public long     duration;   // 再生時間(ms)
    public Uri      videoUri;   // 動画ファイルUri
    public String   source;     // 動画ファイル(xxx.mp4)
    public VideoItem(String title, long duration, Uri videoUri, String source) {
        this.title  = title;
        this.duration = duration;
        this.videoUri = videoUri;
        this.source = source;
    }
}

コントローラを標準のまま使用する

PlayerViewは動画を再生表示するビュー画面です。
PlayerViewには動画の再生や一時停止などを操作するためのコントローラが標準で実装されているため、ExoPlayerをアタッチするだけで、基本的な操作ができるようになります。

コントローラを標準のまま使用したダイアログ画面

標準コントローラの構成は、先頭に戻る(前の動画)、5秒戻る、再生(一時停止)、15秒進む、次の動画を操作できるボタン、動画の再生位置の操作できるスライダー、再生位置(秒)と再生時間(秒)の表示、再生オプションのメニューです。

次の動画ボタンはExoPlayerにセットするMediaItemが複数ある場合のみ表示されます。 

ExoPlayerはBuilderでインスタンス化します。
サンプルではイヤホンが外れた時に再生を止める
setHandleAudioBecomingNoisyを指定しています。
標準コントローラの再生オプションで再生速度を変更することができます。
サンプルではインスタンス化した
ExoPlayerにリスナーを追加し、onPlaybackParametersChangedで再生速度が標準以外に変更された場合にミュートする機能を実装しています。
setPlayerで、PlayerViewExoPlayerをアタッチします。
再生する動画の指定は、
MediaItemを使用します。
動画ファイルの
Uriを使用してMediaItemを生成します。
再生位置(ミリ秒)を引数に
setMediaItemExoPlayerにセットします。
セットした
MediaItemのロードは、prepareを使用します。 

    …
    PlayerView playerView = dialog.findViewById(R.id.video);
    ExoPlayer exoPlayer = new ExoPlayer.Builder(context)
            .setHandleAudioBecomingNoisy(true)
            .build();
    exoPlayer.addListener(new Player.Listener() {
        @Override
        public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) {
            exoPlayer.setVolume(playbackParameters.speed != 1 ? 0 : 1.0);
            Player.Listener.super.onPlaybackParametersChanged(playbackParameters);
        }
    });
    playerView.setPlayer(exoPlayer);
    exoPlayer.setMediaItem(MediaItem.fromUri(uri), 0);
    exoPlayer.prepare();
   …
    // 現在値設定
    dialog.findViewById(R.id.set).setOnClickListener(view -> {
        dialog.findViewById(R.id.set).requestFocus();
        exoPlayer.pause();
        exoPlayer.seekTo(exoPlayer.getContentPosition());
   …
   });
   …
   // closeボタン
   dialog.findViewById(R.id.close).setOnClickListener(view -> {
        exoPlayer.stop();
        exoPlayer.release();
   …
   });
   return dialog;
}
…

再生の一時停止や再生位置の変更はコントローラで行いますが、一時停止はPause、再生位置はseekToにミリ秒を指定することでも可能です。
終了時(画面を閉じる)に停止(
stop)とリリース(release)します。

◎レイアウト(dailog.xml)

    …
    <androidx.media3.ui.PlayerView
        android:id="@+id/video"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:resize_mode="fill"
        app:show_timeout="500"/>
    …
    <Button
       android:id="@+id/set"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    …
    <Button
       android:id="@+id/close"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    …

PlayerViewに動画を再生表示するビュー画面のサイズを指定します。
全体表示は
resize_modeに “fill” を指定します。
動画の再生中のコントローラ表示の時間は、
show_timeoutにミリ秒を指定します。
コントローラを表示しない場合は0を指定します。

◎標準コントローラの黒の透過表示をキャンセル

標準コントローラは表示中、動画の画面は黒色の透過表示です。
colors.xmlに定義を追加するだけで黒色の透過表示をキャンセルできます。

<resources
    xmlns:tools="http://schemas.android.com/tools">
    …
    <color name="exo_bottom_bar_background" tools:override="true" >#00FFFFFF</color>
    <color name="exo_black_opacity_60" tools:override="true" >#00FFFFFF</color>
</resources>
黒色の透過表示をキャンセルしたダイアログ画面

コントローラをカスタムする

標準のコントローラを使用しない場合、レイアウトXMLのPlayerViewの属性(show_timeout)に0を指定し、controller_layout_idにカスタムしたコントローラのレイアウトを指定します。

左側がコントローラを使用しないPlayerView、右側がカスタムしたコントローラをもつPlayerView

ExoPlayerのインスタンス化、再生する動画の指定は、コントローラを標準のまま使用する場合と同じです。
サンプルでは動画の再生位置の操作できるスライダー、再生位置(秒)と再生時間(秒)はカスタムしたコントローラに実装するため、
DefaultTimeBarを非表示にしています。


private mode = 0;
        …
        ExoPlayer exoPlayer = new ExoPlayer.Builder(context)
                .setHandleAudioBecomingNoisy(true)
                .build();
        exoPlayer.setMediaItem(MediaItem.fromUri(uri), 0);
        exoPlayer.prepare();
        videoView = view.findViewById(R.id.videoView);
        DefaultTimeBar timeBar = videoView.findViewById(R.id.exo_progress);
        timeBar.setVisibility(View.GONE);
        videoView.setPlayer(exoPlayer);
        // PLAY & PAUSE
        control = view.findViewById(R.id.control);
        control.setOnClickListener(view -> {
                if (mode == 1) {
                    // PAUSE
                    exoPlayer.pause();
                    mode = 0;
                    …
                } else {
                    // PLAY
                    exoPlayer.play();
                    mode = 1;
                    …
                }
        });
        return view;
    }
    …

    // onPause //
    @Override
    public void onPause() {
        if (exoPlayer != null) {
            exoPlayer.pause();
        }
        super.onPause();
    }

    // onDestroy //
    @Override
    public void onDestroy() {
        if (exoPlayer != null) {
            exoPlayer.stop();
            exoPlayer.release();
        }
        super.onDestroy();
    }
    …

サンプルでは再生と一時停止を操作するボタンで用意し、modeで再生と一時停止を切り替えています。
一時停止は
Pause、再生はplayを使用します。
終了時(画面を閉じる)に停止(
stop)とリリース(release)します。

◎レイアウト(fragment.xml)

    …
    <androidx.media3.ui.PlayerView
        android:id="@+id/videoView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:resize_mode="fill"
        app:show_timeout="0"
        app:controller_layout_id="@layout/dialog_controller"/>
    …

PlayerViewに動画を再生表示するビュー画面のサイズを指定します。
全体表示は
resize_modeに “fill” を指定します。
show_timeoutに0を指定し、controller_layout_idにカスタムコントローラを指定します。

◎カスタムコントローラ(dialog_controller.xml)

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        tools:ignore="UselessParent">
        <androidx.media3.ui.DefaultTimeBar
            android:id="@+id/exo_progress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:buffered_color="@color/blue"
            app:unplayed_color="@color/cyan"
            app:played_color="@color/white"
            app:scrubber_color="@color/white"/>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:layout_marginEnd="4dp"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/exo_position"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="14sp"
                style="@style/TextViewStyle2"/>