Dans l'expérience de télévision en direct, l'utilisateur change de chaîne et voit s'afficher brièvement les informations sur la chaîne et le programme avant qu'elles ne disparaissent. D'autres types d'informations, par exemple des messages ("NE PAS ATTENDRE À LA MAISON"), des sous-titres ou des publicités doivent rester affichés. Comme pour tous les téléviseurs application, ces informations ne doivent pas interférer avec le contenu du programme qui s'affiche à l'écran.
![](https://developer.android.google.cn/static/images/tv/do-not-attempt.png?authuser=19&hl=fr)
Figure 1 : Un message en superposition dans une application de télévision en direct
Demandez également si certains contenus de programme doivent être présentés, compte tenu de la la classification du contenu et les paramètres de contrôle parental, le comportement de votre application et le contenu est bloqué ou indisponible. Cette leçon explique comment définir les paramètres utilisateur de l'entrée TV pour ces considérations.
Essayez le Exemple d'application TV Input Service.
Intégrer le lecteur à la surface
Votre entrée TV doit effectuer le rendu de la vidéo dans un objet Surface
, qui est transmis par
le TvInputService.Session.onSetSurface()
. Voici un exemple d'utilisation d'une instance MediaPlayer
pour la lecture
dans l'objet 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; }
De même, voici comment procéder avec 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; }
Utiliser une superposition
Utilisez une superposition pour afficher des sous-titres, des messages, des annonces ou des diffusions de données MHEG-5. Par défaut,
la superposition est désactivée. Vous pouvez l'activer lorsque vous créez la session en appelant
TvInputService.Session.setOverlayViewEnabled(true)
,
comme dans l'exemple suivant:
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; }
Utilisez un objet View
pour la superposition, renvoyé par TvInputService.Session.onCreateOverlayView()
, comme indiqué ci-dessous:
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; }
Voici à quoi peut ressembler la mise en page de la superposition:
<?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>
Contrôler le contenu
Lorsque l'utilisateur sélectionne une chaîne, votre entrée TV gère le rappel onTune()
dans l'objet TvInputService.Session
. Le téléviseur système
le contrôle parental de l'application détermine le contenu qui s'affiche, en fonction de sa classification.
Les sections suivantes décrivent comment gérer la sélection des chaînes et des programmes à l'aide des
TvInputService.Session
méthode notify
qui
communiquer avec l'application System TV.
Rendre la vidéo indisponible
Lorsque l'utilisateur change de chaîne, vous devez vous assurer que l'écran n'affiche aucun
des artefacts vidéo avant que l'entrée TV ne restitue le contenu. Lorsque vous appelez TvInputService.Session.onTune()
,
vous pouvez empêcher la présentation de la vidéo en appelant TvInputService.Session.notifyVideoUnavailable()
et en transmettant la constante VIDEO_UNAVAILABLE_REASON_TUNING
,
comme illustré dans l'exemple suivant.
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; }
Ensuite, lorsque le contenu est affiché dans Surface
, vous appelez
TvInputService.Session.notifyVideoAvailable()
pour autoriser l'affichage de la vidéo:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
Cette transition ne dure que des fractions de seconde, mais présenter un écran vide est visuellement plus efficace que de laisser la photo clignoter avec des saccades étranges.
Pour en savoir plus sur l'utilisation, consultez également Intégrer le lecteur avec la surface.
avec Surface
pour effectuer le rendu de la vidéo.
Fournir un contrôle parental
Pour déterminer si un contenu donné est bloqué par le contrôle parental et la classification du contenu, consultez le
Méthodes de classe TvInputManager
, isParentalControlsEnabled()
et isRatingBlocked(android.media.tv.TvContentRating)
. Toi
vous pouvez également vous assurer que le TvContentRating
du contenu est inclus dans
ensemble de classifications de contenu actuellement autorisées. Ces considérations sont présentées dans l'exemple suivant.
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); }
Une fois que vous avez déterminé si le contenu doit ou non être bloqué, informez-en le téléviseur système
en appelant la méthode
Méthode notifyContentAllowed()
de TvInputService.Session
ou
notifyContentBlocked()
, comme illustré dans l'exemple précédent.
Utilisez la classe TvContentRating
afin de générer la chaîne définie par le système pour
le COLUMN_CONTENT_RATING
par
TvContentRating.createRating()
, comme indiqué ici:
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");
Gérer la sélection de titres
La classe TvTrackInfo
contient des informations sur les pistes multimédias telles que
comme type de piste (vidéo, audio ou sous-titres), etc.
La première fois que votre session d'entrée TV est en mesure d'obtenir des informations sur un titre, elle doit appeler
TvInputService.Session.notifyTracksChanged()
avec la liste de tous les canaux pour mettre à jour l'appli TV système. Lorsqu'il
est un changement dans les informations sur le titre,
notifyTracksChanged()
pour mettre à jour
le système.
L'application System TV fournit une interface permettant à l'utilisateur de sélectionner une piste spécifique
est disponible pour un type de piste donné ; comme des sous-titres dans différentes langues. Votre téléviseur
répond à l'événement
onSelectTrack()
à partir de l'application system TV en appelant
notifyTrackSelected()
, comme illustré dans l'exemple suivant. Notez que lorsque null
est transmis en tant qu'ID de piste, le titre est désélectionné.
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; }