Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
68
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

ExoPlayerの使い方

ExoPlayerのネット上情報は古いものが多い気がしたので、最新版の使い方についてまとめてみました。

プロジェクトにExoPlayerを追加する

build.gradledependenciesブロックにimplementation 'com.google.android.exoplayer:exoplayer:2.6.1'を追加するだけで利用可能になります(2018年2月9日時点の最新バージョン)。

dependencies {
    implementation 'com.google.android.exoplayer:exoplayer:2.6.1'
}

2.4.0からExoPlayerはモジュール化されました。アプリケーションで必要な機能だけを取り込むことでapkのサイズが小さくなり、ビルドも早くなります。

  • exoplayer-core: 基本的な機能(必須)
  • exoplayer-dash: DASHサポート
  • exoplayer-hls: HLSサポート
  • exoplayer-smoothstreaming: SmoothStreamingサポート
  • exoplayer-ui: UIコンポーネント

また、以下の拡張機能もあります。

  • extension-ima: インタラクティブメディア広告
  • extension-mediasession: Media session
  • extension-okhttp: OkHttpを使用
  • extension-rtmp: RTMP対応
  • extension-gvr: Google VR Audioを使ってAmbisonicsを再生

gradleには以下のようにして指定します。

dependencies {

    // implementation 'com.google.android.exoplayer:exoplayer:2.6.1' は、以下5つを全部指定するのと同じ
    implementation 'com.google.android.exoplayer:exoplayer-core:2.6.1'
    implementation 'com.google.android.exoplayer:exoplayer-dash:2.6.1'
    implementation 'com.google.android.exoplayer:exoplayer-hls:2.6.1'
    implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.6.1'
    implementation 'com.google.android.exoplayer:exoplayer-ui:2.6.1'

    // 拡張機能
    implementation 'com.google.android.exoplayer:extension-ima:2.6.1'
    implementation 'com.google.android.exoplayer:extension-mediasession:2.6.1'
    implementation 'com.google.android.exoplayer:extension-okhttp:2.6.1'
    implementation 'com.google.android.exoplayer:extension-rtmp:2.6.1'
    implementation 'com.google.android.exoplayer:extension-gvr:2.6.1'
}

インスタンスの作成

ExoPlayerのインスタンスを作る最も簡単な方法はExoPlayerFactory.newSimpleInstanceを呼び出す方法です。引数に渡すインターフェースがいくつかありますが、最もシンプルなのはこれです。単純な動画や音声のURIを再生するならこれで十分です。

ExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(context, new DefaultTrackSelector());

ExoPlayerを使いたい大きな理由の一つにアダプティブストリーミングがあります。このような帯域の状況を見ながら読み込むストリームを変える必要がある場合は、DefaultTrackSelectorAdaptiveTrackSelection.Factoryのインスタンスを渡します。

DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
ExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(context, new DefaultTrackSelector(new AdaptiveTrackSelection.Factory(bandwidthMeter)));

注意: DefaultBandwidthMeterを使う時は、この後に登場するDefaultDataSourceFactoryのコンストラクターにも同じインスタンスを渡す必要があります。
DefaultBandwidthMeterを使わない時は、後述の例でDefaultDataSourceFactoryのコンストラクターに渡しているbandwidthMeterはnullにするか省略することができます。

読み込み

MediaPlayerと同様、ExoPlayerでも読み込むメディアはUriで指定します。しかしMediaPlayerと違い、ExoPlayerではUriを直接渡すのではなくMediaSourceというインターフェースを使用します。

MediaSource mediaSource = createMediaSource(); // TODO これから説明します
exoPlayer.prepare(mediaSource);

これはローカルファイルやメディアへの直URIを指定する場合とストリーミングを行う場合では必要な情報が全く異なるためです。したがって、再生するメディアがどのようなものであるかによって、上記createMediaSource()で行うべきことが変わってきます。

メディアを直接読み込む

動画や音声そのものへのURLを読み込んで再生する場合は、ExtractorMediaSourceを使います。

2.6.1 以前

これを作るためにたくさんのインスタンスを指定する必要があるので、まずこれらの依存インスタンスを作ってからExtractorMediaSourceに渡します。
ExoPlayerではいろんなところでHandlerとイベントリスナーを渡す箇所がありますが、イベントリスナーが必要なければそれぞれnullを指定することもできます。

Uri uri = Uri.parse(MEDIA_URL);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "AppName"), bandwidthMeter);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();

// 任意のイベントリスナー
Handler handler = new Handler();
ExtractorMediaSource.EventListener eventListener = new ExtractorMediaSource.EventListener() {
    @Override
    public void onLoadError(IOException error) {
        error.printStackTrace();
    }
};

player.prepare(new ExtractorMediaSource(uri, dataSourceFactory, extractorsFactory, handler, eventListener));

2.6.1以降

ExtractorMediaSourceのコンストラクタは非推奨になり、代わりにExtractorMediaSource.Factoryを使う方法が推奨されています。こちらの方がコードが少なくて良いです。

Uri uri = Uri.parse(MEDIA_URL);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "AppName"), bandwidthMeter);
MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
player.prepare(mediaSource);

MPEG DASH のmpdファイルを読み込む

MPEG DASHのmpgファイルを読み込む場合はExtractorMediaSourceの代わりにDashMediaSourceを使います。これにより必要な依存関係が微妙に異なってきます。

Uri uri = Uri.parse(MPD_URL);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "Test", bandwidthMeter));
DashChunkSource.Factory dashChunkSourceFactory = new DefaultDashChunkSource.Factory(dataSourceFactory);

// Optional event handler
Handler handler = new Handler();
AdaptiveMediaSourceEventListener eventListener = new AdaptiveMediaSourceEventListener() {
    @Override
    public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) {
        Log.d(TAG, "onLoadStarted: ");
    }

    @Override
    public void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
        Log.d(TAG, "onLoadCompleted: ");
    }

    @Override
    public void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
        Log.d(TAG, "onLoadCanceled: ");
    }

    @Override
    public void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) {
        Log.d(TAG, "onLoadError: ");
    }

    @Override
    public void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs) {
        Log.d(TAG, "onUpstreamDiscarded: ");
    }

    @Override
    public void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaTimeMs) {
        Log.d(TAG, "onDownstreamFormatChanged: ");
    }
};
player.prepare(new DashMediaSource(uri, dataSourceFactory, dashChunkSourceFactory, handler, eventListener));

HLSのm3u8ファイルを読み込む

HLSのm3u8ファイルを読み込む場合はHlsMediaSourceを使います。MPEG DASHの時と似ています。

Uri uri = Uri.parse(CONTENT_URL);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "Test", bandwidthMeter));

// Optional event handler
Handler handler = new Handler();
AdaptiveMediaSourceEventListener eventListener = new AdaptiveMediaSourceEventListener() {
    @Override
    public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) {
        Log.d(TAG, "onLoadStarted: ");
    }

    @Override
    public void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
        Log.d(TAG, "onLoadCompleted: ");
    }

    @Override
    public void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
        Log.d(TAG, "onLoadCanceled: ");
    }

    @Override
    public void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) {
        Log.d(TAG, "onLoadError: ");
    }

    @Override
    public void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs) {
        Log.d(TAG, "onUpstreamDiscarded: ");
    }

    @Override
    public void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaTimeMs) {
        Log.d(TAG, "onDownstreamFormatChanged: ");
    }
};

player.prepare(new HlsMediaSource(uri, dataSourceFactory, handler, eventListener));

再生・一時停止

再生を開始したり一時停止をしたりというコントロールを制御するのにstart(),play(),pause()などのメソッドがあるのを期待しますが、なんとExoPlayerにはそのようなものはありません。代わりにsetPlayWhenReady()というメソッドがあります。言われてみれば確かに、「準備ができたら再生する」の方が実際の動作に合っている気がしてきます。

// 再生
player.setPlayWhenReady(true);

// 一時停止
player.setPlayWhenReady(false);

停止

これはstop()メソッドがあります。一時停止との使い分けですが、一時停止は後で再生に戻る予定の時に使います。停止は再生を終えたい時に使います。

player.stop();

シーク

MediaPlayer同様、seekTo()でシークできます。指定する単位もミリ秒で同じです。ただし引数の型がlongになっているところだけが微妙に違います。

player.seekTo(10000);

SurfaceViewに映像を表示する

ExoPlayerでデコードされる映像を画面に表示する最も簡単な方法はSimpleExoPlayer.setVideoSurfaceView()メソッドを使うことです。ExoPlayerSimpleExoPlayerにキャストして使っています。このキャストができるのは、このページの例のようにExoPlayerFactory.newSimpleInstance()でインスタンスを作った場合のみです。キャストを行わないで最初からSimpleExoPlayer型として保持しておくのも良いです。

((SimpleExoPlayer) player).setVideoSurfaceView(surfaceView);

後片付け

使い終わったらExoPlayer.release()を呼び出してリソースを解放します。リソースを解放した後のExoPlayerには触らないようにしましょう。

player.release();

次のようにnullチェックを行うことで、確実に一度だけrelease()を呼び出すようにすることもあります。

if (player != null) {
  player.release();
  player = null;
}

Widevine CENEのDRM付きのMPDを再生する

細かいことはまだよくわかってないですが、ExoPlayerFactory.newSimpleInstance()の引数にDrmSessionManagerを渡すとDRM付きの動画等が再生できるようになるようです。

player = ExoPlayerFactory.newSimpleInstance(context, trackSelector, loadControl, drmSessionManager);

DrmSessionManagerを作成するまでにかなり長い道のりがありますが、とりあえずこんな感じでWidevine CENC + MPEG DASHの再生ができるようです。サンプルコードから拝借しました。

String MPD_URL = ...
String LICENSE_URL = ...

DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, userAgent);
Handler handler = new Handler();

TrackSelector trackSelector = new DefaultTrackSelector();
LoadControl loadControl = new DefaultLoadControl();

HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(LICENSE_URL, new DefaultHttpDataSourceFactory(userAgent));
DefaultDrmSessionManager.EventListener listener = new DefaultDrmSessionManager.EventListener() {
    @Override
    public void onDrmKeysLoaded() {
        Log.d(TAG, "onDrmKeysLoaded: ");
    }

    @Override
    public void onDrmSessionManagerError(Exception e) {
        Log.d(TAG, "onDrmSessionManagerError: ");
    }

    @Override
    public void onDrmKeysRestored() {
        Log.d(TAG, "onDrmKeysRestored: ");
    }

    @Override
    public void onDrmKeysRemoved() {
        Log.d(TAG, "onDrmKeysRemoved: ");
    }
};
DefaultDrmSessionManager<FrameworkMediaCrypto> drmSessionManager = new DefaultDrmSessionManager<>(C.WIDEVINE_UUID, FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), drmCallback, null, handler, listener);

player = ExoPlayerFactory.newSimpleInstance(context, trackSelector, loadControl, drmSessionManager);

DashChunkSource.Factory dashChunkSourceFactory = new DefaultDashChunkSource.Factory(dataSourceFactory);
AdaptiveMediaSourceEventListener eventListener = new AdaptiveMediaSourceEventListener() {
    @Override
    public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) {
        Log.d(TAG, "onLoadStarted: ");
    }

    @Override
    public void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
        Log.d(TAG, "onLoadCompleted: ");
    }

    @Override
    public void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
        Log.d(TAG, "onLoadCanceled: ");
    }

    @Override
    public void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) {
        Log.d(TAG, "onLoadError: ");
    }

    @Override
    public void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs) {
        Log.d(TAG, "onUpstreamDiscarded: ");
    }

    @Override
    public void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaTimeMs) {
        Log.d(TAG, "onDownstreamFormatChanged: ");
    }
};

player.prepare(new DashMediaSource(MPD_URL, dataSourceFactory, dashChunkSourceFactory, handler, eventListener));

Ambisonics動画を再生する

ExoPlayerの拡張機能extension-gvrを使えば、Google Audio SDKを使ってAmbisonics音声をバイノーラル化しながら再生することができるようになります。360度動画のプレイヤーをExoPlayerで作っていれば、ちょっとコードを変更するだけで簡単にAmbisonics対応をすることができます。素晴らしい!

まず、依存ライブラリを追加します。ExoPlayer本体以外にextension-gvr拡張機能とGoogle VR Audioのライブラリをbuild.gradleに追加します。

build.gradle
dependencies {
    implementation 'com.google.android.exoplayer:exoplayer-core:r2.5.4' // ExoPlayer本体
    implementation 'com.google.android.exoplayer:extension-gvr:r2.5.4'  // ExoPlayer+Google VR拡張機能
    implementation 'com.google.vr:sdk-audio:1.101.0'                    // Google VR Audio
}

次に、ExoPlayerをインスタンス化する部分を次のようにします。gvrAudioProcessorは後で使うのでローカル変数ではなくフィールドで宣言しておきます。

gvrAudioProcessor = new GvrAudioProcessor();
RenderersFactory renderersFactory = new DefaultRenderersFactory(context) {
    @Override
    protected AudioProcessor[] buildAudioProcessors() {
        return new AudioProcessor[]{gvrAudioProcessor};
    }
};

exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, new DefaultTrackSelector());

次に、毎フレーム呼ばれるコールバック内でgvrAudioProcessor.updateOrientation()を呼び、ヘッドトラッキング情報を伝えます。

gvrAudioProcessor.updateOrientation(quaternion.w, quaternion.x, quaternion.y, quaternion.z);

これだけです!
既存の360度動画プレイヤーのコードをお持ちの方は、ぜひ試してみてくださいね!

なお、こちらsample-exoplayer というのがGear VRでExoPlayerを使ってAmbisonics対応の360度動画を再生するサンプルです。参考までに。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
68
Help us understand the problem. What are the problem?