プレビュー動画は、ユーザーにテレビアプリへのディープリンクを促すのに最適な方法です。プレビューは、短いクリップから映画の予告編まで、さまざまなものがあります。
プレビューを作成する際は、次のガイドラインに沿って進めてください。
- プレビューに広告を表示しない。クライアントサイドで広告をスティッチする場合は、プレビュー動画にスティッチしないでください。サーバーサイドで広告をステッチする場合は、プレビュー用の広告なしの動画を提供します。
- 最高の品質を実現するため、プレビュー動画は 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 リソース ディレクトリにあり、マニフェストで宣言したリソースの名前と一致する必要があります。前の例のマニフェスト エントリを使用すると、res/xml/previewinputservice.xml に空の tv-input タグを含む 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/<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
}
}
}