אירועי שחקן

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

אירועים, כמו שינויים במצב ובשגיאות הפעלה, ידווחו כאירועים רשומים Player.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 מופיע ב-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. במקרה של כשל, השיטה הזו תיקרא ממש לפני שמצב ההפעלה יעבור ל-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.
          }
        }
      }
    });

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

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

Metadata

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

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

מחפש

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

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

קריאות חוזרות (callback) בודדות לעומת onEvents

המאזינים יכולים לבחור אם להטמיע קריאות חוזרות (callback) ספציפיות, כמו 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 פועל רק על הערכים החדשים שסופקו באמצעות פרמטרים של קריאה חוזרת או מפעיל משהו אחר שלא תלוי בפרמטרים של הקריאה החוזרת.
  • בהטמעת ה-listener יש עדיפות ליצירת אינדיקציה ברורה וקריאה לגבי מה שמתרחש הפעיל את האירוע בשם ה-method.
  • ה-listener מדווח למערכת ניתוח נתונים שאמורה לדעת את כל הפרטים אירועים נפרדים ושינויי מצב.

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

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

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

משתמש ב-AnalyticsListener

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

משתמש ב-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();