প্লেয়ারের স্টেটে পরিবর্তন (যেমন, প্লেব্যাক শুরু হওয়া, বাফারিং বা সমস্যা)
ট্রিগার ইভেন্ট যা রেজিস্টার করা Player.Listener ইন্সট্যান্সে পাঠানো হয়। এইসব
ইভেন্টকে পূর্ণসংখ্যা ধ্রুবক দ্বারা উপস্থাপন করা হয় এবং Player.Event
ও Player.Events দ্বারা সংজ্ঞায়িত করা হয়।
Player.Listener রেজিস্টার করা
রেজিস্টার করা Player.Listener ইনস্ট্যান্সকে প্লেয়ার ইভেন্ট সম্পর্কে রিপোর্ট করা হয়। এই ধরনের ইভেন্ট পেতে
লিসনার রেজিস্টার করতে:
Kotlin
// 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 দেখুন।
প্লেব্যাক স্ট্যাটাসে হওয়া পরিবর্তন
রেজিস্টার করা 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অবস্থায় আছে 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. } } } )
জাভা
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 নেটওয়ার্কিং সমস্যার কারণে ব্যর্থ হলে তা শনাক্ত করতে হয়:
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. } } } } )
জাভা
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 ইনস্ট্যান্সে একের পর এক কলব্যাক করা হয়:
reason=DISCONTINUITY_REASON_SEEK-এর সাথেonPositionDiscontinuity।Player.seekTo-এ কল করার সরাসরি ফলাফল এটি। কলব্যাকেPositionInfoফিল্ড আছে যা খোঁজার আগে ও পরে পজিশন দেখায়।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) } }
জাভা
@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-এ দেওয়াTimelinePlayer.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) } } }
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)-এর কারণ বা
onPositionDiscontinuity(...)-এর oldPosition-এর মতো
স্বতন্ত্র কলব্যাকে পাস করা ট্রানজিয়েন্ট আর্গুমেন্টে অ্যাক্সেস দেয় না। আপনার যুক্তি যদি এইসব নির্দিষ্ট আর্গুমেন্টের উপর নির্ভর করে
(এবং সেগুলি Player-এ স্টেট প্রপার্টি হিসেবে উপলভ্য না থাকে), তাহলে আপনাকে
এর পরিবর্তে স্ট্যান্ডার্ড Player.Listener ইন্টারফেস ব্যবহার করতে হবে।
AnalyticsListener ব্যবহার করুন
ExoPlayer ব্যবহার করার সময়, addAnalyticsListener-এ কল করে প্লেয়ারের সাথে AnalyticsListener রেজিস্টার করা
যায়। AnalyticsListener প্রয়োগ করা হলে, তা
বিশদ ইভেন্ট শুনতে পায় যা বিশ্লেষণ ও লগিং
উদ্দেশ্যে কাজে লাগতে পারে। আরও বিবরণের জন্য অ্যানালিটিক্স পৃষ্ঠা দেখুন।
EventLogger ব্যবহার করুন
EventLogger হল AnalyticsListener যা লাইব্রেরি সরাসরি
লগ করার উদ্দেশ্যে প্রদান করে। একটি ExoPlayer-এ EventLogger যোগ করে একটি লাইন দিয়ে
অতিরিক্ত লগিং চালু করুন:
Kotlin
player.addAnalyticsListener(EventLogger())
জাভা
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()
জাভা
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();