Videos de vista previa

Una vista previa de video es una gran forma de alentar a los usuarios a acceder mediante un vínculo directo a tu app. Las vistas previas pueden variar de clips breves a avances completos de películas.

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

  • No muestres anuncios en una vista previa. Si agregas anuncios del lado del cliente, no los agregues en videos de vista previa. Si agregas anuncios del lado 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. En Atributos de programación de video, puedes consultar los tamaños recomendados para videos de vista previa.
  • Cuando el video de vista previa y el póster de arte tienen diferentes relaciones de aspecto, 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 estar disponible públicamente 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 está accesible públicamente, puedes reproducir la vista previa directamente en la pantalla principal.

Cuando compiles un objeto PreviewProgram, usa setPreviewVideoUri() con una URL HTTPS pública, 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 archivo multimedia no compatible con ExoPlayer, usa un objeto TvInputService. La pantalla principal de Android TV pasa un objeto Surface a tu servicio mediante una llamada a onSetSurface(). Tu app obtiene los videos directamente en esta superficie desde onTune().

El procesamiento en superficie directa le permite a tu app controlar lo que se procesa y cómo se hace. 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 procesar tu 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 de XML de tu app y debe coincidir con el nombre del recurso que declaraste en el manifiesto. Con las entradas del manifiesto del ejemplo anterior, puedes crear un archivo 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 procesando un video, la etiqueta debería 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 la pantalla principal de Android TV, debes crear un URI de video para un objeto PreviewProgram. El URI debería finalizar con el identificador que tu app usar para el contenido, de manera que puedas recuperar el contenido más tarde 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) a fin de 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 objeto PreviewInputService. Ten en cuenta que el servicio usa un objeto 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
            }
        }
    }