Analytics

ExoPlayer תומך במגוון רחב של צורכי ניתוח נתוני הפעלה. בסופו של דבר, ניתוח נתונים הוא איסוף, פירוש, צבירה וסיכום של נתונים מהפעלות. אפשר להשתמש בנתונים האלה במכשיר — לדוגמה, לרישום ביומן, ניפוי באגים או כדי לקבל החלטות מושכלות לגבי הפעלה בעתיד — או לדווח לשרת כדי לעקוב אחרי ההפעלות בכל המכשירים.

בדרך כלל, מערכת ניתוח נתונים צריכה קודם לאסוף אירועים, ואז לעבד אותם כדי שיהיה להם משמעות:

  • איסוף אירועים: אפשר לעשות זאת על ידי רישום AnalyticsListener במכונה של ExoPlayer. מאזינים רשומים של Analytics מקבלים אירועים בזמן שהם מתרחשים במהלך השימוש בנגן. כל אירוע משויך לפריט המדיה התואם בפלייליסט, וגם למיקום ההפעלה ולמטא-נתונים של חותמת הזמן.
  • עיבוד אירועים: יש מערכות ניתוח נתונים שמעלות אירועים גולמיים לשרת, וכל עיבוד האירועים מתבצע בצד השרת. אפשר גם לעבד אירועים במכשיר, וזה יכול להיות פשוט יותר או לצמצם את כמות המידע שצריך להעלות. ExoPlayer מספק את PlaybackStatsListener, שמאפשר לבצע את שלבי העיבוד הבאים:
    1. פרשנות של אירועים: כדי שאירועים יהיו שימושיים למטרות ניתוח נתונים, צריך לפרש אותם בהקשר של הפעלה אחת. לדוגמה, האירוע הגולמי של שינוי מצב הנגן ל-STATE_BUFFERING עשוי להתאים לאגירת נתונים ראשונית, לאגירת נתונים מחדש או לאגירת נתונים שמתרחשת אחרי חיפוש.
    2. מעקב אחר מצב: בשלב הזה האירועים הופכים למספרים. לדוגמה, אפשר להמיר אירועי שינוי מצב למספרים שמתעדים את משך הזמן שבו משתמשים נמצאים בכל מצב הפעלה. התוצאה היא קבוצה בסיסית של ערכים של נתוני ניתוח להפעלה אחת.
    3. צבירה: בשלב הזה משלבים את נתוני הניתוח של כמה הפעלות, בדרך כלל על ידי סיכום של ספירה לאחור.
    4. חישוב מדדי הסיכום: רבים מהמדדים השימושיים ביותר הם אלה שמחושבים ממוצעים או שמשלבים את הערכים הבסיסיים של הנתונים מ-Analytics בדרכים אחרות. אפשר לחשב מדדי סיכום להפעלה אחת או למספר הפעלות.

איסוף אירועים באמצעות 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, עם מונים ומדדים נגזרים, כולל:

  • מדדי סיכום, למשל משך ההפעלה הכולל.
  • מדדים מותאמים של איכות ההפעלה, למשל הרזולוציה הממוצעת של הסרטון.
  • מדדי איכות הרינדור, למשל שיעור הפריימים שהוחמצו.
  • מדדי שימוש במשאבים, למשל מספר הבייטים שנקראים ברשת.

רשימה מלאה של הספירות והמדדים הנגזרים הזמינים מופיעה ב-Javadoc של PlaybackStats.

PlaybackStatsListener מחשב PlaybackStats נפרד לכל פריט מדיה בפלייליסט, וגם לכל מודעה בצד הלקוח שמוכנסת לפריטים האלה. אפשר לספק פונקציית קריאה חוזרת ל-PlaybackStatsListener כדי לקבל עדכונים על הפעלות שהסתיימו, ולהשתמש ב-EventTime שמוענק לפונקציית הקריאה החוזרת כדי לזהות את ההפעלה שהסתיימה. אפשר לבצע צבירת נתונים של ניתוח נתונים לכמה הפעלות. אפשר גם לשלוח שאילתה ל-PlaybackStats על סשן ההפעלה הנוכחי בכל שלב באמצעות PlaybackStatsListener.getPlaybackStats().

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.
        }));

ב-constructor של PlaybackStatsListener יש אפשרות לשמור את ההיסטוריה המלאה של האירועים שעברו עיבוד. לתשומת ליבכם: הפעולה הזו עשויה לגרום לעלויות זיכרון לא ידועות, בהתאם למשך ההפעלה ומספר האירועים. לכן, מומלץ להפעיל את האפשרות הזו רק אם אתם צריכים גישה להיסטוריה המלאה של האירועים שעברו עיבוד, ולא רק לנתוני הניתוח הסופיים.

שימו לב שב-PlaybackStats נעשה שימוש בקבוצה מורחבת של מצבים כדי לציין לא רק את המצב של המדיה, אלא גם את כוונתו של המשתמש להפעיל אותה ומידע מפורט יותר, כמו הסיבה להפסקה או לסיום ההפעלה:

מצב ההפעלה כוונת המשתמש לשחק אין לי כוונה לשחק
לפני ההפעלה JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
הפעלה פעילה PLAYING
הפעלה מופסקת BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
מדינות (States) סיום 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 שבו הן התרחשו. שגיאות קטלניות הן שגיאות שגרמו לסיום ההפעלה, ואילו שגיאות לא קטלניות יכול להיות שאפשר היה לשחזר.

נתוני ניתוח של הפעלה יחידה

הנתונים האלה נאספים באופן אוטומטי אם משתמשים ב-PlaybackStatsListener, גם עם keepHistory=false. הערכים הסופיים הם השדות הציבוריים שניתן למצוא ב-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 יחד באמצעות קריאה ל-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. תג המדיה הוא חלק מהאירועים הגולמיים שמדווחים ב-EventTime, וגם כש-PlaybackStats מסתיימים, כך שאפשר לאחזר אותו בקלות כשמטפלים בנתוני ניתוח הנתונים התואמים:

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