يُعدّ فيديو المعاينة طريقة رائعة لتشجيع المستخدمين على إنشاء روابط تؤدي إلى صفحات في تطبيق التلفزيون. ويمكن أن تتراوح المعاينات بين المقاطع القصيرة والمقاطع الدعائية لأفلام كاملة.
عند إنشاء معاينة، يجب مراعاة الإرشادات التالية:
- عدم عرض الإعلانات في معاينة إذا اخترت تركيب الإعلانات من جهة العميل لا تدمجها في فيديوهات المعاينة. إذا كنت تجمع الإعلانات من جهة الخادم توفير إمكانية معاينة الفيديوهات بدون إعلانات
- للحصول على أفضل جودة، يجب أن تكون فيديوهات المعاينة 16:9 أو 4:3. عرض سمات برنامج الفيديو لمعرفة الأحجام الموصى بها لفيديوهات المعاينة.
- عندما تختلف نِسب العرض إلى الارتفاع لفيديو المعاينة عن صورة الملصق،
تُغيّر الشاشة الرئيسية حجم عرض الملصق إلى نسبة العرض إلى الارتفاع للفيديو قبل تشغيل المعاينة.
لم يتمّ عرض الفيديو مُعدّ للعرض على شاشة عريضة أفقيًا. على سبيل المثال، إذا
تكون نسبة عمل الملصق
ASPECT_RATIO_MOVIE_POSTER
(1.441:1) ولكن نسبة الفيديو هي 16:9، تتحول مشاهدة الملصق إلى منطقة 16:9. - عند إنشاء معاينة، يمكن للجميع الوصول إلى محتواها أو محمي بموجب إدارة الحقوق الرقمية تنطبق إجراءات مختلفة في كل حالة. هذه الصفحة يصف كليهما.
تشغيل المعاينة على الشاشة الرئيسية
في حال إنشاء معاينة باستخدام أيّ من أنواع الفيديو متاح من خلال 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));
عرض المعاينة على سطح
إذا كان الفيديو محميًا بموجب إدارة الحقوق الرقمية (DRM) أو بنوع وسائط غير متوافق مع
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
، استخدِم
TvctCompat.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 } } }