预览视频

预览视频是鼓励用户深层链接到您的电视应用的好方法。 预览视频可以是短片,也可以是完整影片的预告片。

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

  • 不在预览中显示广告。如果您在客户端拼接广告,请勿将广告拼接到预览视频中。如果您在服务器端拼接广告,请提供无广告视频以供预览。
  • 为获得最佳品质,预览视频的宽高比应为 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 渲染,应用可以控制渲染的内容和渲染方式。您可以叠加频道归因等元数据。

在清单中声明 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 创建一个 XML 文件,其中包含一个空的 tv-input 标记:

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