Cómo obtener vistas previas de videos

Un video de vista previa es una excelente manera de alentar a los usuarios a crear un vínculo directo a tu app para TV. Las vistas previas pueden variar desde clips cortos hasta avances completos de películas.

Cuando crees una vista previa, ten en cuenta los siguientes lineamientos:

  • No muestres anuncios en una vista previa. Si unes anuncios del lado del cliente, no los incluyas en videos de vista previa. Si agregas anuncios del servidor, proporciona un video sin anuncios para las vistas previas.
  • Para obtener la mejor calidad, los videos de vista previa deberían ser de 16:9 o 4:3. Consulta Atributos de programación de video para conocer los tamaños recomendados de videos de vista previa.
  • Cuando el video de vista previa y el afiche tienen relaciones de aspecto diferentes, la pantalla principal cambia el tamaño de la vista del póster a la relación de aspecto del video antes de reproducir la vista previa. El video no tiene formato letterbox. Por ejemplo, si la relación de aspecto del póster de arte es de ASPECT_RATIO_MOVIE_POSTER (1:1.441), pero la relación de aspecto del video es de 16:9, la vista del póster se transforma a una región de 16:9.
  • Cuando creas una vista previa, su contenido puede ser de acceso público o estar protegido por DRM. En cada caso, se aplican diferentes procedimientos. En esta página, se describen ambos.

Reproduce la vista previa en la pantalla principal

Si creas una vista previa con cualquiera de los tipos de video compatibles con ExoPlayer y la vista previa es de acceso público, puedes reproducir la vista previa directamente en la pantalla principal.

Cuando compiles un objeto PreviewProgram, usa setPreviewVideoUri() con una URL HTTPS de acceso público, como se muestra en el siguiente ejemplo. La vista previa puede ser de video o audio.

Kotlin

val previewVideoUrl = Uri.parse("https://www.example.com/preview.mp4")
val builder = PreviewProgram.Builder()
builder.setChannelId(channelId)
    // ...
    .setPreviewVideoUri(previewVideoUrl)

Java

Uri previewVideoUrl = Uri.parse("https://www.example.com/preview.mp4");
PreviewProgram.Builder builder = new PreviewProgram.Builder();
builder.setChannelId(channelId)
    // ...
    .setPreviewVideoUri(Uri.parse(previewVideoUrl));

Procesa la vista previa en una superficie

Si tu video está protegido por DRM o se encuentra en un tipo de contenido multimedia no compatible con ExoPlayer, usa un TvInputService. La pantalla principal de Android TV pasa un Surface a tu servicio llamando a onSetSurface(). Tu app obtiene los videos directamente en esta superficie desde onTune().

La renderización en superficie directa le permite a tu app controlar lo que se renderiza y cómo. Puedes superponer metadatos, como una atribución de canal.

Declara tu TvInputService en el manifiesto

Tu app debe proporcionar una implementación de TvInputService para que la pantalla principal pueda renderizar la vista previa.

En tu declaración de servicio, incluye un filtro de intents que especifique TvInputService como la acción a realizar con el intent. También declara los metadatos del servicio como recurso XML independiente. La declaración del servicio, el filtro de intents y la declaración de metadatos del servicio se muestran en el siguiente ejemplo:

<service android:name=".rich.PreviewInputService"
    android:permission="android.permission.BIND_TV_INPUT">
    <!-- Required filter used by the system to launch our account service. -->
    <intent-filter>
        <action android:name="android.media.tv.TvInputService" />
    </intent-filter>
    <!-- An XML file which describes this input. -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/previewinputservice" />
</service>

Define los metadatos del servicio en un archivo XML independiente. El archivo de metadatos del servicio se encuentra en el directorio de recursos XML de tu app y debe coincidir con el nombre del recurso que declaraste en el manifiesto. Con las entradas de manifiesto del ejemplo anterior, podrías crear un archivo en formato XML en res/xml/previewinputservice.xml, con una etiqueta tv-input vacía:

<?xml version="1.0" encoding="utf-8"?>
<tv-input/>

El marco de trabajo de entrada de TV debe tener esta etiqueta. Sin embargo, solo se usa para configurar canales en vivo. Como estás renderizando un video, la etiqueta debe estar vacía.

Crea un URI de video

Para indicar que tu video de vista previa debería procesarse en tu app en lugar de en la pantalla principal de Android TV, debes crear un URI de video para un PreviewProgram. El URI debe terminar con el identificador que tu app use para el contenido, de manera que puedas recuperar el contenido más adelante en TvInputService.

Si tu identificador es del tipo Long, usa TvContractCompat.buildPreviewProgramUri():

Kotlin

val id: Long = 1L // content identifier
val componentName = new ComponentName(context, PreviewVideoInputService.class)
val previewProgramVideoUri = TvContractCompat.buildPreviewProgramUri(id)
   .buildUpon()
   .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
   .build()

Java

Long id = 1L; // content identifier
ComponentName componentName = new ComponentName(context, PreviewVideoInputService.class);
previewProgramVideoUri = TvContractCompat.buildPreviewProgramUri(id)
       .buildUpon()
       .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
       .build();

Si tu identificador no es del tipo Long, compila el URI mediante Uri.withAppendedPath():

Kotlin

val previewProgramVideoUri = Uri.withAppendedPath(PreviewPrograms.CONTENT_URI, "content-identifier")
       .buildUpon()
       .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
       .build()

Java

previewProgramVideoUri = Uri.withAppendedPath(PreviewPrograms.CONTENT_URI, "content-identifier")
       .buildUpon()
       .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
       .build();

Tu app llama a onTune(Uri videoUri) para que Android TV inicie la vista previa del video.

Crea un servicio

En el siguiente ejemplo, se muestra cómo extender TvInputService para crear tu propio PreviewInputService. Ten en cuenta que el servicio usa un MediaPlayer para la reproducción, pero tu código puede usar cualquier reproductor de video disponible.

Kotlin

import android.content.Context
import android.media.MediaPlayer
import android.media.tv.TvInputService
import android.net.Uri
import android.util.Log
import android.view.Surface
import java.io.IOException

class PreviewVideoInputService : TvInputService() {

    override fun onCreateSession(inputId: String): TvInputService.Session? {
        return PreviewSession(this)
    }

    private inner class PreviewSession(
        internal var context: Context
    ) : TvInputService.Session(context) {
    
        internal var mediaPlayer: MediaPlayer? = MediaPlayer()

        override fun onRelease() {
            mediaPlayer?.release()
            mediaPlayer = null
        }

        override fun onTune(uri: Uri): Boolean {
            // Let the TvInputService know that the video is being loaded.
            notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING)
            // Fetch the stream url from the TV Provider database
            // for content://android.media.tv/preview_program/
            val id = uri.lastPathSegment
            // Load your video in the background.
            retrieveYourVideoPreviewUrl(id) { videoUri ->
                if (videoUri == null) {
                  Log.d(TAG, "Could not find video $id")
                  notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN)
                }

                try {
                    mPlayer.setDataSource(getApplicationContext(), videoUri)
                    mPlayer.prepare()
                    mPlayer.start()
                    notifyVideoAvailable()
                } catch (IOException e) {
                    Log.e(TAG, "Could not prepare media player", e)
                    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN)
                }
              }
          return true
        }

        override fun onSetSurface(surface: Surface?): Boolean {
            mediaPlayer?.setSurface(surface)
            return true
        }

        override fun onSetStreamVolume(volume: Float) {
            // The home screen may fade in and out the video's volume.
            // Your player should be updated accordingly.
            mediaPlayer?.setVolume(volume, volume)
        }

        override fun onSetCaptionEnabled(b: Boolean) {
            // enable/disable captions here
        }
    }

    companion object {
        private const val TAG = "PreviewInputService"
    }
}

Java

import android.content.Context;
import android.media.MediaPlayer;
import android.media.tv.TvInputService;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Surface;
import java.io.IOException;

public class PreviewVideoInputService extends TvInputService {
    private static final String TAG = "PreviewVideoInputService";

    @Nullable
    @Override
    public Session onCreateSession(String inputId) {
        return new PreviewSession(this);
    }

    private class PreviewSession extends TvInputService.Session {

        private MediaPlayer mPlayer;

        PreviewSession(Context context) {
            super(context);
            mPlayer = new MediaPlayer();
        }

        @Override
        public boolean onTune(Uri channelUri) {
            // Let the TvInputService know that the video is being loaded.
            notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING);
            // Fetch the stream url from the TV Provider database
            // for content://android.media.tv/preview_program/
            String id = uri.getLastPathSegment();
            // Load your video in the background.
            retrieveYourVideoPreviewUrl(id, new MyCallback() {
              public void callback(Uri videoUri) {
                if (videoUri == null) {
                  Log.d(TAG, "Could not find video" + id);
                  notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
                }

                try {
                    mPlayer.setDataSource(getApplicationContext(), videoUri);
                    mPlayer.prepare();
                    mPlayer.start();
                    notifyVideoAvailable();
                } catch (IOException e) {
                    Log.e(TAG, "Could not prepare media player", e);
                    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
                }
              }
            });
            return true;
        }

        @Override
        public boolean onSetSurface(@Nullable Surface surface) {
            if (mPlayer != null) {
                mPlayer.setSurface(surface);
            }
            return true;
        }

        @Override
        public void onRelease() {
            if (mPlayer != null) {
                mPlayer.release();
            }
            mPlayer = null;
        }

        @Override
        public void onSetStreamVolume(float volume) {
            if (mPlayer != null) {
                // The home screen may fade in and out the video's volume.
                // Your player should be updated accordingly.
                mPlayer.setVolume(volume, volume);
            }
        }

        @Override
        public void onSetCaptionEnabled(boolean enabled) {
            // enable/disable captions here
        }
    }
}