อินพุตทีวีของคุณต้องให้ข้อมูลคู่มือโปรแกรมอิเล็กทรอนิกส์ (EPG) เป็นเวลาอย่างน้อย 1 ช่องในกิจกรรมการตั้งค่า นอกจากนี้คุณควรอัปเดตข้อมูลเป็นระยะๆ ข้อมูลโดยพิจารณาจากขนาดของการอัปเดตและเธรดการประมวลผล ที่จัดการเรื่องนี้ นอกจากนี้ ยังระบุลิงก์แอปสำหรับช่องต่างๆ ได้อีกด้วย ที่พาผู้ใช้ไปยังเนื้อหาและกิจกรรมที่เกี่ยวข้อง บทเรียนนี้จะกล่าวถึงการสร้างและอัปเดตข้อมูลช่องและโปรแกรมใน ฐานข้อมูลระบบโดยคำนึงถึงปัจจัยเหล่านี้
ลองใช้ แอปตัวอย่างของบริการอินพุตทีวี
รับสิทธิ์
เพื่อให้อินพุตของทีวีทำงานกับข้อมูล EPG ได้ อินพุตดังกล่าวจะต้องประกาศ สิทธิ์การเขียนในไฟล์ Manifest ของ 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
- รูปแบบวิดีโอเริ่มต้น สำหรับช่อง
แม้ว่าเฟรมเวิร์กของอินพุตทีวีจะมีทั่วไปพอที่จะรองรับทั้งการออกอากาศแบบดั้งเดิมและ เนื้อหาการแพร่ภาพและเสียงผ่านโครงข่ายอินเทอร์เน็ต (Over The Top หรือ OTT) โดยไม่มีความแตกต่างใดๆ คุณอาจต้องการกำหนดคอลัมน์ต่อไปนี้ใน นอกเหนือจากรายการข้างต้นเพื่อให้ระบุช่องออกอากาศแบบดั้งเดิมได้ดียิ่งขึ้น:
COLUMN_ORIGINAL_NETWORK_ID
- โทรทัศน์ รหัสเครือข่ายCOLUMN_SERVICE_ID
- รหัสบริการCOLUMN_TRANSPORT_STREAM_ID
- ลำธารขนส่ง รหัส
หากต้องการระบุรายละเอียดลิงก์แอปสำหรับช่อง คุณต้อง อัปเดตช่องเพิ่มเติมบางช่อง ดูข้อมูลเพิ่มเติมเกี่ยวกับช่องลิงก์แอปได้ที่ เพิ่มข้อมูล App Link
สำหรับอินพุตทีวีสตรีมมิงทางอินเทอร์เน็ต ให้กำหนดค่าของคุณเองให้กับข้อมูลข้างต้นเพื่อให้ แต่ละแชแนลจะถูกระบุ โดยไม่ซ้ำกัน
ดึงข้อมูลเมตาของช่อง (ใน 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
เป็นออบเจ็กต์ที่เก็บข้อมูลเมตาของช่องจาก
เซิร์ฟเวอร์แบ็กเอนด์
นำเสนอข้อมูลช่องและรายการ
แอป System TV นำเสนอข้อมูลช่องและรายการแก่ผู้ใช้เมื่อพวกเขาดูช่องต่างๆ ดังที่แสดงในรูปที่ 1 ตรวจสอบว่าข้อมูลช่องและรายการทำงานร่วมกับแอป System TV ได้ ผู้นำเสนอข้อมูลช่องและโปรแกรม โปรดปฏิบัติตามหลักเกณฑ์ด้านล่าง
- หมายเลขช่อง (
COLUMN_DISPLAY_NUMBER
) - ไอคอน
(
android:icon
ในส่วน ไฟล์ Manifest ของอินพุตทีวี) - รายละเอียดโปรแกรม (
COLUMN_SHORT_DESCRIPTION
) - ชื่อโปรแกรม (
COLUMN_TITLE
) - โลโก้ของช่อง (
TvContract.Channels.Logo
)- ใช้สี #EEEEEE ให้ตรงกับข้อความโดยรอบ
- ไม่รวมระยะห่างจากขอบ
- ภาพโปสเตอร์ (
COLUMN_POSTER_ART_URI
)- สัดส่วนภาพระหว่าง 16:9 และ 4:3

รูปที่ 1 ผู้นำเสนอช่องและข้อมูลรายการของแอป System TV
แอป System TV ให้ข้อมูลเดียวกันผ่านคู่มือรายการทีวี ซึ่งรวมถึงภาพโปสเตอร์ ดังที่แสดงในรูปที่ 2

รูปที่ 2 คู่มือรายการทีวีของระบบ
อัปเดตข้อมูลช่อง
เมื่ออัปเดตข้อมูลแชแนลที่มีอยู่ ให้ใช้
update()
แทนการลบและเพิ่มข้อมูลอีกครั้ง คุณระบุข้อมูลเวอร์ชันปัจจุบันได้
โดยใช้ Channels.COLUMN_VERSION_NUMBER
และ Programs.COLUMN_VERSION_NUMBER
เมื่อเลือกระเบียนที่จะอัปเดต
หมายเหตุ: กำลังเพิ่มข้อมูลช่องลงใน ContentProvider
อาจต้องใช้เวลา เพิ่มรายการปัจจุบัน (รายการภายใน 2 ชั่วโมงนับจากเวลาปัจจุบัน)
เฉพาะเมื่อคุณกำหนดค่า EpgSyncJobService
เพื่ออัปเดตส่วนที่เหลือเท่านั้น
ของข้อมูลช่องในเบื้องหลัง โปรดดู
ตัวอย่างแอปตัวอย่างสำหรับ Android TV Live 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
ถือเป็น 1
วิธีดำเนินการอัปเดตแบบไม่พร้อมกัน เช่น เมื่อโหลดข้อมูลแชแนลจากเซิร์ฟเวอร์แบ็กเอนด์
คุณใช้ 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 ที่แอปช่องระบุไว้ เนื้อหาของช่องจะเล่นต่อไป ขณะที่กิจกรรม App Link ทำงานอยู่ ผู้ใช้สามารถกลับไปยังช่องได้ โดยการกดกลับ
ระบุข้อมูลช่องทางลิงก์แอป
Android TV จะสร้าง App Link ให้แต่ละช่องโดยอัตโนมัติ
โดยใช้ข้อมูลจากช่อง วิธีระบุข้อมูล App Link
โปรดระบุรายละเอียดต่อไปนี้ใน
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 เดิมด้วยparseUri()
COLUMN_APP_LINK_POSTER_ART_URI
- URI สำหรับภาพโปสเตอร์ที่ใช้เป็นพื้นหลังของลิงก์แอป สำหรับช่องนี้ สำหรับภาพโปสเตอร์ตัวอย่าง ให้ดูรูปที่ 2 ข้อความเสริม 1COLUMN_APP_LINK_TEXT
- ข้อความลิงก์ที่สื่อความหมายของลิงก์แอปสำหรับช่องนี้ ตัวอย่างเช่น คำอธิบายลิงก์แอป ให้ดูข้อความในรูปที่ 2 ไฮไลต์ที่ 3

รูปที่ 2 รายละเอียดลิงก์แอป
หากระบบไม่ได้ระบุข้อมูลลิงก์ของแอปไว้ในข้อมูลแชแนล สร้างลิงก์แอปเริ่มต้น ระบบจะเลือกรายละเอียดเริ่มต้นดังนี้
- สำหรับ URI ของ Intent
(
COLUMN_APP_LINK_INTENT_URI
), ระบบจะใช้ACTION_MAIN
กิจกรรมสำหรับหมวดหมู่CATEGORY_LEANBACK_LAUNCHER
ซึ่งโดยทั่วไปกำหนดไว้ในไฟล์ Manifest ของแอป หากไม่ได้กำหนดกิจกรรมนี้ไว้ ลิงก์แอปที่ใช้งานไม่ได้จะปรากฏขึ้น หาก เมื่อผู้ใช้คลิกแล้วจะไม่มีอะไรเกิดขึ้น - สำหรับข้อความอธิบาย
(
COLUMN_APP_LINK_TEXT
) ระบบ ใช้ "เปิด app-name" หากไม่ได้กำหนด URI จุดประสงค์ของลิงก์แอปที่ใช้งานได้ ระบบจะใช้ "ไม่มีลิงก์ที่พร้อมใช้งาน" - สำหรับสีเฉพาะจุด
(
COLUMN_APP_LINK_COLOR
), ระบบจะใช้สีเริ่มต้นของแอป - สำหรับภาพโปสเตอร์
(
COLUMN_APP_LINK_POSTER_ART_URI
), ระบบจะใช้แบนเนอร์หน้าจอหลักของแอป หากแอปไม่มี ระบบจะใช้รูปภาพแอปทีวีเริ่มต้น - สำหรับไอคอนป้าย
(
COLUMN_APP_LINK_ICON_URI
) ระบบจะใช้ป้ายที่แสดงชื่อแอป หากระบบใช้ แบนเนอร์แอปหรือรูปภาพแอปเริ่มต้นสำหรับภาพโปสเตอร์ ระบบจะไม่แสดงป้ายแอป
คุณระบุรายละเอียดลิงก์แอปสำหรับช่องใน
กิจกรรมการตั้งค่า คุณสามารถอัปเดตรายละเอียดลิงก์แอปเหล่านี้ได้ทุกเมื่อ ดังนั้น
หาก App Link ต้องตรงกับการเปลี่ยนแปลงช่อง ให้อัปเดตแอป
รายละเอียดลิงก์และการโทร
ContentResolver.update()
ตามต้องการ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการอัปเดต
โปรดดูอัปเดตข้อมูลช่อง