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: イベントのウォール クロック時間。
  • timeline、windowIndex、mediaPeriodId: イベントが属する再生リストと再生リスト内のアイテムを定義します。mediaPeriodId には、イベントがアイテム内の広告に属しているかどうかを示すなど、オプションの追加情報が含まれます。
  • eventPlaybackPositionMs: イベントが発生したときのアイテムの再生位置。
  • currentTimeline、currentWindowIndex、currentMediaPeriodId、currentPlaybackPositionMs: 前述のとおりですが、現在再生中のアイテムを対象とします。現在再生中のアイテムは、イベントが属するアイテムとは異なる場合があります。たとえば、イベントが次に再生されるアイテムの事前バッファリングに対応している場合などです。

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_STARTED、JOINING_BACKGROUND
アクティブな再生 PLAYING
再生が中断された BUFFERING、SEEKING PAUSED、PAUSED_BUFFERING、SUPPRESSED、SUPPRESSED_BUFFERING、INTERRUPTED_BY_AD
終了状態 ENDED、STOPPED、FAILED、ABANDONED

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

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

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

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

1 回の再生の分析データ

このデータは、keepHistory=false を使用している場合でも、PlaybackStatsListener を使用すると自動的に収集されます。最終的な値は、PlaybackStats Javadoc で確認できるパブリック フィールドと、getPlaybackStateDurationMs によって返される再生状態の期間です。また、特定の再生状態の組み合わせの期間を返す getTotalPlayTimeMs や getTotalWaitTimeMs などのメソッドも用意されています。

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.merge を呼び出すことで、複数の PlaybackStats を組み合わせることができます。結果の 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

@OptIn(UnstableApi::class)
private interface ExtendedListener : AnalyticsListener {
  fun onCustomEvent(eventTime: EventTime)
}

@OptIn(UnstableApi::class)
private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) {

  fun customEvent() {
    val eventTime = super.generateCurrentPlayerMediaPeriodEventTime()
    super.sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener ->
      if (listener is ExtendedListener) {
        listener.onCustomEvent(eventTime)
      }
    }
  }
}

@OptIn(UnstableApi::class)
fun useExtendedAnalyticsCollector(context: Context) {
  // 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

@OptIn(markerClass = UnstableApi.class)
private interface ExtendedListener extends AnalyticsListener {
  void onCustomEvent(EventTime eventTime);
}

@OptIn(markerClass = UnstableApi.class)
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);
          }
        });
  }
}

@OptIn(markerClass = UnstableApi.class)
public static void useExtendedAnalyticsCollector(Context context) {
  // 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();
}

フィーチャー トグルとカナリアモードのトラッキング

A/B テストを実施する場合や、新しい Media3 の最適化の影響を評価する場合は、再生分析データをアクティブなフィーチャー トグルやカナリアモードに関連付けることができます。

Flags.getLogString をクエリするか、Flags.getEnabledOverrides と Flags.getDisabledOverrides を使用して特定のフラグのオーバーライドを検査し、カスタム分析ペイロードまたはセッション メタデータにフラグの状態を含めることができます。