লাইভ টিভি দেখার সময়, ব্যবহারকারী চ্যানেল পরিবর্তন করলে চ্যানেল ও অনুষ্ঠানের তথ্য অল্প সময়ের জন্য দেখতে পান এবং এরপর তা অদৃশ্য হয়ে যায়। অন্যান্য ধরনের তথ্য, যেমন বার্তা ("বাড়িতে চেষ্টা করবেন না"), সাবটাইটেল বা বিজ্ঞাপন থেকে যাওয়ার প্রয়োজন হতে পারে। যেকোনো টিভি অ্যাপের মতোই, এই ধরনের তথ্য যেন স্ক্রিনে চলতে থাকা অনুষ্ঠানের বিষয়বস্তুতে কোনো বাধা সৃষ্টি না করে।

এছাড়াও বিবেচনা করুন যে, কন্টেন্টের রেটিং এবং প্যারেন্টাল কন্ট্রোল সেটিংস মাথায় রেখে নির্দিষ্ট কোনো প্রোগ্রাম কন্টেন্ট দেখানো উচিত কি না, এবং কোনো কন্টেন্ট ব্লক বা অনুপলব্ধ হলে আপনার অ্যাপ কীভাবে আচরণ করে ও ব্যবহারকারীকে জানায়। এই পাঠে বর্ণনা করা হয়েছে, কীভাবে এই বিষয়গুলো মাথায় রেখে আপনার টিভি ইনপুটের ইউজার এক্সপেরিয়েন্স উন্নত করা যায়।
টিভি ইনপুট সার্ভিস স্যাম্পল অ্যাপটি ব্যবহার করে দেখুন।
প্লেয়ারকে পৃষ্ঠের সাথে একীভূত করুন
আপনার টিভি ইনপুটকে অবশ্যই একটি Surface অবজেক্টে ভিডিও রেন্ডার করতে হবে, যা TvInputService.Session.onSetSurface() মেথডের মাধ্যমে পাস করা হয়। Surface অবজেক্টের কন্টেন্ট প্লে করার জন্য কীভাবে একটি MediaPlayer ইনস্ট্যান্স ব্যবহার করতে হয়, তার একটি উদাহরণ নিচে দেওয়া হলো:
কোটলিন
override fun onSetSurface(surface: Surface?): Boolean { player?.setSurface(surface) mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.setVolume(volume, volume) mVolume = volume }
জাভা
@Override public boolean onSetSurface(Surface surface) { if (player != null) { player.setSurface(surface); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.setVolume(volume, volume); } mVolume = volume; }
একইভাবে, ExoPlayer ব্যবহার করে এটি করার পদ্ধতি নিচে দেওয়া হলো:
কোটলিন
override fun onSetSurface(surface: Surface?): Boolean { player?.createMessage(videoRenderer)?.apply { type = MSG_SET_SURFACE payload = surface send() } mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.createMessage(audioRenderer)?.apply { type = MSG_SET_VOLUME payload = volume send() } mVolume = volume }
জাভা
@Override public boolean onSetSurface(@Nullable Surface surface) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_SURFACE) .setPayload(surface) .send(); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_VOLUME) .setPayload(volume) .send(); } mVolume = volume; }
একটি ওভারলে ব্যবহার করুন
সাবটাইটেল, বার্তা, বিজ্ঞাপন বা MHEG-5 ডেটা ব্রডকাস্ট প্রদর্শন করতে একটি ওভারলে ব্যবহার করুন। ডিফল্টরূপে, ওভারলেটি নিষ্ক্রিয় থাকে। সেশন তৈরি করার সময় TvInputService.Session.setOverlayViewEnabled(true) কল করে আপনি এটি সক্রিয় করতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:
কোটলিন
override fun onCreateSession(inputId: String): Session = onCreateSessionInternal(inputId).apply { setOverlayViewEnabled(true) sessions.add(this) }
জাভা
@Override public final Session onCreateSession(String inputId) { BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); session.setOverlayViewEnabled(true); sessions.add(session); return session; }
এখানে দেখানো অনুযায়ী, TvInputService.Session.onCreateOverlayView() থেকে রিটার্ন করা একটি View অবজেক্ট ওভারলে-এর জন্য ব্যবহার করুন:
কোটলিন
override fun onCreateOverlayView(): View = (context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater).run { inflate(R.layout.overlayview, null).apply { subtitleView = findViewById<SubtitleView>(R.id.subtitles).apply { // Configure the subtitle view. val captionStyle: CaptionStyleCompat = CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle) setStyle(captionStyle) setFractionalTextSize(captioningManager.fontScale) } } }
জাভা
@Override public View onCreateOverlayView() { LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.overlayview, null); subtitleView = (SubtitleView) view.findViewById(R.id.subtitles); // Configure the subtitle view. CaptionStyleCompat captionStyle; captionStyle = CaptionStyleCompat.createFromCaptionStyle( captioningManager.getUserStyle()); subtitleView.setStyle(captionStyle); subtitleView.setFractionalTextSize(captioningManager.fontScale); return view; }
ওভারলে-র লেআউট সংজ্ঞাটি দেখতে অনেকটা এইরকম হতে পারে:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.exoplayer.text.SubtitleView android:id="@+id/subtitles" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="32dp" android:visibility="invisible"/> </FrameLayout>
বিষয়বস্তু নিয়ন্ত্রণ করুন
যখন ব্যবহারকারী একটি চ্যানেল নির্বাচন করেন, তখন আপনার টিভি ইনপুট TvInputService.Session অবজেক্টের onTune() কলব্যাকটি পরিচালনা করে। সিস্টেম টিভি অ্যাপের প্যারেন্টাল কন্ট্রোল, কন্টেন্ট রেটিং-এর উপর ভিত্তি করে, কোন কন্টেন্ট প্রদর্শিত হবে তা নির্ধারণ করে। নিম্নলিখিত বিভাগগুলিতে বর্ণনা করা হয়েছে কীভাবে সিস্টেম টিভি অ্যাপের সাথে যোগাযোগকারী TvInputService.Session notify মেথডগুলি ব্যবহার করে চ্যানেল এবং প্রোগ্রাম নির্বাচন পরিচালনা করা যায়।
ভিডিওটি অনুপলব্ধ করুন
যখন ব্যবহারকারী চ্যানেল পরিবর্তন করেন, তখন আপনার টিভি ইনপুট কন্টেন্ট রেন্ডার করার আগে স্ক্রিনে যেন কোনো অবাঞ্ছিত ভিডিও আর্টিফ্যাক্ট প্রদর্শিত না হয়, তা নিশ্চিত করতে হবে। যখন আপনি TvInputService.Session.onTune() কল করেন, তখন TvInputService.Session.notifyVideoUnavailable() কল করে এবং VIDEO_UNAVAILABLE_REASON_TUNING কনস্ট্যান্টটি পাস করার মাধ্যমে ভিডিওটি প্রদর্শিত হওয়া থেকে আটকাতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে।
কোটলিন
override fun onTune(channelUri: Uri): Boolean { subtitleView?.visibility = View.INVISIBLE notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) unblockedRatingSet.clear() dbHandler.apply { removeCallbacks(playCurrentProgramRunnable) playCurrentProgramRunnable = PlayCurrentProgramRunnable(channelUri) post(playCurrentProgramRunnable) } return true }
জাভা
@Override public boolean onTune(Uri channelUri) { if (subtitleView != null) { subtitleView.setVisibility(View.INVISIBLE); } notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); unblockedRatingSet.clear(); dbHandler.removeCallbacks(playCurrentProgramRunnable); playCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri); dbHandler.post(playCurrentProgramRunnable); return true; }
তারপর, যখন কন্টেন্টটি Surface রেন্ডার করা হয়, তখন ভিডিওটি প্রদর্শনের অনুমতি দেওয়ার জন্য আপনি TvInputService.Session.notifyVideoAvailable() কল করেন, যেমনটা নিচে দেখানো হয়েছে:
কোটলিন
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
জাভা
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
এই পরিবর্তনটি মাত্র কয়েক মুহূর্তের জন্য স্থায়ী হয়, কিন্তু ছবিটিকে অদ্ভুতভাবে কাঁপতে ও ঝিকমিক করতে দেওয়ার চেয়ে একটি ফাঁকা পর্দা দেখানো দৃষ্টিনন্দন।
Surface ব্যবহার করে ভিডিও রেন্ডার করার বিষয়ে আরও তথ্যের জন্য, ‘Integrate player with surface’ অংশটিও দেখুন।
অভিভাবকীয় নিয়ন্ত্রণ প্রদান করুন
কোনো নির্দিষ্ট কন্টেন্ট প্যারেন্টাল কন্ট্রোল এবং কন্টেন্ট রেটিং দ্বারা ব্লক করা হয়েছে কিনা তা নির্ধারণ করতে, আপনাকে TvInputManager ক্লাসের isParentalControlsEnabled() এবং isRatingBlocked(android.media.tv.TvContentRating) মেথডগুলো পরীক্ষা করতে হবে। এছাড়াও, আপনি নিশ্চিত হতে চাইতে পারেন যে কন্টেন্টটির TvContentRating বর্তমানে অনুমোদিত কন্টেন্ট রেটিংগুলোর তালিকায় অন্তর্ভুক্ত আছে। এই বিষয়গুলো নিম্নলিখিত নমুনায় দেখানো হয়েছে।
কোটলিন
private fun checkContentBlockNeeded() { currentContentRating?.also { rating -> if (!tvInputManager.isParentalControlsEnabled || !tvInputManager.isRatingBlocked(rating) || unblockedRatingSet.contains(rating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null) return } } lastBlockedRating = currentContentRating player?.run { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer() } notifyContentBlocked(currentContentRating) }
জাভা
private void checkContentBlockNeeded() { if (currentContentRating == null || !tvInputManager.isParentalControlsEnabled() || !tvInputManager.isRatingBlocked(currentContentRating) || unblockedRatingSet.contains(currentContentRating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null); return; } lastBlockedRating = currentContentRating; if (player != null) { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer(); } notifyContentBlocked(currentContentRating); }
কন্টেন্টটি ব্লক করা হবে কি হবে না তা নির্ধারণ করার পর, পূর্ববর্তী উদাহরণে দেখানো অনুযায়ী TvInputService.Session মেথড notifyContentAllowed() বা notifyContentBlocked() কল করে সিস্টেম টিভি অ্যাপকে অবহিত করুন।
এখানে দেখানো পদ্ধতি অনুযায়ী, TvContentRating ক্লাসের ` TvContentRating.createRating() মেথড ব্যবহার করে COLUMN_CONTENT_RATING এর জন্য সিস্টেম-সংজ্ঞায়িত স্ট্রিংটি তৈরি করুন:
কোটলিন
val rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L" )
জাভা
TvContentRating rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L");
ট্র্যাক নির্বাচন পরিচালনা করুন
TvTrackInfo ক্লাসটি মিডিয়া ট্র্যাক সম্পর্কিত তথ্য ধারণ করে, যেমন ট্র্যাকের ধরন (ভিডিও, অডিও, বা সাবটাইটেল) ইত্যাদি।
আপনার টিভি ইনপুট সেশন যখন প্রথমবার ট্র্যাকের তথ্য পেতে সক্ষম হবে, তখন সিস্টেম টিভি অ্যাপ আপডেট করার জন্য সমস্ত ট্র্যাকের একটি তালিকা সহ TvInputService.Session.notifyTracksChanged() কল করা উচিত। ট্র্যাকের তথ্যে কোনো পরিবর্তন হলে, সিস্টেম আপডেট করার জন্য আবার notifyTracksChanged() কল করুন।
সিস্টেম টিভি অ্যাপটি ব্যবহারকারীকে একটি নির্দিষ্ট ট্র্যাক নির্বাচন করার জন্য একটি ইন্টারফেস প্রদান করে, যদি কোনো নির্দিষ্ট ট্র্যাক টাইপের জন্য একাধিক ট্র্যাক উপলব্ধ থাকে; যেমন, বিভিন্ন ভাষার সাবটাইটেল। আপনার টিভি ইনপুট, সিস্টেম টিভি অ্যাপ থেকে আসা onSelectTrack() কলের জবাবে notifyTrackSelected() কল করে, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে। লক্ষ্য করুন যে, ট্র্যাক আইডি হিসেবে null পাস করা হলে, এটি ট্র্যাকটিকে অনির্বাচিত করে দেয় ।
কোটলিন
override fun onSelectTrack(type: Int, trackId: String?): Boolean = mPlayer?.let { player -> if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) return false selectedSubtitleTrackId = trackId subtitleView.visibility = if (trackId == null) View.INVISIBLE else View.VISIBLE } player.trackInfo.indexOfFirst { it.trackType == type }.let { trackIndex -> if( trackIndex >= 0) { player.selectTrack(trackIndex) notifyTrackSelected(type, trackId) true } else false } } ?: false
জাভা
@Override public boolean onSelectTrack(int type, String trackId) { if (player != null) { if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) { return false; } selectedSubtitleTrackId = trackId; if (trackId == null) { subtitleView.setVisibility(View.INVISIBLE); } } int trackIndex = -1; MediaPlayer.TrackInfo[] trackInfos = player.getTrackInfo(); for (int index = 0; index < trackInfos.length; index++) { MediaPlayer.TrackInfo trackInfo = trackInfos[index]; if (trackInfo.getTrackType() == type) { trackIndex = index; break; } } if (trackIndex >= 0) { player.selectTrack(trackIndex); notifyTrackSelected(type, trackId); return true; } } return false; }
লাইভ টিভি দেখার সময়, ব্যবহারকারী চ্যানেল পরিবর্তন করলে চ্যানেল ও অনুষ্ঠানের তথ্য অল্প সময়ের জন্য দেখতে পান এবং এরপর তা অদৃশ্য হয়ে যায়। অন্যান্য ধরনের তথ্য, যেমন বার্তা ("বাড়িতে চেষ্টা করবেন না"), সাবটাইটেল বা বিজ্ঞাপন থেকে যাওয়ার প্রয়োজন হতে পারে। যেকোনো টিভি অ্যাপের মতোই, এই ধরনের তথ্য যেন স্ক্রিনে চলতে থাকা অনুষ্ঠানের বিষয়বস্তুতে কোনো বাধা সৃষ্টি না করে।

এছাড়াও বিবেচনা করুন যে, কন্টেন্টের রেটিং এবং প্যারেন্টাল কন্ট্রোল সেটিংস মাথায় রেখে নির্দিষ্ট কোনো প্রোগ্রাম কন্টেন্ট দেখানো উচিত কি না, এবং কোনো কন্টেন্ট ব্লক বা অনুপলব্ধ হলে আপনার অ্যাপ কীভাবে আচরণ করে ও ব্যবহারকারীকে জানায়। এই পাঠে বর্ণনা করা হয়েছে, কীভাবে এই বিষয়গুলো মাথায় রেখে আপনার টিভি ইনপুটের ইউজার এক্সপেরিয়েন্স উন্নত করা যায়।
টিভি ইনপুট সার্ভিস স্যাম্পল অ্যাপটি ব্যবহার করে দেখুন।
প্লেয়ারকে পৃষ্ঠের সাথে একীভূত করুন
আপনার টিভি ইনপুটকে অবশ্যই একটি Surface অবজেক্টে ভিডিও রেন্ডার করতে হবে, যা TvInputService.Session.onSetSurface() মেথডের মাধ্যমে পাস করা হয়। Surface অবজেক্টের কন্টেন্ট প্লে করার জন্য কীভাবে একটি MediaPlayer ইনস্ট্যান্স ব্যবহার করতে হয়, তার একটি উদাহরণ নিচে দেওয়া হলো:
কোটলিন
override fun onSetSurface(surface: Surface?): Boolean { player?.setSurface(surface) mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.setVolume(volume, volume) mVolume = volume }
জাভা
@Override public boolean onSetSurface(Surface surface) { if (player != null) { player.setSurface(surface); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.setVolume(volume, volume); } mVolume = volume; }
একইভাবে, ExoPlayer ব্যবহার করে এটি করার পদ্ধতি নিচে দেওয়া হলো:
কোটলিন
override fun onSetSurface(surface: Surface?): Boolean { player?.createMessage(videoRenderer)?.apply { type = MSG_SET_SURFACE payload = surface send() } mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.createMessage(audioRenderer)?.apply { type = MSG_SET_VOLUME payload = volume send() } mVolume = volume }
জাভা
@Override public boolean onSetSurface(@Nullable Surface surface) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_SURFACE) .setPayload(surface) .send(); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_VOLUME) .setPayload(volume) .send(); } mVolume = volume; }
একটি ওভারলে ব্যবহার করুন
সাবটাইটেল, বার্তা, বিজ্ঞাপন বা MHEG-5 ডেটা ব্রডকাস্ট প্রদর্শন করতে একটি ওভারলে ব্যবহার করুন। ডিফল্টরূপে, ওভারলেটি নিষ্ক্রিয় থাকে। সেশন তৈরি করার সময় TvInputService.Session.setOverlayViewEnabled(true) কল করে আপনি এটি সক্রিয় করতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:
কোটলিন
override fun onCreateSession(inputId: String): Session = onCreateSessionInternal(inputId).apply { setOverlayViewEnabled(true) sessions.add(this) }
জাভা
@Override public final Session onCreateSession(String inputId) { BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); session.setOverlayViewEnabled(true); sessions.add(session); return session; }
এখানে দেখানো অনুযায়ী, TvInputService.Session.onCreateOverlayView() থেকে রিটার্ন করা একটি View অবজেক্ট ওভারলে-এর জন্য ব্যবহার করুন:
কোটলিন
override fun onCreateOverlayView(): View = (context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater).run { inflate(R.layout.overlayview, null).apply { subtitleView = findViewById<SubtitleView>(R.id.subtitles).apply { // Configure the subtitle view. val captionStyle: CaptionStyleCompat = CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle) setStyle(captionStyle) setFractionalTextSize(captioningManager.fontScale) } } }
জাভা
@Override public View onCreateOverlayView() { LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.overlayview, null); subtitleView = (SubtitleView) view.findViewById(R.id.subtitles); // Configure the subtitle view. CaptionStyleCompat captionStyle; captionStyle = CaptionStyleCompat.createFromCaptionStyle( captioningManager.getUserStyle()); subtitleView.setStyle(captionStyle); subtitleView.setFractionalTextSize(captioningManager.fontScale); return view; }
ওভারলে-র লেআউট সংজ্ঞাটি দেখতে অনেকটা এইরকম হতে পারে:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.exoplayer.text.SubtitleView android:id="@+id/subtitles" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="32dp" android:visibility="invisible"/> </FrameLayout>
বিষয়বস্তু নিয়ন্ত্রণ করুন
যখন ব্যবহারকারী একটি চ্যানেল নির্বাচন করেন, তখন আপনার টিভি ইনপুট TvInputService.Session অবজেক্টের onTune() কলব্যাকটি পরিচালনা করে। সিস্টেম টিভি অ্যাপের প্যারেন্টাল কন্ট্রোল, কন্টেন্ট রেটিং-এর উপর ভিত্তি করে, কোন কন্টেন্ট প্রদর্শিত হবে তা নির্ধারণ করে। নিম্নলিখিত বিভাগগুলিতে বর্ণনা করা হয়েছে কীভাবে সিস্টেম টিভি অ্যাপের সাথে যোগাযোগকারী TvInputService.Session notify মেথডগুলি ব্যবহার করে চ্যানেল এবং প্রোগ্রাম নির্বাচন পরিচালনা করা যায়।
ভিডিওটি অনুপলব্ধ করুন
যখন ব্যবহারকারী চ্যানেল পরিবর্তন করেন, তখন আপনার টিভি ইনপুট কন্টেন্ট রেন্ডার করার আগে স্ক্রিনে যেন কোনো অবাঞ্ছিত ভিডিও আর্টিফ্যাক্ট প্রদর্শিত না হয়, তা নিশ্চিত করতে হবে। যখন আপনি TvInputService.Session.onTune() কল করেন, তখন TvInputService.Session.notifyVideoUnavailable() কল করে এবং VIDEO_UNAVAILABLE_REASON_TUNING কনস্ট্যান্টটি পাস করার মাধ্যমে ভিডিওটি প্রদর্শিত হওয়া থেকে আটকাতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে।
কোটলিন
override fun onTune(channelUri: Uri): Boolean { subtitleView?.visibility = View.INVISIBLE notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) unblockedRatingSet.clear() dbHandler.apply { removeCallbacks(playCurrentProgramRunnable) playCurrentProgramRunnable = PlayCurrentProgramRunnable(channelUri) post(playCurrentProgramRunnable) } return true }
জাভা
@Override public boolean onTune(Uri channelUri) { if (subtitleView != null) { subtitleView.setVisibility(View.INVISIBLE); } notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); unblockedRatingSet.clear(); dbHandler.removeCallbacks(playCurrentProgramRunnable); playCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri); dbHandler.post(playCurrentProgramRunnable); return true; }
তারপর, যখন কন্টেন্টটি Surface রেন্ডার করা হয়, তখন ভিডিওটি প্রদর্শনের অনুমতি দেওয়ার জন্য আপনি TvInputService.Session.notifyVideoAvailable() কল করেন, যেমনটা নিচে দেখানো হয়েছে:
কোটলিন
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
জাভা
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
এই পরিবর্তনটি মাত্র কয়েক মুহূর্তের জন্য স্থায়ী হয়, কিন্তু ছবিটিকে অদ্ভুতভাবে কাঁপতে ও ঝিকমিক করতে দেওয়ার চেয়ে একটি ফাঁকা পর্দা দেখানো দৃষ্টিনন্দন।
Surface ব্যবহার করে ভিডিও রেন্ডার করার বিষয়ে আরও তথ্যের জন্য, ‘Integrate player with surface’ অংশটিও দেখুন।
অভিভাবকীয় নিয়ন্ত্রণ প্রদান করুন
কোনো নির্দিষ্ট কন্টেন্ট প্যারেন্টাল কন্ট্রোল এবং কন্টেন্ট রেটিং দ্বারা ব্লক করা হয়েছে কিনা তা নির্ধারণ করতে, আপনাকে TvInputManager ক্লাসের isParentalControlsEnabled() এবং isRatingBlocked(android.media.tv.TvContentRating) মেথডগুলো পরীক্ষা করতে হবে। এছাড়াও, আপনি নিশ্চিত হতে চাইতে পারেন যে কন্টেন্টটির TvContentRating বর্তমানে অনুমোদিত কন্টেন্ট রেটিংগুলোর তালিকায় অন্তর্ভুক্ত আছে। এই বিষয়গুলো নিম্নলিখিত নমুনায় দেখানো হয়েছে।
কোটলিন
private fun checkContentBlockNeeded() { currentContentRating?.also { rating -> if (!tvInputManager.isParentalControlsEnabled || !tvInputManager.isRatingBlocked(rating) || unblockedRatingSet.contains(rating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null) return } } lastBlockedRating = currentContentRating player?.run { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer() } notifyContentBlocked(currentContentRating) }
জাভা
private void checkContentBlockNeeded() { if (currentContentRating == null || !tvInputManager.isParentalControlsEnabled() || !tvInputManager.isRatingBlocked(currentContentRating) || unblockedRatingSet.contains(currentContentRating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null); return; } lastBlockedRating = currentContentRating; if (player != null) { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer(); } notifyContentBlocked(currentContentRating); }
কন্টেন্টটি ব্লক করা হবে কি হবে না তা নির্ধারণ করার পর, পূর্ববর্তী উদাহরণে দেখানো অনুযায়ী TvInputService.Session মেথড notifyContentAllowed() বা notifyContentBlocked() কল করে সিস্টেম টিভি অ্যাপকে অবহিত করুন।
এখানে দেখানো পদ্ধতি অনুযায়ী, TvContentRating ক্লাসের ` TvContentRating.createRating() মেথড ব্যবহার করে COLUMN_CONTENT_RATING এর জন্য সিস্টেম-সংজ্ঞায়িত স্ট্রিংটি তৈরি করুন:
কোটলিন
val rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L" )
জাভা
TvContentRating rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L");
ট্র্যাক নির্বাচন পরিচালনা করুন
TvTrackInfo ক্লাসটি মিডিয়া ট্র্যাক সম্পর্কিত তথ্য ধারণ করে, যেমন ট্র্যাকের ধরন (ভিডিও, অডিও, বা সাবটাইটেল) ইত্যাদি।
আপনার টিভি ইনপুট সেশন যখন প্রথমবার ট্র্যাকের তথ্য পেতে সক্ষম হবে, তখন সিস্টেম টিভি অ্যাপ আপডেট করার জন্য সমস্ত ট্র্যাকের একটি তালিকা সহ TvInputService.Session.notifyTracksChanged() কল করা উচিত। ট্র্যাকের তথ্যে কোনো পরিবর্তন হলে, সিস্টেম আপডেট করার জন্য আবার notifyTracksChanged() কল করুন।
সিস্টেম টিভি অ্যাপটি ব্যবহারকারীকে একটি নির্দিষ্ট ট্র্যাক নির্বাচন করার জন্য একটি ইন্টারফেস প্রদান করে, যদি কোনো নির্দিষ্ট ট্র্যাক টাইপের জন্য একাধিক ট্র্যাক উপলব্ধ থাকে; যেমন, বিভিন্ন ভাষার সাবটাইটেল। আপনার টিভি ইনপুট, সিস্টেম টিভি অ্যাপ থেকে আসা onSelectTrack() কলের জবাবে notifyTrackSelected() কল করে, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে। লক্ষ্য করুন যে, ট্র্যাক আইডি হিসেবে null পাস করা হলে, এটি ট্র্যাকটিকে অনির্বাচিত করে দেয় ।
কোটলিন
override fun onSelectTrack(type: Int, trackId: String?): Boolean = mPlayer?.let { player -> if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) return false selectedSubtitleTrackId = trackId subtitleView.visibility = if (trackId == null) View.INVISIBLE else View.VISIBLE } player.trackInfo.indexOfFirst { it.trackType == type }.let { trackIndex -> if( trackIndex >= 0) { player.selectTrack(trackIndex) notifyTrackSelected(type, trackId) true } else false } } ?: false
জাভা
@Override public boolean onSelectTrack(int type, String trackId) { if (player != null) { if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) { return false; } selectedSubtitleTrackId = trackId; if (trackId == null) { subtitleView.setVisibility(View.INVISIBLE); } } int trackIndex = -1; MediaPlayer.TrackInfo[] trackInfos = player.getTrackInfo(); for (int index = 0; index < trackInfos.length; index++) { MediaPlayer.TrackInfo trackInfo = trackInfos[index]; if (trackInfo.getTrackType() == type) { trackIndex = index; break; } } if (trackIndex >= 0) { player.selectTrack(trackIndex); notifyTrackSelected(type, trackId); return true; } } return false; }
লাইভ টিভি দেখার সময়, ব্যবহারকারী চ্যানেল পরিবর্তন করলে চ্যানেল ও অনুষ্ঠানের তথ্য অল্প সময়ের জন্য দেখতে পান এবং এরপর তা অদৃশ্য হয়ে যায়। অন্যান্য ধরনের তথ্য, যেমন বার্তা ("বাড়িতে চেষ্টা করবেন না"), সাবটাইটেল বা বিজ্ঞাপন থেকে যাওয়ার প্রয়োজন হতে পারে। যেকোনো টিভি অ্যাপের মতোই, এই ধরনের তথ্য যেন স্ক্রিনে চলতে থাকা অনুষ্ঠানের বিষয়বস্তুতে কোনো বাধা সৃষ্টি না করে।

এছাড়াও বিবেচনা করুন যে, কন্টেন্টের রেটিং এবং প্যারেন্টাল কন্ট্রোল সেটিংস মাথায় রেখে নির্দিষ্ট কোনো প্রোগ্রাম কন্টেন্ট দেখানো উচিত কি না, এবং কোনো কন্টেন্ট ব্লক বা অনুপলব্ধ হলে আপনার অ্যাপ কীভাবে আচরণ করে ও ব্যবহারকারীকে জানায়। এই পাঠে বর্ণনা করা হয়েছে, কীভাবে এই বিষয়গুলো মাথায় রেখে আপনার টিভি ইনপুটের ইউজার এক্সপেরিয়েন্স উন্নত করা যায়।
টিভি ইনপুট সার্ভিস স্যাম্পল অ্যাপটি ব্যবহার করে দেখুন।
প্লেয়ারকে পৃষ্ঠের সাথে একীভূত করুন
আপনার টিভি ইনপুটকে অবশ্যই একটি Surface অবজেক্টে ভিডিও রেন্ডার করতে হবে, যা TvInputService.Session.onSetSurface() মেথডের মাধ্যমে পাস করা হয়। Surface অবজেক্টের কন্টেন্ট প্লে করার জন্য কীভাবে একটি MediaPlayer ইনস্ট্যান্স ব্যবহার করতে হয়, তার একটি উদাহরণ নিচে দেওয়া হলো:
কোটলিন
override fun onSetSurface(surface: Surface?): Boolean { player?.setSurface(surface) mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.setVolume(volume, volume) mVolume = volume }
জাভা
@Override public boolean onSetSurface(Surface surface) { if (player != null) { player.setSurface(surface); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.setVolume(volume, volume); } mVolume = volume; }
একইভাবে, ExoPlayer ব্যবহার করে এটি করার পদ্ধতি নিচে দেওয়া হলো:
কোটলিন
override fun onSetSurface(surface: Surface?): Boolean { player?.createMessage(videoRenderer)?.apply { type = MSG_SET_SURFACE payload = surface send() } mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.createMessage(audioRenderer)?.apply { type = MSG_SET_VOLUME payload = volume send() } mVolume = volume }
জাভা
@Override public boolean onSetSurface(@Nullable Surface surface) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_SURFACE) .setPayload(surface) .send(); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_VOLUME) .setPayload(volume) .send(); } mVolume = volume; }
একটি ওভারলে ব্যবহার করুন
সাবটাইটেল, বার্তা, বিজ্ঞাপন বা MHEG-5 ডেটা ব্রডকাস্ট প্রদর্শন করতে একটি ওভারলে ব্যবহার করুন। ডিফল্টরূপে, ওভারলেটি নিষ্ক্রিয় থাকে। সেশন তৈরি করার সময় TvInputService.Session.setOverlayViewEnabled(true) কল করে আপনি এটি সক্রিয় করতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:
কোটলিন
override fun onCreateSession(inputId: String): Session = onCreateSessionInternal(inputId).apply { setOverlayViewEnabled(true) sessions.add(this) }
জাভা
@Override public final Session onCreateSession(String inputId) { BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); session.setOverlayViewEnabled(true); sessions.add(session); return session; }
এখানে দেখানো অনুযায়ী, TvInputService.Session.onCreateOverlayView() থেকে রিটার্ন করা একটি View অবজেক্ট ওভারলে-এর জন্য ব্যবহার করুন:
কোটলিন
override fun onCreateOverlayView(): View = (context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater).run { inflate(R.layout.overlayview, null).apply { subtitleView = findViewById<SubtitleView>(R.id.subtitles).apply { // Configure the subtitle view. val captionStyle: CaptionStyleCompat = CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle) setStyle(captionStyle) setFractionalTextSize(captioningManager.fontScale) } } }
জাভা
@Override public View onCreateOverlayView() { LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.overlayview, null); subtitleView = (SubtitleView) view.findViewById(R.id.subtitles); // Configure the subtitle view. CaptionStyleCompat captionStyle; captionStyle = CaptionStyleCompat.createFromCaptionStyle( captioningManager.getUserStyle()); subtitleView.setStyle(captionStyle); subtitleView.setFractionalTextSize(captioningManager.fontScale); return view; }
ওভারলে-র লেআউট সংজ্ঞাটি দেখতে অনেকটা এইরকম হতে পারে:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.exoplayer.text.SubtitleView android:id="@+id/subtitles" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="32dp" android:visibility="invisible"/> </FrameLayout>
বিষয়বস্তু নিয়ন্ত্রণ করুন
যখন ব্যবহারকারী একটি চ্যানেল নির্বাচন করেন, তখন আপনার টিভি ইনপুট TvInputService.Session অবজেক্টের onTune() কলব্যাকটি পরিচালনা করে। সিস্টেম টিভি অ্যাপের প্যারেন্টাল কন্ট্রোল, কন্টেন্ট রেটিং-এর উপর ভিত্তি করে, কোন কন্টেন্ট প্রদর্শিত হবে তা নির্ধারণ করে। নিম্নলিখিত বিভাগগুলিতে বর্ণনা করা হয়েছে কীভাবে সিস্টেম টিভি অ্যাপের সাথে যোগাযোগকারী TvInputService.Session notify মেথডগুলি ব্যবহার করে চ্যানেল এবং প্রোগ্রাম নির্বাচন পরিচালনা করা যায়।
ভিডিওটি অনুপলব্ধ করুন
যখন ব্যবহারকারী চ্যানেল পরিবর্তন করেন, তখন আপনার টিভি ইনপুট কন্টেন্ট রেন্ডার করার আগে স্ক্রিনে যেন কোনো অবাঞ্ছিত ভিডিও আর্টিফ্যাক্ট প্রদর্শিত না হয়, তা নিশ্চিত করতে হবে। যখন আপনি TvInputService.Session.onTune() কল করেন, তখন TvInputService.Session.notifyVideoUnavailable() কল করে এবং VIDEO_UNAVAILABLE_REASON_TUNING কনস্ট্যান্টটি পাস করার মাধ্যমে ভিডিওটি প্রদর্শিত হওয়া থেকে আটকাতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে।
কোটলিন
override fun onTune(channelUri: Uri): Boolean { subtitleView?.visibility = View.INVISIBLE notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) unblockedRatingSet.clear() dbHandler.apply { removeCallbacks(playCurrentProgramRunnable) playCurrentProgramRunnable = PlayCurrentProgramRunnable(channelUri) post(playCurrentProgramRunnable) } return true }
জাভা
@Override public boolean onTune(Uri channelUri) { if (subtitleView != null) { subtitleView.setVisibility(View.INVISIBLE); } notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); unblockedRatingSet.clear(); dbHandler.removeCallbacks(playCurrentProgramRunnable); playCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri); dbHandler.post(playCurrentProgramRunnable); return true; }
তারপর, যখন কন্টেন্টটি Surface রেন্ডার করা হয়, তখন ভিডিওটি প্রদর্শনের অনুমতি দেওয়ার জন্য আপনি TvInputService.Session.notifyVideoAvailable() কল করেন, যেমনটা নিচে দেখানো হয়েছে:
কোটলিন
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
জাভা
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
এই পরিবর্তনটি মাত্র কয়েক মুহূর্তের জন্য স্থায়ী হয়, কিন্তু ছবিটিকে অদ্ভুতভাবে কাঁপতে ও ঝিকমিক করতে দেওয়ার চেয়ে একটি ফাঁকা পর্দা দেখানো দৃষ্টিনন্দন।
Surface ব্যবহার করে ভিডিও রেন্ডার করার বিষয়ে আরও তথ্যের জন্য, ‘Integrate player with surface’ অংশটিও দেখুন।
অভিভাবকীয় নিয়ন্ত্রণ প্রদান করুন
কোনো নির্দিষ্ট কন্টেন্ট প্যারেন্টাল কন্ট্রোল এবং কন্টেন্ট রেটিং দ্বারা ব্লক করা হয়েছে কিনা তা নির্ধারণ করতে, আপনাকে TvInputManager ক্লাসের isParentalControlsEnabled() এবং isRatingBlocked(android.media.tv.TvContentRating) মেথডগুলো পরীক্ষা করতে হবে। এছাড়াও, আপনি নিশ্চিত হতে চাইতে পারেন যে কন্টেন্টটির TvContentRating বর্তমানে অনুমোদিত কন্টেন্ট রেটিংগুলোর তালিকায় অন্তর্ভুক্ত আছে। এই বিষয়গুলো নিম্নলিখিত নমুনায় দেখানো হয়েছে।
কোটলিন
private fun checkContentBlockNeeded() { currentContentRating?.also { rating -> if (!tvInputManager.isParentalControlsEnabled || !tvInputManager.isRatingBlocked(rating) || unblockedRatingSet.contains(rating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null) return } } lastBlockedRating = currentContentRating player?.run { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer() } notifyContentBlocked(currentContentRating) }
জাভা
private void checkContentBlockNeeded() { if (currentContentRating == null || !tvInputManager.isParentalControlsEnabled() || !tvInputManager.isRatingBlocked(currentContentRating) || unblockedRatingSet.contains(currentContentRating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null); return; } lastBlockedRating = currentContentRating; if (player != null) { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer(); } notifyContentBlocked(currentContentRating); }
কন্টেন্টটি ব্লক করা হবে কি হবে না তা নির্ধারণ করার পর, পূর্ববর্তী উদাহরণে দেখানো অনুযায়ী TvInputService.Session মেথড notifyContentAllowed() বা notifyContentBlocked() কল করে সিস্টেম টিভি অ্যাপকে অবহিত করুন।
এখানে দেখানো পদ্ধতি অনুযায়ী, TvContentRating ক্লাসের ` TvContentRating.createRating() মেথড ব্যবহার করে COLUMN_CONTENT_RATING এর জন্য সিস্টেম-সংজ্ঞায়িত স্ট্রিংটি তৈরি করুন:
কোটলিন
val rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L" )
জাভা
TvContentRating rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L");
ট্র্যাক নির্বাচন পরিচালনা করুন
TvTrackInfo ক্লাসটি মিডিয়া ট্র্যাক সম্পর্কিত তথ্য ধারণ করে, যেমন ট্র্যাকের ধরন (ভিডিও, অডিও, বা সাবটাইটেল) ইত্যাদি।
আপনার টিভি ইনপুট সেশন যখন প্রথমবার ট্র্যাকের তথ্য পেতে সক্ষম হবে, তখন সিস্টেম টিভি অ্যাপ আপডেট করার জন্য সমস্ত ট্র্যাকের একটি তালিকা সহ TvInputService.Session.notifyTracksChanged() কল করা উচিত। ট্র্যাকের তথ্যে কোনো পরিবর্তন হলে, সিস্টেম আপডেট করার জন্য আবার notifyTracksChanged() কল করুন।
সিস্টেম টিভি অ্যাপটি ব্যবহারকারীকে একটি নির্দিষ্ট ট্র্যাক নির্বাচন করার জন্য একটি ইন্টারফেস প্রদান করে, যদি কোনো নির্দিষ্ট ট্র্যাক টাইপের জন্য একাধিক ট্র্যাক উপলব্ধ থাকে; যেমন, বিভিন্ন ভাষার সাবটাইটেল। আপনার টিভি ইনপুট, সিস্টেম টিভি অ্যাপ থেকে আসা onSelectTrack() কলের জবাবে notifyTrackSelected() কল করে, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে। লক্ষ্য করুন যে, ট্র্যাক আইডি হিসেবে null পাস করা হলে, এটি ট্র্যাকটিকে অনির্বাচিত করে দেয় ।
কোটলিন
override fun onSelectTrack(type: Int, trackId: String?): Boolean = mPlayer?.let { player -> if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) return false selectedSubtitleTrackId = trackId subtitleView.visibility = if (trackId == null) View.INVISIBLE else View.VISIBLE } player.trackInfo.indexOfFirst { it.trackType == type }.let { trackIndex -> if( trackIndex >= 0) { player.selectTrack(trackIndex) notifyTrackSelected(type, trackId) true } else false } } ?: false
জাভা
@Override public boolean onSelectTrack(int type, String trackId) { if (player != null) { if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) { return false; } selectedSubtitleTrackId = trackId; if (trackId == null) { subtitleView.setVisibility(View.INVISIBLE); } } int trackIndex = -1; MediaPlayer.TrackInfo[] trackInfos = player.getTrackInfo(); for (int index = 0; index < trackInfos.length; index++) { MediaPlayer.TrackInfo trackInfo = trackInfos[index]; if (trackInfo.getTrackType() == type) { trackIndex = index; break; } } if (trackIndex >= 0) { player.selectTrack(trackIndex); notifyTrackSelected(type, trackId); return true; } } return false; }