Mengelola interaksi pengguna TV

Dalam pengalaman menonton TV live, pengguna berganti saluran dan saluran dan program secara singkat sebelum informasi tersebut menghilang. Jenis informasi lainnya, seperti pesan ("JANGAN COBA DI RUMAH"), subtitel, atau iklan mungkin harus ada. Seperti pada TV lainnya 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 disajikan, mengingat setelan kontrol orang tua dan rating konten, serta perilaku aplikasi dan memberi tahu pengguna jika konten diblokir atau tidak tersedia. Pelajaran ini menjelaskan cara mengembangkan pengguna input TV pengalaman pengguna atas pertimbangan ini.

Cobalah Aplikasi contoh Layanan Input TV.

Mengintegrasikan pemutar dengan permukaan

Input TV Anda harus merender video ke objek Surface, yang diteruskan oleh TvInputService.Session.onSetSurface() . Berikut adalah contoh cara menggunakan instance MediaPlayer untuk memutar konten dalam 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; } 

Demikian pula, berikut ini cara melakukannya menggunakan 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 dalam 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. TV sistem kontrol orang tua pada aplikasi menentukan konten yang ditampilkan, berdasarkan rating kontennya. Bagian berikut menjelaskan cara mengelola pemilihan saluran dan program menggunakan TvInputService.Session metode notify yang berkomunikasi dengan aplikasi TV yang ada di sistem.

Menjadikan video tidak tersedia

Saat pengguna mengganti saluran, Anda ingin memastikan layar tidak menampilkan jaringan yang artefak video sebelum input TV Anda merender konten. Saat Anda memanggil TvInputService.Session.onTune(), Anda dapat mencegah video ditampilkan dengan memanggil TvInputService.Session.notifyVideoUnavailable() dan meneruskan konstanta VIDEO_UNAVAILABLE_REASON_TUNING, sebagai yang ditunjukkan dalam 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; } 

Kemudian, saat konten dirender ke Surface, panggil TvInputService.Session.notifyVideoAvailable() untuk mengizinkan video ditampilkan, seperti ini:

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 secara visual lebih baik daripada membiarkan gambar berkedip-kedip ganjil dan gelisah.

Lihat juga, Mengintegrasikan pemutar dengan permukaan untuk informasi selengkapnya tentang cara kerja dengan Surface untuk merender video.

Menyediakan kontrol orang tua

Untuk menentukan apakah konten tertentu diblokir oleh kontrol orang tua dan rating konten, periksa Metode class TvInputManager, isParentalControlsEnabled() dan isRatingBlocked(android.media.tv.TvContentRating). Anda Anda mungkin juga ingin memastikan 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 Anda menentukan apakah konten harus diblokir atau tidak, beri tahu TV sistem aplikasi Anda dengan memanggil TvInputService.Session metode notifyContentAllowed() atau notifyContentBlocked() , seperti yang ditunjukkan dalam contoh sebelumnya.

Gunakan class TvContentRating untuk membuat string yang ditentukan sistem untuk COLUMN_CONTENT_RATING dengan atribut 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 seperti jenis trek (video, audio, atau subtitel) dan sebagainya.

Pertama kali sesi input TV Anda bisa mendapatkan informasi trek, sesi input TV harus memanggil TvInputService.Session.notifyTracksChanged() dengan daftar semua jalur untuk mengupdate aplikasi TV sistem. Jika ada adalah perubahan informasi jalur, notifyTracksChanged() lagi untuk memperbarui sistem.

Aplikasi TV yang ada pada sistem menyediakan antarmuka bagi pengguna untuk memilih jalur tertentu jika lebih dari satu jalur tersedia untuk jenis jalur tertentu; misalnya, subtitel dalam berbagai bahasa. TV Anda input merespons onSelectTrack() dari aplikasi TV sistem dengan memanggil notifyTrackSelected() , seperti yang ditunjukkan dalam contoh berikut. Perhatikan bahwa saat null diteruskan sebagai ID trek, sehingga membatalkan pilihan trek tersebut.

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; }