ข่าวสารผลิตภัณฑ์

ยกระดับการเล่นสื่อ: ขอแนะนำการโหลดล่วงหน้าด้วย Media3 - ตอนที่ 1

ใช้เวลาอ่าน 8 นาที
Mayuri Khinvasara Khabya
วิศวกรนักพัฒนาซอฟต์แวร์สัมพันธ์

ในแอปที่เน้นสื่อเป็นหลักในปัจจุบัน การมอบประสบการณ์การเล่นที่ราบรื่นและไม่ขาดตอนเป็นกุญแจสำคัญในการสร้างประสบการณ์ที่น่าพึงพอใจให้แก่ผู้ใช้ ผู้ใช้คาดหวังว่าวิดีโอจะเริ่มเล่นทันทีและเล่นได้อย่างราบรื่นโดยไม่มีการหยุดชั่วคราว

ความท้าทายหลักคือเวลาในการตอบสนอง โดยปกติแล้ว วิดีโอเพลเยอร์จะเริ่มทำงาน (เชื่อมต่อ ดาวน์โหลด แยกวิเคราะห์ บัฟเฟอร์) หลังจากที่ผู้ใช้เลือกรายการที่จะเล่นแล้วเท่านั้น แนวทางเชิงรับนี้ช้าเกินไปสำหรับบริบทวิดีโอแบบสั้นในปัจจุบัน วิธีแก้ปัญหาคือการป้องกันไว้ล่วงหน้า เราต้องคาดการณ์ว่าผู้ใช้จะดูอะไรต่อไปและเตรียมเนื้อหาให้พร้อมล่วงหน้า นี่คือหัวใจสำคัญของการโหลดล่วงหน้า

ประโยชน์หลักๆ ของการโหลดล่วงหน้ามีดังนี้

  • 🚀 เริ่มเล่นได้เร็วขึ้น: วิดีโอพร้อมเล่นแล้ว ทำให้เปลี่ยนระหว่างรายการต่างๆ ได้เร็วขึ้นและเริ่มเล่นได้ทันที
  • 📉 บัฟเฟอร์น้อยลง: การโหลดข้อมูลล่วงหน้าจะช่วยลดโอกาสที่การเล่นจะหยุดชะงัก เช่น เนื่องจากเครือข่ายขัดข้อง
  • ✨ ประสบการณ์ของผู้ใช้ที่ราบรื่นยิ่งขึ้น: การเริ่มต้นที่เร็วขึ้นและการบัฟเฟอร์ที่น้อยลงจะช่วยให้ผู้ใช้ได้รับประสบการณ์การโต้ตอบที่ราบรื่นและไร้รอยต่อ

ในซีรีส์ 3 ตอนนี้ เราจะแนะนำและเจาะลึกยูทิลิตีที่มีประสิทธิภาพของ Media3 สำหรับการโหลด (ล่วงหน้า) คอมโพเนนต์

  • ในส่วนที่ 1 เราจะพูดถึงพื้นฐาน ได้แก่ การทำความเข้าใจกลยุทธ์การโหลดล่วงหน้าต่างๆ ที่มีใน Media3, การเปิดใช้ PreloadConfiguration และการตั้งค่า DefaultPreloadManager เพื่อให้แอปโหลดรายการล่วงหน้าได้ เมื่ออ่านบล็อกนี้จบ คุณจะสามารถโหลดรายการสื่อล่วงหน้าและเล่นรายการเหล่านั้นได้โดยใช้การจัดอันดับและระยะเวลาที่คุณกำหนดค่าไว้
  • ในส่วนที่ 2 เราจะเจาะลึกหัวข้อขั้นสูงเพิ่มเติมของ DefaultPreloadManager ได้แก่ การใช้ Listener สำหรับข้อมูลวิเคราะห์ การสำรวจแนวทางปฏิบัติแนะนำที่พร้อมใช้งานจริง เช่น รูปแบบหน้าต่างเลื่อน และคอมโพเนนต์ที่แชร์ที่กำหนดเองของ DefaultPreloadManager และ ExoPlayer
  • ในส่วนที่ 3 เราจะเจาะลึกการแคชดิสก์ด้วย DefaultPreloadManager

การโหลดล่วงหน้าช่วยได้ 🦸‍♀️

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

เปรียบเหมือนร้านอาหาร ห้องครัวที่วุ่นวายจะไม่รอให้มีออเดอร์ก่อนแล้วค่อยเริ่มหั่นหัวหอม 🧅 เตรียมงานล่วงหน้า การโหลดล่วงหน้าคือการเตรียมการสำหรับวิดีโอเพลเยอร์

เมื่อเปิดใช้ การโหลดล่วงหน้าจะช่วยลดเวลาในการตอบสนองของการเข้าร่วมเมื่อผู้ใช้ข้ามไปยังรายการถัดไปก่อนที่บัฟเฟอร์การเล่นจะไปถึงรายการถัดไป ระบบจะเตรียมช่วงแรกของหน้าต่างถัดไปและบัฟเฟอร์ตัวอย่างวิดีโอ เสียง และข้อความ จากนั้นระบบจะจัดคิวช่วงที่โหลดล่วงหน้าลงในเพลเยอร์ โดยมีตัวอย่างที่บัฟเฟอร์พร้อมใช้งานทันทีและพร้อมที่จะส่งไปยังตัวแปลงรหัสเพื่อการแสดงผล

ใน Media3 มี API หลัก 2 รายการสำหรับการโหลดล่วงหน้า ซึ่งแต่ละรายการเหมาะกับกรณีการใช้งานที่แตกต่างกัน การเลือก API ที่เหมาะสมเป็นขั้นตอนแรก

1. การโหลดรายการในเพลย์ลิสต์ล่วงหน้าด้วย PreloadConfiguration

วิธีนี้เป็นวิธีที่ง่ายและมีประโยชน์สำหรับสื่อแบบเชิงเส้นและตามลำดับ เช่น เพลย์ลิสต์ที่สามารถคาดการณ์ลำดับการเล่นได้ (เช่น ตอนต่างๆ ในซีรีส์) คุณให้รายการไอเทมสื่อทั้งหมดแก่ผู้เล่นโดยใช้ API เพลย์ลิสต์ของ ExoPlayer และตั้งค่า PreloadConfiguration สำหรับเพลเยอร์ จากนั้นเพลเยอร์จะโหลดไอเทมถัดไปในลำดับตามที่กำหนดค่าไว้ล่วงหน้าโดยอัตโนมัติ API นี้พยายามเพิ่มประสิทธิภาพเวลาในการเข้าร่วมเมื่อผู้ใช้ข้ามไปยังรายการถัดไปก่อนที่บัฟเฟอร์การเล่นจะทับซ้อนกับรายการถัดไป

ระบบจะเริ่มโหลดล่วงหน้าเมื่อไม่มีการโหลดสื่อสำหรับการเล่นที่กำลังดำเนินอยู่เท่านั้น ซึ่งจะช่วยป้องกันไม่ให้การโหลดล่วงหน้าแย่งแบนด์วิดท์กับการเล่นหลัก

หากยังไม่แน่ใจว่าต้องใช้การโหลดล่วงหน้าหรือไม่ API นี้เป็นตัวเลือกที่ยอดเยี่ยมและง่ายต่อการลองใช้

player.preloadConfiguration =
    PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)

เมื่อใช้ PreloadConfiguration ข้างต้น เพลเยอร์จะพยายามโหลดสื่อล่วงหน้า 5 วินาทีสำหรับรายการถัดไปในเพลย์ลิสต์

เมื่อเลือกใช้แล้ว คุณจะปิดการโหลดเพลย์ลิสต์ล่วงหน้าได้อีกครั้งโดยใช้ PreloadConfiguration.DEFAULT เพื่อปิดใช้การโหลดเพลย์ลิสต์ล่วงหน้า

player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. การโหลดรายการแบบไดนามิกล่วงหน้าด้วย PreloadManager

สำหรับ UI แบบไดนามิก เช่น ฟีดแนวตั้งหรือภาพสไลด์ ซึ่งระบบจะกำหนดรายการ "ถัดไป" ตามการโต้ตอบของผู้ใช้ API ของ PreloadManager จะเหมาะสม นี่คือคอมโพเนนต์แบบสแตนด์อโลนใหม่ที่มีประสิทธิภาพภายในไลบรารี ExoPlayer ของ Media3 ซึ่งออกแบบมาโดยเฉพาะเพื่อโหลดล่วงหน้าอย่างรวดเร็ว โดยจะจัดการคอลเล็กชันของ MediaSource ที่เป็นไปได้ โดยจัดลำดับความสำคัญตามระยะใกล้กับตำแหน่งปัจจุบันของผู้ใช้ และให้การควบคุมแบบละเอียดว่าจะโหลดล่วงหน้าอะไร เหมาะสำหรับสถานการณ์ที่ซับซ้อน เช่น ฟีดวิดีโอแบบสั้นแบบไดนามิก

การตั้งค่า PreloadManager

DefaultPreloadManager คือการใช้งานที่ชัดเจนสำหรับ PreloadManager

ตัวสร้างของ DefaultPreloadManager สามารถสร้างทั้ง DefaultPreloadManager และอินสแตนซ์ ExoPlayer ใดก็ได้ที่จะเล่นเนื้อหาที่โหลดล่วงหน้า หากต้องการสร้าง DefaultPreloadManager คุณจะต้องส่ง TargetPreloadStatusControl ซึ่งเครื่องมือจัดการการโหลดล่วงหน้าจะค้นหาเพื่อดูว่าควรโหลดรายการมากน้อยเพียงใด เราจะอธิบายและกำหนดตัวอย่าง TargetPreloadStatusControl ในส่วนด้านล่าง

val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
val preloadManager = val preloadManagerBuilder.build()

// Build ExoPlayer with DefaultPreloadManager.Builder
val player = preloadManagerBuilder.buildExoPlayer()

คุณต้องใช้ตัวสร้างเดียวกันสำหรับทั้ง ExoPlayer และ DefaultPreloadManager เพื่อให้มั่นใจว่าคอมโพเนนต์ที่อยู่เบื้องหลังจะได้รับการแชร์อย่างถูกต้อง

เท่านี้ก็เรียบร้อย! ตอนนี้คุณมีผู้จัดการที่พร้อมรับคำสั่งแล้ว

กำหนดค่าระยะเวลาและการจัดอันดับด้วย TargetPreloadStatusControl

จะเกิดอะไรขึ้นหากคุณต้องการโหลดวิดีโอล่วงหน้า เช่น 10 วินาที คุณระบุตำแหน่งของรายการสื่อในภาพสไลด์ได้ และ DefaultPreloadManager จะจัดลำดับความสำคัญในการโหลดรายการตามระยะห่างจากรายการที่ผู้ใช้กำลังเล่นอยู่

หากต้องการควบคุมระยะเวลาของรายการที่จะโหลดล่วงหน้า คุณสามารถระบุได้ด้วย DefaultPreloadManager.PreloadStatus ที่คุณส่งคืน

ตัวอย่างเช่น

  • รายการ "A" มีลำดับความสำคัญสูงสุด ให้โหลดวิดีโอ 5 วินาที
  • รายการ "ข" มีลำดับความสำคัญปานกลาง แต่เมื่อถึงรายการนี้ ให้โหลดวิดีโอ 3 วินาที
  • รายการ "C" มีลำดับความสำคัญน้อยกว่า โหลดเฉพาะแทร็ก
  • รายการ "ง" มีลำดับความสำคัญต่ำกว่านั้นอีก เพียงแค่เตรียมไว้
  • หากมีรายการอื่นๆ อยู่ไกลออกไป ให้ไม่ต้องโหลดล่วงหน้า

การควบคุมแบบละเอียดนี้จะช่วยเพิ่มประสิทธิภาพการใช้ทรัพยากร ซึ่งเป็นวิธีที่แนะนำเพื่อให้การเล่นราบรื่น

import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus


class MyTargetPreloadStatusControl(
    currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {


    // The app is responsible for updating this based on UI state
    override fun getTargetPreloadStatus(index: Int): PreloadStatus? {

        val distance = index - currentPlayingIndex

        // Adjacent items (Next): preload 5 seconds
        if (distance == 1) { 
        // Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
                    return PreloadStatus.specifiedRangeLoaded(5000L)
                } 

        // Adjacent items (Previous): preload 3 seconds
        else if (distance == -1) { 
        // Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
                    return PreloadStatus.specifiedRangeLoaded(3000L)
                } 

        // Items two positions away: just select tracks
        else if (distance) == 2) {
        // Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
                    return PreloadStatus.TRACKS_SELECTED
                } 

        // Items four positions away: just select prepare
        else if (abs(distance) <= 4) {
        // Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
                    return PreloadStatus.SOURCE_PREPARED
                }

             // All other items are too far away
             return null
            }
}

เคล็ดลับ: PreloadManager สามารถโหลดล่วงหน้าทั้งรายการก่อนหน้าและรายการถัดไปได้ ในขณะที่ PreloadConfiguration จะโหลดล่วงหน้าเฉพาะรายการถัดไป

การจัดการรายการที่โหลดล่วงหน้า

เมื่อสร้างผู้จัดการแล้ว คุณก็เริ่มบอกได้ว่าจะให้ทำอะไร ขณะที่ผู้ใช้เลื่อนดูฟีด คุณจะเห็นวิดีโอที่กำลังจะเผยแพร่และเพิ่มวิดีโอเหล่านั้นลงในเครื่องมือจัดการ การโต้ตอบกับ PreloadManager คือการสนทนาที่ขับเคลื่อนด้วยสถานะระหว่าง UI กับเครื่องมือโหลดล่วงหน้า

1. เพิ่มรายการสื่อ

เมื่อป้อนข้อมูลในฟีด คุณต้องแจ้งให้ผู้จัดการทราบเกี่ยวกับสื่อที่ต้องติดตาม หากเพิ่งเริ่มต้น คุณสามารถเพิ่มทั้งรายการที่ต้องการโหลดล่วงหน้าได้ หลังจากนั้น คุณจะเพิ่มรายการเดียวลงในลิสต์ได้เรื่อยๆ ตามต้องการ คุณสามารถควบคุมได้อย่างเต็มที่ว่าจะให้มีรายการใดอยู่ในรายการการโหลดล่วงหน้า ซึ่งหมายความว่าคุณต้องจัดการสิ่งที่เพิ่มและนำออกจากเครื่องมือจัดการด้วย

val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
    preloadManager.add(
        initialMediaItems.get(index),index)
    )
}

ตอนนี้ผู้จัดการจะเริ่มดึงข้อมูลสำหรับ MediaItem นี้ในเบื้องหลัง

หลังจากเพิ่มแล้ว ให้บอกผู้จัดการให้ประเมินรายการใหม่ (บอกเป็นนัยว่ามีการเปลี่ยนแปลง เช่น การเพิ่ม/ นำรายการออก หรือผู้ใช้เปลี่ยนไปเล่นรายการใหม่)

preloadManager.invalidate()

2. ดึงข้อมูลและเล่นรายการ

มาถึงตรรกะการเล่นหลักแล้ว เมื่อผู้ใช้ตัดสินใจที่จะเล่นวิดีโอนั้น คุณไม่จำเป็นต้องสร้าง MediaSource ใหม่ แต่คุณจะขอ PreloadManager สำหรับรายการที่เตรียมไว้แล้วแทน คุณสามารถดึงข้อมูล MediaSource จาก Preload Manager โดยใช้ MediaItem ได้

หากรายการที่ดึงมาจาก PreloadManager เป็น null แสดงว่ายังไม่ได้โหลด MediaItem ล่วงหน้าหรือเพิ่มลงใน PreloadMamager ดังนั้นคุณจึงเลือกตั้งค่า MediaItem โดยตรงได้

// When a media item is about to displ​​ay on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
  player.setMediaSource(mediaSource)
} else {
  // If mediaSource is null, that mediaItem hasn't been added yet.
  // So, send it directly to the player.
  player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()

การเตรียม MediaSource ที่ดึงมาจาก PreloadManager จะช่วยให้คุณเปลี่ยนจากการโหลดล่วงหน้าเป็นการเล่นได้อย่างราบรื่นโดยใช้ข้อมูลที่อยู่ในหน่วยความจำอยู่แล้ว ซึ่งจะช่วยให้เวลาเริ่มต้นเร็วขึ้น

3. ซิงค์ดัชนีปัจจุบันกับ UI

เนื่องจากฟีด / รายการของเราอาจมีการเปลี่ยนแปลง จึงควรแจ้ง PreloadManager เกี่ยวกับดัชนีที่กำลังเล่นอยู่เพื่อให้ระบบจัดลำดับความสำคัญของรายการที่อยู่ใกล้กับดัชนีปัจจุบันมากที่สุดสำหรับการโหลดล่วงหน้าได้เสมอ

preloadManager.setCurrentPlayingIndex(currentIndex)
// Need to call invalidate() to update the priorities
preloadManager.invalidate()

4. นำรายการออก

คุณควรนำรายการที่เครื่องมือจัดการไม่จำเป็นต้องติดตามออก เช่น รายการที่อยู่ไกลจากตำแหน่งปัจจุบันของผู้ใช้ เพื่อให้เครื่องมือจัดการทำงานได้อย่างมีประสิทธิภาพ

// When an item is too far from the current playing index
preloadManager.remove(mediaItem)

หากต้องการล้างข้อมูลทั้งหมดในครั้งเดียว คุณสามารถโทรหา preloadManager.reset() ได้

5. ปล่อยตัวผู้จัดการ

เมื่อไม่ต้องการใช้ PreloadManager อีกต่อไป (เช่น เมื่อ UI ถูกทำลาย) คุณต้องปล่อย PreloadManager เพื่อเพิ่มพื้นที่ว่างสำหรับทรัพยากร คุณควรทำในที่ที่คุณเผยแพร่ทรัพยากรของเพลเยอร์อยู่แล้ว เราขอแนะนำให้ปล่อยเครื่องมือก่อนเพลเยอร์ เนื่องจากเพลเยอร์จะเล่นต่อไปได้หากคุณไม่ต้องการโหลดล่วงหน้าอีก

// In your Activity's onDestroy() or Composable's onDispose
preloadManager.release()

ช่วงเวลาสาธิตการใช้งาน

ดูการใช้งานจริงได้เลย 👍

ในเดโมด้านล่าง เราจะเห็นผลกระทบของ PreloadManager ทางด้านขวาซึ่งมีเวลาในการโหลดที่เร็วกว่า ในขณะที่ด้านซ้ายแสดงประสบการณ์การใช้งานที่มีอยู่ นอกจากนี้ คุณยังดูตัวอย่างโค้ดสำหรับเดโมได้ด้วย (โบนัส: ยังแสดงเวลาในการเริ่มต้นสำหรับวิดีโอทุกรายการด้วย)

Demo-PreloadManager_2.webp

ขั้นตอนต่อไปคือ

และนี่คือตอนที่ 1 ตอนนี้คุณมีเครื่องมือในการสร้างระบบการโหลดล่วงหน้าแบบไดนามิกแล้ว คุณสามารถใช้ PreloadConfiguration เพื่อโหลดรายการถัดไปของเพลย์ลิสต์ใน ExoPlayer ล่วงหน้า หรือตั้งค่า DefaultPreloadManager เพิ่มและนำรายการออกได้ทันที กำหนดค่าสถานะการโหลดล่วงหน้าเป้าหมาย และดึงเนื้อหาที่โหลดล่วงหน้ามาเล่นได้อย่างถูกต้อง

ในส่วนที่ 2 เราจะเจาะลึกเกี่ยวกับ DefaultPreloadManager เราจะมาดูวิธีฟังเหตุการณ์การโหลดล่วงหน้า อภิปรายแนวทางปฏิบัติแนะนำ เช่น การใช้หน้าต่างเลื่อนเพื่อหลีกเลี่ยงปัญหาเกี่ยวกับหน่วยความจำ และดูรายละเอียดเกี่ยวกับคอมโพเนนต์ที่แชร์ที่กำหนดเองของ ExoPlayer และ DefaultPreloadManager

หากมีความคิดเห็นที่ต้องการแชร์ เรายินดีรับฟังความคิดเห็นจากคุณ

โปรดติดตามข่าวสารต่อไป และไปทำให้แอปของคุณเร็วขึ้นกันเลย 🚀

เขียนโดย

อ่านต่อ