يُعد فيديو المعاينة طريقة رائعة لتشجيع المستخدمين على إنشاء رابط لصفحة معيّنة في تطبيق التلفزيون. يمكن أن تتراوح المعاينات بين المقاطع القصيرة والمقاطع الدعائية للأفلام الكاملة.
عند إنشاء معاينة، يُرجى مراعاة الإرشادات التالية:
- عدم عرض الإعلانات في معاينة إذا جمعت الإعلانات من جهة العميل، فلا تجمعها في فيديوهات معاينة. في حال دمج الإعلانات على الخادم، يمكنك توفير فيديو للمعاينات بلا إعلانات.
- للحصول على أفضل جودة، يجب أن تكون ن��بة فيديوهات المعاينة 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));
عرض المعاينة على سطح
إذا كان الفيديو محميًا بموجب إدارة الحقوق الرقمية أو نوع وسائط لا يتوافق مع
ExoPlayer، استخدِم TvInputService
.
ترسل شاشة Android TV الرئيسية 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/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 } } }