Dalam pengalaman menonton TV live, pengguna akan berganti-ganti saluran dan informasi tentang saluran dan program akan ditampilkan secara singkat sebelum informasi tersebut hilang. Jenis informasi lainnya, seperti pesan ("JANGAN COBA DI RUMAH"), subtitel, atau iklan mungkin terus ada. Seperti halnya semua aplikasi TV, informasi tersebut tidak boleh mengganggu konten program yang diputar di layar.

Gambar 1. Pesan overlay pada aplikasi TV live.
Pertimbangkan juga apakah konten program tertentu harus ditampilkan berdasarkan setelan kontrol orang tua dan rating konten, serta cara aplikasi Anda berperilaku dan menginformasikan kepada pengguna jika konten tersebut diblokir atau tidak tersedia. Atas pertimbangan di atas, tutorial ini menjelaskan cara mengembangakan pengalaman pengguna input TV Anda.
Coba aplikasi contoh Layanan Input TV.
Mengintegrasikan pemutar dengan permukaan
Input TV Anda harus merender video ke objek Surface
, yang diteruskan oleh metode TvInputService.Session.onSetSurface()
. Berikut adalah contoh cara menggunakan instance MediaPlayer
untuk memutar konten di objek Surface
:
Kotlin
override fun onSetSurface(surface: Surface?): Boolean { player?.setSurface(surface) mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.setVolume(volume, volume) mVolume = volume }
Java
@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; }
Sama seperti di atas, ini adalah cara menggunakannya dengan ExoPlayer:
Kotlin
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 }
Java
@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; }
Menggunakan overlay
Gunakan overlay untuk menampilkan subtitel, pesan, iklan, atau siaran data MHEG-5. Secara default, overlay dinonaktifkan. Anda dapat mengaktifkannya saat membuat sesi dengan memanggil TvInputService.Session.setOverlayViewEnabled(true)
, seperti contoh berikut:
Kotlin
override fun onCreateSession(inputId: String): Session = onCreateSessionInternal(inputId).apply { setOverlayViewEnabled(true) sessions.add(this) }
Java
@Override public final Session onCreateSession(String inputId) { BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); session.setOverlayViewEnabled(true); sessions.add(session); return session; }
Gunakan objek View
untuk overlay, yang ditampilkan dari TvInputService.Session.onCreateOverlayView()
, seperti yang ditunjukkan di sini:
Kotlin
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) } } }
Java
@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; }
Definisi tata letak untuk overlay mungkin terlihat seperti ini:
<?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>
Mengontrol konten
Saat pengguna memilih saluran, input TV Anda akan menangani callback onTune()
di objek TvInputService.Session
. Kontrol orang tua pada aplikasi TV sistem menentukan konten yang ditampilkan, berdasarkan rating kontennya.
Bagian berikut menjelaskan cara mengelola pemilihan saluran dan program menggunakan metode TvInputService.Session
notify
yang berkomunikasi dengan aplikasi TV sistem.
Menjadikan video tidak tersedia
Saat pengguna mengganti saluran, pastikan layar tidak menampilkan artefak video terpisah sebelum input TV Anda merende r konten. Saat memanggil TvInputService.Session.onTune()
, Anda dapat mencegah agar video tidak ditampilkan dengan memanggil TvInputService.Session.notifyVideoUnavailable()
dan meneruskan konstanta VIDEO_UNAVAILABLE_REASON_TUNING
, seperti yang ditunjukkan pada contoh berikut.
Kotlin
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 }
Java
@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; }
Lalu, saat konten dirender ke Surface
, panggil TvInputService.Session.notifyVideoAvailable()
untuk mengizinkan video ditampilkan, seperti:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
Transisi ini hanya berlangsung selama sepersekian detik, tetapi menampilkan layar kosong yang secara visual lebih baik daripada membiarkan gambar menjalankan flash jitter dan blip aneh.
Lihat juga, Mengintegrasikan pemutar dengan permukaan untuk informasi selengkapnya tentang bekerja dengan Surface
untuk merender video.
Menyediakan kontrol orang tua
Untuk menentukan apakah konten yang disajikan diblokir oleh kontrol orang tua dan rating konten, periksa TvInputManager
metode class, isParentalControlsEnabled()
dan isRatingBlocked(android.media.tv.TvContentRating)
. Anda mungkin juga ingin memastikan bahwa TvContentRating
konten disertakan dalam serangkaian rating konten yang saat ini diizinkan. Pertimbangan ini ditampilkan pada contoh berikut.
Kotlin
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) }
Java
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); }
Setelah menentukan apakah konten perlu diblokir atau tidak, beri tahu aplikasi TV sistem dengan memanggil metode TvInputService.Session
notifyContentAllowed()
atau notifyContentBlocked()
, seperti yang ditunjukkan pada contoh sebelumnya.
Gunakan class TvContentRating
untuk membuat string yang ditentukan sistem untuk COLUMN_CONTENT_RATING
dengan metode TvContentRating.createRating()
, seperti yang ditampilkan di sini:
Kotlin
val rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L" )
Java
TvContentRating rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L");
Menangani pemilihan trek
Class TvTrackInfo
menyimpan informasi tentang trek media seperti jenis trek (video, audio, atau subtitel) dan seterusnya.
Pertama kali sesi input TV Anda bisa mendapatkan informasi lagu, sesi tersebut harus memanggil TvInputService.Session.notifyTracksChanged()
dengan daftar semua trek untuk mengupdate aplikasi TV sistem. Jika ada perubahan informasi lagu, panggil notifyTracksChanged()
lagi untuk mengupdate sistem.
Aplikasi TV sistem menyediakan antarmuka bagi pengguna untuk memilih trek tertentu jika lebih dari satu trek tersedia untuk jenis trek tersebut; misalnya subtitel dalam berbagai bahasa. Input TV Anda merespons panggilan onSelectTrack()
dari aplikasi TV sistem dengan memanggil notifyTrackSelected()
, seperti yang ditunjukkan pada contoh berikut. Perhatikan bahwa saat null
diteruskan sebagai ID trek, batalkan pilihan trek ini.
Kotlin
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
Java
@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; }