עבודה עם נתוני הערוץ

קלט הטלוויזיה חייב לספק נתונים של מדריך התוכניות האלקטרוני (EPG) למשך ערוץ אחד בפעילות ההגדרה שלו. כדאי גם לעדכן מדי פעם את הפרטים הבאים תוך התייחסות לגודל העדכון ולשרשור העיבוד שמטפל בזה. בנוסף, אפשר לספק קישורים לאפליקציה לערוצים שינחו את המשתמשים לפעילויות ולתכנים קשורים. השיעור הזה עוסק ביצירה ובעדכון של נתונים לגבי ערוצים ותוכנית את מסד הנתונים של המערכת מתוך מחשבה על השיקולים האלה.

אפשר לנסות את אפליקציה לדוגמה של TV קלט Service.

לקבלת הרשאה

כדי שקלט הטלוויזיה יפעל עם נתוני EPG, עליו להצהיר על הרשאת כתיבה בקובץ המניפסט של Android באופן הבא:

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

רישום ערוצים במסד הנתונים

מסד הנתונים של המערכת של Android TV שומר רשומות של נתוני הערוץ עבור קלט של טלוויזיה. בהגדרה עליכם למפות את נתוני הערוץ לשדות הבאים של כל אחד מהערוצים שלכם, כיתה אחת (TvContract.Channels):

למרות שמסגרת הקלט של הטלוויזיה היא כללית מספיק כדי לטפל גם בשידורים רגילים וגם תוכן 'ישירות ללקוח' (OTT) ללא הבחנה, מומלץ להגדיר את העמודות הבאות בנוסף לאלו שצוינו למעלה, כדי לזהות טוב יותר ערוצי שידור מסורתיים:

אם ברצונך לספק את פרטי הקישור לאפליקציה עבור הערוצים שלך, עליך: לעדכן שדות נוספים. למידע נוסף על שדות קישור לאפליקציות: מוסיפים מידע על קישור לאפליקציה.

לכניסות טלוויזיה שמבוססות על סטרימינג באינטרנט, מקצים את הערכים שלכם לערכים שלמעלה בהתאם, ניתן לזהות כל ערוץ באופן ייחודי.

שולפים את המטא-נתונים של הערוץ (ב-XML, JSON או כל דבר אחר) משרת הקצה העורפי ובהגדרה מיפוי הערכים למסד הנתונים של המערכת באופן הבא:

Kotlin

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)

Java

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 הוא אובייקט שמכיל מטא-נתונים של ערוץ שרת עורפי.

הצגת מידע על הערוץ והתוכנית

אפליקציית המערכת של YouTube TV מציגה למשתמשים מידע על הערוצים והתוכניות בזמן שהם מעיינים בערוצים, כפי שמתואר באיור 1. כדי לוודא שפרטי הערוץ והתוכנית פועלים עם אם אתם מציגים את פרטי הערוץ והתוכנית, עליכם לפעול לפי ההנחיות הבאות.

  1. מספר הערוץ (COLUMN_DISPLAY_NUMBER)
  2. סמל (android:icon ב מניפסט של קלט טלוויזיה)
  3. תיאור המועדון (COLUMN_SHORT_DESCRIPTION)
  4. שם המועדון (COLUMN_TITLE)
  5. הלוגו של הערוץ (TvContract.Channels.Logo)
    • צריך להשתמש בצבע #EEEEEE כדי להתאים לטקסט שמסביב
    • אין לכלול מרווח פנימי
  6. עיצוב פוסטר (COLUMN_POSTER_ART_URI)
    • יחס גובה-רוחב בין 16:9 ל-4:3

איור 1. מציג פרטי התוכנית והערוץ של אפליקציית המערכת לטלוויזיה.

אפליקציית המערכת של YouTube TV מספקת את אותו מידע דרך מדריך התוכניות, כולל פוסטר, כפי שמתואר באיור 2.

איור 2. מדריך התוכניות של אפליקציית המערכת לטלוויזיה.

עדכון נתוני הערוץ

כאשר אתם מעדכנים נתוני ערוצים קיימים, השתמשו update() במקום למחוק את הנתונים ולהוסיף אותם מחדש. אפשר לזהות את הגרסה הנוכחית של הנתונים באמצעות Channels.COLUMN_VERSION_NUMBER ו-Programs.COLUMN_VERSION_NUMBER כשבוחרים את הרשומות לעדכון.

הערה: אנחנו מוסיפים את נתוני הערוץ אל ContentProvider יכול לקחת זמן. מוסיפים את התוכניות הנוכחיות (תוכניות בשעתיים מהשעה הנוכחית) רק כשהגדרת את EpgSyncJobService לעדכון השאר של נתוני הערוץ ברקע. צפייה ה למשל, אפליקציית Android TV Live TV Sample.

טעינת נתוני ערוצים באצווה

כשמעדכנים את מסד הנתונים של המערכת עם כמות גדולה של נתוני ערוץ, צריך להשתמש בפונקציה ContentResolver applyBatch() או bulkInsert() . הנה דוגמה באמצעות applyBatch():

Kotlin

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()
    }
}

Java

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();
    }
}

עיבוד נתוני הערוץ באופן אסינכרוני

ביצוע מניפולציות על נתונים, כמו אחזור זרם מהשרת או גישה למסד הנתונים, לא לחסום את ה-thread של ממשק המשתמש. השימוש ב-AsyncTask הוא אחד לבצע עדכונים באופן אסינכרוני. לדוגמה, כשטוענים את פרטי הערוץ משרת עורפי, אפשר להשתמש ב-AsyncTask באופן הבא:

Kotlin

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()
                }
            }
        }
    }
}

Java

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 כדי להריץ את תהליך העדכון בזמן חוסר פעילות, למשל כל יום בשעה 3:00 בבוקר.

שיטות אחרות להפרדת משימות עדכון הנתונים מהשרשור ב-UI: HandlerThread כיתה, או שאפשר להטמיע משלך באמצעות Looper ו-Handler כיתות. ראו תהליכים ושרשורים למידע נוסף.

ערוצים יכולים להשתמש בקישורים לאפליקציות כדי לאפשר למשתמשים להפעיל בקלות קמפיין קשור פעילות בזמן שהם צופים בתוכן של הערוץ. שימוש באפליקציות ערוץ קישורים לאפליקציה כדי להגדיל את מעורבות המשתמשים על ידי הפעלת פעילויות מידע קשור או תוכן נוסף. לדוגמה, אפשר להשתמש בקישורים לאפליקציות לבצע את הפעולות הבאות:

  • להנחות את המשתמשים לגלות תוכן קשור ולרכוש אותו.
  • ניתן לספק מידע נוסף על התוכן שמופעל כרגע.
  • בזמן הצפייה בתוכן שמתייחס לפרקים, מתחילים לצפות בפרק הבא סדרות.
  • מתן אפשרות למשתמש ליצור אינטראקציה עם התוכן — לדוגמה, דירוג או כתיבת ביקורת תוכן — בלי להפריע להפעלת התוכן.

קישורים לאפליקציות מוצגים כשהמשתמש לוחץ על בחירה כדי להציג את תפריט טלוויזיה בזמן צפייה בתוכן של ערוץ.

איור 1. קישור לאפליקציה לדוגמה בשורה ערוצים בזמן שתוכן הערוץ מוצג.

כשהמשתמש לוחץ על הקישור לאפליקציה, המערכת מתחילה פעילות באמצעות URI של Intent שצוין על ידי אפליקציית הערוץ. תוכן הערוץ ממשיך לפעול כשהפעילות של הקישור לאפליקציה פעילה. המשתמש יכול לחזור לערוץ לוחצים על הקודם.

שליחת נתונים לגבי ערוץ של קישור לאפליקציה

מערכת Android TV יוצרת באופן אוטומטי קישור לאפליקציה עבור כל ערוץ, על סמך מידע מנתוני הערוץ. כדי לספק מידע על קישור לאפליקציה: צריך לציין את הפרטים הבאים TvContract.Channels שדות:

  • COLUMN_APP_LINK_COLOR – הצבע המשני של הקישור לאפליקציה בערוץ הזה. כדי לראות דוגמה לצבע משני: ראו איור 2, הסבר 3.
  • COLUMN_APP_LINK_ICON_URI – ה-URI של סמל תג האפליקציה של הקישור לאפליקציה עבור הערוץ הזה. עבור סמל של תג אפליקציה לדוגמה, ראו איור 2, יתרונות מרכזיים 2.
  • COLUMN_APP_LINK_INTENT_URI – ה-URI של ה-Intent של הקישור לאפליקציה עבור הערוץ הזה. אפשר ליצור את ה-URI שימוש ב-toUri(int) עם URI_INTENT_SCHEME והקבוצה ממירים את ה-URI בחזרה ל-Intent המקורי עם parseUri().
  • COLUMN_APP_LINK_POSTER_ART_URI - ה-URI של הפוסטר שמשמש כרקע של הקישור לאפליקציה לערוץ הזה. לתמונה של פוסטר לדוגמה, ראו איור 2, הסבר 1.
  • COLUMN_APP_LINK_TEXT – טקסט הקישור התיאורי של הקישור לאפליקציה בערוץ הזה. לדוגמה תיאור הקישור לאפליקציה, לראות את הטקסט בתרשים 2, יתרונות מרכזיים 3.

איור 2. פרטי הקישור לאפליקציה.

אם נתוני הערוץ לא מציינים פרטי קישור לאפליקציה, המערכת יוצר קישור ברירת מחדל לאפליקציה. המערכת בוחרת את פרטי ברירת המחדל באופן הבא:

  • ל-Intent ה-URI (COLUMN_APP_LINK_INTENT_URI), המערכת משתמשת ב-ACTION_MAIN פעילות לקטגוריה CATEGORY_LEANBACK_LAUNCHER, שמוגדרת בדרך כלל בקובץ המניפסט של האפליקציה. אם הפעילות הזו לא מוגדרת, יופיע קישור לא פעיל לאפליקציה, אם שהמשתמש לוחץ עליו, לא קורה כלום.
  • לטקסט התיאורי (COLUMN_APP_LINK_TEXT), המערכת משתמש באפשרות 'פתיחת app-name'. אם לא מוגדר URI קיים של Intent לקישור לאפליקציה, המערכת משתמשת באפשרות 'אין קישור זמין'.
  • לצבע המשני (COLUMN_APP_LINK_COLOR), המערכת משתמשת בצבע ברירת המחדל של האפליקציה.
  • לגבי תמונת הפוסטר (COLUMN_APP_LINK_POSTER_ART_URI), המערכת משתמשת בבאנר של האפליקציה במסך הבית. אם האפליקציה לא מספקת מודעת באנר, המערכת משתמשת בתמונת ברירת מחדל של אפליקציית טלוויזיה.
  • לסמל התג (COLUMN_APP_LINK_ICON_URI), מערכת משתמשת בתג שמציג את שם האפליקציה. אם המערכת משתמשת גם באנר של אפליקציה או תמונת ברירת מחדל של האפליקציה לתמונת הפוסטר, לא מוצג תג אפליקציה.

מציינים את פרטי הקישור לאפליקציה בערוצים של האפליקציה פעילות הגדרה. תמיד אפשר לעדכן את פרטי הקישור לאפליקציה, כדי אם הקישור לאפליקציה צריך להתאים לשינויים בערוץ, צריך לעדכן את האפליקציה פרטי הקישור והשיחה ContentResolver.update() לפי הצורך. לפרטים נוספים על העדכון נתוני הערוץ, כדאי לעיין במאמר עדכון נתוני הערוץ.