動画をプレビューする

プレビュー動画は、ユーザーに 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
        }
    }
}