วิดีโอตัวอย่างเป็นวิธีที่ยอดเยี่ยมในการกระตุ้นให้ผู้ใช้ทำ Deep Link ไปยังแอปทีวีของคุณ โดยตัวอย่างอาจเป็นคลิปสั้นๆ หรือตัวอย่างภาพยนตร์แบบเต็มก็ได้
เมื่อสร้างตัวอย่าง ให้พิจารณาหลักเกณฑ์ต่อไปนี้
- อย่าแสดงโฆษณาในตัวอย่าง หากคุณรวมโฆษณาทางฝั่งไคลเอ็นต์ อย่ารวมโฆษณาในวิดีโอตัวอย่าง หากคุณรวมโฆษณาทางฝั่งเซิร์ฟเวอร์ ให้ใช้วิดีโอที่ไม่มีโฆษณาสำหรับตัวอย่าง
- วิดีโอตัวอย่างควรมีขนาด 16:9 หรือ 4:3 เพื่อให้ได้คุณภาพที่ดีที่สุด ดูขนาดที่แนะนำสำหรับวิดีโอตัวอย่างได้ที่แอตทริบิวต์โปรแกรมวิดีโอ
- เมื่อวิดีโอตัวอย่างและภาพโปสเตอร์มีสัดส่วนภาพต่างกัน หน้าจอหลักจะปรับขนาดมุมมองโปสเตอร์ให้เป็นสัดส่วนภาพของวิดีโอก่อนที่จะเล่นตัวอย่าง วิดีโอจะไม่เป็นแบบ Letterbox ตัวอย่างเช่น หากสัดส่วนภาพโปสเตอร์คือ
ASPECT_RATIO_MOVIE_POSTER(1:1.441) แต่วิดีโอมีสัดส่วนภาพ 16:9 มุมมองโปสเตอร์จะเปลี่ยนเป็นพื้นที่ 16:9 - เมื่อคุณสร้างตัวอย่าง เนื้อหาของตัวอย่างอาจเข้าถึงได้แบบสาธารณะหรือได้รับการคุ้มครองภายใต้ DRM ซึ่งแต่ละกรณีจะมีขั้นตอนที่แตกต่างกัน หน้านี้จะอธิบายทั้ง 2 กรณี
เล่นตัวอย่างบนหน้าจอหลัก
หากคุณสร้างตัวอย่างโดยใช้วิดีโอประเภทใดก็ได้ที่ 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));
แสดงตัวอย่างบน Surface
หากวิดีโอได้รับการคุ้มครองด้วย DRM หรืออยู่ในประเภทสื่อที่
ExoPlayerไม่รองรับ ให้ใช้ TvInputService หน้าจอหลักของ Android TV จะส่ง a
Surface ไปยังบริการของคุณโดยเรียกใช้ onSetSurface() แอปจะวาด
วิดีโอโดยตรงบน Surface นี้จาก onTune()
การแสดงผลบน Surface โดยตรงช่วยให้แอปควบคุมสิ่งที่แสดงผลและวิธีแสดงผลได้ คุณสามารถวางซ้อนข้อมูลเมตา เช่น การระบุแหล่งที่มาของช่อง
ประกาศ TvInputService ในไฟล์ Manifest
แอปของคุณต้องมีการใช้งาน TvInputService เพื่อให้หน้าจอหลัก
แสดงตัวอย่างได้
ในการประกาศบริการ ให้ใส่ตัวกรอง Intent ที่ระบุ
TvInputService เป็นการดำเนินการที่จะทำกับ Intent นอกจากนี้ ให้ประกาศข้อมูลเมตาของบริการเป็นทรัพยากร XML แยกต่างหากด้วย การประกาศบริการ ตัวกรอง Intent และการประกาศข้อมูลเมตาของบริการแสดงอยู่ในตัวอย่างต่อไปนี้
<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 ของแอปและต้องมีชื่อตรงกับชื่อทรัพยากรที่คุณประกาศไว้ในไฟล์ Manifest เมื่อใช้รายการไฟล์ Manifest จากตัวอย่างก่อนหน้า คุณจะต้องสร้างไฟล์ XML ที่ res/xml/previewinputservice.xml โดยมีแท็ก tv-input ว่างเปล่า
<?xml version="1.0" encoding="utf-8"?>
<tv-input/>
TV Input Framework ต้องมีแท็กนี้ อย่างไรก็ตาม แท็กนี้ใช้เพื่อกำหนดค่าช่องรายการสดเท่านั้น เนื่องจากคุณกำลังแสดงผลวิดีโอ แท็กนี้จึงควรว่างเปล่า
สร้าง URI ของวิดีโอ
หากต้องการระบุว่าแอปของคุณควรแสดงผลวิดีโอตัวอย่างแทนหน้าจอหลักของ Android TV คุณต้องสร้าง URI ของวิดีโอสำหรับ PreviewProgram
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 โดยใช้
Uri.withAppendedPath()
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
}
}
}