تطوير خدمة إدخال تلفزيون

تمثل خدمة الإدخال التلفزيوني مصدر بث وسائط، وتتيح لك عرض محتوى الوسائط في شكل قنوات وبرامج تلفزيونية بشكل خطي. باستخدام خدمة إدخال التلفزيون، يمكنك توفير أدوات الرقابة الأبوية ومعلومات دليل البرامج وتقييمات المحتوى. تعمل خدمة إدخال التلفزيون مع تطبيق Android TV. يتحكم هذا التطبيق في نهاية المطاف في محتوى القناة ويعرضه على التلفزيون. تم تطوير تطبيق بث تلفزيوني من النظام خصيصًا للجهاز، وهو غير قابل للتغيير عند استخدام تطبيقات تابعة لجهات خارجية. للحصول على مزيد من المعلومات حول بنية إطار عمل إدخال التلفزيون (TIF) ومكوّناته، يمكنك مراجعة إطار عمل إدخال التلفزيون.

إنشاء خدمة إدخال تلفزيون باستخدام المكتبة المصاحبة في TIF

مكتبة TIF المصاحبة هي إطار عمل يوفر عمليات تنفيذ قابلة للتوسُّع لميزات خدمة إدخال التلفزيون الشائعة. من المفترض أن يستخدمه المصنّعون الأصليون للأجهزة لإنشاء قنوات من Android 5.0 (مستوى واجهة برمجة التطبيقات 21) إلى Android 7.1 (المستوى 25 من واجهة برمجة التطبيقات) فقط.

تعديل مشروعك

إنّ "مكتبة TIF المصاحبة" متاحة للاستخدام القديم من قِبل المصنّعين الأصليين للأجهزة في مستودع androidtv-sample-inputs. راجع هذا المستودع للحصول على مثال حول كيفية تضمين المكتبة في أحد التطبيقات.

تعريف خدمة إدخال التلفزيون في البيان

يجب أن يوفّر تطبيقك خدمة متوافقة مع TvInputService يستخدمها النظام للوصول إلى تطبيقك. توفّر TIF Companion Library الفئة BaseTvInputService التي توفّر تنفيذًا تلقائيًا لـ TvInputService يمكنك تخصيصه. أنشئ فئة فرعية من BaseTvInputService، وحدِّد الفئة الفرعية في ملف البيان كخدمة.

ضمن بيان البيان، حدِّد الإذن BIND_TV_INPUT للسماح للخدمة بتوصيل إدخال التلفزيون بالنظام. وتنفّذ إحدى خدمات النظام عملية الربط ولديها إذن BIND_TV_INPUT. يرسل تطبيق تلفزيون النظام الطلبات إلى خدمات إدخال التلفزيون من خلال واجهة TvInputManager.

في بيان الخدمة، عليك تضمين فلتر أهداف يحدّد TvInputService باعتباره الإجراء المطلوب تنفيذه مع النية. يجب أيضًا تعريف البيانات الوصفية للخدمة كمورد XML منفصل. يتم عرض بيان الخدمة وفلتر الأهداف وبيان البيانات الوصفية للخدمة في المثال التالي:

<service android:name=".rich.RichTvInputService"
    android:label="@string/rich_input_label"
    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. This provides pointers to
    the RichTvInputSetupActivity to the system/TV app. -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/richtvinputservice" />
</service>

حدِّد البيانات الوصفية للخدمة في ملف XML منفصل. يجب أن يتضمّن ملف XML للبيانات الوصفية الخاصة بالخدمة واجهة إعداد توضّح الإعدادات الأولية لإدخال التلفزيون والبحث عن القنوات. يجب أن يحتوي ملف البيانات الوصفية أيضًا على علامة توضح ما إذا كان المستخدمون قادرين على تسجيل المحتوى أم لا. لمزيد من المعلومات حول طريقة إتاحة تسجيل المحتوى في تطبيقك، يُرجى الاطّلاع على مقالة دعم تسجيل المحتوى.

يمكن العثور على ملف البيانات الوصفية للخدمة في دليل موارد XML لتطبيقك ويجب أن يتطابق مع اسم المورد الذي اخترته في البيان. باستخدام إدخالات البيان من المثال السابق، يمكنك إنشاء ملف XML على res/xml/richtvinputservice.xml، مع المحتوى التالي:

<?xml version="1.0" encoding="utf-8"?>
<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
  android:canRecord="true"
  android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity" />

تحديد القنوات وإنشاء نشاط الإعداد

يجب أن تحدّد خدمة إدخال التلفزيون قناة واحدة على الأقل يمكن للمستخدمين الوصول إليها من خلال تطبيق YouTube TV. ويجب تسجيل قنواتك في قاعدة بيانات النظام، وتقديم نشاط إعداد يستدعي النظام عندما يتعذّر عليه العثور على قناة لتطبيقك.

أولاً، احرص على تمكين التطبيق من القراءة والكتابة في دليل البرمجة الإلكتروني (EPG) للنظام، والذي تتضمن بياناته القنوات والبرامج المتاحة للمستخدم. لتمكين تطبيقك من تنفيذ هذه الإجراءات ومزامنته مع EPG بعد إعادة تشغيل الجهاز، أضِف العناصر التالية إلى بيان التطبيق:

<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED "/>

أضِف العنصر التالي لضمان ظهور تطبيقك في "متجر Google Play" كتطبيق يوفّر قنوات المحتوى في Android TV:

<uses-feature
    android:name="android.software.live_tv"
    android:required="true" />

الخطوة التالية هي إنشاء صف يوسّع صف EpgSyncJobService. هذا الفصل المجرّد يجعل من السهل إنشاء خدمة عمل تؤدي إلى إنشاء القنوات وتحديثها في قاعدة بيانات النظام.

في الفئة الفرعية، أنشئ قائمة كاملة من القنوات وأعد إرسالها في getChannels(). إذا كان مصدر قنواتك ملف XMLTV، استخدِم الفئة XmlTvParser. ويمكنك بدلاً من ذلك إنشاء القنوات آليًا باستخدام الفئة Channel.Builder.

بالنسبة إلى كل قناة، يستدعي النظام getProgramsForChannel() عندما يحتاج إلى قائمة بالبرامج التي يمكن مشاهدتها خلال فترة زمنية معيّنة على القناة. عرض قائمة من عناصر Program للقناة. استخدِم الفئة XmlTvParser للحصول على برامج من ملف XMLTV أو إنشاؤها آليًا باستخدام الفئة Program.Builder.

بالنسبة إلى كل عنصر Program، استخدِم عنصر InternalProviderData لتحديد معلومات البرنامج، مثل نوع فيديو البرنامج. إذا كان لديك عدد محدود من البرامج التي تريد أن تتكرّرها القناة بشكل متكرّر، استخدِم القيمة InternalProviderData.setRepeatable() مع القيمة true عند إعداد معلومات عن برنامجك.

بعد تنفيذ خدمة العمل، أضِفها إلى بيان التطبيق:

<service
    android:name=".sync.SampleJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true" />

وأخيرًا، أنشئ نشاط إعداد. يوفر نشاط الإعداد طريقة لمزامنة بيانات القناة والبرنامج. تتمثل إحدى طرق القيام بذلك في قيام المستخدم بذلك عبر واجهة المستخدم في النشاط. قد تجعل التطبيق يقوم بذلك تلقائيًا عند بدء النشاط. عندما يحتاج نشاط الإعداد إلى مزامنة معلومات القناة والبرنامج، يجب أن يبدأ التطبيق خدمة المهمة:

Kotlin

val inputId = getActivity().intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID)
EpgSyncJobService.cancelAllSyncRequests(getActivity())
EpgSyncJobService.requestImmediateSync(
        getActivity(),
        inputId,
        ComponentName(getActivity(), SampleJobService::class.java)
)

Java

String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
EpgSyncJobService.cancelAllSyncRequests(getActivity());
EpgSyncJobService.requestImmediateSync(getActivity(), inputId,
        new ComponentName(getActivity(), SampleJobService.class));

استخدِم الطريقة requestImmediateSync() لمزامنة خدمة المهام. يجب على المستخدم الانتظار حتى تنتهي المزامنة، لذا عليك إبقاء فترة الطلب قصيرة نسبيًا.

استخدِم طريقة setUpPeriodicSync() لضبط خدمة العمل على مزامنة بيانات القناة والبرنامج بشكل دوري في الخلفية:

Kotlin

EpgSyncJobService.setUpPeriodicSync(
        context,
        inputId,
        ComponentName(context, SampleJobService::class.java)
)

Java

EpgSyncJobService.setUpPeriodicSync(context, inputId,
        new ComponentName(context, SampleJobService.class));

توفّر TIF Companion Library طريقة إضافية لتحميل البيانات بشكل زائد requestImmediateSync()، ما يتيح لك تحديد مدة بيانات القناة المطلوب مزامنتها بالمللي ثانية. تعمل الطريقة الافتراضية على مزامنة ما يعادل ساعة واحدة من بيانات القناة.

توفّر TIF Companion Library أيضًا طريقة تحميل زائدة في setUpPeriodicSync() تتيح لك تحديد مدة بيانات القناة المطلوب مزامنتها وعدد المرات التي يجب أن تتم المزامنة الدورية فيها. تعمل الطريقة التلقائية على مزامنة 48 ساعة من بيانات القناة كل 12 ساعة.

لمزيد من التفاصيل حول بيانات القناة وميزة EPG، يُرجى الاطّلاع على مقالة استخدام بيانات القناة.

التعامل مع طلبات الضبط وتشغيل الوسائط

عندما يختار المستخدم قناة معيّنة، يستخدم تطبيق YouTube TV Session الذي أنشأه تطبيقك لضبط القناة المطلوبة وتشغيل المحتوى. توفر TIF Companion Library العديد من الفئات التي يمكنك توسيعها للتعامل مع مكالمات القناة والجلسات من النظام.

تنشئ الفئة الفرعية BaseTvInputService جلسات تعالج طلبات الضبط. يمكنك إلغاء طريقة onCreateSession() وإنشاء جلسة موسّعة من الصف BaseTvInputService.Session وطلب جلستك الجديدة بـ super.sessionCreated(). في المثال التالي، تعرض onCreateSession() كائن RichTvInputSessionImpl يمتدّ BaseTvInputService.Session:

Kotlin

override fun onCreateSession(inputId: String): Session =
        RichTvInputSessionImpl(this, inputId).apply {
            setOverlayViewEnabled(true)
        }

Java

@Override
public final Session onCreateSession(String inputId) {
    RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId);
    session.setOverlayViewEnabled(true);
    return session;
}

عندما يستخدم المستخدم تطبيق التلفزيون الخاص بالنظام لبدء مشاهدة إحدى قنواتك، يستدعي النظام طريقة onPlayChannel() لجلستك. يمكنك إلغاء هذه الطريقة إذا كنت تحتاج إلى أي إعداد خاص للقناة قبل بدء تشغيل البرنامج.

بعد ذلك، يحصل النظام على البرنامج المجدول حاليًا ويُستدعي طريقة onPlayProgram() الخاصة بالجلسة، مع تحديد معلومات البرنامج ووقت البدء بالمللي ثانية. استخدِم واجهة TvPlayer لبدء تشغيل البرنامج.

يجب أن يستخدم رمز مشغّل الوسائط TvPlayer للتعامل مع أحداث تشغيل محددة. تتعامل فئة TvPlayer مع ميزات مثل عناصر التحكّم في عملية التسجيل على الوقت بدون إضافة تعقيد إلى عملية تنفيذ BaseTvInputService.

في طريقة getTvPlayer() لجلستك، اعرض مشغّل الوسائط الذي ينفّذ TvPlayer. يستخدم نموذج تطبيق TV Enter Service مشغّل وسائط يستخدم ExoPlayer.

إنشاء خدمة إدخال تلفزيون باستخدام إطار عمل إدخال التلفزيون

إذا لم تتمكن خدمة إدخال التلفزيون من استخدام المكتبة المصاحبة TIF، فستحتاج إلى تنفيذ المكونات التالية:

  • يوفر TvInputService التوفّر لفترة طويلة والمتاحة في الخلفية لإدخال التلفزيون.
  • يحافظ TvInputService.Session على حالة إدخال التلفزيون ويتواصل مع التطبيق المضيف.
  • يصف TvContract القنوات والبرامج المتاحة لإدخال التلفزيون.
  • تمثل TvContract.Channels معلومات عن قناة تلفزيونية
  • تصف السمة TvContract.Programs برنامج تلفزيوني باستخدام بيانات مثل عنوان البرنامج ووقت بدئه.
  • تشير السمة TvTrackInfo إلى مقطع صوتي أو فيديو أو مقطع ترجمة.
  • يصف الحقل TvContentRating تقييمًا للمحتوى، ويسمح بأنظمة تقييم المحتوى المخصّصة.
  • يوفّر TvInputManager واجهة برمجة تطبيقات لتطبيق التلفزيون على النظام ويدير التفاعل مع إدخالات التلفزيون وتطبيقاته.

عليك أيضًا إجراء ما يلي:

  1. يُرجى تعريف خدمة إدخال التلفزيون في البيان، على النحو الموضَّح في تعريف خدمة إدخال التلفزيون في البيان.
  2. أنشِئ ملف البيانات الوصفية للخدمة.
  3. إنشاء معلومات عن قناتك والبرنامج وتسجيلها
  4. أنشئ نشاط الإعداد.

تحديد خدمة إدخال التلفزيون

بالنسبة إلى خدمتك، يمكنك تمديد الفئة TvInputService. عملية تنفيذ TvInputService هي خدمة مرتبطة حيث تكون خدمة النظام هي العميل الذي يرتبط بها. يتم توضيح طرق دورة حياة الخدمة التي تحتاج إلى تنفيذها في الشكل 1.

تعمل الطريقة onCreate() على إعداد وتبدأ عملية HandlerThread التي توفّر سلسلة عمليات منفصلة عن سلسلة تعليمات واجهة المستخدم لمعالجة الإجراءات المستندة إلى النظام. في المثال التالي، تُعِدّ الطريقة onCreate() CaptioningManager وتستعدّ للتعامل مع الإجراءَين ACTION_BLOCKED_RATINGS_CHANGED وACTION_PARENTAL_CONTROLS_ENABLED_CHANGED. وتصف هذه الإجراءات أهداف النظام التي يتم تنشيطها عندما يغيّر المستخدم إعدادات أدوات الرقابة الأبوية، وعند حدوث تغيير في قائمة التقييمات المحظورة.

Kotlin

override fun onCreate() {
    super.onCreate()
    handlerThread = HandlerThread(javaClass.simpleName).apply {
        start()
    }
    dbHandler = Handler(handlerThread.looper)
    handler = Handler()
    captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar)

    sessions = mutableListOf<BaseTvInputSessionImpl>()
    val intentFilter = IntentFilter().apply {
        addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED)
        addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)
    }
    registerReceiver(broadcastReceiver, intentFilter)
}

Java

@Override
public void onCreate() {
    super.onCreate();
    handlerThread = new HandlerThread(getClass()
      .getSimpleName());
    handlerThread.start();
    dbHandler = new Handler(handlerThread.getLooper());
    handler = new Handler();
    captioningManager = (CaptioningManager)
      getSystemService(Context.CAPTIONING_SERVICE);

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar);

    sessions = new ArrayList<BaseTvInputSessionImpl>();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(TvInputManager
      .ACTION_BLOCKED_RATINGS_CHANGED);
    intentFilter.addAction(TvInputManager
      .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
    registerReceiver(broadcastReceiver, intentFilter);
}

الشكل 1.دورة حياة TvInputService.

راجع صفحة التحكم في المحتوى للحصول على مزيد من المعلومات حول التعامل مع المحتوى المحظور وتوفير أدوات الرقابة الأبوية. يمكنك مراجعة TvInputManager لمعرفة المزيد من الإجراءات المستندة إلى النظام التي قد تريد معالجتها في خدمة إدخال التلفزيون.

تنشئ TvInputService عنصر TvInputService.Session ينفّذ Handler.Callback للتعامل مع تغييرات حالة اللاعب. في حال استخدام onSetSurface()، تضبط السمة TvInputService.Session السمة Surface بمحتوى الفيديو. راجِع دمج المشغّل مع السطح للحصول على مزيد من المعلومات حول استخدام Surface لعرض الفيديو.

تعالج TvInputService.Session حدث onTune() عندما يختار المستخدم قناة، وتُبلغ تطبيق YouTube TV بأي تغييرات تطرأ على المحتوى والبيانات الوصفية للمحتوى. يمكن الاطّلاع أيضًا على طريقة notify() هذه في قسم التحكّم في المحتوى واختيار مسار الاسم المعرِّف في مزيد من المعلومات في هذا التدريب.

تحديد نشاط الإعداد

يعمل تطبيق YouTube TV مع نشاط الإعداد الذي تحدِّده لإدخال التلفزيون. يكون نشاط الإعداد مطلوبًا ويجب أن يوفر سجل قناة واحدًا على الأقل لقاعدة بيانات النظام. يستدعي تطبيق YouTube TV نشاط الإعداد عندما يتعذّر عليه العثور على قناة لإدخال التلفزيون.

يصف نشاط الإعداد لتطبيق YouTube TV الذي وفّرته القنوات من خلال إدخال التلفزيون، كما هو موضَّح في الدرس التالي، إنشاء بيانات القناة وتعديلها.

مراجع إضافية