ทำงานกับข้อมูลช่อง

อินพุตทีวีของคุณต้องให้ข้อมูลคู่มือโปรแกรมอิเล็กทรอนิกส์ (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) โดยไม่มีความแตกต่างใดๆ คุณอาจต้องการกำหนดคอลัมน์ต่อไปนี้ใน นอกเหนือจากรายการข้างต้นเพื่อให้ระบุช่องออกอากาศแบบดั้งเดิมได้ดียิ่งขึ้น:

หากต้องการระบุรายละเอียดลิงก์แอปสำหรับช่อง คุณต้อง อัปเดตช่องเพิ่มเติมบางช่อง ดูข้อมูลเพิ่มเติมเกี่ยวกับช่องลิงก์แอปได้ที่ เพิ่มข้อมูล 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 ได้ ผู้นำเสนอข้อมูลช่องและโปรแกรม โปรดปฏิบัติตามหลักเกณฑ์ด้านล่าง

  1. หมายเลขช่อง (COLUMN_DISPLAY_NUMBER)
  2. ไอคอน (android:icon ในส่วน ไฟล์ Manifest ของอินพุตทีวี)
  3. รายละเอียดโปรแกรม (COLUMN_SHORT_DESCRIPTION)
  4. ชื่อโปรแกรม (COLUMN_TITLE)
  5. โลโก้ของช่อง (TvContract.Channels.Logo)
    • ใช้สี #EEEEEE ให้ตรงกับข้อความโดยรอบ
    • ไม่รวมระยะห่างจากขอบ
  6. ภาพโปสเตอร์ (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 ข้อความเสริม 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 รายละเอียดลิงก์แอป

หากระบบไม่ได้ระบุข้อมูลลิงก์ของแอปไว้ในข้อมูลแชแนล สร้างลิงก์แอปเริ่มต้น ระบบจะเลือกรายละเอียดเริ่มต้นดังนี้

  • สำหรับ 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()ตามต้องการ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการอัปเดต โปรดดูอัปเดตข้อมูลช่อง