預覽影片是鼓勵使用者透過深層連結前往電視應用程式的好方法。預覽影片可以是短片或完整電影預告片。
建立預覽時,請考量下列準則:
- 不要在預覽畫面中顯示廣告。如果您是在用戶端拼接廣告 也不要拼接成預覽影片如果您在伺服器端撰寫廣告,提供無廣告的預覽影片。
- 為獲得最佳畫質,請將預覽影片設為 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 } } }