Analytics

ExoPlayer は、再生分析に関する幅広いニーズをサポートしています。最終的に分析とは、再生からデータを収集、解釈、集計、要約することです。このデータは、デバイス上で使用して(ロギング、デバッグ、または今後の再生判断の通知など)、またはサーバーに報告して、すべてのデバイスの再生をモニタリングすることができます。

分析システムは通常、最初にイベントを収集し、それらを有意義なものにするためにさらに処理する必要があります。

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

AnalyticsListener によるイベントの収集

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

Kotlin

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

    override fun onDroppedVideoFrames(
      eventTime: EventTime,
      droppedFrames: Int,
      elapsedMs: Long,
    ) {}
  }
)

Java

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 をクエリすることもできます。

Kotlin

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

Java

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.getTotalWaitTimeMs は、JOINING_FOREGROUNDBUFFERINGSEEKING 状態で費やされた合計時間は返しますが、再生が一時停止された時間は返しません。同様に、PlaybackStats.getTotalPlayAndWaitTimeMs は、ユーザーがプレイを意図していた合計時間、つまりアクティブな待機時間の合計と PLAYING 状態で費やされた合計時間を返します。

処理および解釈されたイベント

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

  • playbackStateHistory: 適用を開始した EventTime を含む拡張再生状態の順序付きリスト。また、PlaybackStats.getPlaybackStateAtTime を使用して、実時間での状態をルックアップすることもできます。
  • mediaTimeHistory: 実経過時間とメディア時間のペアの履歴。メディアのどの部分がいつ再生されたかを再構築できます。また、PlaybackStats.getMediaTimeMsAtRealtimeMs を使用して、指定した実時間の再生位置を検索することもできます。
  • videoFormatHistoryaudioFormatHistory: 再生中に使用される動画形式と音声形式の順序付きリスト。使用を開始した EventTime とともに使用します。
  • fatalErrorHistorynonFatalErrorHistory: 致命的なエラーと非致命的なエラーの順序付きリスト。発生した EventTime を含みます。致命的なエラーとは、再生を終了したエラーであり、致命的でないエラーは回復可能である可能性があります。

シングル再生の分析データ

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

Kotlin

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

Java

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

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

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

PlaybackStatsListener.getCombinedPlaybackStats を使用すると、PlaybackStatsListener の有効期間中に収集されたすべてのアナリティクス データの集計ビューを取得できます。

計算サマリーの指標

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

Kotlin

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

Java

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

高度なトピック

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

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

メディア固有のメタデータは、MediaItem.Builder.setTag で設定することをおすすめします。 メディアタグは、未加工のイベントと PlaybackStats の完了時にレポートされる EventTime の一部であるため、対応する分析データを処理する際に簡単に取得できます。

Kotlin

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

Java

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 インスタンスを生成して、すでに登録されているリスナーに送信するようにできます。

Kotlin

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()

Java

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();