預覽影片是鼓勵使用者深層連結至電視應用程式的絕佳方式。 預覽影片可以是短片,也可以是完整的電影預告。
建立預覽畫面時,請注意下列事項:
- 預覽時不顯示廣告。如果您在用戶端縫合廣告,請勿將廣告縫合至預覽影片。如果是在伺服器端縫合廣告,請提供無廣告的影片做為預覽畫面。
- 為確保最佳品質,預覽影片應為 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() 在這個介面上繪製影片。
應用程式可透過直接 Surface 算繪功能,控制算繪內容和方式。您可以疊加管道歸因等中繼資料。
在資訊清單中宣告 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/>
TV Input Framework 必須包含這個標記。不過,這項設定僅用於設定直播頻道。由於您要算繪影片,標記應為空白。
建立影片 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/<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
}
}
}