با داده های کانال کار کنید

ورودی تلویزیون شما باید داده‌های راهنمای برنامه الکترونیکی (EPG) را برای حداقل یک کانال در فعالیت راه‌اندازی خود ارائه دهد. همچنین باید به صورت دوره‌ای آن داده‌ها را با در نظر گرفتن اندازه به‌روزرسانی و رشته پردازشی که آن را مدیریت می‌کند، به‌روزرسانی کنید. علاوه بر این، می‌توانید پیوندهای برنامه‌ای را برای کانال‌هایی ارائه دهید که کاربر را به محتوا و فعالیت‌های مرتبط هدایت می‌کنند. این درس با در نظر گرفتن این ملاحظات، ایجاد و به‌روزرسانی داده‌های کانال و برنامه در پایگاه داده سیستم را مورد بحث قرار می‌دهد.

برنامه نمونه سرویس ورودی تلویزیون را امتحان کنید.

اجازه بگیرید

برای اینکه ورودی تلویزیون شما با داده‌های EPG کار کند، باید مجوز نوشتن را در فایل مانیفست اندروید خود به شرح زیر اعلام کند:

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

ثبت کانال‌ها در پایگاه داده

پایگاه داده سیستم تلویزیون اندروید، رکوردهای داده‌های کانال را برای ورودی‌های تلویزیون نگهداری می‌کند. در فعالیت راه‌اندازی خود، برای هر یک از کانال‌هایتان، باید داده‌های کانال خود را به فیلدهای زیر از کلاس TvContract.Channels نگاشت کنید:

اگرچه چارچوب ورودی تلویزیون به اندازه کافی عمومی است که بتواند هم محتوای پخش سنتی و هم محتوای OTT را بدون هیچ تمایزی مدیریت کند، اما ممکن است بخواهید علاوه بر شناسایی بهتر کانال‌های پخش سنتی، ستون‌های زیر را نیز تعریف کنید:

اگر می‌خواهید جزئیات لینک برنامه را برای کانال‌های خود ارائه دهید، باید برخی فیلدهای اضافی را به‌روزرسانی کنید. برای اطلاعات بیشتر در مورد فیلدهای لینک برنامه، به افزودن اطلاعات لینک برنامه مراجعه کنید.

برای ورودی‌های تلویزیون مبتنی بر پخش اینترنتی، مقادیر خودتان را بر این اساس تعیین کنید تا هر کانال به صورت منحصر به فرد شناسایی شود.

متادیتای کانال خود (به صورت XML، JSON یا هر چیز دیگری) را از سرور backend خود دریافت کنید و در فعالیت راه‌اندازی خود، مقادیر را به صورت زیر به پایگاه داده سیستم نگاشت کنید:

کاتلین

val values = ContentValues().apply {
    put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.number)
    put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.name)
    put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId)
    put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId)
    put(TvContract.Channels.COLUMN_SERVICE_ID, channel.serviceId)
    put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat)
}
val uri = context.contentResolver.insert(TvContract.Channels.CONTENT_URI, values)

جاوا

ContentValues values = new ContentValues();

values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId);
values.put(Channels.COLUMN_SERVICE_ID, channel.serviceId);
values.put(Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat);

Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);

در این مثال، channel شیء‌ای است که فراداده‌های کانال را از سرور backend در خود نگه می‌دارد.

ارائه اطلاعات کانال و برنامه

همانطور که در شکل ۱ نشان داده شده است، برنامه‌ی System TV اطلاعات کانال و برنامه را هنگام تغییر کانال‌ها به کاربران ارائه می‌دهد. برای اطمینان از اینکه اطلاعات کانال و برنامه با ارائه‌دهنده‌ی اطلاعات کانال و برنامه‌ی برنامه‌ی System TV کار می‌کند، این دستورالعمل‌ها را دنبال کنید:

  1. شماره کانال ( COLUMN_DISPLAY_NUMBER )
  2. آیکون ( android:icon در مانیفست ورودی تلویزیون)
  3. شرح برنامه ( COLUMN_SHORT_DESCRIPTION )
  4. عنوان برنامه ( COLUMN_TITLE )
  5. لوگوی کانال ( TvContract.Channels.Logo )
    • از رنگ #EEEEEE برای مطابقت با متن اطراف استفاده کنید
    • پدینگ (padding) را لحاظ نکنید
  6. هنر پوستر ( COLUMN_POSTER_ART_URI )
    • نسبت تصویر بین ۱۶:۹ و ۴:۳
شکل ۱. ارائه‌دهنده اطلاعات کانال و برنامه در اپلیکیشن تلویزیون سیستمی.

برنامه تلویزیون سیستمی، اطلاعات مشابهی را از طریق راهنمای برنامه، از جمله پوستر هنری، همانطور که در شکل 2 نشان داده شده است، ارائه می‌دهد.

شکل ۲. راهنمای برنامه‌ی اپلیکیشن تلویزیون سیستمی.

به‌روزرسانی داده‌های کانال

هنگام به‌روزرسانی داده‌های کانال موجود، به جای حذف و اضافه کردن مجدد داده‌ها، از روش update استفاده کنید. می‌توانید نسخه فعلی داده‌ها را با استفاده از Channels.COLUMN_VERSION_NUMBER و Programs.COLUMN_VERSION_NUMBER هنگام انتخاب رکوردها برای به‌روزرسانی، شناسایی کنید.

توجه: اضافه کردن داده‌های کانال به ContentProvider می‌تواند زمان‌بر باشد. برنامه‌های فعلی (آن‌هایی که در فاصله دو ساعت از زمان فعلی قرار دارند) را فقط زمانی اضافه کنید که EpgSyncJobService خود را طوری پیکربندی کنید که بقیه داده‌های کانال را در پس‌زمینه به‌روزرسانی کند. برای مثال، به برنامه نمونه تلویزیون زنده اندروید تی‌وی مراجعه کنید.

بارگذاری دسته‌ای داده‌های کانال

هنگام به‌روزرسانی پایگاه داده سیستم با حجم زیادی از داده‌های کانال، از متدهای applyBatch یا bulkInsert از ContentResolver استفاده کنید. در اینجا مثالی از استفاده applyBatch آورده شده است:

کاتلین

val ops = ArrayList<ContentProviderOperation>()
val programsCount = channelInfo.mPrograms.size
channelInfo.mPrograms.forEachIndexed { index, program ->
    ops += ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI).run {
        withValues(programs[index])
        withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartSec * 1000)
        withValue(
                TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
                (programStartSec + program.durationSec) * 1000
        )
        build()
    }
    programStartSec += program.durationSec
    if (index % 100 == 99 || index == programsCount - 1) {
        try {
            contentResolver.applyBatch(TvContract.AUTHORITY, ops)
        } catch (e: RemoteException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        } catch (e: OperationApplicationException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        }
        ops.clear()
    }
}

جاوا

ArrayList<ContentProviderOperation> ops = new ArrayList<>();
int programsCount = channelInfo.mPrograms.size();
for (int j = 0; j < programsCount; ++j) {
    ProgramInfo program = channelInfo.mPrograms.get(j);
    ops.add(ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI)
            .withValues(programs.get(j))
            .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS,
                    programStartSec * 1000)
            .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS,
                    (programStartSec + program.durationSec) * 1000)
            .build());
    programStartSec = programStartSec + program.durationSec;
    if (j % 100 == 99 || j == programsCount - 1) {
        try {
            getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
        } catch (RemoteException | OperationApplicationException e) {
            Log.e(TAG, "Failed to insert programs.", e);
            return;
        }
        ops.clear();
    }
}

پردازش داده‌های کانال به صورت ناهمزمان

دستکاری داده‌ها، مانند دریافت یک جریان از سرور یا دسترسی به پایگاه داده، نباید نخ رابط کاربری را مسدود کند. استفاده از AsyncTask یکی از راه‌های انجام به‌روزرسانی‌ها به صورت غیرهمزمان است. برای مثال، هنگام بارگیری اطلاعات کانال از یک سرور backend، می‌توانید AsyncTask به صورت زیر استفاده کنید:

کاتلین

private class LoadTvInputTask(val context: Context) : AsyncTask<Uri, Unit, Unit>() {

    override fun doInBackground(vararg uris: Uri) {
        try {
            fetchUri(uris[0])
        } catch (e: IOException) {
            Log.d("LoadTvInputTask", "fetchUri error")
        }
    }

    @Throws(IOException::class)
    private fun fetchUri(videoUri: Uri) {
        context.contentResolver.openInputStream(videoUri).use { inputStream ->
            Xml.newPullParser().also { parser ->
                try {
                    parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
                    parser.setInput(inputStream, null)
                    sTvInput = ChannelXMLParser.parseTvInput(parser)
                    sSampleChannels = ChannelXMLParser.parseChannelXML(parser)
                } catch (e: XmlPullParserException) {
                    e.printStackTrace()
                }
            }
        }
    }
}

جاوا

private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void> {

    private Context mContext;

    public LoadTvInputTask(Context context) {
        mContext = context;
    }

    @Override
    protected Void doInBackground(Uri... uris) {
        try {
            fetchUri(uris[0]);
        } catch (IOException e) {
          Log.d("LoadTvInputTask", "fetchUri error");
        }
        return null;
    }

    private void fetchUri(Uri videoUri) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = mContext.getContentResolver().openInputStream(videoUri);
            XmlPullParser parser = Xml.newPullParser();
            try {
                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
                parser.setInput(inputStream, null);
                sTvInput = ChannelXMLParser.parseTvInput(parser);
                sSampleChannels = ChannelXMLParser.parseChannelXML(parser);
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}

اگر نیاز دارید که داده‌های EPG را به‌طور منظم به‌روزرسانی کنید، استفاده از WorkManager را برای اجرای فرآیند به‌روزرسانی در زمان‌های بیکاری، مثلاً هر روز ساعت ۳ بامداد، در نظر بگیرید.

تکنیک‌های دیگر برای جداسازی وظایف به‌روزرسانی داده‌ها از نخ رابط کاربری شامل استفاده از کلاس HandlerThread است، یا می‌توانید با استفاده از کلاس‌های Looper و Handler ، روش خودتان را پیاده‌سازی کنید. برای اطلاعات بیشتر به Processes and threads مراجعه کنید.

کانال‌ها می‌توانند از لینک‌های برنامه استفاده کنند تا به کاربران اجازه دهند هنگام تماشای محتوای کانال، یک فعالیت مرتبط را اجرا کنند. برنامه‌های کانال از لینک‌های برنامه برای افزایش تعامل کاربر با راه‌اندازی فعالیت‌هایی که اطلاعات مرتبط یا محتوای اضافی را نشان می‌دهند، استفاده می‌کنند. به عنوان مثال، می‌توانید از لینک‌های برنامه برای انجام موارد زیر استفاده کنید:

  • کاربر را برای کشف و خرید محتوای مرتبط راهنمایی کنید.
  • اطلاعات بیشتری در مورد محتوای در حال پخش ارائه دهید.
  • هنگام تماشای محتوای اپیزودیک، شروع به تماشای قسمت بعدی در یک مجموعه کنید.
  • به کاربر اجازه دهید بدون ایجاد وقفه در پخش محتوا، با محتوا تعامل داشته باشد - برای مثال، محتوا را ارزیابی یا بررسی کند.

پیوندهای برنامه زمانی نمایش داده می‌شوند که کاربر هنگام تماشای محتوای کانال، دکمه انتخاب را برای نمایش منوی تلویزیون فشار دهد.

شکل ۱. یک نمونه لینک برنامه که در ردیف کانال‌ها نمایش داده می‌شود، در حالی که محتوای کانال نمایش داده می‌شود.

وقتی کاربر لینک برنامه را انتخاب می‌کند، سیستم یک فعالیت را با استفاده از یک URI هدف مشخص شده توسط برنامه کانال آغاز می‌کند. محتوای کانال در حالی که فعالیت لینک برنامه فعال است، پخش می‌شود. کاربر می‌تواند با فشار دادن دکمه برگشت به محتوای کانال بازگردد .

ارائه داده‌های کانال لینک برنامه

اندروید تی‌وی به طور خودکار با استفاده از اطلاعات داده‌های کانال، یک لینک برنامه برای هر کانال ایجاد می‌کند. برای ارائه اطلاعات لینک برنامه، جزئیات زیر را در فیلدهای TvContract.Channels خود مشخص کنید:

  • COLUMN_APP_LINK_COLOR - رنگ تأکیدی لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۳ مراجعه کنید.
  • COLUMN_APP_LINK_ICON_URI - آدرس اینترنتی (URI) مربوط به آیکون نشان برنامه مربوط به لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۲ مراجعه کنید.
  • COLUMN_APP_LINK_INTENT_URI - آدرس اینترنتی (URI) مربوط به لینک اپلیکیشن برای این کانال. می‌توانید با استفاده از toUri(int) به همراه URI_INTENT_SCHEME این آدرس اینترنتی را ایجاد کنید و با parseUri آن را به آدرس اینترنتی اصلی تبدیل کنید.
  • COLUMN_APP_LINK_POSTER_ART_URI - آدرس اینترنتی پوستری که به عنوان پس‌زمینه لینک برنامه برای این کانال استفاده می‌شود. برای مثال، تصویر پوستر، به شکل ۲، توضیحات ۱ مراجعه کنید.
  • COLUMN_APP_LINK_TEXT - متن لینک توصیفی لینک برنامه برای این کانال. برای مثال، توضیحات لینک برنامه، به متن موجود در شکل ۲، راهنمای ۳ مراجعه کنید.
شکل ۲. جزئیات لینک برنامه.

اگر داده‌های کانال اطلاعات لینک برنامه را مشخص نکنند، سیستم یک لینک برنامه پیش‌فرض ایجاد می‌کند. سیستم جزئیات پیش‌فرض را به شرح زیر انتخاب می‌کند:

  • برای URL هدف ( COLUMN_APP_LINK_INTENT_URI )، سیستم از فعالیت ACTION_MAIN برای دسته CATEGORY_LEANBACK_LAUNCHER استفاده می‌کند که معمولاً در مانیفست برنامه تعریف شده است. اگر این فعالیت تعریف نشده باشد، یک لینک برنامه غیرفعال ظاهر می‌شود - اگر کاربر روی آن کلیک کند، هیچ اتفاقی نمی‌افتد.
  • برای متن توصیفی ( COLUMN_APP_LINK_TEXT )، سیستم از "Open app-name " استفاده می‌کند. اگر هیچ URI برای لینک اپلیکیشن تعریف نشده باشد، سیستم از "No link available" استفاده می‌کند.
  • برای رنگ تأکیدی ( COLUMN_APP_LINK_COLOR )، سیستم از رنگ پیش‌فرض برنامه استفاده می‌کند.
  • برای تصویر پوستر ( COLUMN_APP_LINK_POSTER_ART_URI )، سیستم از بنر صفحه اصلی برنامه استفاده می‌کند. اگر برنامه بنری ارائه ندهد، سیستم از تصویر پیش‌فرض برنامه تلویزیونی استفاده می‌کند.
  • برای آیکون نشان ( COLUMN_APP_LINK_ICON_URI )، سیستم از نشانه‌ای استفاده می‌کند که نام برنامه را نشان می‌دهد. اگر سیستم از بنر برنامه یا تصویر پیش‌فرض برنامه برای تصویر پوستر نیز استفاده کند، هیچ نشان برنامه‌ای نشان داده نمی‌شود.

شما جزئیات لینک برنامه را برای کانال‌های خود در فعالیت راه‌اندازی برنامه خود مشخص می‌کنید. می‌توانید این جزئیات لینک برنامه را در هر زمانی به‌روزرسانی کنید، بنابراین اگر یک لینک برنامه نیاز به مطابقت با تغییرات کانال دارد، جزئیات لینک برنامه را به‌روزرسانی کنید و در صورت نیاز ContentResolver.update را فراخوانی کنید. برای جزئیات بیشتر در مورد به‌روزرسانی داده‌های کانال، به به‌روزرسانی داده‌های کانال مراجعه کنید.

،

ورودی تلویزیون شما باید داده‌های راهنمای برنامه الکترونیکی (EPG) را برای حداقل یک کانال در فعالیت راه‌اندازی خود ارائه دهد. همچنین باید به صورت دوره‌ای آن داده‌ها را با در نظر گرفتن اندازه به‌روزرسانی و رشته پردازشی که آن را مدیریت می‌کند، به‌روزرسانی کنید. علاوه بر این، می‌توانید پیوندهای برنامه‌ای را برای کانال‌هایی ارائه دهید که کاربر را به محتوا و فعالیت‌های مرتبط هدایت می‌کنند. این درس با در نظر گرفتن این ملاحظات، ایجاد و به‌روزرسانی داده‌های کانال و برنامه در پایگاه داده سیستم را مورد بحث قرار می‌دهد.

برنامه نمونه سرویس ورودی تلویزیون را امتحان کنید.

اجازه بگیرید

برای اینکه ورودی تلویزیون شما با داده‌های EPG کار کند، باید مجوز نوشتن را در فایل مانیفست اندروید خود به شرح زیر اعلام کند:

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

ثبت کانال‌ها در پایگاه داده

پایگاه داده سیستم تلویزیون اندروید، رکوردهای داده‌های کانال را برای ورودی‌های تلویزیون نگهداری می‌کند. در فعالیت راه‌اندازی خود، برای هر یک از کانال‌هایتان، باید داده‌های کانال خود را به فیلدهای زیر از کلاس TvContract.Channels نگاشت کنید:

اگرچه چارچوب ورودی تلویزیون به اندازه کافی عمومی است که بتواند هم محتوای پخش سنتی و هم محتوای OTT را بدون هیچ تمایزی مدیریت کند، اما ممکن است بخواهید علاوه بر شناسایی بهتر کانال‌های پخش سنتی، ستون‌های زیر را نیز تعریف کنید:

اگر می‌خواهید جزئیات لینک برنامه را برای کانال‌های خود ارائه دهید، باید برخی فیلدهای اضافی را به‌روزرسانی کنید. برای اطلاعات بیشتر در مورد فیلدهای لینک برنامه، به افزودن اطلاعات لینک برنامه مراجعه کنید.

برای ورودی‌های تلویزیون مبتنی بر پخش اینترنتی، مقادیر خودتان را بر این اساس تعیین کنید تا هر کانال به صورت منحصر به فرد شناسایی شود.

متادیتای کانال خود (به صورت XML، JSON یا هر چیز دیگری) را از سرور backend خود دریافت کنید و در فعالیت راه‌اندازی خود، مقادیر را به صورت زیر به پایگاه داده سیستم نگاشت کنید:

کاتلین

val values = ContentValues().apply {
    put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.number)
    put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.name)
    put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId)
    put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId)
    put(TvContract.Channels.COLUMN_SERVICE_ID, channel.serviceId)
    put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat)
}
val uri = context.contentResolver.insert(TvContract.Channels.CONTENT_URI, values)

جاوا

ContentValues values = new ContentValues();

values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId);
values.put(Channels.COLUMN_SERVICE_ID, channel.serviceId);
values.put(Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat);

Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);

در این مثال، channel شیء‌ای است که فراداده‌های کانال را از سرور backend در خود نگه می‌دارد.

ارائه اطلاعات کانال و برنامه

همانطور که در شکل ۱ نشان داده شده است، برنامه‌ی System TV اطلاعات کانال و برنامه را هنگام تغییر کانال‌ها به کاربران ارائه می‌دهد. برای اطمینان از اینکه اطلاعات کانال و برنامه با ارائه‌دهنده‌ی اطلاعات کانال و برنامه‌ی برنامه‌ی System TV کار می‌کند، این دستورالعمل‌ها را دنبال کنید:

  1. شماره کانال ( COLUMN_DISPLAY_NUMBER )
  2. آیکون ( android:icon در مانیفست ورودی تلویزیون)
  3. شرح برنامه ( COLUMN_SHORT_DESCRIPTION )
  4. عنوان برنامه ( COLUMN_TITLE )
  5. لوگوی کانال ( TvContract.Channels.Logo )
    • از رنگ #EEEEEE برای مطابقت با متن اطراف استفاده کنید
    • پدینگ (padding) را لحاظ نکنید
  6. هنر پوستر ( COLUMN_POSTER_ART_URI )
    • نسبت تصویر بین ۱۶:۹ و ۴:۳
شکل ۱. ارائه‌دهنده اطلاعات کانال و برنامه در اپلیکیشن تلویزیون سیستمی.

برنامه تلویزیون سیستمی، اطلاعات مشابهی را از طریق راهنمای برنامه، از جمله پوستر هنری، همانطور که در شکل 2 نشان داده شده است، ارائه می‌دهد.

شکل ۲. راهنمای برنامه‌ی اپلیکیشن تلویزیون سیستمی.

به‌روزرسانی داده‌های کانال

هنگام به‌روزرسانی داده‌های کانال موجود، به جای حذف و اضافه کردن مجدد داده‌ها، از روش update استفاده کنید. می‌توانید نسخه فعلی داده‌ها را با استفاده از Channels.COLUMN_VERSION_NUMBER و Programs.COLUMN_VERSION_NUMBER هنگام انتخاب رکوردها برای به‌روزرسانی، شناسایی کنید.

توجه: اضافه کردن داده‌های کانال به ContentProvider می‌تواند زمان‌بر باشد. برنامه‌های فعلی (آن‌هایی که در فاصله دو ساعت از زمان فعلی قرار دارند) را فقط زمانی اضافه کنید که EpgSyncJobService خود را طوری پیکربندی کنید که بقیه داده‌های کانال را در پس‌زمینه به‌روزرسانی کند. برای مثال، به برنامه نمونه تلویزیون زنده اندروید تی‌وی مراجعه کنید.

بارگذاری دسته‌ای داده‌های کانال

هنگام به‌روزرسانی پایگاه داده سیستم با حجم زیادی از داده‌های کانال، از متدهای applyBatch یا bulkInsert از ContentResolver استفاده کنید. در اینجا مثالی از استفاده applyBatch آورده شده است:

کاتلین

val ops = ArrayList<ContentProviderOperation>()
val programsCount = channelInfo.mPrograms.size
channelInfo.mPrograms.forEachIndexed { index, program ->
    ops += ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI).run {
        withValues(programs[index])
        withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartSec * 1000)
        withValue(
                TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
                (programStartSec + program.durationSec) * 1000
        )
        build()
    }
    programStartSec += program.durationSec
    if (index % 100 == 99 || index == programsCount - 1) {
        try {
            contentResolver.applyBatch(TvContract.AUTHORITY, ops)
        } catch (e: RemoteException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        } catch (e: OperationApplicationException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        }
        ops.clear()
    }
}

جاوا

ArrayList<ContentProviderOperation> ops = new ArrayList<>();
int programsCount = channelInfo.mPrograms.size();
for (int j = 0; j < programsCount; ++j) {
    ProgramInfo program = channelInfo.mPrograms.get(j);
    ops.add(ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI)
            .withValues(programs.get(j))
            .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS,
                    programStartSec * 1000)
            .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS,
                    (programStartSec + program.durationSec) * 1000)
            .build());
    programStartSec = programStartSec + program.durationSec;
    if (j % 100 == 99 || j == programsCount - 1) {
        try {
            getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
        } catch (RemoteException | OperationApplicationException e) {
            Log.e(TAG, "Failed to insert programs.", e);
            return;
        }
        ops.clear();
    }
}

پردازش داده‌های کانال به صورت ناهمزمان

دستکاری داده‌ها، مانند دریافت یک جریان از سرور یا دسترسی به پایگاه داده، نباید نخ رابط کاربری را مسدود کند. استفاده از AsyncTask یکی از راه‌های انجام به‌روزرسانی‌ها به صورت غیرهمزمان است. برای مثال، هنگام بارگیری اطلاعات کانال از یک سرور backend، می‌توانید AsyncTask به صورت زیر استفاده کنید:

کاتلین

private class LoadTvInputTask(val context: Context) : AsyncTask<Uri, Unit, Unit>() {

    override fun doInBackground(vararg uris: Uri) {
        try {
            fetchUri(uris[0])
        } catch (e: IOException) {
            Log.d("LoadTvInputTask", "fetchUri error")
        }
    }

    @Throws(IOException::class)
    private fun fetchUri(videoUri: Uri) {
        context.contentResolver.openInputStream(videoUri).use { inputStream ->
            Xml.newPullParser().also { parser ->
                try {
                    parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
                    parser.setInput(inputStream, null)
                    sTvInput = ChannelXMLParser.parseTvInput(parser)
                    sSampleChannels = ChannelXMLParser.parseChannelXML(parser)
                } catch (e: XmlPullParserException) {
                    e.printStackTrace()
                }
            }
        }
    }
}

جاوا

private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void> {

    private Context mContext;

    public LoadTvInputTask(Context context) {
        mContext = context;
    }

    @Override
    protected Void doInBackground(Uri... uris) {
        try {
            fetchUri(uris[0]);
        } catch (IOException e) {
          Log.d("LoadTvInputTask", "fetchUri error");
        }
        return null;
    }

    private void fetchUri(Uri videoUri) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = mContext.getContentResolver().openInputStream(videoUri);
            XmlPullParser parser = Xml.newPullParser();
            try {
                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
                parser.setInput(inputStream, null);
                sTvInput = ChannelXMLParser.parseTvInput(parser);
                sSampleChannels = ChannelXMLParser.parseChannelXML(parser);
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}

اگر نیاز دارید که داده‌های EPG را به‌طور منظم به‌روزرسانی کنید، استفاده از WorkManager را برای اجرای فرآیند به‌روزرسانی در زمان‌های بیکاری، مثلاً هر روز ساعت ۳ بامداد، در نظر بگیرید.

تکنیک‌های دیگر برای جداسازی وظایف به‌روزرسانی داده‌ها از نخ رابط کاربری شامل استفاده از کلاس HandlerThread است، یا می‌توانید با استفاده از کلاس‌های Looper و Handler ، روش خودتان را پیاده‌سازی کنید. برای اطلاعات بیشتر به Processes and threads مراجعه کنید.

کانال‌ها می‌توانند از لینک‌های برنامه استفاده کنند تا به کاربران اجازه دهند هنگام تماشای محتوای کانال، یک فعالیت مرتبط را اجرا کنند. برنامه‌های کانال از لینک‌های برنامه برای افزایش تعامل کاربر با راه‌اندازی فعالیت‌هایی که اطلاعات مرتبط یا محتوای اضافی را نشان می‌دهند، استفاده می‌کنند. به عنوان مثال، می‌توانید از لینک‌های برنامه برای انجام موارد زیر استفاده کنید:

  • کاربر را برای کشف و خرید محتوای مرتبط راهنمایی کنید.
  • اطلاعات بیشتری در مورد محتوای در حال پخش ارائه دهید.
  • هنگام تماشای محتوای اپیزودیک، شروع به تماشای قسمت بعدی در یک مجموعه کنید.
  • به کاربر اجازه دهید بدون ایجاد وقفه در پخش محتوا، با محتوا تعامل داشته باشد - برای مثال، محتوا را ارزیابی یا بررسی کند.

پیوندهای برنامه زمانی نمایش داده می‌شوند که کاربر هنگام تماشای محتوای کانال، دکمه انتخاب را برای نمایش منوی تلویزیون فشار دهد.

شکل ۱. یک نمونه لینک برنامه که در ردیف کانال‌ها نمایش داده می‌شود، در حالی که محتوای کانال نمایش داده می‌شود.

وقتی کاربر لینک برنامه را انتخاب می‌کند، سیستم یک فعالیت را با استفاده از یک URI هدف مشخص شده توسط برنامه کانال آغاز می‌کند. محتوای کانال در حالی که فعالیت لینک برنامه فعال است، پخش می‌شود. کاربر می‌تواند با فشار دادن دکمه برگشت به محتوای کانال بازگردد .

ارائه داده‌های کانال لینک برنامه

اندروید تی‌وی به طور خودکار با استفاده از اطلاعات داده‌های کانال، یک لینک برنامه برای هر کانال ایجاد می‌کند. برای ارائه اطلاعات لینک برنامه، جزئیات زیر را در فیلدهای TvContract.Channels خود مشخص کنید:

  • COLUMN_APP_LINK_COLOR - رنگ تأکیدی لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۳ مراجعه کنید.
  • COLUMN_APP_LINK_ICON_URI - آدرس اینترنتی (URI) مربوط به آیکون نشان برنامه مربوط به لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۲ مراجعه کنید.
  • COLUMN_APP_LINK_INTENT_URI - آدرس اینترنتی (URI) مربوط به لینک اپلیکیشن برای این کانال. می‌توانید با استفاده از toUri(int) به همراه URI_INTENT_SCHEME این آدرس اینترنتی را ایجاد کنید و با parseUri آن را به آدرس اینترنتی اصلی تبدیل کنید.
  • COLUMN_APP_LINK_POSTER_ART_URI - آدرس اینترنتی پوستری که به عنوان پس‌زمینه لینک برنامه برای این کانال استفاده می‌شود. برای مثال، تصویر پوستر، به شکل ۲، توضیحات ۱ مراجعه کنید.
  • COLUMN_APP_LINK_TEXT - متن لینک توصیفی لینک برنامه برای این کانال. برای مثال، توضیحات لینک برنامه، به متن موجود در شکل ۲، راهنمای ۳ مراجعه کنید.
شکل ۲. جزئیات لینک برنامه.

اگر داده‌های کانال اطلاعات لینک برنامه را مشخص نکنند، سیستم یک لینک برنامه پیش‌فرض ایجاد می‌کند. سیستم جزئیات پیش‌فرض را به شرح زیر انتخاب می‌کند:

  • برای URL هدف ( COLUMN_APP_LINK_INTENT_URI )، سیستم از فعالیت ACTION_MAIN برای دسته CATEGORY_LEANBACK_LAUNCHER استفاده می‌کند که معمولاً در مانیفست برنامه تعریف شده است. اگر این فعالیت تعریف نشده باشد، یک لینک برنامه غیرفعال ظاهر می‌شود - اگر کاربر روی آن کلیک کند، هیچ اتفاقی نمی‌افتد.
  • برای متن توصیفی ( COLUMN_APP_LINK_TEXT )، سیستم از "Open app-name " استفاده می‌کند. اگر هیچ URI برای لینک اپلیکیشن تعریف نشده باشد، سیستم از "No link available" استفاده می‌کند.
  • برای رنگ تأکیدی ( COLUMN_APP_LINK_COLOR )، سیستم از رنگ پیش‌فرض برنامه استفاده می‌کند.
  • برای تصویر پوستر ( COLUMN_APP_LINK_POSTER_ART_URI )، سیستم از بنر صفحه اصلی برنامه استفاده می‌کند. اگر برنامه بنری ارائه ندهد، سیستم از تصویر پیش‌فرض برنامه تلویزیونی استفاده می‌کند.
  • برای آیکون نشان ( COLUMN_APP_LINK_ICON_URI )، سیستم از نشانه‌ای استفاده می‌کند که نام برنامه را نشان می‌دهد. اگر سیستم از بنر برنامه یا تصویر پیش‌فرض برنامه برای تصویر پوستر نیز استفاده کند، هیچ نشان برنامه‌ای نشان داده نمی‌شود.

شما جزئیات لینک برنامه را برای کانال‌های خود در فعالیت راه‌اندازی برنامه خود مشخص می‌کنید. می‌توانید این جزئیات لینک برنامه را در هر زمانی به‌روزرسانی کنید، بنابراین اگر یک لینک برنامه نیاز به مطابقت با تغییرات کانال دارد، جزئیات لینک برنامه را به‌روزرسانی کنید و در صورت نیاز ContentResolver.update را فراخوانی کنید. برای جزئیات بیشتر در مورد به‌روزرسانی داده‌های کانال، به به‌روزرسانی داده‌های کانال مراجعه کنید.

،

ورودی تلویزیون شما باید داده‌های راهنمای برنامه الکترونیکی (EPG) را برای حداقل یک کانال در فعالیت راه‌اندازی خود ارائه دهد. همچنین باید به صورت دوره‌ای آن داده‌ها را با در نظر گرفتن اندازه به‌روزرسانی و رشته پردازشی که آن را مدیریت می‌کند، به‌روزرسانی کنید. علاوه بر این، می‌توانید پیوندهای برنامه‌ای را برای کانال‌هایی ارائه دهید که کاربر را به محتوا و فعالیت‌های مرتبط هدایت می‌کنند. این درس با در نظر گرفتن این ملاحظات، ایجاد و به‌روزرسانی داده‌های کانال و برنامه در پایگاه داده سیستم را مورد بحث قرار می‌دهد.

برنامه نمونه سرویس ورودی تلویزیون را امتحان کنید.

اجازه بگیرید

برای اینکه ورودی تلویزیون شما با داده‌های EPG کار کند، باید مجوز نوشتن را در فایل مانیفست اندروید خود به شرح زیر اعلام کند:

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

ثبت کانال‌ها در پایگاه داده

پایگاه داده سیستم تلویزیون اندروید، رکوردهای داده‌های کانال را برای ورودی‌های تلویزیون نگهداری می‌کند. در فعالیت راه‌اندازی خود، برای هر یک از کانال‌هایتان، باید داده‌های کانال خود را به فیلدهای زیر از کلاس TvContract.Channels نگاشت کنید:

اگرچه چارچوب ورودی تلویزیون به اندازه کافی عمومی است که بتواند هم محتوای پخش سنتی و هم محتوای OTT را بدون هیچ تمایزی مدیریت کند، اما ممکن است بخواهید علاوه بر شناسایی بهتر کانال‌های پخش سنتی، ستون‌های زیر را نیز تعریف کنید:

اگر می‌خواهید جزئیات لینک برنامه را برای کانال‌های خود ارائه دهید، باید برخی فیلدهای اضافی را به‌روزرسانی کنید. برای اطلاعات بیشتر در مورد فیلدهای لینک برنامه، به افزودن اطلاعات لینک برنامه مراجعه کنید.

برای ورودی‌های تلویزیون مبتنی بر پخش اینترنتی، مقادیر خودتان را بر این اساس تعیین کنید تا هر کانال به صورت منحصر به فرد شناسایی شود.

متادیتای کانال خود (به صورت XML، JSON یا هر چیز دیگری) را از سرور backend خود دریافت کنید و در فعالیت راه‌اندازی خود، مقادیر را به صورت زیر به پایگاه داده سیستم نگاشت کنید:

کاتلین

val values = ContentValues().apply {
    put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.number)
    put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.name)
    put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId)
    put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId)
    put(TvContract.Channels.COLUMN_SERVICE_ID, channel.serviceId)
    put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat)
}
val uri = context.contentResolver.insert(TvContract.Channels.CONTENT_URI, values)

جاوا

ContentValues values = new ContentValues();

values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId);
values.put(Channels.COLUMN_SERVICE_ID, channel.serviceId);
values.put(Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat);

Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);

در این مثال، channel شیء‌ای است که فراداده‌های کانال را از سرور backend در خود نگه می‌دارد.

ارائه اطلاعات کانال و برنامه

همانطور که در شکل ۱ نشان داده شده است، برنامه‌ی System TV اطلاعات کانال و برنامه را هنگام تغییر کانال‌ها به کاربران ارائه می‌دهد. برای اطمینان از اینکه اطلاعات کانال و برنامه با ارائه‌دهنده‌ی اطلاعات کانال و برنامه‌ی برنامه‌ی System TV کار می‌کند، این دستورالعمل‌ها را دنبال کنید:

  1. شماره کانال ( COLUMN_DISPLAY_NUMBER )
  2. آیکون ( android:icon در مانیفست ورودی تلویزیون)
  3. شرح برنامه ( COLUMN_SHORT_DESCRIPTION )
  4. عنوان برنامه ( COLUMN_TITLE )
  5. لوگوی کانال ( TvContract.Channels.Logo )
    • از رنگ #EEEEEE برای مطابقت با متن اطراف استفاده کنید
    • پدینگ (padding) را لحاظ نکنید
  6. هنر پوستر ( COLUMN_POSTER_ART_URI )
    • نسبت تصویر بین ۱۶:۹ و ۴:۳
شکل ۱. ارائه‌دهنده اطلاعات کانال و برنامه در اپلیکیشن تلویزیون سیستمی.

برنامه تلویزیون سیستمی، اطلاعات مشابهی را از طریق راهنمای برنامه، از جمله پوستر هنری، همانطور که در شکل 2 نشان داده شده است، ارائه می‌دهد.

شکل ۲. راهنمای برنامه‌ی اپلیکیشن تلویزیون سیستمی.

به‌روزرسانی داده‌های کانال

هنگام به‌روزرسانی داده‌های کانال موجود، به جای حذف و اضافه کردن مجدد داده‌ها، از روش update استفاده کنید. می‌توانید نسخه فعلی داده‌ها را با استفاده از Channels.COLUMN_VERSION_NUMBER و Programs.COLUMN_VERSION_NUMBER هنگام انتخاب رکوردها برای به‌روزرسانی، شناسایی کنید.

توجه: اضافه کردن داده‌های کانال به ContentProvider می‌تواند زمان‌بر باشد. برنامه‌های فعلی (آن‌هایی که در فاصله دو ساعت از زمان فعلی قرار دارند) را فقط زمانی اضافه کنید که EpgSyncJobService خود را طوری پیکربندی کنید که بقیه داده‌های کانال را در پس‌زمینه به‌روزرسانی کند. برای مثال، به برنامه نمونه تلویزیون زنده اندروید تی‌وی مراجعه کنید.

بارگذاری دسته‌ای داده‌های کانال

هنگام به‌روزرسانی پایگاه داده سیستم با حجم زیادی از داده‌های کانال، از متدهای applyBatch یا bulkInsert از ContentResolver استفاده کنید. در اینجا مثالی از استفاده applyBatch آورده شده است:

کاتلین

val ops = ArrayList<ContentProviderOperation>()
val programsCount = channelInfo.mPrograms.size
channelInfo.mPrograms.forEachIndexed { index, program ->
    ops += ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI).run {
        withValues(programs[index])
        withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartSec * 1000)
        withValue(
                TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
                (programStartSec + program.durationSec) * 1000
        )
        build()
    }
    programStartSec += program.durationSec
    if (index % 100 == 99 || index == programsCount - 1) {
        try {
            contentResolver.applyBatch(TvContract.AUTHORITY, ops)
        } catch (e: RemoteException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        } catch (e: OperationApplicationException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        }
        ops.clear()
    }
}

جاوا

ArrayList<ContentProviderOperation> ops = new ArrayList<>();
int programsCount = channelInfo.mPrograms.size();
for (int j = 0; j < programsCount; ++j) {
    ProgramInfo program = channelInfo.mPrograms.get(j);
    ops.add(ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI)
            .withValues(programs.get(j))
            .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS,
                    programStartSec * 1000)
            .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS,
                    (programStartSec + program.durationSec) * 1000)
            .build());
    programStartSec = programStartSec + program.durationSec;
    if (j % 100 == 99 || j == programsCount - 1) {
        try {
            getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
        } catch (RemoteException | OperationApplicationException e) {
            Log.e(TAG, "Failed to insert programs.", e);
            return;
        }
        ops.clear();
    }
}

پردازش داده‌های کانال به صورت ناهمزمان

دستکاری داده‌ها، مانند دریافت یک جریان از سرور یا دسترسی به پایگاه داده، نباید نخ رابط کاربری را مسدود کند. استفاده از AsyncTask یکی از راه‌های انجام به‌روزرسانی‌ها به صورت غیرهمزمان است. برای مثال، هنگام بارگیری اطلاعات کانال از یک سرور backend، می‌توانید AsyncTask به صورت زیر استفاده کنید:

کاتلین

private class LoadTvInputTask(val context: Context) : AsyncTask<Uri, Unit, Unit>() {

    override fun doInBackground(vararg uris: Uri) {
        try {
            fetchUri(uris[0])
        } catch (e: IOException) {
            Log.d("LoadTvInputTask", "fetchUri error")
        }
    }

    @Throws(IOException::class)
    private fun fetchUri(videoUri: Uri) {
        context.contentResolver.openInputStream(videoUri).use { inputStream ->
            Xml.newPullParser().also { parser ->
                try {
                    parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
                    parser.setInput(inputStream, null)
                    sTvInput = ChannelXMLParser.parseTvInput(parser)
                    sSampleChannels = ChannelXMLParser.parseChannelXML(parser)
                } catch (e: XmlPullParserException) {
                    e.printStackTrace()
                }
            }
        }
    }
}

جاوا

private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void> {

    private Context mContext;

    public LoadTvInputTask(Context context) {
        mContext = context;
    }

    @Override
    protected Void doInBackground(Uri... uris) {
        try {
            fetchUri(uris[0]);
        } catch (IOException e) {
          Log.d("LoadTvInputTask", "fetchUri error");
        }
        return null;
    }

    private void fetchUri(Uri videoUri) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = mContext.getContentResolver().openInputStream(videoUri);
            XmlPullParser parser = Xml.newPullParser();
            try {
                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
                parser.setInput(inputStream, null);
                sTvInput = ChannelXMLParser.parseTvInput(parser);
                sSampleChannels = ChannelXMLParser.parseChannelXML(parser);
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}

اگر نیاز دارید که داده‌های EPG را به‌طور منظم به‌روزرسانی کنید، استفاده از WorkManager را برای اجرای فرآیند به‌روزرسانی در زمان‌های بیکاری، مثلاً هر روز ساعت ۳ بامداد، در نظر بگیرید.

تکنیک‌های دیگر برای جداسازی وظایف به‌روزرسانی داده‌ها از نخ رابط کاربری شامل استفاده از کلاس HandlerThread است، یا می‌توانید با استفاده از کلاس‌های Looper و Handler ، روش خودتان را پیاده‌سازی کنید. برای اطلاعات بیشتر به Processes and threads مراجعه کنید.

کانال‌ها می‌توانند از لینک‌های برنامه استفاده کنند تا به کاربران اجازه دهند هنگام تماشای محتوای کانال، یک فعالیت مرتبط را اجرا کنند. برنامه‌های کانال از لینک‌های برنامه برای افزایش تعامل کاربر با راه‌اندازی فعالیت‌هایی که اطلاعات مرتبط یا محتوای اضافی را نشان می‌دهند، استفاده می‌کنند. به عنوان مثال، می‌توانید از لینک‌های برنامه برای انجام موارد زیر استفاده کنید:

  • کاربر را برای کشف و خرید محتوای مرتبط راهنمایی کنید.
  • اطلاعات بیشتری در مورد محتوای در حال پخش ارائه دهید.
  • هنگام تماشای محتوای اپیزودیک، شروع به تماشای قسمت بعدی در یک مجموعه کنید.
  • به کاربر اجازه دهید بدون ایجاد وقفه در پخش محتوا، با محتوا تعامل داشته باشد - برای مثال، محتوا را ارزیابی یا بررسی کند.

پیوندهای برنامه زمانی نمایش داده می‌شوند که کاربر هنگام تماشای محتوای کانال، دکمه انتخاب را برای نمایش منوی تلویزیون فشار دهد.

شکل ۱. یک نمونه لینک برنامه که در ردیف کانال‌ها نمایش داده می‌شود، در حالی که محتوای کانال نمایش داده می‌شود.

وقتی کاربر لینک برنامه را انتخاب می‌کند، سیستم یک فعالیت را با استفاده از یک URI هدف مشخص شده توسط برنامه کانال آغاز می‌کند. محتوای کانال در حالی که فعالیت لینک برنامه فعال است، پخش می‌شود. کاربر می‌تواند با فشار دادن دکمه برگشت به محتوای کانال بازگردد .

ارائه داده‌های کانال لینک برنامه

اندروید تی‌وی به طور خودکار با استفاده از اطلاعات داده‌های کانال، یک لینک برنامه برای هر کانال ایجاد می‌کند. برای ارائه اطلاعات لینک برنامه، جزئیات زیر را در فیلدهای TvContract.Channels خود مشخص کنید:

  • COLUMN_APP_LINK_COLOR - رنگ تأکیدی لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۳ مراجعه کنید.
  • COLUMN_APP_LINK_ICON_URI - آدرس اینترنتی (URI) مربوط به آیکون نشان برنامه مربوط به لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۲ مراجعه کنید.
  • COLUMN_APP_LINK_INTENT_URI - آدرس اینترنتی (URI) مربوط به لینک اپلیکیشن برای این کانال. می‌توانید با استفاده از toUri(int) به همراه URI_INTENT_SCHEME این آدرس اینترنتی را ایجاد کنید و با parseUri آن را به آدرس اینترنتی اصلی تبدیل کنید.
  • COLUMN_APP_LINK_POSTER_ART_URI - آدرس اینترنتی پوستری که به عنوان پس‌زمینه لینک برنامه برای این کانال استفاده می‌شود. برای مثال، تصویر پوستر، به شکل ۲، توضیحات ۱ مراجعه کنید.
  • COLUMN_APP_LINK_TEXT - متن لینک توصیفی لینک برنامه برای این کانال. برای مثال، توضیحات لینک برنامه، به متن موجود در شکل ۲، راهنمای ۳ مراجعه کنید.
شکل ۲. جزئیات لینک برنامه.

اگر داده‌های کانال اطلاعات لینک برنامه را مشخص نکنند، سیستم یک لینک برنامه پیش‌فرض ایجاد می‌کند. سیستم جزئیات پیش‌فرض را به شرح زیر انتخاب می‌کند:

  • برای URL هدف ( COLUMN_APP_LINK_INTENT_URI )، سیستم از فعالیت ACTION_MAIN برای دسته CATEGORY_LEANBACK_LAUNCHER استفاده می‌کند که معمولاً در مانیفست برنامه تعریف شده است. اگر این فعالیت تعریف نشده باشد، یک لینک برنامه غیرفعال ظاهر می‌شود - اگر کاربر روی آن کلیک کند، هیچ اتفاقی نمی‌افتد.
  • برای متن توصیفی ( COLUMN_APP_LINK_TEXT )، سیستم از "Open app-name " استفاده می‌کند. اگر هیچ URI برای لینک اپلیکیشن تعریف نشده باشد، سیستم از "No link available" استفاده می‌کند.
  • برای رنگ تأکیدی ( COLUMN_APP_LINK_COLOR )، سیستم از رنگ پیش‌فرض برنامه استفاده می‌کند.
  • برای تصویر پوستر ( COLUMN_APP_LINK_POSTER_ART_URI )، سیستم از بنر صفحه اصلی برنامه استفاده می‌کند. اگر برنامه بنری ارائه ندهد، سیستم از تصویر پیش‌فرض برنامه تلویزیونی استفاده می‌کند.
  • برای آیکون نشان ( COLUMN_APP_LINK_ICON_URI )، سیستم از نشانه‌ای استفاده می‌کند که نام برنامه را نشان می‌دهد. اگر سیستم از بنر برنامه یا تصویر پیش‌فرض برنامه برای تصویر پوستر نیز استفاده کند، هیچ نشان برنامه‌ای نشان داده نمی‌شود.

شما جزئیات لینک برنامه را برای کانال‌های خود در فعالیت راه‌اندازی برنامه خود مشخص می‌کنید. می‌توانید این جزئیات لینک برنامه را در هر زمانی به‌روزرسانی کنید، بنابراین اگر یک لینک برنامه نیاز به مطابقت با تغییرات کانال دارد، جزئیات لینک برنامه را به‌روزرسانی کنید و در صورت نیاز ContentResolver.update را فراخوانی کنید. برای جزئیات بیشتر در مورد به‌روزرسانی داده‌های کانال، به به‌روزرسانی داده‌های کانال مراجعه کنید.

،

ورودی تلویزیون شما باید داده‌های راهنمای برنامه الکترونیکی (EPG) را برای حداقل یک کانال در فعالیت راه‌اندازی خود ارائه دهد. همچنین باید به صورت دوره‌ای آن داده‌ها را با در نظر گرفتن اندازه به‌روزرسانی و رشته پردازشی که آن را مدیریت می‌کند، به‌روزرسانی کنید. علاوه بر این، می‌توانید پیوندهای برنامه‌ای را برای کانال‌هایی ارائه دهید که کاربر را به محتوا و فعالیت‌های مرتبط هدایت می‌کنند. این درس با در نظر گرفتن این ملاحظات، ایجاد و به‌روزرسانی داده‌های کانال و برنامه در پایگاه داده سیستم را مورد بحث قرار می‌دهد.

برنامه نمونه سرویس ورودی تلویزیون را امتحان کنید.

اجازه بگیرید

برای اینکه ورودی تلویزیون شما با داده‌های EPG کار کند، باید مجوز نوشتن را در فایل مانیفست اندروید خود به شرح زیر اعلام کند:

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

ثبت کانال‌ها در پایگاه داده

پایگاه داده سیستم تلویزیون اندروید، رکوردهای داده‌های کانال را برای ورودی‌های تلویزیون نگهداری می‌کند. در فعالیت راه‌اندازی خود، برای هر یک از کانال‌هایتان، باید داده‌های کانال خود را به فیلدهای زیر از کلاس TvContract.Channels نگاشت کنید:

اگرچه چارچوب ورودی تلویزیون به اندازه کافی عمومی است که بتواند هم محتوای پخش سنتی و هم محتوای OTT را بدون هیچ تمایزی مدیریت کند، اما ممکن است بخواهید علاوه بر شناسایی بهتر کانال‌های پخش سنتی، ستون‌های زیر را نیز تعریف کنید:

اگر می‌خواهید جزئیات لینک برنامه را برای کانال‌های خود ارائه دهید، باید برخی فیلدهای اضافی را به‌روزرسانی کنید. برای اطلاعات بیشتر در مورد فیلدهای لینک برنامه، به افزودن اطلاعات لینک برنامه مراجعه کنید.

برای ورودی‌های تلویزیون مبتنی بر پخش اینترنتی، مقادیر خودتان را بر این اساس تعیین کنید تا هر کانال به صورت منحصر به فرد شناسایی شود.

متادیتای کانال خود (به صورت XML، JSON یا هر چیز دیگری) را از سرور backend خود دریافت کنید و در فعالیت راه‌اندازی خود، مقادیر را به صورت زیر به پایگاه داده سیستم نگاشت کنید:

کاتلین

val values = ContentValues().apply {
    put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.number)
    put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.name)
    put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId)
    put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId)
    put(TvContract.Channels.COLUMN_SERVICE_ID, channel.serviceId)
    put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat)
}
val uri = context.contentResolver.insert(TvContract.Channels.CONTENT_URI, values)

جاوا

ContentValues values = new ContentValues();

values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId);
values.put(Channels.COLUMN_SERVICE_ID, channel.serviceId);
values.put(Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat);

Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);

در این مثال، channel شیء‌ای است که فراداده‌های کانال را از سرور backend در خود نگه می‌دارد.

ارائه اطلاعات کانال و برنامه

همانطور که در شکل ۱ نشان داده شده است، برنامه‌ی System TV اطلاعات کانال و برنامه را هنگام تغییر کانال‌ها به کاربران ارائه می‌دهد. برای اطمینان از اینکه اطلاعات کانال و برنامه با ارائه‌دهنده‌ی اطلاعات کانال و برنامه‌ی برنامه‌ی System TV کار می‌کند، این دستورالعمل‌ها را دنبال کنید:

  1. شماره کانال ( COLUMN_DISPLAY_NUMBER )
  2. آیکون ( android:icon در مانیفست ورودی تلویزیون)
  3. شرح برنامه ( COLUMN_SHORT_DESCRIPTION )
  4. عنوان برنامه ( COLUMN_TITLE )
  5. لوگوی کانال ( TvContract.Channels.Logo )
    • از رنگ #EEEEEE برای مطابقت با متن اطراف استفاده کنید
    • پدینگ (padding) را لحاظ نکنید
  6. هنر پوستر ( COLUMN_POSTER_ART_URI )
    • نسبت تصویر بین ۱۶:۹ و ۴:۳
شکل ۱. ارائه‌دهنده اطلاعات کانال و برنامه در اپلیکیشن تلویزیون سیستمی.

برنامه تلویزیون سیستمی، اطلاعات مشابهی را از طریق راهنمای برنامه، از جمله پوستر هنری، همانطور که در شکل 2 نشان داده شده است، ارائه می‌دهد.

شکل ۲. راهنمای برنامه‌ی اپلیکیشن تلویزیون سیستمی.

به‌روزرسانی داده‌های کانال

هنگام به‌روزرسانی داده‌های کانال موجود، به جای حذف و اضافه کردن مجدد داده‌ها، از روش update استفاده کنید. می‌توانید نسخه فعلی داده‌ها را با استفاده از Channels.COLUMN_VERSION_NUMBER و Programs.COLUMN_VERSION_NUMBER هنگام انتخاب رکوردها برای به‌روزرسانی، شناسایی کنید.

توجه: اضافه کردن داده‌های کانال به ContentProvider می‌تواند زمان‌بر باشد. برنامه‌های فعلی (آن‌هایی که در فاصله دو ساعت از زمان فعلی قرار دارند) را فقط زمانی اضافه کنید که EpgSyncJobService خود را طوری پیکربندی کنید که بقیه داده‌های کانال را در پس‌زمینه به‌روزرسانی کند. برای مثال، به برنامه نمونه تلویزیون زنده اندروید تی‌وی مراجعه کنید.

بارگذاری دسته‌ای داده‌های کانال

هنگام به‌روزرسانی پایگاه داده سیستم با حجم زیادی از داده‌های کانال، از متدهای applyBatch یا bulkInsert از ContentResolver استفاده کنید. در اینجا مثالی از استفاده applyBatch آورده شده است:

کاتلین

val ops = ArrayList<ContentProviderOperation>()
val programsCount = channelInfo.mPrograms.size
channelInfo.mPrograms.forEachIndexed { index, program ->
    ops += ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI).run {
        withValues(programs[index])
        withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartSec * 1000)
        withValue(
                TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
                (programStartSec + program.durationSec) * 1000
        )
        build()
    }
    programStartSec += program.durationSec
    if (index % 100 == 99 || index == programsCount - 1) {
        try {
            contentResolver.applyBatch(TvContract.AUTHORITY, ops)
        } catch (e: RemoteException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        } catch (e: OperationApplicationException) {
            Log.e(TAG, "Failed to insert programs.", e)
            return
        }
        ops.clear()
    }
}

جاوا

ArrayList<ContentProviderOperation> ops = new ArrayList<>();
int programsCount = channelInfo.mPrograms.size();
for (int j = 0; j < programsCount; ++j) {
    ProgramInfo program = channelInfo.mPrograms.get(j);
    ops.add(ContentProviderOperation.newInsert(
            TvContract.Programs.CONTENT_URI)
            .withValues(programs.get(j))
            .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS,
                    programStartSec * 1000)
            .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS,
                    (programStartSec + program.durationSec) * 1000)
            .build());
    programStartSec = programStartSec + program.durationSec;
    if (j % 100 == 99 || j == programsCount - 1) {
        try {
            getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
        } catch (RemoteException | OperationApplicationException e) {
            Log.e(TAG, "Failed to insert programs.", e);
            return;
        }
        ops.clear();
    }
}

پردازش داده‌های کانال به صورت ناهمزمان

دستکاری داده‌ها، مانند دریافت یک جریان از سرور یا دسترسی به پایگاه داده، نباید نخ رابط کاربری را مسدود کند. استفاده از AsyncTask یکی از راه‌های انجام به‌روزرسانی‌ها به صورت غیرهمزمان است. برای مثال، هنگام بارگیری اطلاعات کانال از یک سرور backend، می‌توانید AsyncTask به صورت زیر استفاده کنید:

کاتلین

private class LoadTvInputTask(val context: Context) : AsyncTask<Uri, Unit, Unit>() {

    override fun doInBackground(vararg uris: Uri) {
        try {
            fetchUri(uris[0])
        } catch (e: IOException) {
            Log.d("LoadTvInputTask", "fetchUri error")
        }
    }

    @Throws(IOException::class)
    private fun fetchUri(videoUri: Uri) {
        context.contentResolver.openInputStream(videoUri).use { inputStream ->
            Xml.newPullParser().also { parser ->
                try {
                    parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
                    parser.setInput(inputStream, null)
                    sTvInput = ChannelXMLParser.parseTvInput(parser)
                    sSampleChannels = ChannelXMLParser.parseChannelXML(parser)
                } catch (e: XmlPullParserException) {
                    e.printStackTrace()
                }
            }
        }
    }
}

جاوا

private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void> {

    private Context mContext;

    public LoadTvInputTask(Context context) {
        mContext = context;
    }

    @Override
    protected Void doInBackground(Uri... uris) {
        try {
            fetchUri(uris[0]);
        } catch (IOException e) {
          Log.d("LoadTvInputTask", "fetchUri error");
        }
        return null;
    }

    private void fetchUri(Uri videoUri) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = mContext.getContentResolver().openInputStream(videoUri);
            XmlPullParser parser = Xml.newPullParser();
            try {
                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
                parser.setInput(inputStream, null);
                sTvInput = ChannelXMLParser.parseTvInput(parser);
                sSampleChannels = ChannelXMLParser.parseChannelXML(parser);
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}

اگر نیاز دارید که داده‌های EPG را به‌طور منظم به‌روزرسانی کنید، استفاده از WorkManager را برای اجرای فرآیند به‌روزرسانی در زمان‌های بیکاری، مثلاً هر روز ساعت ۳ بامداد، در نظر بگیرید.

تکنیک‌های دیگر برای جداسازی وظایف به‌روزرسانی داده‌ها از نخ رابط کاربری شامل استفاده از کلاس HandlerThread است، یا می‌توانید با استفاده از کلاس‌های Looper و Handler ، روش خودتان را پیاده‌سازی کنید. برای اطلاعات بیشتر به Processes and threads مراجعه کنید.

کانال‌ها می‌توانند از لینک‌های برنامه استفاده کنند تا به کاربران اجازه دهند هنگام تماشای محتوای کانال، یک فعالیت مرتبط را اجرا کنند. برنامه‌های کانال از لینک‌های برنامه برای افزایش تعامل کاربر با راه‌اندازی فعالیت‌هایی که اطلاعات مرتبط یا محتوای اضافی را نشان می‌دهند، استفاده می‌کنند. به عنوان مثال، می‌توانید از لینک‌های برنامه برای انجام موارد زیر استفاده کنید:

  • کاربر را برای کشف و خرید محتوای مرتبط راهنمایی کنید.
  • اطلاعات بیشتری در مورد محتوای در حال پخش ارائه دهید.
  • هنگام تماشای محتوای اپیزودیک، شروع به تماشای قسمت بعدی در یک مجموعه کنید.
  • به کاربر اجازه دهید بدون ایجاد وقفه در پخش محتوا، با محتوا تعامل داشته باشد - برای مثال، محتوا را ارزیابی یا بررسی کند.

پیوندهای برنامه زمانی نمایش داده می‌شوند که کاربر هنگام تماشای محتوای کانال، دکمه انتخاب را برای نمایش منوی تلویزیون فشار دهد.

شکل ۱. یک نمونه لینک برنامه که در ردیف کانال‌ها نمایش داده می‌شود، در حالی که محتوای کانال نمایش داده می‌شود.

وقتی کاربر لینک برنامه را انتخاب می‌کند، سیستم یک فعالیت را با استفاده از یک URI هدف مشخص شده توسط برنامه کانال آغاز می‌کند. محتوای کانال در حالی که فعالیت لینک برنامه فعال است، پخش می‌شود. کاربر می‌تواند با فشار دادن دکمه برگشت به محتوای کانال بازگردد .

ارائه داده‌های کانال لینک برنامه

اندروید تی‌وی به طور خودکار با استفاده از اطلاعات داده‌های کانال، یک لینک برنامه برای هر کانال ایجاد می‌کند. برای ارائه اطلاعات لینک برنامه، جزئیات زیر را در فیلدهای TvContract.Channels خود مشخص کنید:

  • COLUMN_APP_LINK_COLOR - رنگ تأکیدی لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۳ مراجعه کنید.
  • COLUMN_APP_LINK_ICON_URI - آدرس اینترنتی (URI) مربوط به آیکون نشان برنامه مربوط به لینک برنامه برای این کانال. برای مثال، به شکل ۲، توضیحات ۲ مراجعه کنید.
  • COLUMN_APP_LINK_INTENT_URI - آدرس اینترنتی (URI) مربوط به لینک اپلیکیشن برای این کانال. می‌توانید با استفاده از toUri(int) به همراه URI_INTENT_SCHEME این آدرس اینترنتی را ایجاد کنید و با parseUri آن را به آدرس اینترنتی اصلی تبدیل کنید.
  • COLUMN_APP_LINK_POSTER_ART_URI - آدرس اینترنتی پوستری که به عنوان پس‌زمینه لینک برنامه برای این کانال استفاده می‌شود. برای مثال، تصویر پوستر، به شکل ۲، توضیحات ۱ مراجعه کنید.
  • COLUMN_APP_LINK_TEXT - The descriptive link text of the app link for this channel. For an example app link description, see the text in figure 2, callout 3.
Figure 2. App link details.

If the channel data doesn't specify app link information, the system creates a default app link. The system chooses default details as follows:

  • For the intent URI ( COLUMN_APP_LINK_INTENT_URI ), the system uses the ACTION_MAIN activity for the CATEGORY_LEANBACK_LAUNCHER category, typically defined in the app manifest. If this activity is not defined, a non-functioning app link appears—if the user clicks it, nothing happens.
  • For the descriptive text ( COLUMN_APP_LINK_TEXT ), the system uses "Open app-name ". If no viable app link intent URI is defined, the system uses "No link available".
  • For the accent color ( COLUMN_APP_LINK_COLOR ), the system uses the default app color.
  • For the poster image ( COLUMN_APP_LINK_POSTER_ART_URI ), the system uses the app's home screen banner. If the app doesn't provide a banner, the system uses a default TV app image.
  • For the badge icon ( COLUMN_APP_LINK_ICON_URI ), the system uses a badge that shows the app name. If the system is also using the app banner or default app image for the poster image, no app badge is shown.

You specify app link details for your channels in your app's setup activity. You can update these app link details at any point, so if an app link needs to match channel changes, update app link details and call ContentResolver.update as needed. For more details on updating channel data, see Update channel data .