预览视频

要鼓励用户访问您的 TV 应用中的深层链接,预览视频是一种不错的方法。预览内容可以是简短的视频剪辑,也可以是完整的电影预告片。

在创建预览时,请注意以下准则:

  • 不要在预览中显示广告。如果您在客户端拼接广告,请勿将它们拼接到预览视频中。如果您在服务器端拼接广告,请提供无广告的预览视频。
  • 为获得最佳品质,预览视频的宽高比应为 16:9 或 4:3。请参阅视频节目属性,以获取预览视频的建议尺寸。
  • 当预览视频和海报图片具有不同的宽高比时,在播放预览视频之前,主屏幕会将海报视图调整到视频的宽高比。视频不是信箱模式的。例如,如果海报图片宽高比是 ASPECT_RATIO_MOVIE_POSTER (1:1.441),但视频宽高比是 16:9,则海报视图会转换为 16:9 区域。
  • 在您创建预览时,其内容可供公开访问,也可以受 DRM 保护。每种情形采用的步骤不同。本页对两者都做了介绍。

在主屏幕中播放预览

如果您使用 ExoPlayer 支持的任何视频类型创建预览,并且预览可供公开访问,那么您可以直接在主屏幕中播放预览。

当您构建 PreviewProgram 时,请将 setPreviewVideoUri() 用于可公开访问的 HTTPS 网址,如以下示例所示。预览可以是视频音频

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

在 Surface 上呈现预览

如果您的视频受 DRM 保护,或不是 ExoPlayer 支持的媒体类型,请使用 TvInputService。Android TV 主屏幕会通过调用 onSetSurface()Surface 传递到您的服务。您的应用会从 onTune() 直接在这个 Surface 上绘制视频。

通过直接在 Surface 上呈现,您的应用可以控制呈现的内容和方式。您可以叠加频道归因等元数据。

在清单中声明 TvInputService

您的应用必须提供 TvInputService 的实现,以便主屏幕能够呈现您的预览。

在您的服务声明中,请包含一个 intent 过滤器,将 TvInputService 指定为通过该 intent 执行的操作。另外,请将服务元数据声明为单独的 XML 资源。以下示例演示了服务声明、intent 过滤器和服务元数据声明:

    <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 资源目录中,并且必须与您在清单中声明的资源名称相匹配。利用上例中的清单条目,您可以在 res/xml/previewinputservice.xml 创建一个含有空 tv-input 标记的 XML 文件:

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

TV 输入框架必须包含这个标记。不过,它仅用于配置直播频道。由于您要呈现的是视频,因此该标记应为空。

创建视频 URI

要指明您的预览视频应通过您的应用而非 Android TV 主屏幕来呈现,您必须为 PreviewProgram 创建一个视频 URI。此 URI 应当以您的应用用于内容的标识符结尾,以便您可以稍后在 TvInputService 中检索该内容。

如果标识符是 Long 类型,请使用 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();

如果标识符不是 Long 类型,请使用 Uri.withAppendedPath() 来构建 URI:

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

您的应用通过调用 onTune(Uri videoUri) 来使 Android TV 启动预览视频。

创建服务

以下示例演示了如何通过扩展 TvInputService 来创建您自己的 PreviewInputService。注意该服务使用 MediaPlayer 进行播放,但您的代码可以使用任何可用的视频播放器。

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