رویدادهای بازیکن

تغییرات در وضعیت «پخش‌کننده» (مانند شروع بازپخش، میان‌گیری، یا خطاها) رویدادهایی را راه‌اندازی می‌کند که به نمونه‌های ثبت‌شده Player.Listener ارسال می‌شوند. این رویدادها با ثابت‌های عدد صحیح نشان داده می‌شوند و با Player.Event و Player.Events تعریف می‌شوند.

ثبت Player.Listener

رویدادهای بازیکن به نمونه‌های ثبت‌شده Player.Listener گزارش می‌شوند. برای ثبت شنونده برای دریافت چنین رویدادهایی:

کاتلین

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

جاوا

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

اگر از Kotlin استفاده می‌کنید، می‌توانید از توابع افزونه تعلیق ارائه‌شده توسط ماژول media3-common-ktx نیز برای گوش دادن به رویدادها بااستفاده از کوروتین‌ها استفاده کنید. در این صورت، نیازی به ثبت یا لغو ثبت صریح Player.Listener نخواهید داشت.

گوش دادن به رویدادهای بازپخش بااستفاده از Player.Listener

Player.Listener روش‌های پیش‌فرض خالی دارد، بنابراین فقط باید روش‌هایی را که به آن‌ها علاقه دارید پیاده‌سازی کنید. برای شرح کامل روش‌ها و زمان فراخوانی آن‌ها، به Javadoc مراجعه کنید. برخی‌از مهم‌ترین روش‌ها در ادامه با جزئیات بیشتر شرح داده شده است.

شنوندگان می‌توانند بین پیاده‌سازی فراخوانی‌های برگشتی رویدادهای فردی یا یک فراخوانی برگشتی عمومی 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) دریافت کرد:

کاتلین

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

جاوا

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 ناموفق بوده است:

کاتلین

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

جاوا

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())، تکرار همان مورد، یا ناشی از تغییر فهرست پخش (مثلاً اگر موردی که درحال پخش است برداشته شود).

فراداده

فراداده برگشتی از player.getCurrentMediaMetadata() می‌تواند به دلایل زیادی تغییر کند: انتقال فهرست پخش، به‌روزرسانی فراداده جاری، یا به‌روزرسانی MediaItem فعلی در حین بازپخش.

اگر به تغییرات فراداده علاقه دارید، برای مثال برای به‌روزرسانی واسط کاربری‌ای که عنوان فعلی را نشان می‌دهد، می‌توانید به onMediaMetadataChanged گوش دهید.

درحال جستجو

فراخوانی روش‌های Player.seekTo منجر به مجموعه‌ای از فراخوانی‌های برگشتی به نمونه‌های Player.Listener ثبت‌شده می‌شود:

  1. ‫onPositionDiscontinuity با reason=DISCONTINUITY_REASON_SEEK. این نتیجه مستقیم تماس با Player.seekTo است. فراخوانی دارای PositionInfo فیلد برای موقعیت قبل و بعداز جستجو است.
  2. onPlaybackStateChanged با هرگونه تغییر وضعیت فوری مربوط به جستجو. توجه داشته باشید که ممکن است چنین تغییری وجود نداشته باشد.

تماس‌های برگشتی فردی درمقابل onEvents

شنوندگان می‌توانند بین پیاده‌سازی تماس‌های برگشتی فردی مانند onIsPlayingChanged(boolean isPlaying) و تماس برگشتی عمومی onEvents(Player player, Events events) انتخاب کنند. فراخوانی برگشتی عمومی دسترسی به شیء Player را فراهم می‌کند و مجموعه events را که باهم رخ داده‌اند مشخص می‌کند. این فراخوانی برگشتی همیشه بعداز فراخوانی‌های برگشتی مربوط به رویدادهای جداگانه فراخوانی می‌شود.

کاتلین

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

جاوا

@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.
  • شنونده فقط روی مقادیر جدید ارائه‌شده ازطریق پارامترهای callback عمل می‌کند یا چیز دیگری را راه‌اندازی می‌کند که به پارامترهای callback وابسته نیست.
  • پیاده‌سازی شنونده ترجیح می‌دهد که نام روش به‌طور واضح و خوانا نشان دهد چه چیزی رویداد را راه‌اندازی کرده است.
  • شنونده به سیستم تجزیه‌وتحلیلی گزارش می‌دهد که باید از همه رویدادهای تکی و تغییرات وضعیت مطلع باشد.

در موارد زیر، onEvents(Player player, Events events) عمومی باید ترجیح داده شود:

  • شنونده می‌خواهد منطق یکسانی را برای چند رویداد راه‌اندازی کند. برای مثال، به‌روزرسانی واسط کاربر برای هر دو onPlaybackStateChanged و onPlayWhenReadyChanged.
  • شنونده برای راه‌اندازی رویدادهای بیشتر به دسترسی به شیء Player نیاز دارد، برای مثال، جستجو پس‌از انتقال فایل رسانه‌ای.
  • شنونده قصد دارد از چندین مقدار وضعیت که ازطریق فراخوان‌های برگشتی جداگانه گزارش می‌شوند، باهم یا در ترکیب با روش‌های دریافت‌کننده Player استفاده کند. برای مثال، استفاده از Player.getCurrentWindowIndex() با Timeline ارائه‌شده در onTimelineChanged فقط از داخل فراخوانی برگشتی onEvents ایمن است.
  • شنونده به این موضوع علاقه دارد که آیا رویدادها به‌طور منطقی باهم رخ داده‌اند یا نه. برای مثال، onPlaybackStateChanged به STATE_BUFFERING به‌دلیل انتقال فایل رسانه‌ای.

In some cases, listeners may need to combine the individual callbacks with the generic onEvents callback, for example to record media item change reasons with onMediaItemTransition, but only act once all state changes can be used together in onEvents.

گوش دادن به رویدادهای بازپخش بااستفاده از روال‌های همگام

یا می‌توانید روال مشترک Kotlin را بااستفاده از Player.listenTo راه‌اندازی کنید و Player.Event مربوطه را مشخص کنید:

توجه داشته باشید که Player.listen و Player.listenTo را می‌توان از هر رشته‌ای فراخوانی کرد، درحالی‌که لامبدا فراخوان برگشتی همیشه در رشته مرتبط با Player.getApplicationLooper فراخوانی می‌شود. Therefore, it is safe to access Player methods and state properties inside the callback lambda even if the coroutine was launched on a different thread.

تغییرات وضعیت بازپخش

coroutineScope.launch {
  player.listenTo(Player.EVENT_IS_PLAYING_CHANGED) {
    // `Player` is a receiver scope for this trailing lambda
    if (isPlaying) {
      // Active playback.
    } else {
      // Not playing.
    }
  }
}

خطاهای بازپخش

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYER_ERROR) {
    val error = playerError ?: return@listenTo
    val cause = error.cause
    if (cause is HttpDataSourceException) {
      // An HTTP error occurred.
      if (cause is InvalidResponseCodeException) {
        // Retrieve the response code, message and headers
      } else {
        // Try calling cause.cause to retrieve the underlying cause
      }
    }
  }
}

تماس‌های برگشتی فردی درمقابل onEvents

هنگام گوش دادن به رویدادهای بازیکن در داخل یک کوروتین، همیشه پیاده‌سازی را برای callback onEvents ارائه خواهید داد، برخلاف یک callback جداگانه. می‌توانید بین Player.listen و Player.listenTo انتخاب کنید، بسته به اینکه کدام رویدادها باید فراخوانی لامبدا را راه‌اندازی کنند. اما توابع در موارد دیگر معادل هستند:

گوش دادن

coroutineScope.launch {
  player.listen { events ->
    // `Player` is a receiver scope for this trailing lambda
    if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
      // Access the player state directly from the receiver
      updateUi(playbackState)
    }

    if (events.contains(Player.EVENT_PLAYER_ERROR)) {
      // Access the error directly from the player
      handleError(playerError)
    }
  }
}

listenTo

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events ->
    // `Player` is a receiver scope for this trailing lambda
    if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
      // Access the player state directly from the receiver
      updateUi(playbackState)
    }

    if (events.contains(Player.EVENT_PLAYER_ERROR)) {
      // Access the error directly from the player
      handleError(playerError)
    }
  }
}

اگر به چند نوع رویداد علاقه‌مندید، می‌توانید فهرستی از رویدادها را به Player.listenTo ارسال کنید. هرزمان که هریک از آن رویدادها رخ دهد، لامبدا شما فراخوانی می‌شود و می‌توانید پارامتر Events را بررسی کنید تا ببینید کدام رویدادها واقعاً فعال شده‌اند:

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events ->
    // Unclear which event got triggered without querying `events` parameter
    // The following function will fire whenever either one is caught
    updateUiAndHandleError(playbackState, playerError)
  }
}

چون این توابع روی onEvents عمل می‌کنند، دسترسی به آرگومان‌های گذرا را که به بازخوان‌های فردی ارسال می‌شوند فراهم نمی‌کنند، مانند دلیل در onMediaItemTransition(..., int reason) یا oldPosition در onPositionDiscontinuity(...). اگر منطق شما به این آرگومان‌های خاص متکی است (و این آرگومان‌ها به‌عنوان ویژگی‌های وضعیت در Player دردسترس نیستند)، باید به‌جای آن از میانای استاندارد Player.Listener استفاده کنید.

استفاده از AnalyticsListener

هنگام استفاده از ExoPlayer، می‌توان با فراخوانی addAnalyticsListener، یک AnalyticsListener را در پخش‌کننده ثبت کرد. پیاده‌سازی‌های AnalyticsListener می‌توانند رویدادهای دقیق را که ممکن است برای اهداف تجزیه‌وتحلیل و ثبت مفید باشند بشنوند. برای جزئیات بیشتر، به صفحه Analytics مراجعه کنید.

استفاده از EventLogger

‫EventLogger یک AnalyticsListener است که مستقیماً توسط کتابخانه برای اهداف ثبت گزارش ارائه شده است. EventLogger را به ExoPlayer اضافه کنید تا ثبت گزارش مفید اضافی با یک خط فعال شود:

کاتلین

player.addAnalyticsListener(EventLogger())

جاوا

player.addAnalyticsListener(new EventLogger());

برای جزئیات بیشتر، صفحه ثبت گزارش اشکال‌زدایی را ببینید.

رویدادهای Fire در موقعیت‌های بازپخش مشخص‌شده

برخی‌از موارد استفاده نیاز به راه‌اندازی رویدادها در موقعیت‌های بازپخش مشخص‌شده دارند. این مورد بااستفاده از PlayerMessage پشتیبانی می‌شود. PlayerMessage را می‌توان بااستفاده از ExoPlayer.createMessage ایجاد کرد. موقعیت بازپخش که باید در آن اجرا شود می‌تواند بااستفاده از PlayerMessage.setPosition تنظیم شود. پیام‌ها به‌طور پیش‌فرض در رشته بازپخش اجرا می‌شوند، اما این رفتار را می‌توان بااستفاده از PlayerMessage.setLooper سفارشی‌سازی کرد. از PlayerMessage.setDeleteAfterDelivery می‌توان برای کنترل اینکه آیا پیام هر بار که به موقعیت بازپخش مشخص‌شده می‌رسد اجرا شود یا نه (این اتفاق ممکن است چندین بار به‌دلیل جستجو و حالت‌های تکرار رخ دهد) یا فقط بار اول استفاده کرد. وقتی PlayerMessage پیکربندی شد، می‌توان آن را بااستفاده از PlayerMessage.send زمان‌بندی کرد.

کاتلین

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

جاوا

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