プレビュー動画は、ユーザーに TV アプリへのディープリンクを促すのに効果的です。短いクリップか���映画の予告編全体まで、さまざまなプレビューが可能です。
プレビューを作成する際は、次のガイドラインに沿って進めてください。
- プレビュー内に広告を表示しないでください。クライアントサイドで広告を合成する場合、プレビュー動画では広告を合成しないでください。サーバーサイドで広告を合成する場合は、プレビュー用に広告なしの動画を用�������す。
- 最高の品質を実現するため、プレビュー動画は 16:9 または 4:3 にします。プレビュー動画の推奨サイズについては、動画プログラムの属性をご覧ください。
- プレビュー動画とポスターアートのアスペクト比が異なる場合、ホーム画面はプレビューを再生する前に、ポスタービューのサイズを動画のアスペクト比に変更します。動画はレターボックス化されません。たとえば、ポスターアートのアスペクト比が
ASPECT_RATIO_MOVIE_POSTER
(1:1.441)で、動画のアスペクト比が 16:9 の場合、ポスタービューは 16:9 の領域に変換されます。 - プレビューを作成する際に、そのコンテンツを一般公開するか、DRM の下で保護できます。それぞれの場合で異なる手順が適用されます。このページでは、その両方について説明します。
ホーム画面でプレビューを再生する
ExoPlayer でサポートされている動画タイプのいずれかを使用してプレビューを作成し、プレビューが一般公開されている場合は、ホーム画面で直接プレビューを再生できます。
PreviewProgram をビルドする際には、以下の例に示すように、一般公開された HTTPS URL で setPreviewVideoUri()
を使用します。プレビューは、動画または音声のいずれかです。
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 リソース ディレクトリにあり、マニフェストで宣言したリソースの名前と一致する必要があります。前の例のマニフェスト エントリを使用して、空の tv-input
タグを含む XML ファイルを res/xml/previewinputservice.xml
に作成します。
<?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 } } }