الفيديو المعاينة هو طريقة رائعة لتشجيع المستخدمين على الانتقال مباشرةً إلى تطبيق بث تلفزيوني. ويمكن أن تتراوح المعاينات بين مقاطع قصيرة ومقاطع دعائية كاملة للأفلام.
عند إنشاء معاينة، يُرجى مراعاة الإرشادات التالية:
- لا تعرض الإعلانات في المعاينة. إذا كنت تدمج الإعلانات من جهة العميل، لا تدمجها في فيديوهات المعاينة. إذا كنت تدمج الإعلانات من جهة الخادم، قدِّم فيديو بدون إعلانات للمعاينات.
- للحصول على أفضل جودة، يجب أن تكون فيديوهات المعاينة بنسبة عرض إلى ارتفاع تبلغ 16:9 أو 4:3. راجِع سمات برنامج الفيديو للاطّلاع على الأحجام المقترَحة لفيديوهات المعاينة.
- عندما تكون نسبة العرض إلى الارتفاع مختلفة بين فيديو المعاينة والعمل الفني للملصق، تغيّر الشاشة الرئيسية حجم عرض الملصق ليصبح بنسبة العرض إلى الارتفاع للفيديو قبل تشغيل المعاينة. لا يتم عرض الفيديو في إطار. على سبيل المثال، إذا كانت نسبة عرض الملصق إلى ارتفاعه هي
ASPECT_RATIO_MOVIE_POSTER(1:1.441) ولكن نسبة عرض الفيديو إلى ارتفاعه هي 16:9، يتحوّل عرض الملصق إلى منطقة بنسبة 16:9. - عند إنشاء معاينة، يمكن أن يكون محتواها متاحًا للجميع أو محميًا بموجب إدارة الحقوق الرقمية (DRM). يتم تطبيق إجراءات مختلفة في كل حالة. تصف هذه الصفحة كلتيهما.
تشغيل المعاينة على الشاشة الرئيسية
إذا أنشأت معاينة باستخدام أي من أنواع الفيديوهات التي يتيحها ExoPlayer وكانت المعاينة متاحة للجميع، يمكنك تشغيلها مباشرةً على الشاشة الرئيسية.
عند إنشاء عنصر PreviewProgram، استخدِم setPreviewVideoUri() مع عنوان URL عبر بروتوكول 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 عنصر a
Surface على خدمتك من خلال استدعاء onSetSurface(). يرسم تطبيقك
الفيديو مباشرةً على هذا السطح من 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 لتطبيقك ويجب أن يتطابق مع اسم المورد الذي عرّفته في البيان. باستخدام إدخالات البيان من المثال السابق، يمكنك إنشاء ملف XML في res/xml/previewinputservice.xml باستخدام علامة tv-input فارغة:
<?xml version="1.0" encoding="utf-8"?>
<tv-input/>
يجب أن تتضمّن "إطار عمل إدخال التلفزيون" هذه العلامة. ومع ذلك، لا يتم استخدامها إلا لضبط القنوات المباشرة. بما أنّك تعرض فيديو، يجب أن تكون العلامة فارغة.
إنشاء معرّف 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
}
}
}