Analytics

ExoPlayer は、幅広い再生分析ニーズに対応しています。結局のところ、分析とは再生からデータを収集、解釈、集計、要約することです。このデータは、ロギング、デバッグ、将来の再生に関する意思決定など、デバイス上で使用することも、サーバーに報告してすべてのデバイスでの再生をモニタリングすることもできます。

通常、分析システムでは、まずイベントを収集し、さらに処理して意味のあるものにする必要があります。

  • イベントの収集: ExoPlayer インスタンスに AnalyticsListener を登録することで行えます。登録されたアナリティクス リスナーは、プレーヤーの使用中に発生したイベントを受信します。各イベントは、再生リスト内の対応するメディア アイテム、再生位置、タイムスタンプのメタデータに関連付けられます。
  • イベント処理: 一部のアナリティクス システムでは、未加工のイベントがサーバーにアップロードされ、すべてのイベント処理がサーバーサイドで実行されます。また、デバイス上でイベントを処理することもできます。そうすることで、処理が簡単になるか、アップロードする必要がある情報の量を減らすことができます。ExoPlayer には PlaybackStatsListener が用意されており、次の処理ステップを実行できます。
    1. イベントの解釈: アナリティクスで有用にするには、イベントを単一の再生のコンテキストで解釈する必要があります。たとえば、プレーヤーの状態が STATE_BUFFERING に変更されたという元のイベントは、最初のバッファリング、再バッファリング、シーク後に発生するバッファリングに対応している可能性があります。
    2. 状態のトラッキング: このステップでは、イベントをカウンタに変換します。たとえば、状態変化イベントをカウンタに変換して、各再生状態に費やした時間をトラッキングできます。その結果、1 回の再生に対する一連のアナリティクス データの値が出力されます。
    3. 集計: このステップでは、通常はカウンタを加算して、複数の再生全体のアナリティクス データを結合します。
    4. 概要指標の計算: 最も有用な指標の多くは、平均を計算したり、基本的な分析データ値を他の方法で組み合わせたりします。概要指標は、1 回または複数回の再生に対して計算できます。

AnalyticsListener によるイベントの収集

プレーヤーからの未加工の再生イベントは、AnalyticsListener 実装に報告されます。独自のリスナーを簡単に追加し、関心のあるメソッドのみをオーバーライドできます。

KotlinJava
exoPlayer.addAnalyticsListener(
 
object : AnalyticsListener {
   
override fun onPlaybackStateChanged(
      eventTime
: EventTime, @Player.State state: Int
   
) {}

   
override fun onDroppedVideoFrames(
      eventTime
: EventTime,
      droppedFrames
: Int,
      elapsedMs
: Long,
   
) {}
 
}
)
exoPlayer.addAnalyticsListener(
   
new AnalyticsListener() {
     
@Override
     
public void onPlaybackStateChanged(
         
EventTime eventTime, @Player.State int state) {}

     
@Override
     
public void onDroppedVideoFrames(
         
EventTime eventTime, int droppedFrames, long elapsedMs) {}
   
});

各コールバックに渡される EventTime は、イベントを再生リスト内のメディア アイテムに関連付け、再生位置とタイムスタンプのメタデータを関連付けます。

  • realtimeMs: イベントの壁時計時間。
  • timelinewindowIndexmediaPeriodId: 再生リストと、イベントが属する再生リスト内のアイテムを定義します。mediaPeriodId には、イベントがアイテム内の広告に属するかどうかなど、オプションの追加情報が含まれます。
  • eventPlaybackPositionMs: イベントが発生したとき、アイテム内の再生位置。
  • currentTimelinecurrentWindowIndexcurrentMediaPeriodIdcurrentPlaybackPositionMs: 上記と同じですが、現在再生中のアイテムに適用されます。イベントが再生される次のアイテムのプリバッファリングに対応している場合など、現在再生中のアイテムは、イベントが属するアイテムとは異なる場合があります。

PlaybackStatsListener を使用したイベント処理

PlaybackStatsListener は、デバイス上のイベント処理を実装する AnalyticsListener です。PlaybackStats は、次のようなカウンタと派生指標を使用して計算されます。

  • 概要指標(合計再生時間など)。
  • アダプティブ再生品質の指標(動画の平均解像度など)。
  • レンダリング品質の指標(フレーム落ち率など)。
  • リソース使用量の指標(ネットワーク経由で読み取られたバイト数など)。

使用可能なカウントと派生指標の一覧については、PlaybackStats Javadoc をご覧ください。

PlaybackStatsListener は、再生リスト内のメディア アイテムごと、およびこれらのアイテム内に挿入されたクライアントサイド広告ごとに別々の PlaybackStats を計算します。PlaybackStatsListener にコールバックを指定して、再生が終了したことを通知し、コールバックに渡された EventTime を使用して、どの再生が終了したかを特定できます。複数の再生の分析データを集計できます。また、PlaybackStatsListener.getPlaybackStats() を使用して、現在の再生セッションの PlaybackStats をいつでもクエリすることもできます。

KotlinJava
exoPlayer.addAnalyticsListener(
 
PlaybackStatsListener(/* keepHistory= */ true) {
    eventTime
: EventTime?,
    playbackStats
: PlaybackStats?,
   
-> // Analytics data for the session started at `eventTime` is ready.
 
}
)
exoPlayer.addAnalyticsListener(
   
new PlaybackStatsListener(
       
/* keepHistory= */ true,
       
(eventTime, playbackStats) -> {
         
// Analytics data for the session started at `eventTime` is ready.
       
}));

PlaybackStatsListener のコンストラクタには、処理されたイベントの完全な履歴を保持するオプションがあります。なお、再生時間とイベント数によっては、不明なメモリ オーバーヘッドが発生する可能性があります。したがって、このオプションは、最終的な分析データだけでなく、処理されたイベントの完全な履歴にアクセスする必要がある場合にのみオンにしてください。

PlaybackStats は、拡張された状態セットを使用して、メディアの状態だけでなく、ユーザーの再生の意図や、再生が中断された理由や終了した理由などの詳細情報も示します。

再生状態 プレイするユーザーの意図 再生する意思がない
再生前 JOINING_FOREGROUND NOT_STARTEDJOINING_BACKGROUND
アクティブな再生 PLAYING
再生が中断された BUFFERINGSEEKING PAUSEDPAUSED_BUFFERINGSUPPRESSEDSUPPRESSED_BUFFERINGINTERRUPTED_BY_AD
終了状態 ENDEDSTOPPEDFAILEDABANDONED

ユーザーの再生意図は、ユーザーが再生の継続を積極的に待っていた時間と受動的な待ち時間を区別するために重要です。たとえば、PlaybackStats.getTotalWaitTimeMsJOINING_FOREGROUNDBUFFERINGSEEKING の各状態で費やされた合計時間を返しますが、再生が一時停止された時間を返しません。同様に、PlaybackStats.getTotalPlayAndWaitTimeMs は、ユーザーが再生を意図した合計時間を返します。つまり、アクティブな待ち時間の合計と PLAYING 状態の合計時間を返します。

処理済みイベントと解釈済みイベント

PlaybackStatsListenerkeepHistory=true とともに使用すると、処理済みイベントと解釈済みイベントを記録できます。生成された PlaybackStats には、次のイベントリストが含まれます。

  • playbackStateHistory: 適用が開始された EventTime を含む拡張再生状態の順序付きリスト。PlaybackStats.getPlaybackStateAtTime を使用して、特定の壁時計時間の状態を検索することもできます。
  • mediaTimeHistory: 実経過時間とメディア時刻のペアの履歴。メディアのどの部分がいつ再生されたかを再構成できます。PlaybackStats.getMediaTimeMsAtRealtimeMs を使用して、特定の壁時計時間の再生位置を検索することもできます。
  • videoFormatHistoryaudioFormatHistory: 再生中に使用された動画と音声のフォーマットの順序付きリスト。使用が開始された EventTime も含まれます。
  • fatalErrorHistorynonFatalErrorHistory: 致命的エラーと致命的でないエラーの順序付きリストと、それが発生した EventTime。致命的なエラーは再生を終了させたエラーで、致命的でないエラーは復元可能なエラーです。

1 回の再生のアナリティクス データ

このデータは、PlaybackStatsListener を使用すると自動的に収集されます(keepHistory=false を使用している場合も同様です)。最終的な値は、PlaybackStats Javadoc にある公開フィールドと、getPlaybackStateDurationMs によって返される再生状態の継続時間です。便利なように、特定の再生状態の組み合わせの再生時間を返す getTotalPlayTimeMsgetTotalWaitTimeMs などのメソッドもあります。

KotlinJava
Log.d(
 
"DEBUG",
 
"Playback summary: " +
   
"play time = " +
    playbackStats
.totalPlayTimeMs +
   
", rebuffers = " +
    playbackStats
.totalRebufferCount
)
Log.d(
   
"DEBUG",
   
"Playback summary: "
       
+ "play time = "
       
+ playbackStats.getTotalPlayTimeMs()
       
+ ", rebuffers = "
       
+ playbackStats.totalRebufferCount);

複数の再生の分析データを集計する

複数の PlaybackStats を組み合わせるには、PlaybackStats.merge を呼び出します。結果の PlaybackStats には、統合されたすべての再生の集計データが含まれます。個々の再生イベントの履歴は集計できないため、このレポートには含まれません。

PlaybackStatsListener.getCombinedPlaybackStats を使用すると、PlaybackStatsListener の存続期間中に収集されたすべての分析データの集計ビューを取得できます。

計算済みサマリー指標

PlaybackStats には、基本的な分析データに加えて、概要指標を計算するための多くのメソッドが用意されています。

KotlinJava
Log.d(
 
"DEBUG",
 
"Additional calculated summary metrics: " +
   
"average video bitrate = " +
    playbackStats
.meanVideoFormatBitrate +
   
", mean time between rebuffers = " +
    playbackStats
.meanTimeBetweenRebuffers
)
Log.d(
   
"DEBUG",
   
"Additional calculated summary metrics: "
       
+ "average video bitrate = "
       
+ playbackStats.getMeanVideoFormatBitrate()
       
+ ", mean time between rebuffers = "
       
+ playbackStats.getMeanTimeBetweenRebuffers());

高度なトピック

分析データを再生メタデータに関連付ける

個々の再生の分析データを収集する場合は、再生分析データと、再生中のメディアに関するメタデータを関連付けることができます。

メディア固有のメタデータは MediaItem.Builder.setTag で設定することをおすすめします。メディアタグは、元のイベントで報告され、PlaybackStats が終了したときに報告される EventTime の一部であるため、対応するアナリティクス データを処理するときに簡単に取得できます。

KotlinJava
PlaybackStatsListener(/* keepHistory= */ false) {
  eventTime
: EventTime,
  playbackStats
: PlaybackStats ->
 
val mediaTag =
    eventTime
.timeline
     
.getWindow(eventTime.windowIndex, Timeline.Window())
     
.mediaItem
     
.localConfiguration
     
?.tag
   
// Report playbackStats with mediaTag metadata.
}
new PlaybackStatsListener(
   
/* keepHistory= */ false,
   
(eventTime, playbackStats) -> {
     
Object mediaTag =
          eventTime
.timeline.getWindow(eventTime.windowIndex, new Timeline.Window())
             
.mediaItem
             
.localConfiguration
             
.tag;
     
// Report playbackStats with mediaTag metadata.
   
});

カスタム分析イベントの報告

アナリティクス データにカスタム イベントを追加する必要がある場合は、これらのイベントを独自のデータ構造に保存し、後で報告された PlaybackStats と組み合わせる必要があります。必要に応じて、DefaultAnalyticsCollector を拡張して、カスタム イベントの EventTime インスタンスを生成し、すでに登録されているリスナーに送信できるようにします。次の例をご覧ください。

KotlinJava
private interface ExtendedListener : AnalyticsListener {
 
fun onCustomEvent(eventTime: EventTime)
}

private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) {
 
fun customEvent() {
   
val eventTime = generateCurrentPlayerMediaPeriodEventTime()
    sendEvent
(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener ->
     
if (listener is ExtendedListener) {
        listener
.onCustomEvent(eventTime)
     
}
   
}
 
}
}

// Usage - Setup and listener registration.
val player = ExoPlayer.Builder(context).setAnalyticsCollector(ExtendedCollector()).build()
player
.addAnalyticsListener(
 
object : ExtendedListener {
   
override fun onCustomEvent(eventTime: EventTime?) {
     
// Save custom event for analytics data.
   
}
 
}
)
// Usage - Triggering the custom event.
(player.analyticsCollector as ExtendedCollector).customEvent()
private interface ExtendedListener extends AnalyticsListener {
 
void onCustomEvent(EventTime eventTime);
}

private static class ExtendedCollector extends DefaultAnalyticsCollector {
 
public ExtendedCollector() {
   
super(Clock.DEFAULT);
 
}

 
public void customEvent() {
   
AnalyticsListener.EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
    sendEvent
(
        eventTime
,
        CUSTOM_EVENT_ID
,
        listener
-> {
         
if (listener instanceof ExtendedListener) {
           
((ExtendedListener) listener).onCustomEvent(eventTime);
         
}
       
});
 
}
}

// Usage - Setup and listener registration.
ExoPlayer player =
   
new ExoPlayer.Builder(context).setAnalyticsCollector(new ExtendedCollector()).build();
player
.addAnalyticsListener(
   
(ExtendedListener) eventTime -> {
     
// Save custom event for analytics data.
   
});
// Usage - Triggering the custom event.
((ExtendedCollector) player.getAnalyticsCollector()).customEvent();