W telewizji na żywo użytkownik zmienia kanał, na którym wyświetlane są informacje o kanale i programie. Inne typy informacji np. komunikaty („NIE ATTEMPT AT HOME”), napisy lub reklamy. Jak w każdym telewizorze aplikacji, takie informacje nie powinny zakłócać odtwarzania treści programu na ekranie.
Zastanów się też, czy określone treści z programu powinny być prezentowane, oceny treści i ustawień kontroli rodzicielskiej oraz tego, jak aplikacja zachowuje się i informuje użytkownika, treść jest zablokowana lub niedostępna. Ta lekcja opisuje, jak skonfigurować użytkownika wejścia TV tych kwestii.
Wypróbuj Przykładowa aplikacja do obsługi wejścia TV.
Zintegruj odtwarzacz z powierzchnią
Wejście TV musi renderować film na obiekcie Surface
, który jest przekazywany przez
TvInputService.Session.onSetSurface()
. Oto przykład użycia instancji MediaPlayer
do odtwarzania
zawartość obiektu 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; }
Oto jak to zrobić przy użyciu 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; }
Użyj nakładki
Użyj nakładki do wyświetlania napisów, wiadomości, reklam lub transmisji danych MHEG-5. Domyślnie atrybut
nakładka jest wyłączona. Możesz ją włączyć podczas tworzenia sesji, wywołując
TvInputService.Session.setOverlayViewEnabled(true)
,
Jak w tym przykładzie:
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; }
Użyj obiektu View
dla nakładki zwracanej z narzędzia TvInputService.Session.onCreateOverlayView()
, jak pokazano tutaj:
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; }
Definicja układu nakładki może wyglądać np. tak:
<?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>
Kontrola treści
Gdy użytkownik wybierze kanał, wejście TV obsługuje wywołanie zwrotne onTune()
w obiekcie TvInputService.Session
. System TV
funkcje kontroli rodzicielskiej w aplikacji określają, jakie treści są wyświetlane w zależności od oceny treści.
W poniższych sekcjach opisano, jak zarządzać wyborem kanałów i programów za pomocą
TvInputService.Session
notify
metody, które
komunikują się z systemową aplikacją TV.
Ustaw film jako niedostępny
Gdy użytkownik zmienia kanał, chcesz mieć pewność, że na ekranie nie pojawią się żadne przypadkowe ruchy.
przed wyrenderowaniem treści przez wejście TV. Gdy dzwonisz pod numer TvInputService.Session.onTune()
,
możesz zapobiec wyświetlaniu filmu wideo, dzwoniąc pod numer TvInputService.Session.notifyVideoUnavailable()
i przekazując stałą VIDEO_UNAVAILABLE_REASON_TUNING
, jak
w poniższym przykładzie.
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; }
Następnie, gdy treść jest renderowana w narzędziu Surface
, wywołujesz funkcję
TvInputService.Session.notifyVideoAvailable()
aby film mógł się wyświetlić, na przykład:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
To przejście trwa tylko ułamki sekundy, ale prezentowanie pustego ekranu jest jest lepszy wizualnie niż zezwalanie na wyświetlanie na zdjęciu dziwnych skoków i zakłóceń.
Więcej informacji o pracy znajdziesz w artykule Integrowanie odtwarzacza z platformą.
używając pola Surface
, by wygenerować film.
Włącz kontrolę rodzicielską
Aby określić, czy dane treści są blokowane przez kontrolę rodzicielską i ocenę treści, sprawdź
TvInputManager
metody klasy, isParentalControlsEnabled()
i isRatingBlocked(android.media.tv.TvContentRating)
. Ty
sprawdź też, czy atrybut TvContentRating
treści jest uwzględniony w
zestaw aktualnie dozwolonych ocen treści. Wzięte pod uwagę te kwestie znajdziesz w przykładzie poniżej.
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); }
Gdy ustalisz, czy treść powinna zostać zablokowana, powiadom system TV
przez wywołanie
TvInputService.Session
metoda notifyContentAllowed()
lub
notifyContentBlocked()
tak jak w poprzednim przykładzie.
Użyj klasy TvContentRating
, aby wygenerować zdefiniowany przez system ciąg znaków dla
COLUMN_CONTENT_RATING
z
TvContentRating.createRating()
Jak poniżej:
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");
Obsługa wyboru ścieżki
Klasa TvTrackInfo
zawiera informacje o ścieżkach multimedialnych, takie jak
typ ścieżki (wideo, dźwięk lub napisy) itd.
Za pierwszym razem, gdy sesja wejścia TV może uzyskać informacje o utworze, powinna wywołać metodę
TvInputService.Session.notifyTracksChanged()
z listą wszystkich utworów do zaktualizowania systemowej aplikacji TV. Kiedy tam
dotyczy zmiany informacji o ścieżce,
notifyTracksChanged()
aby zaktualizować system.
Systemowa aplikacja TV udostępnia interfejs, w którym użytkownik może wybrać konkretną ścieżkę
ścieżka audio jest dostępna dla danego typu ścieżki; np. napisów w różnych językach. Twój telewizor
dane wejściowe reagują na
onSelectTrack()
z systemowej aplikacji TV, wybierając
notifyTrackSelected()
Jak widać w przykładzie poniżej. Pamiętaj, że gdy null
jest przekazywany jako identyfikator ścieżki, co powoduje usunięcie ścieżki.
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; }