Nell'esperienza con la TV in diretta, l'utente cambia canale e visualizza le informazioni sul canale e sul programma prima che le informazioni scompaiano. Altri tipi di informazioni, come messaggi ("NON TENTARE A CASA"), sottotitoli o annunci potrebbero dover persistere. Come con qualsiasi TV tali informazioni non devono interferire con il contenuto del programma riprodotto sullo schermo.
Valuta anche se devono essere presentati alcuni contenuti del programma, dato il la classificazione dei contenuti e le impostazioni del Controllo genitori, nonché il comportamento dell'app e informazioni all'utente quando se i contenuti sono bloccati o non disponibili. Questa lezione descrive come sviluppare l'input TV dell'utente per queste considerazioni.
Prova App di esempio del servizio di input TV.
Integra il player con la superficie
L'ingresso della TV deve eseguire il rendering del video su un oggetto Surface
, che viene passato da
TvInputService.Session.onSetSurface()
. Ecco un esempio di come utilizzare un'istanza MediaPlayer
per riprodurre
contenuti nell'oggetto 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; }
Analogamente, ecco come fare utilizzando 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; }
Utilizzare un overlay
Utilizza un overlay per visualizzare sottotitoli, messaggi, annunci o trasmissioni di dati MHEG-5. Per impostazione predefinita,
l'overlay è disattivato. Puoi abilitarlo quando crei la sessione chiamando
TvInputService.Session.setOverlayViewEnabled(true)
,
come nell'esempio seguente:
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; }
Utilizza un oggetto View
per l'overlay, restituito da TvInputService.Session.onCreateOverlayView()
, come mostrato qui:
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; }
La definizione del layout dell'overlay potrebbe avere un aspetto simile al seguente:
<?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>
Controlla i contenuti
Quando l'utente seleziona un canale, l'ingresso TV gestisce il callback onTune()
nell'oggetto TvInputService.Session
. La TV di sistema
il Controllo genitori dell'app determina quali contenuti mostrare in base alla classificazione dei contenuti.
Le seguenti sezioni descrivono come gestire la selezione del canale e del programma utilizzando il
TvInputService.Session
metodo notify
che
comunicare con l'app di sistema per la TV.
Rendi il video non disponibile
Quando l'utente cambia canale, devi assicurarti che lo schermo non mostri
artefatti video prima che l'ingresso TV esegua il rendering dei contenuti. Quando chiami TvInputService.Session.onTune()
,
puoi impedire che il video venga presentato chiamando il numero TvInputService.Session.notifyVideoUnavailable()
e passando la costante VIDEO_UNAVAILABLE_REASON_TUNING
, come
come mostrato nell'esempio che segue.
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; }
Quando i contenuti vengono visualizzati in Surface
, chiami
TvInputService.Session.notifyVideoAvailable()
per consentire la visualizzazione del video, in questo modo:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
Questa transizione dura solo frazioni di secondo, ma la presentazione di una schermata vuota visivamente migliore rispetto a consentire all'immagine di lampeggiare strani blip e tremolio.
Vedi anche Integrare il player con la piattaforma per maggiori informazioni sul funzionamento.
con Surface
per eseguire il rendering del video.
Fornire il Controllo genitori
Per stabilire se determinati contenuti sono bloccati dal Controllo genitori e dalla classificazione dei contenuti, controlla le
TvInputManager
metodo del corso, isParentalControlsEnabled()
e isRatingBlocked(android.media.tv.TvContentRating)
. Tu
potresti anche voler assicurarti che l'elemento TvContentRating
dei contenuti sia incluso in un
un insieme di classificazioni dei contenuti attualmente consentite. Queste considerazioni sono mostrate nel seguente esempio.
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); }
Dopo aver stabilito se i contenuti devono o non devono essere bloccati, informa la TV di sistema
richiamando l'app
Metodo TvInputService.Session
notifyContentAllowed()
o
notifyContentBlocked()
, come mostrato nell'esempio precedente.
Utilizza la classe TvContentRating
per generare la stringa definita dal sistema per
COLUMN_CONTENT_RATING
con
TvContentRating.createRating()
come mostrato qui:
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");
Gestire la selezione delle tracce
Il corso TvTrackInfo
contiene informazioni sulle tracce multimediali, ad esempio
come tipo di traccia (video, audio o sottotitoli) e così via.
La prima volta che la sessione di input TV riesce a ottenere informazioni sui brani, dovrebbe chiamare
TvInputService.Session.notifyTracksChanged()
con un elenco di tutti i canali per aggiornare l'app di sistema TV. Quando ci sono
è un cambiamento nelle informazioni sulla traccia,
notifyTracksChanged()
di nuovo per aggiornare il sistema.
L'app TV di sistema fornisce all'utente un'interfaccia per selezionare un canale specifico se è presente più di un canale
la traccia sia disponibile per un determinato tipo di canale; ad esempio sottotitoli in lingue diverse. La tua TV
l'input risponde
onSelectTrack()
chiama dall'app di sistema per la TV chiamando
notifyTrackSelected()
, come mostrato nell'esempio seguente. Tieni presente che quando null
viene trasmesso come ID traccia, la traccia deseleziona.
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; }