Предварительный просмотр видео

Предварительный просмотр видеоролика — отличный способ побудить пользователей перейти по прямой ссылке в ваше приложение для ТВ. Предварительные просмотры могут варьироваться от коротких клипов до полноценных трейлеров фильмов.

При создании предварительного просмотра учитывайте следующие рекомендации:

  • Не показывайте рекламу в предварительном просмотре. Если вы встраиваете рекламу на стороне клиента, не встраивайте её в видео предварительного просмотра. Если вы встраиваете рекламу на стороне сервера, предоставляйте видео без рекламы для предварительного просмотра.
  • Для наилучшего качества предварительные видеоролики следует просматривать в формате 16:9 или 4:3. Рекомендуемые размеры предварительных видеороликов указаны в разделе «Атрибуты видеопрограммы» .
  • Если у предварительного просмотра видео и постера разные соотношения сторон, на главном экране размер постера изменяется в соответствии с соотношением сторон видео перед воспроизведением предварительного просмотра. Видео не отображается в формате Letterbox. Например, если соотношение сторон постера — ASPECT_RATIO_MOVIE_POSTER (1:1.441), а соотношение сторон видео — 16:9, то изображение постера преобразуется в область 16:9.
  • При создании предварительного просмотра его содержимое может быть общедоступным или защищенным с помощью DRM. В каждом случае применяются разные процедуры. На этой странице описаны оба варианта.

Воспроизведите предварительную версию на главном экране.

Если вы создадите предварительный просмотр, используя любой из поддерживаемых ExoPlayer типов видео , и этот предварительный просмотр будет общедоступным, вы сможете воспроизвести его непосредственно на главном экране.

При создании объекта PreviewProgram используйте setPreviewVideoUri() с общедоступным HTTPS-адресом, как показано в следующем примере. Предварительный просмотр может быть либо видео , либо аудио .

Котлин

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));

Отобразите предварительный просмотр на поверхности.

Если ваше видео защищено DRM или относится к типу мультимедиа, не поддерживаемому ExoPlayer , используйте TvInputService . Главный экран Android TV передает Surface вашему сервису, вызывая метод onSetSurface() . Ваше приложение отрисовывает видео непосредственно на этой поверхности из onTune() .

Прямая отрисовка поверхности позволяет вашему приложению контролировать, что и как отображается. Вы можете накладывать метаданные, такие как атрибуция канала.

Объявите свой TvInputService в манифесте.

Ваше приложение должно предоставлять реализацию класса TvInputService , чтобы главный экран мог отображать предварительный просмотр.

В объявлении вашей службы включите фильтр намерений, указывающий TvInputService в качестве действия, которое необходимо выполнить с помощью намерения. Также объявите метаданные службы как отдельный XML-ресурс. Объявление службы, фильтр намерений и объявление метаданных службы показаны в следующем примере:

<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>

Определите метаданные сервиса в отдельном XML-файле. Файл метаданных сервиса находится в каталоге XML-ресурсов вашего приложения и должен совпадать с именем ресурса, объявленного вами в манифесте. Используя записи манифеста из предыдущего примера, вы должны создать XML-файл по адресу res/xml/previewinputservice.xml с пустым тегом tv-input :

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

В компоненте TV Input Framework этот тег необходим. Однако он используется только для настройки каналов прямого эфира. Поскольку вы рендерите видео, тег должен быть пустым.

Создать URI видео

Чтобы указать, что предварительный просмотр видео должен отображаться вашим приложением, а не на главном экране Android TV, необходимо создать URI видео для PreviewProgram . URI должен заканчиваться идентификатором, который ваше приложение использует для контента, чтобы вы могли получить доступ к контенту позже в TvInputService .

Если ваш идентификатор имеет тип Long , используйте TvContractCompat.buildPreviewProgramUri() :

Котлин

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();

Если ваш идентификатор не относится к типу Long , сформируйте URI с помощью Uri.withAppendedPath() :

Котлин

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();

Ваше приложение вызывает onTune(Uri videoUri) , чтобы запустить предварительный просмотр видео на Android TV.

Создать сервис

В следующем примере показано, как расширить класс TvInputService для создания собственного PreviewInputService . Обратите внимание, что для воспроизведения используется MediaPlayer , но ваш код может использовать любой доступный видеоплеер.

Котлин

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/<id>
            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/<id>
            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
        }
    }
}