Beim Live-TV wechselt der Nutzer den Kanal und erhält kurz Kanal- und Programminformationen, bevor diese ausgeblendet werden. Andere Arten von Informationen wie Nachrichten („DO NOT ATTEMPT AT HOME“), Untertitel oder Anzeigen müssen möglicherweise beibehalten werden. Wie bei jeder TV-App dürfen diese Informationen die auf dem Bildschirm wiedergegebenen Programminhalte nicht beeinträchtigen.
Überlegen Sie auch, ob bestimmte Programminhalte präsentiert werden sollen. Berücksichtigen Sie dabei die Altersfreigabe und Jugendschutzeinstellungen der Inhalte, das Verhalten Ihrer App und eine Benachrichtigung, wenn Inhalte blockiert oder nicht verfügbar sind. In dieser Lektion wird beschrieben, wie Sie anhand dieser Überlegungen die User Experience Ihrer TV-Eingabe entwickeln können.
Probieren Sie die Beispiel-App TV Input Service aus.
Player in Oberfläche einbinden
Ihre TV-Eingabe muss das Video in einem Surface
-Objekt rendern, das von der Methode TvInputService.Session.onSetSurface()
übergeben wird. Hier ein Beispiel für die Verwendung einer MediaPlayer
-Instanz zum Abspielen von Inhalten im Surface
-Objekt:
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; }
So verwendest du 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; }
Overlay verwenden
Mit Overlays können Sie Untertitel, Nachrichten, Werbung oder MHEG-5-Datensendungen einblenden. Standardmäßig ist das Overlay deaktiviert. Sie können sie beim Erstellen der Sitzung aktivieren, indem Sie TvInputService.Session.setOverlayViewEnabled(true)
wie im folgenden Beispiel aufrufen:
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; }
Verwenden Sie wie hier gezeigt ein View
-Objekt für das Overlay, das von TvInputService.Session.onCreateOverlayView()
zurückgegeben wird:
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; }
Die Layoutdefinition für das Overlay könnte wie folgt aussehen:
<?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>
Inhalte verwalten
Wenn der Nutzer einen Kanal auswählt, verarbeitet Ihre TV-Eingabe den onTune()
-Callback im TvInputService.Session
-Objekt. Die Jugendschutzeinstellungen des Systems bestimmen, welche Inhalte aufgrund der Altersfreigabe angezeigt werden.
In den folgenden Abschnitten wird beschrieben, wie du die Kanal- und Programmauswahl mit den TvInputService.Session
notify
-Methoden verwaltest, die mit der System-TV-App kommunizieren.
Video nicht verfügbar machen
Wenn der Nutzer den Kanal wechselt, sollten Sie darauf achten, dass auf dem Bildschirm vor dem Rendern des Inhalts keine irgendeinen Videoartefakte auf dem Fernseher erscheinen. Wenn Sie TvInputService.Session.onTune()
aufrufen, können Sie verhindern, dass das Video präsentiert wird. Dazu rufen Sie TvInputService.Session.notifyVideoUnavailable()
auf und übergeben die Konstante VIDEO_UNAVAILABLE_REASON_TUNING
, wie im folgenden Beispiel gezeigt.
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; }
Wenn der Inhalt dann im Surface
gerendert wird, rufen Sie TvInputService.Session.notifyVideoAvailable()
auf, damit das Video angezeigt werden kann. Beispiel:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
Dieser Übergang dauert nur Bruchteile einer Sekunde, aber die Darstellung eines leeren Bildschirms ist optisch besser, als im Bild seltsame Ausbrüche und Bildrisse zu erzeugen.
Weitere Informationen zum Rendern von Videos mit Surface
findest du unter Player in Oberfläche integrieren.
Jugendschutzeinstellungen bereitstellen
Wenn Sie feststellen möchten, ob bestimmte Inhalte durch Jugendschutzeinstellungen und Altersfreigaben blockiert werden, sehen Sie sich die TvInputManager
-Klassenmethoden isParentalControlsEnabled()
und isRatingBlocked(android.media.tv.TvContentRating)
an. Außerdem ist es sinnvoll, dafür zu sorgen, dass die TvContentRating
des Inhalts in einer Reihe derzeit zulässiger Altersfreigaben enthalten ist. Diese Überlegungen werden im folgenden Beispiel gezeigt.
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); }
Sobald Sie festgestellt haben, ob der Inhalt blockiert werden soll, informieren Sie die System-TV-App durch Aufrufen der TvInputService.Session
-Methode notifyContentAllowed()
oder notifyContentBlocked()
, wie im vorherigen Beispiel gezeigt.
Verwenden Sie die Klasse TvContentRating
, um den systemdefinierten String für COLUMN_CONTENT_RATING
mit der Methode TvContentRating.createRating()
zu generieren, wie hier gezeigt:
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");
Track-Auswahl behandeln
Die Klasse TvTrackInfo
enthält Informationen zu Medienspuren, z. B. den Tracktyp (Video, Audio oder Untertitel).
Wenn Ihre TV-Eingabesitzung zum ersten Mal Titelinformationen abrufen kann, sollte sie TvInputService.Session.notifyTracksChanged()
mit einer Liste aller Tracks aufrufen, um die System-TV-App zu aktualisieren. Wenn sich die Titelinformationen ändern, rufen Sie noch einmal notifyTracksChanged()
auf, um das System zu aktualisieren.
Die System-TV-App bietet dem Nutzer eine Schnittstelle zur Auswahl eines bestimmten Titels, wenn für einen bestimmten Titeltyp mehr als ein Titel verfügbar ist, z. B. Untertitel in verschiedenen Sprachen. Die TV-Eingabe reagiert auf den onSelectTrack()
-Aufruf der System-TV-App durch Aufrufen von notifyTrackSelected()
, wie im folgenden Beispiel gezeigt. Wenn null
als Track-ID übergeben wird, wird die Auswahl des Tracks aufgehoben.
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; }