Un video de vista previa 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 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 material gráfico de póster 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 llamando 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 que se va 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 en 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 use 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 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 } } }