הקלט של הטלוויזיה צריך לספק נתונים של מדריך תוכניות אלקטרוני (EPG) לפחות לערוץ אחד בפעילות ההגדרה שלו. כדאי גם לעדכן את הנתונים האלה מדי פעם, תוך התחשבות בגודל העדכון ובשרשור העיבוד שמטפל בו. בנוסף, ניתן לספק קישורי אפליקציות לערוצים שמובילים את המשתמש לתכנים ופעילויות קשורים. בשיעור הזה נדון ביצירה ובעדכון של נתוני ערוצים ותוכניות במסד הנתונים של המערכת, תוך התחשבות בשיקולים האלה.
נסה את אפליקציית הדוגמה של שירות קלט הטלוויזיה.
קבל אישור
כדי שהקלט של הטלוויזיה יפעל עם נתוני EPG, צריך להצהיר על הרשאת הכתיבה בקובץ המניפסט של Android באופן הבא:
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
רישום ערוצים במסד הנתונים
במסד הנתונים של מערכת Android TV נשמרים רשומות של נתוני ערוצים עבור מקורות קלט של טלוויזיה. בפעילות ההגדרה, לכל אחד מהערוצים, צריך למפות את נתוני הערוץ לשדות הבאים של המחלקה TvContract.Channels:
-
COLUMN_DISPLAY_NAME– השם המוצג של הערוץ -
COLUMN_DISPLAY_NUMBER– מספר הערוץ שמוצג COLUMN_INPUT_ID- המזהה של שירות קלט הטלוויזיה-
COLUMN_SERVICE_TYPE– סוג השירות של הערוץ COLUMN_TYPE- סוג השידור הסטנדרטי של הערוץ-
COLUMN_VIDEO_FORMAT– פורמט ברירת המחדל של הסרטונים בערוץ
למרות שמסגרת קלט הטלוויזיה כללית מספיק כדי לטפל הן בתכני שידור מסורתיים והן בתכני OTT (Over-the-Top) ללא כל הבחנה, ייתכן שתרצו להגדיר את העמודות הבאות בנוסף כדי לזהות טוב יותר ערוצי שידור מסורתיים:
-
COLUMN_ORIGINAL_NETWORK_ID– מזהה רשת הטלוויזיה COLUMN_SERVICE_ID- מזהה השירות-
COLUMN_TRANSPORT_STREAM_ID– מזהה מקור הנתונים של התעבורה
אם ברצונך לספק פרטי קישור לאפליקציה עבור הערוצים שלך, עליך לעדכן כמה שדות נוספים. מידע נוסף על שדות של קישורים לאפליקציות זמין במאמר בנושא הוספת פרטים של קישורים לאפליקציות.
עבור כניסות טלוויזיה המבוססות על הזרמת אינטרנט, הקצה ערכים משלך בהתאם כך שניתן יהיה לזהות כל ערוץ באופן ייחודי.
שולפים את המטא-נתונים של הערוץ (בפורמט 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 הוא אובייקט שמכיל מטא-נתונים של הערוץ משרת הקצה העורפי.
מידע על ערוצים ותוכניות
אפליקציית הטלוויזיה של המערכת מציגה למשתמשים מידע על הערוצים והתוכניות כשהם עוברים בין הערוצים, כמו שמוצג באיור 1. כדי לוודא שפרטי הערוץ והתוכנית פועלים יחד עם מגיש פרטי הערוצים והתוכנית של אפליקציית הטלוויזיה של המערכת, יש לפעול לפי ההנחיות הבאות:
- מספר הערוץ (
COLUMN_DISPLAY_NUMBER) - אייקון (
android:iconבמניפסט של קלט הטלוויזיה) - תיאור התוכנית (
COLUMN_SHORT_DESCRIPTION) - כותרת התוכנית (
COLUMN_TITLE) - הלוגו של הערוץ (
TvContract.Channels.Logo)- שימוש בצבע #EEEEEE כדי להתאים לטקסט שמסביב
- לא לכלול מרווח פנימי
- Poster art (
COLUMN_POSTER_ART_URI) - יחס גובה-רוחב: בין 16:9 ל-4:3
אפליקציית הטלוויזיה של המערכת מספקת את אותו מידע דרך לוח השידורים, כולל תמונת הפוסטר, כמו שמוצג באיור 2.
עדכון נתוני הערוץ
כשמעדכנים נתונים קיימים של ערוץ, צריך להשתמש בשיטה update במקום למחוק את הנתונים ולהוסיף אותם מחדש. כדי לזהות את הגרסה הנוכחית של הנתונים, אפשר להשתמש ב-Channels.COLUMN_VERSION_NUMBER וב-Programs.COLUMN_VERSION_NUMBER כשבוחרים את הרשומות לעדכון.
הערה: יכול להיות שיעבור זמן עד שנתוני הערוץ יתווספו אל ContentProvider. מוסיפים תוכניות שמשודרות כרגע (בטווח של שעתיים מהשעה הנוכחית) רק כשמגדירים את EpgSyncJobService לעדכן את שאר נתוני הערוץ ברקע. דוגמה אפשר לראות ב
אפליקציית הדוגמה של טלוויזיה בשידור חי ב-Android TV.
טעינה באצווה של נתוני ערוצים
כשמעדכנים את מסד הנתונים של המערכת עם כמות גדולה של נתוני ערוצים, משתמשים בשיטה 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(); } }
עיבוד נתוני ערוצים באופן אסינכרוני
פעולות של מניפולציה של נתונים, כמו אחזור של זרם מהשרת או גישה למסד הנתונים, לא צריכות לחסום את שרשור ה-UI. אחת הדרכים לבצע עדכונים באופן אסינכרוני היא באמצעות AsyncTask. לדוגמה, כשמעלים פרטי ערוץ משרת backend, אפשר להשתמש ב-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(); } } } }
אם אתם צריכים לעדכן את נתוני מדריך התוכניות באופן קבוע, כדאי להשתמש ב-WorkManager כדי להריץ את תהליך העדכון בזמן שהמכשיר בלי פעילות, למשל כל יום בשעה 3:00.
טכניקות נוספות להפרדה בין משימות עדכון הנתונים לבין שרשור UI כוללות שימוש במחלקה HandlerThread, או שאתם יכולים להטמיע משלכם באמצעות המחלקות Looper ו-Handler. מידע נוסף זמין במאמר
תהליכים ושרשורים.
הוספת פרטים של קישור לאפליקציה
בערוצים אפשר להשתמש בקישורים לאפליקציות כדי לאפשר למשתמשים להפעיל פעילות קשורה בזמן שהם צופים בתוכן של הערוץ. אפליקציות של ערוצים משתמשות בקישורים לאפליקציות כדי להרחיב את האינטראקציה עם המשתמשים. הן מפעילות פעילויות שמציגות מידע שקשור לתוכן או תוכן נוסף. לדוגמה, אתם יכולים להשתמש בקישורים לאפליקציות כדי:
- לעזור למשתמש לגלות ולקנות תוכן שקשור לתוכן שהוא צופה בו.
- כאן אפשר לציין מידע נוסף על התוכן שמופעל כרגע.
- בזמן צפייה בתוכן שמחולק לפרקים, אפשר להתחיל לצפות בפרק הבא בסדרה.
- לאפשר למשתמש ליצור אינטראקציה עם התוכן – למשל, לדרג או לכתוב ביקורת על התוכן – בלי להפריע להפעלת התוכן.
קישורים לאפליקציות מוצגים כשהמשתמש לוחץ על Select כדי להציג את תפריט הטלוויזיה בזמן הצפייה בתוכן של הערוץ.
איור 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 של הכוונה של קישור האפליקציה לערוץ הזה. אפשר ליצור את ה-URI באמצעותtoUri(int)עםURI_INTENT_SCHEMEולהמיר את ה-URI בחזרה ל-Intent המקורי באמצעותparseUri. COLUMN_APP_LINK_POSTER_ART_URI- ה-URI של עיצוב הפוסטר שמשמש כרקע לקישור לאפליקציה בערוץ הזה. דוגמה לתמונה של פוסטר מופיעה באיור 2, הערה 1.COLUMN_APP_LINK_TEXT- הטקסט התיאורי של הקישור לאפליקציה בערוץ הזה. דוגמה לתיאור של קישור לאפליקציה מופיעה בטקסט באיור 2, בהערה 3.
אם בנתוני הערוץ לא מצוינים פרטים על קישור לאפליקציה, המערכת יוצרת קישור לאפליקציה כברירת מחדל. המערכת בוחרת את פרטי ברירת המחדל באופן הבא:
- לגבי ה-URI של ה-Intent (
COLUMN_APP_LINK_INTENT_URI), המערכת משתמשת בפעילותACTION_MAINבקטגוריהCATEGORY_LEANBACK_LAUNCHER, שמוגדרת בדרך כלל בקובץ מניפסט של אפליקציה. אם פעילות זו אינה מוגדרת, יופיע קישור לאפליקציה שאינה מתפקדת - אם המשתמש לוחץ עליו, לא יקרה דבר. - לגבי הטקסט התיאורי (
COLUMN_APP_LINK_TEXT), המערכת משתמשת בטקסט 'פתיחת app-name'. אם לא מוגדר URI של כוונת קישור לאפליקציה, המערכת משתמשת בטקסט 'אין קישור זמין'. - לגבי הצבע המשני (
COLUMN_APP_LINK_COLOR), המערכת משתמשת בצבע ברירת המחדל של האפליקציה. - לתמונה של הפוסטר (
COLUMN_APP_LINK_POSTER_ART_URI), המערכת משתמשת בבאנר של מסך הבית של האפליקציה. אם האפליקציה לא מספקת באנר, המערכת משתמשת בתמונה של אפליקציית טלוויזיה שמוגדרת כברירת מחדל. - עבור סמל התג (
COLUMN_APP_LINK_ICON_URI), המערכת משתמשת בתג המציג את שם האפליקציה. אם המערכת משתמשת גם בבאנר האפליקציה או בתמונת ברירת המחדל של האפליקציה כתמונת הפוסטר, לא יוצג תג האפליקציה.
מציינים את פרטי הקישור לאפליקציה עבור הערוצים בפעילות ההגדרה של האפליקציה. אפשר לעדכן את פרטי הקישור לאפליקציה בכל שלב. לכן, אם צריך להתאים קישור לאפליקציה לשינויים בערוץ, צריך לעדכן את פרטי הקישור לאפליקציה ולהתקשר אל ContentResolver.update לפי הצורך. פרטים נוספים על עדכון נתוני הערוץ זמינים במאמר בנושא עדכון נתוני הערוץ.