אירועי שחקן

האזנה לאירועי הפעלה

אירועים כמו שינויים במצב ושגיאות בהפעלה מדווחים למופעים רשומים של Player.Listener. כדי לרשום listener לקבלת אירועים כאלה:

Kotlin

// Add a listener to receive events from the player.
player.addListener(listener)

Java

// Add a listener to receive events from the player.
player.addListener(listener);

ב-Player.Listener יש שיטות ריקות כברירת מחדל, כך שצריך להטמיע רק את השיטות שמעניינות אתכם. ב-Javadoc מופיע תיאור מלא של השיטות ומתי הן נקראות. בהמשך מפורטות כמה מהשיטות החשובות ביותר.

למאזינים יש אפשרות להטמיע קריאות חוזרות (callback) לאירועים ספציפיים או קריאה חוזרת (callback) כללית של onEvents שמופעלת אחרי שמתרחשים אירוע אחד או יותר ביחד. במאמר Individual callbacks vs onEvents מוסבר איזה סוג של תוסף עדיף לתרחישי שימוש שונים.

שינויים במצב ההפעלה

כדי לקבל שינויים במצב הנגן, צריך להטמיע את onPlaybackStateChanged(@State int state) ב-Player.Listener רשום. הנגן יכול להיות באחד מארבעה מצבי הפעלה:

  • Player.STATE_IDLE: זהו המצב ההתחלתי, המצב שבו הנגן מושבת והמצב שבו ההפעלה נכשלה. במצב הזה, הנגן יחזיק רק משאבים מוגבלים.
  • Player.STATE_BUFFERING: אי אפשר להפעיל את הנגן באופן מיידי מהמיקום הנוכחי. בדרך כלל זה קורה כי צריך לטעון עוד נתונים.
  • Player.STATE_READY: המשתמש יכול להפעיל את הסרטון באופן מיידי מהמיקום הנוכחי.
  • Player.STATE_ENDED: נגמר הניגון של כל המדיה בנגן.

בנוסף למצבים האלה, לנגן יש דגל playWhenReady שמציין את כוונת המשתמש להפעיל את הסרטון. כדי לקבל שינויים בדגל הזה, צריך להטמיע את onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).

המשחק פועל (כלומר, המיקום מתקדם והתוכן מוצג למשתמש) אם מתקיימים שלושת התנאים הבאים:

  • השחקן במצב Player.STATE_READY
  • playWhenReady הוא true
  • ההפעלה לא מושבתת בגלל סיבה שמוחזרת על ידי Player.getPlaybackSuppressionReason

במקום לבדוק כל נכס בנפרד, אפשר להפעיל את הפונקציה Player.isPlaying. כדי לקבל שינויים במצב הזה, צריך להטמיע את onIsPlayingChanged(boolean isPlaying):

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onIsPlayingChanged(isPlaying: Boolean) {
      if (isPlaying) {
        // Active playback.
      } else {
        // Not playing because playback is paused, ended, suppressed, or the player
        // is buffering, stopped or failed. Check player.playWhenReady,
        // player.playbackState, player.playbackSuppressionReason and
        // player.playerError for details.
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onIsPlayingChanged(boolean isPlaying) {
        if (isPlaying) {
          // Active playback.
        } else {
          // Not playing because playback is paused, ended, suppressed, or the player
          // is buffering, stopped or failed. Check player.getPlayWhenReady,
          // player.getPlaybackState, player.getPlaybackSuppressionReason and
          // player.getPlaybackError for details.
        }
      }
    });

שגיאות הפעלה

אפשר לקבל שגיאות שגורמות להפעלה להיכשל על ידי הטמעה של onPlayerError(PlaybackException error) ב-Player.Listener רשום. אם מתרחשת שגיאה, ה-method הזו מופעלת מיד לפני שמצב ההפעלה משתנה ל-Player.STATE_IDLE. אם ההפעלה נכשלה או נעצרה, אפשר לנסות שוב על ידי התקשרות אל ExoPlayer.prepare.

שימו לב שבחלק מההטמעות של Player מועברים מקרים של מחלקות משנה של PlaybackException כדי לספק מידע נוסף על הכשל. לדוגמה, ExoPlayer מעביר את ExoPlaybackException, שכולל את type,‏ rendererIndex ושדות נוספים שספציפיים ל-ExoPlayer.

בדוגמה הבאה מוצג איך לזהות מקרים שבהם ההפעלה נכשלה בגלל בעיה ברשת HTTP:

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onPlayerError(error: PlaybackException) {
      val cause = error.cause
      if (cause is HttpDataSourceException) {
        // An HTTP error occurred.
        val httpError = cause
        // It's possible to find out more about the error both by casting and by querying
        // the cause.
        if (httpError is InvalidResponseCodeException) {
          // Cast to InvalidResponseCodeException and retrieve the response code, message
          // and headers.
        } else {
          // Try calling httpError.getCause() to retrieve the underlying cause, although
          // note that it may be null.
        }
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onPlayerError(PlaybackException error) {
        @Nullable Throwable cause = error.getCause();
        if (cause instanceof HttpDataSourceException) {
          // An HTTP error occurred.
          HttpDataSourceException httpError = (HttpDataSourceException) cause;
          // It's possible to find out more about the error both by casting and by querying
          // the cause.
          if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
            // Cast to InvalidResponseCodeException and retrieve the response code, message
            // and headers.
          } else {
            // Try calling httpError.getCause() to retrieve the underlying cause, although
            // note that it may be null.
          }
        }
      }
    });

מעברים בין פלייליסטים

בכל פעם שהנגן עובר לפריט מדיה חדש בפלייליסט, מתבצעת קריאה לאובייקטים הרשומים Player.Listener onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason). הסיבה מציינת אם זה היה מעבר אוטומטי, מעבר למיקום מסוים (לדוגמה, אחרי קריאה ל-player.next()), חזרה על אותו פריט או שינוי בפלייליסט (לדוגמה, אם הפריט שמופעל כרגע הוסר).

Metadata

יכול להיות שהמטא-נתונים שמוחזרים מ-player.getCurrentMediaMetadata() ישתנו בגלל הרבה סיבות: מעברים בין פלייליסטים, עדכונים של מטא-נתונים בתוך הסטרימינג או עדכון של MediaItem הנוכחי באמצע ההפעלה.

אם אתם רוצים לעקוב אחרי שינויים במטא-נתונים, למשל כדי לעדכן ממשק משתמש שמציג את השם הנוכחי, אתם יכולים להאזין ל-onMediaMetadataChanged.

מחפש

הפעלת שיטות Player.seekTo גורמת לסדרה של קריאות חוזרות (callbacks) למופעים רשומים של Player.Listener:

  1. onPositionDiscontinuity עם reason=DISCONTINUITY_REASON_SEEK. זוהי תוצאה ישירה של קריאה ל-Player.seekTo. פונקציית הקריאה החוזרת כוללת PositionInfo שדות למיקום לפני ואחרי החיפוש.
  2. onPlaybackStateChanged עם כל שינוי מיידי במצב שקשור לחיפוש. שימו לב: יכול להיות שלא יבוצע שינוי כזה.

הבדלים בין שיחות חוזרות אישיות לבין onEvents

המאזינים יכולים לבחור בין הטמעה של קריאות חוזרות נפרדות כמו onIsPlayingChanged(boolean isPlaying), לבין הקריאה החוזרת הכללית onEvents(Player player, Events events). הקריאה החוזרת הגנרית מספקת גישה לאובייקט Player ומציינת את קבוצת events שהתרחשו יחד. הקריאה החוזרת הזו מתבצעת תמיד אחרי הקריאות החוזרות שמתאימות לאירועים הספציפיים.

Kotlin

override fun onEvents(player: Player, events: Player.Events) {
  if (
    events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) ||
      events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
  ) {
    uiModule.updateUi(player)
  }
}

Java

@Override
public void onEvents(Player player, Events events) {
  if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
      || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
    uiModule.updateUi(player);
  }
}

מומלץ להשתמש באירועים בודדים במקרים הבאים:

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

עדיף להשתמש ב-onEvents(Player player, Events events) הגנרי במקרים הבאים:

  • המאזין רוצה להפעיל את אותה לוגיקה עבור כמה אירועים. לדוגמה, עדכון ממשק משתמש גם ל-onPlaybackStateChanged וגם ל-onPlayWhenReadyChanged.
  • ל-listener צריכה להיות גישה לאובייקט Player כדי להפעיל אירועים נוספים, למשל מעבר למיקום מסוים אחרי מעבר בין פריטי מדיה.
  • המאזין מתכוון להשתמש בכמה ערכי מצב שמדווחים באמצעות קריאות חוזרות נפרדות, או בשילוב עם Player שיטות getter. לדוגמה, השימוש ב-Player.getCurrentWindowIndex() עם Timeline שסופק ב-onTimelineChanged בטוח רק מתוך הקריאה החוזרת (callback) של onEvents.
  • הפונקציה event listener מתעניינת בשאלה אם האירועים התרחשו יחד באופן לוגי. לדוגמה, onPlaybackStateChanged ל-STATE_BUFFERING בגלל מעבר בין פריטי מדיה.

במקרים מסוימים, יכול להיות שהמאזינים יצטרכו לשלב את הקריאות החוזרות הנפרדות עם הקריאה החוזרת onEvents הגנרית, למשל כדי לתעד את הסיבות לשינוי פריט המדיה באמצעות onMediaItemTransition, אבל רק אחרי שכל השינויים במצב יהיו זמינים לשימוש ביחד ב-onEvents.

משתמש ב-AnalyticsListener

כשמשתמשים ב-ExoPlayer, אפשר לרשום AnalyticsListener אצל השחקן על ידי קריאה ל-addAnalyticsListener. הטמעות של AnalyticsListener יכולות להאזין לאירועים מפורטים שעשויים להיות שימושיים למטרות ניתוח ורישום ביומן. פרטים נוספים זמינים בדף Analytics.

משתמש ב-EventLogger

EventLogger הוא AnalyticsListener שמסופק ישירות על ידי הספרייה למטרות רישום ביומן. מוסיפים EventLogger ל-ExoPlayer כדי להפעיל רישום נוסף שימושי ביומן באמצעות שורה אחת:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

פרטים נוספים זמינים בדף בנושא רישום באגים.

הפעלת אירועים במיקומי הפעלה ספציפיים

בתרחישי שימוש מסוימים נדרשת הפעלת אירועים במיקומי הפעלה ספציפיים. התכונה הזו נתמכת באמצעות PlayerMessage. אפשר ליצור PlayerMessage באמצעות ExoPlayer.createMessage. אפשר להגדיר את מיקום ההפעלה שבו הפעולה צריכה להתבצע באמצעות PlayerMessage.setPosition. ההודעות מופעלות בשרשור ההפעלה כברירת מחדל, אבל אפשר להתאים את זה אישית באמצעות PlayerMessage.setLooper. אפשר להשתמש ב-PlayerMessage.setDeleteAfterDelivery כדי לקבוע אם ההודעה תופעל בכל פעם שנתקלים במיקום ההפעלה שצוין (זה יכול לקרות כמה פעמים בגלל מצבי חיפוש והפעלה חוזרת), או רק בפעם הראשונה. אחרי שמגדירים את PlayerMessage, אפשר לתזמן אותו באמצעות PlayerMessage.send.

Kotlin

player
  .createMessage { messageType: Int, payload: Any? -> }
  .setLooper(Looper.getMainLooper())
  .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120000)
  .setPayload(customPayloadData)
  .setDeleteAfterDelivery(false)
  .send()

Java

player
    .createMessage(
        (messageType, payload) -> {
          // Do something at the specified playback position.
        })
    .setLooper(Looper.getMainLooper())
    .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120_000)
    .setPayload(customPayloadData)
    .setDeleteAfterDelivery(false)
    .send();