تغییرات در وضعیت پخشکننده (مانند شروع پخش، بافر شدن یا خطاها) رویدادهایی را ایجاد میکنند که به نمونههای ثبتشدهی Player.Listener ارسال میشوند. این رویدادها توسط ثابتهای عدد صحیح نمایش داده میشوند و توسط Player.Event و Player.Events تعریف میشوند.
یک Player.Listener ثبت کنید
رویدادهای بازیکن به نمونههای ثبتشدهی Player.Listener گزارش میشوند. برای ثبت یک شنونده (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);
اگر از کاتلین استفاده میکنید، میتوانید از توابع افزونهی تعلیق ارائه شده توسط ماژول 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قرار دارد. -
playWhenReadytrueاست - پخش به دلیلی که توسط
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 میشود:
-
onPositionDiscontinuityباreason=DISCONTINUITY_REASON_SEEK. این نتیجه مستقیم فراخوانیPlayer.seekToاست. این فراخوانی دارای فیلدهایPositionInfoبرای موقعیت قبل و بعد از جستجو است. -
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تغییر یافته است.
در برخی موارد، ممکن است شنوندهها نیاز داشته باشند که فراخوانیهای منفرد را با فراخوانی عمومی onEvents ترکیب کنند، برای مثال برای ثبت دلایل تغییر آیتم رسانه با onMediaItemTransition ، اما فقط زمانی عمل میکنند که همه تغییرات حالت بتوانند با هم در onEvents استفاده شوند.
گوش دادن به رویدادهای پخش با استفاده از کوروتینها
از طرف دیگر، میتوانید یک کوروتین کاتلین را با استفاده از Player.listenTo راهاندازی کنید و Player.Event مربوطه را مشخص کنید:
توجه داشته باشید که Player.listen و Player.listenTo میتوانند از هر نخی فراخوانی شوند، در حالی که تابع لامبدا فراخوانی همیشه در نخ مرتبط با Player.getApplicationLooper فراخوانی میشود. بنابراین، حتی اگر کوروتین در نخ دیگری اجرا شده باشد، دسترسی به متدهای Player و ویژگیهای حالت در داخل تابع لامبدا فراخوانی ایمن است.
تغییرات وضعیت پخش
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
هنگام گوش دادن به رویدادهای بازیکن درون یک کوروتین، شما همیشه پیادهسازی مربوط به فراخوانی onEvents را ارائه میدهید، نه یک فراخوانی تکی . بسته به اینکه کدام رویدادها باید فراخوانی لامبدا شما را آغاز کنند، میتوانید بین 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) } } }
گوش کن
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 عمل میکنند، به آرگومانهای گذرای ارسال شده به callbackهای منفرد، مانند reason در onMediaItemTransition(..., int reason) یا oldPosition در onPositionDiscontinuity(...) دسترسی ندارند. اگر منطق شما به این آرگومانهای خاص متکی است (و آنها به عنوان ویژگیهای حالت در Player در دسترس نیستند)، باید به جای آن از رابط استاندارد Player.Listener استفاده کنید.
استفاده از AnalyticsListener
هنگام استفاده از ExoPlayer ، میتوان با فراخوانی addAnalyticsListener ، یک AnalyticsListener را در پخشکننده ثبت کرد. پیادهسازیهای AnalyticsListener قادر به گوش دادن به رویدادهای دقیقی هستند که ممکن است برای اهداف تحلیلی و ثبت وقایع مفید باشند. لطفاً برای جزئیات بیشتر به صفحه تحلیلی مراجعه کنید.
استفاده از EventLogger
EventLogger یک AnalyticsListener است که مستقیماً توسط کتابخانه برای اهداف ثبت وقایع ارائه میشود. EventLogger به ExoPlayer اضافه کنید تا ثبت وقایع اضافی مفید را با یک خط کد فعال کنید:
کاتلین
player.addAnalyticsListener(EventLogger())
جاوا
player.addAnalyticsListener(new EventLogger());
برای جزئیات بیشتر به صفحه ثبت اشکالزدایی مراجعه کنید.
در موقعیتهای پخش مشخصشده، رویدادها را فعال کنید
برخی از موارد استفاده نیاز به اجرای رویدادها در موقعیتهای پخش مشخص دارند. این امر با استفاده از 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();