预览视频是鼓励用户深度链接到您的电视应用的好方法。预览可以从短片到完整的电影预告片不等。
创建预览时,请考虑以下准则
- 不要在预览中显示广告。如果您在客户端拼接广告,请不要将其拼接到预览视频中。如果您在服务器端拼接广告,请为预览提供无广告视频。
- 为了获得最佳质量,预览视频应为 16:9 或 4:3。请参阅 视频节目属性,了解推荐的预览视频尺寸。
- 当预览视频和海报艺术具有不同的纵横比时,主屏幕会在播放预览之前将海报视图的大小调整为视频的纵横比。视频不会进行黑边处理。例如,如果海报艺术比例为
ASPECT_RATIO_MOVIE_POSTER
(1:1.441),但视频比例为 16:9,则海报视图将转换为 16:9 区域。 - 创建预览时,其内容可以公开访问或受 DRM 保护。每种情况下适用的程序不同。此页面描述了这两种情况。
在主屏幕上播放预览
如果您使用 ExoPlayer 支持的任何 视频类型 创建预览,并且预览可公开访问,则可以直接在主屏幕上播放预览。
构建 PreviewProgram 时,使用 setPreviewVideoUri()
和可公开访问的 HTTPS URL,如下面的示例所示。预览可以是 视频 或 音频。
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));
在表面上渲染预览
如果您的视频受 DRM 保护或使用 ExoPlayer 不支持的媒体类型,请使用 TvInputService
。Android TV 主屏幕通过调用 onSetSurface()
将 Surface
传递到您的服务。您的应用从 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 资源目录中,并且必须与您在清单中声明的资源名称匹配。使用前面示例中的清单条目,您将在 res/xml/previewinputservice.xml
创建一个 XML 文件,其中包含一个空的 tv-input
标签
<?xml version="1.0" encoding="utf-8"?>
<tv-input/>
电视输入框架 必须具有此标签。但是,它仅用于配置直播频道。由于您正在渲染视频,因此该标签应为空。
创建视频 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 } } }