動画をプレビューする

プレビュー動画は、テレビアプリに遷移するディープリンクをユーザーに促す優れた方法です。短いクリップから完全な映画の予告編まで、プレビューにはさまざまな種類があります。

プレビューを作成する際は、次のガイドラインに沿って進めてください。

  • プレビュー内に広告を表示しないでください。クライアント サイドで広告を合成する場合は、プレビュー動画には広告を合成しないようにしてください。サーバーサイドで広告を合成する場合は、プレビュー用に広告のない動画を提供してください。
  • 最高の品質を実現するため、プレビュー動画は 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 リソース ディレクトリに存在しますが、マニフェストで宣言したリソースの名前と一致する必要があります。上記の例のマニフェスト エントリを使用して、次のように空の 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
            }
        }
    }