প্লেয়ারের অবস্থার পরিবর্তন (যেমন প্লেব্যাক শুরু হওয়া, বাফারিং বা ত্রুটি) ইভেন্ট ট্রিগার করে, যা নিবন্ধিত Player.Listener ইনস্ট্যান্সগুলিতে পাঠানো হয়। এই ইভেন্টগুলি পূর্ণসংখ্যা ধ্রুবক দ্বারা উপস্থাপিত হয় এবং Player.Event ও Player.Events দ্বারা সংজ্ঞায়িত করা হয়।
একজন প্লেয়ার/শ্রোতা নিবন্ধন করুন।
প্লেয়ার ইভেন্টগুলো নিবন্ধিত 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);
আপনি যদি কোটলিন ব্যবহার করেন, তাহলে কো-রুটিন ব্যবহার করে ইভেন্ট শোনার জন্য media3-common-ktx মডিউলের সাসপেন্ডিং এক্সটেনশন ফাংশনগুলোও ব্যবহার করতে পারেন। সেক্ষেত্রে, আপনাকে আলাদাভাবে Player.Listener রেজিস্টার বা আনরেজিস্টার করার প্রয়োজন হবে না।
Player.Listener ব্যবহার করে প্লেব্যাক ইভেন্টগুলো শুনুন।
Player.Listener কিছু ডিফল্ট মেথড খালি থাকে, তাই আপনাকে শুধু প্রয়োজনীয় মেথডগুলোই ইমপ্লিমেন্ট করতে হবে। মেথডগুলোর সম্পূর্ণ বিবরণ এবং কখন সেগুলো কল করা হয়, তা জানতে Javadoc দেখুন। সবচেয়ে গুরুত্বপূর্ণ কয়েকটি মেথড নিচে আরও বিস্তারিতভাবে বর্ণনা করা হলো।
লিসেনারদের কাছে দুটি বিকল্প থাকে: হয় স্বতন্ত্র ইভেন্ট কলব্যাক প্রয়োগ করা, অথবা একটি জেনেরিক ' onEvents কলব্যাক ব্যবহার করা যা এক বা একাধিক ইভেন্ট একসাথে ঘটার পর কল করা হয়। বিভিন্ন ব্যবহারের ক্ষেত্রে কোনটি বেশি পছন্দনীয়, তার ব্যাখ্যার জন্য Individual callbacks vs onEvents দেখুন।
প্লেব্যাক অবস্থার পরিবর্তন
একটি নিবন্ধিত Player.Listener এ onPlaybackStateChanged(@State int state) ইমপ্লিমেন্ট করার মাধ্যমে প্লেয়ারের অবস্থার পরিবর্তন গ্রহণ করা যায়। প্লেয়ারটি চারটি প্লেব্যাক অবস্থার যেকোনো একটিতে থাকতে পারে:
-
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. } } });
প্লেব্যাক ত্রুটি
একটি নিবন্ধিত Player.Listener এ onPlayerError(PlaybackException error) ইমপ্লিমেন্ট করার মাধ্যমে প্লেব্যাক ব্যর্থ হওয়ার মতো ত্রুটিগুলো গ্রহণ করা যায়। যখন কোনো ব্যর্থতা ঘটে, তখন প্লেব্যাক স্টেট 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. } } } });
প্লেলিস্ট পরিবর্তন
যখনই প্লেয়ার প্লেলিস্টের কোনো নতুন মিডিয়া আইটেমে পরিবর্তিত হয়, তখন নিবন্ধিত Player.Listener অবজেক্টগুলিতে onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason) কল করা হয়। কারণটি নির্দেশ করে যে এটি একটি স্বয়ংক্রিয় পরিবর্তন ছিল, নাকি একটি সিক (উদাহরণস্বরূপ player.next() কল করার পরে), একই আইটেমের পুনরাবৃত্তি, অথবা প্লেলিস্ট পরিবর্তনের কারণে ঘটেছে (উদাহরণস্বরূপ, যদি বর্তমানে প্লে হওয়া আইটেমটি সরিয়ে ফেলা হয়)।
মেটাডেটা
player.getCurrentMediaMetadata() থেকে প্রাপ্ত মেটাডেটা বিভিন্ন কারণে পরিবর্তিত হতে পারে: প্লেলিস্ট পরিবর্তন, স্ট্রিমিং চলাকালীন মেটাডেটার হালনাগাদ অথবা প্লেব্যাকের মাঝপথে বর্তমান MediaItem হালনাগাদ করা।
আপনি যদি মেটাডেটা পরিবর্তন সম্পর্কে জানতে আগ্রহী হন, যেমন বর্তমান শিরোনাম প্রদর্শনকারী কোনো UI আপডেট করতে, তাহলে আপনি onMediaMetadataChanged শুনতে পারেন।
অনুসন্ধান
Player.seekTo মেথডগুলো কল করলে নিবন্ধিত Player.Listener ইনস্ট্যান্সগুলোতে ধারাবাহিক কলব্যাক পাঠানো হয়:
-
onPositionDiscontinuityকলটিরreason=DISCONTINUITY_REASON_SEEK। এটিPlayer.seekToকল করার সরাসরি ফলাফল। কলব্যাকটিতে seek-এর আগের ও পরের অবস্থানের জন্যPositionInfoফিল্ড রয়েছে। -
onPlaybackStateChangedseek-এর সাথে সম্পর্কিত যেকোনো তাৎক্ষণিক স্টেট পরিবর্তন ঘটে। উল্লেখ্য যে, এমন কোনো পরিবর্তন নাও ঘটতে পারে।
স্বতন্ত্র কলব্যাক বনাম 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জন্য প্রদত্ত কারণগুলো। - লিসেনারটি শুধুমাত্র কলব্যাক প্যারামিটারের মাধ্যমে প্রদত্ত নতুন মানগুলোর ওপর কাজ করে, অথবা এমন কিছু ট্রিগার করে যা কলব্যাক প্যারামিটারগুলোর ওপর নির্ভরশীল নয়।
- লিসেনার ইমপ্লিমেন্টেশন চায় যে মেথডের নামে ইভেন্টটি কী কারণে ট্রিগার হয়েছে তার একটি স্পষ্ট ও পাঠযোগ্য ইঙ্গিত থাকুক।
- লিসেনার একটি অ্যানালিটিক্স সিস্টেমে রিপোর্ট করে, যেটিকে সমস্ত স্বতন্ত্র ইভেন্ট এবং অবস্থার পরিবর্তন সম্পর্কে জানতে হয়।
নিম্নলিখিত ক্ষেত্রগুলিতে জেনেরিক onEvents(Player player, Events events) ব্যবহার করা শ্রেয়:
- লিসেনার একাধিক ইভেন্টের জন্য একই লজিক ট্রিগার করতে চায়। উদাহরণস্বরূপ,
onPlaybackStateChangedএবংonPlayWhenReadyChangedউভয় ক্ষেত্রেই একটি UI আপডেট করা। - পরবর্তী ইভেন্টগুলো ট্রিগার করার জন্য, যেমন কোনো মিডিয়া আইটেম পরিবর্তনের পর সিক করার জন্য, লিসেনারকে
Playerঅবজেক্টটিতে অ্যাক্সেস করতে হয়। - লিসেনার আলাদা কলব্যাকের মাধ্যমে রিপোর্ট করা একাধিক স্টেট ভ্যালু একসাথে, অথবা
Playerগেটার মেথডগুলোর সাথে মিলিয়ে ব্যবহার করতে চায়। উদাহরণস্বরূপ,onTimelineChangedএ দেওয়াTimelineসাথেPlayer.getCurrentWindowIndex()ব্যবহার করা শুধুমাত্রonEventsকলব্যাকের ভেতর থেকেই নিরাপদ। - শ্রোতা জানতে আগ্রহী যে ঘটনাগুলো যৌক্তিকভাবে একসাথে ঘটেছে কিনা। উদাহরণস্বরূপ, একটি মিডিয়া আইটেম পরিবর্তনের কারণে
onPlaybackStateChangedথেকেSTATE_BUFFERINGএ পরিবর্তিত হওয়া।
কিছু ক্ষেত্রে, লিসেনারদের স্বতন্ত্র কলব্যাকগুলোকে জেনেরিক onEvents কলব্যাকের সাথে একত্রিত করার প্রয়োজন হতে পারে; উদাহরণস্বরূপ, onMediaItemTransition ব্যবহার করে মিডিয়া আইটেম পরিবর্তনের কারণগুলো রেকর্ড করতে, কিন্তু সমস্ত স্টেট পরিবর্তন একসাথে onEvents এ ব্যবহার করার জন্য কেবল তখনই কাজ করতে হবে।
কো-রুটিন ব্যবহার করে প্লেব্যাক ইভেন্টগুলো শুনুন
বিকল্পভাবে, আপনি Player.listenTo ব্যবহার করে একটি Kotlin coroutine চালু করতে পারেন এবং প্রাসঙ্গিক 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 এর উপর কাজ করে, তাই এগুলো স্বতন্ত্র কলব্যাকে পাঠানো ট্রানজিয়েন্ট আর্গুমেন্টগুলোতে অ্যাক্সেস দেয় না, যেমন onMediaItemTransition(..., int reason) এর reason অথবা onPositionDiscontinuity(...) -এর oldPosition । যদি আপনার লজিক এই নির্দিষ্ট আর্গুমেন্টগুলোর উপর নির্ভর করে (এবং এগুলো Player এর স্টেট প্রপার্টি হিসেবে উপলব্ধ না থাকে), তাহলে আপনার পরিবর্তে স্ট্যান্ডার্ড Player.Listener ইন্টারফেস ব্যবহার করা উচিত।
AnalyticsListener ব্যবহার করুন
ExoPlayer ব্যবহার করার সময়, addAnalyticsListener কল করে প্লেয়ারের সাথে একটি AnalyticsListener রেজিস্টার করা যায়। AnalyticsListener ইমপ্লিমেন্টেশনগুলো বিস্তারিত ইভেন্ট শুনতে সক্ষম, যা অ্যানালিটিক্স এবং লগিংয়ের জন্য উপযোগী হতে পারে। আরও বিস্তারিত জানতে অনুগ্রহ করে অ্যানালিটিক্স পেজটি দেখুন।
EventLogger ব্যবহার করুন
EventLogger হলো লগিংয়ের উদ্দেশ্যে লাইব্রেরি দ্বারা সরাসরি প্রদত্ত একটি AnalyticsListener । একটি মাত্র লাইনে প্রয়োজনীয় অতিরিক্ত লগিং চালু করতে একটি ExoPlayer এ EventLogger যোগ করুন:
কোটলিন
player.addAnalyticsListener(EventLogger())
জাভা
player.addAnalyticsListener(new EventLogger());
আরও বিস্তারিত জানতে ডিবাগ লগিং পৃষ্ঠাটি দেখুন।
নির্দিষ্ট প্লেব্যাক অবস্থানে ফায়ার ইভেন্ট
কিছু ক্ষেত্রে নির্দিষ্ট প্লেব্যাক পজিশনে ইভেন্ট ফায়ার করার প্রয়োজন হয়। এটি PlayerMessage ব্যবহার করে সমর্থন করা হয়। ExoPlayer.createMessage ব্যবহার করে একটি PlayerMessage তৈরি করা যায়। এটি কোন প্লেব্যাক পজিশনে এক্সিকিউট হবে তা 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();