อินพุตทีวีต้องระบุข้อมูลคู่มือรายการอิเล็กทรอนิกส์ (EPG) สำหรับช่องอย่างน้อย 1 ช่องในกิจกรรมการตั้งค่า นอกจากนี้ คุณควรจะอัปเดตข้อมูลดังกล่าวเป็นระยะๆ โดยคำนึงถึงขนาดของการอัปเดตและเธรดการประมวลผลที่จัดการข้อมูลนั้น อีกทั้งยังระบุลิงก์แอปสำหรับช่องที่จะแนะนำผู้ใช้ไปยังเนื้อหาและกิจกรรมที่เกี่ยวข้องได้ด้วย บทเรียนนี้จะกล่าวถึงการสร้างและอัปเดตข้อมูลช่องและข้อมูลรายการในฐานข้อมูลระบบโดยคำนึงถึงข้อควรพิจารณาเหล่านี้
ลองใช้แอปตัวอย่าง บริการอินพุตทีวี
ขอสิทธิ์
อินพุตทีวีจะต้องประกาศสิทธิ์ เขียนในไฟล์ Manifest ของ Android ดังนี้จึงจะทำงานกับข้อมูล EPG ได้
<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- รูปแบบวิดีโอเริ่มต้น ของช่อง
แม้ว่าเฟรมเวิร์กอินพุตทีวีจะมีความทั่วไปมากพอที่จะจัดการเนื้อหาการออกอากาศแบบดั้งเดิมและเนื้อหา Over-the-Top (OTT) ได้โดยไม่มีความแตกต่างกัน แต่คุณอาจต้องการกำหนดคอลัมน์ต่อไปนี้เพิ่มเติมเพื่อระบุช่องการออกอากาศแบบดั้งเดิมได้ดียิ่งขึ้น
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ในไฟล์ Manifest ของอินพุตทีวี) - คำอธิบายรายการ (
COLUMN_SHORT_DESCRIPTION) - ชื่อรายการ (
COLUMN_TITLE) - โลโก้ช่อง (
TvContract.Channels.Logo)- ใช้สี #EEEEEE ให้ตรงกับข้อความโดยรอบ
- ไม่ใส่ระยะขอบ
- ภาพโปสเตอร์ (
COLUMN_POSTER_ART_URI) - สัดส่วนภาพระหว่าง 16:9 ถึง 4:3
แอป TV ของระบบจะแสดงข้อมูลเดียวกันผ่านคู่มือรายการทีวี รวมถึงภาพโปสเตอร์ ดังที่แสดงในรูปที่ 2
อัปเดตข้อมูลช่อง
เมื่ออัปเดตข้อมูลช่องที่มีอยู่ ให้ใช้วิธี update แทนการลบและเพิ่มข้อมูลอีกครั้ง คุณสามารถระบุข้อมูลเวอร์ชันปัจจุบันได้โดยใช้ Channels.COLUMN_VERSION_NUMBER และ Programs.COLUMN_VERSION_NUMBER เมื่อเลือกบันทึกที่จะอัปเดต
หมายเหตุ: การเพิ่มข้อมูลช่องลงใน ContentProvider อาจใช้เวลา ให้เพิ่มรายการปัจจุบัน (รายการที่ออกอากาศภายใน 2 ชั่วโมงจากเวลาปัจจุบัน) ก็ต่อเมื่อคุณกำหนดค่า EpgSyncJobService ให้อัปเดตข้อมูลช่องที่เหลือในเบื้องหลัง ดูตัวอย่างได้ที่
แอปตัวอย่างรายการทีวีสดของ
Android TV
การโหลดข้อมูลช่องแบบกลุ่ม
เมื่ออัปเดตฐานข้อมูลระบบด้วยข้อมูลช่องจำนวนมาก ให้ใช้วิธี applyBatch หรือ bulkInsert ของ ContentResolver ตัวอย่างการใช้ 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 เป็นวิธีหนึ่งในการอัปเดตแบบไม่พร้อมกัน ตัวอย่างเช่น เมื่อโหลดข้อมูลช่องจากเซิร์ฟเวอร์แบ็กเอนด์ คุณสามารถใช้ 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
เพื่อเรียกใช้กระบวนการอัปเดตในช่วงเวลาที่ไม่มีการใช้งาน เช่น ทุกวันเวลา 03:00 น.
เทคนิคอื่นๆ ในการแยกงานอัปเดตข้อมูลออกจากเธรด UI ได้แก่ การใช้คลาส
HandlerThread หรือคุณอาจใช้คลาส Looper
และ Handler เพื่อสร้างเทคนิคของคุณเองก็ได้ ดูข้อมูลเพิ่มเติมได้ที่
กระบวนการและเธรด
เพิ่มข้อมูลลิงก์แอป
ช่องสามารถใช้ ลิงก์แอป เพื่อให้ผู้ใช้เปิดกิจกรรมที่เกี่ยวข้องขณะรับชมเนื้อหาของช่องได้ แอปช่องจะใช้ลิงก์แอปเพื่อเพิ่มการมีส่วนร่วมของผู้ใช้โดยการเปิดกิจกรรมที่แสดงข้อมูลที่เกี่ยวข้องหรือเนื้อหาเพิ่มเติม ตัวอย่างเช่น คุณสามารถใช้ลิงก์แอปเพื่อทำสิ่งต่อไปนี้
- แนะนำผู้ใช้ให้ค้นพบและซื้อเนื้อหาที่เกี่ยวข้อง
- ให้ข้อมูลเพิ่มเติมเกี่ยวกับเนื้อหาที่กำลังเล่น
- ขณะดูเนื้อหาแบบเป็นตอน ให้เริ่มดูตอนถัดไปในซีรีส์
- อนุญาตให้ผู้ใช้โต้ตอบกับเนื้อหา เช่น ให้คะแนนหรือเขียนรีวิวเนื้อหา โดยไม่ขัดจังหวะการเล่นเนื้อหา
ลิงก์แอปจะแสดงขึ้นเมื่อผู้ใช้กดเลือก เพื่อแสดงเมนูทีวีขณะรับชมเนื้อหาของช่อง
รูปที่ 1 ตัวอย่างลิงก์แอปที่แสดงในแถวช่อง ขณะแสดงเนื้อหาของช่อง
เมื่อผู้ใช้เลือกลิงก์แอป ระบบจะเริ่มกิจกรรมโดยใช้ URI ของ Intent ที่แอปช่องระบุไว้ เนื้อหาของช่องจะเล่นต่อขณะที่กิจกรรมลิงก์แอปทำงานอยู่ ผู้ใช้สามารถกลับไปที่เนื้อหาของช่องได้โดยกดกลับ
ระบุข้อมูลช่องลิงก์แอป
Android TV จะสร้างลิงก์แอปสำหรับแต่ละช่องโดยอัตโนมัติโดยใช้ข้อมูลจากข้อมูลช่อง หากต้องการระบุข้อมูลลิงก์แอป ให้ระบุรายละเอียดต่อไปนี้ในช่อง TvContract.Channels
COLUMN_APP_LINK_COLOR- สีเฉพาะจุดของลิงก์แอปสำหรับช่องนี้ ดูตัวอย่างสีเฉพาะจุดได้ที่รูปที่ 2 ข้อความเสริม 3COLUMN_APP_LINK_ICON_URI- URI สำหรับไอคอนป้ายแอปของลิงก์แอปสำหรับช่องนี้ ดูตัวอย่างไอคอนป้ายแอปได้ที่รูปที่ 2 คำอธิบาย 2COLUMN_APP_LINK_INTENT_URI- URI ของ Intent ของลิงก์แอปสำหรับช่องนี้ คุณสามารถสร้าง URI ได้โดยใช้toUri(int)กับURI_INTENT_SCHEMEและแปลง URI กลับเป็น Intent เดิมด้วยparseUriCOLUMN_APP_LINK_POSTER_ART_URI- URI สำหรับภาพโปสเตอร์ที่ใช้เป็นพื้นหลังของลิงก์แอป สำหรับช่องนี้ ดูตัวอย่างภาพโปสเตอร์ได้ที่รูปที่ 2 คำอธิบาย 1COLUMN_APP_LINK_TEXT- ข้อความลิงก์อธิบายของลิงก์แอปสำหรับช่องนี้ ดูตัวอย่างคำอธิบายลิงก์แอปได้ที่ข้อความในรูปที่ 2 คำอธิบาย 3
หากข้อมูลช่องไม่ได้ระบุข้อมูลลิงก์แอป ระบบจะสร้างลิงก์แอปเริ่มต้น ระบบจะเลือกรายละเอียดเริ่มต้นดังนี้
- สำหรับ URI ของ Intent (
COLUMN_APP_LINK_INTENT_URI) ระบบจะใช้กิจกรรมACTION_MAINสำหรับหมวดหมู่CATEGORY_LEANBACK_LAUNCHERซึ่งโดยทั่วไปจะกำหนดไว้ในไฟล์ Manifest ของแอป หากไม่ได้กำหนดกิจกรรมนี้ไว้ ลิงก์แอปที่ไม่ทำงานจะปรากฏขึ้น และหากผู้ใช้คลิกลิงก์ดังกล่าว ก็จะไม่มีอะไรเกิดขึ้น - สำหรับข้อความอธิบาย
(
COLUMN_APP_LINK_TEXT) ระบบ จะใช้ "เปิดapp-name" หากไม่ได้กำหนด URI ของ Intent ของลิงก์แอปที่ใช้งานได้ ระบบจะใช้ "ไม่มีลิงก์" - สำหรับสีเน้น (
COLUMN_APP_LINK_COLOR) ระบบจะใช้สีเริ่มต้นของแอป - สำหรับภาพโปสเตอร์ (
COLUMN_APP_LINK_POSTER_ART_URI) ระบบจะใช้แบนเนอร์หน้าจอหลักของแอป หากแอปไม่มีแบนเนอร์ ระบบจะใช้รูปภาพเริ่มต้นของแอป TV - สำหรับไอคอนป้าย (
COLUMN_APP_LINK_ICON_URI) ระบบจะใช้ป้ายที่แสดงชื่อแอป หากระบบใช้แบนเนอร์แอปหรือรูปภาพเริ่มต้นของแอปสำหรับภาพโปสเตอร์ด้วย ระบบจะไม่แสดงป้ายแอป
คุณระบุรายละเอียดลิงก์แอปสำหรับช่องในกิจกรรมการตั้งค่าของแอป คุณสามารถอัปเดตรายละเอียดลิงก์แอปเหล่านี้ได้ทุกเมื่อ ดังนั้นหากลิงก์แอปต้องตรงกับการเปลี่ยนแปลงของช่อง ให้อัปเดตรายละเอียดลิงก์แอปและเรียกใช้ ContentResolver.update ตามที่จำเป็น ดูรายละเอียดเพิ่มเติมเกี่ยวกับการอัปเดต
ข้อมูลช่องได้ที่อัปเดตข้อมูลช่อง