預覽影片

預覽影片是鼓勵使用者透過深層連結前往電視應用程式的好方法。預覽影片可以是短片或完整電影預告片。

建立預覽時,請考量下列準則:

  • 不要在預覽畫面中顯示廣告。如果您是在用戶端拼接廣告 也不要拼接成預覽影片如果您在伺服器端撰寫廣告,提供無廣告的預覽影片。
  • 為獲得最佳畫質,請將預覽影片設為 16:9 或 4:3。如需預覽影片的建議大小,請參閱「影片節目屬性」。
  • 如果預覽影片和海報圖片的顯示比例不同,主畫面會在播放預覽畫面前,將海報檢視畫面調整為影片的長寬比。影片並未加上黑邊。舉例來說,如果海報圖片比率為 ASPECT_RATIO_MOVIE_POSTER (1:1.441),但影片比例為 16:9,海報檢視畫面會轉換為 16:9 區域。
  • 建立預覽時,其內容可透過 DRM 公開存取或保護。這種情況各有不同。本頁面將說明這兩種做法。

在主畫面上播放預覽畫面

如果您使用 ExoPlayer 支援的任何影片類型建立預覽,且可公開存取預覽,可直接在主畫面上播放預覽畫面。

建構 PreviewProgram 時,請使用 setPreviewVideoUri() 搭配可公開存取的 HTTPS 網址,如以下範例所示。預覽可以是影片音訊

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 應以應用程式用於內容的 ID 結尾,方便您稍後在 TvInputService 中擷取內容。

如果 ID 類型為 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();

如果 ID 不是 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
        }
    }
}