Tin tức về sản phẩm

Nâng cao trải nghiệm phát nội dung nghe nhìn: Giới thiệu tính năng tải trước bằng Media3 – Phần 1

Đọc trong 8 phút
Mayuri Khinvasara Khabya
Kỹ sư Quan hệ với nhà phát triển

Trong các ứng dụng tập trung vào nội dung nghe nhìn hiện nay, việc mang đến trải nghiệm phát mượt mà và không bị gián đoạn là yếu tố then chốt để mang lại trải nghiệm người dùng thú vị. Người dùng mong muốn video của họ bắt đầu phát ngay lập tức và phát liên tục mà không bị tạm dừng.

Thách thức cốt lõi là độ trễ. Theo truyền thống, trình phát video chỉ bắt đầu hoạt động (kết nối, tải xuống, phân tích cú pháp, lưu vào bộ nhớ đệm) sau khi người dùng chọn một mục để phát. Cách tiếp cận phản ứng này không phù hợp với bối cảnh video dạng ngắn hiện nay. Giải pháp là chủ động. Chúng ta cần dự đoán nội dung mà người dùng sẽ xem tiếp theo và chuẩn bị sẵn nội dung trước thời gian đó. Đây là bản chất của việc tải trước.

Sau đây là những lợi ích chính của việc tải trước:

  • 🚀 Bắt đầu phát nhanh hơn: Video đã sẵn sàng phát, giúp chuyển đổi nhanh hơn giữa các mục và bắt đầu phát ngay lập tức.
  • 📉 Giảm tình trạng đệm: Bằng cách chủ động tải dữ liệu, quá trình phát ít có khả năng bị gián đoạn hơn, chẳng hạn như do mạng gặp sự cố.
  • ✨ Trải nghiệm người dùng mượt mà hơn: Việc kết hợp giữa thời gian bắt đầu nhanh hơn và ít bị giật hơn sẽ tạo ra một trải nghiệm tương tác liền mạch và mượt mà hơn cho người dùng.

Trong loạt bài gồm 3 phần này, chúng ta sẽ giới thiệu và tìm hiểu kỹ về các tiện ích mạnh mẽ của Media3 để (tải trước) các thành phần.

  • Trong Phần 1, chúng ta sẽ tìm hiểu những kiến thức cơ bản: tìm hiểu các chiến lược tải trước có trong Media3, bật PreloadConfiguration và thiết lập DefaultPreloadManager, cho phép ứng dụng của bạn tải trước các mục. Đến cuối bài đăng này trên blog, bạn sẽ có thể tải trước và phát các mục nội dung nghe nhìn với thứ hạng và thời lượng đã định cấu hình.
  • Trong Phần 2, chúng ta sẽ tìm hiểu các chủ đề nâng cao hơn về DefaultPreloadManager: sử dụng trình nghe để phân tích, khám phá các phương pháp hay nhất sẵn sàng cho việc phát hành công khai như mẫu cửa sổ trượt và các thành phần dùng chung tuỳ chỉnh của DefaultPreloadManager và ExoPlayer.
  • Trong Phần 3, chúng ta sẽ tìm hiểu kỹ về tính năng lưu vào bộ nhớ đệm trên đĩa bằng DefaultPreloadManager.

Tính năng tải trước sẽ giúp bạn! 🦸‍♀️

Ý tưởng cốt lõi đằng sau việc tải trước rất đơn giản: tải nội dung nghe nhìn trước khi bạn cần. Khi người dùng vuốt để chuyển sang video tiếp theo, các phân đoạn đầu tiên của video đã được tải xuống và sẵn sàng phát ngay lập tức.

Hãy hình dung việc này giống như một nhà hàng. Một nhà bếp bận rộn sẽ không chờ đơn đặt hàng rồi mới bắt đầu thái hành. 🧅 Họ chuẩn bị trước. Tải trước là bước chuẩn bị cho trình phát video.

Khi được bật, tính năng tải trước có thể giúp giảm thiểu độ trễ khi người dùng chuyển sang mục tiếp theo trước khi vùng đệm phát đạt đến mục tiếp theo. Khoảng thời gian đầu tiên của cửa sổ tiếp theo được chuẩn bị và các mẫu video, âm thanh và văn bản được lưu vào bộ nhớ đệm. Khoảng thời gian tải trước sau đó được đưa vào hàng đợi trong trình phát với các mẫu được lưu vào bộ nhớ đệm có sẵn ngay lập tức và sẵn sàng được cung cấp cho bộ mã hoá và giải mã để kết xuất.

Trong Media3, có 2 API chính để tải trước, mỗi API phù hợp với các trường hợp sử dụng khác nhau. Chọn API phù hợp là bước đầu tiên.

1. Tải trước các mục trong danh sách phát bằng PreloadConfiguration

Đây là phương pháp đơn giản, hữu ích cho nội dung nghe nhìn tuyến tính, tuần tự như danh sách phát, trong đó thứ tự phát có thể dự đoán được (chẳng hạn như một loạt tập). Bạn cung cấp cho người chơi danh sách đầy đủ các mục nội dung nghe nhìn bằng cách sử dụng API danh sách phát của ExoPlayer và đặt PreloadConfiguration cho trình phát. Sau đó, trình phát sẽ tự động tải trước các mục tiếp theo trong chuỗi theo cấu hình. API này cố gắng tối ưu hoá độ trễ kết hợp khi người dùng chuyển sang mục tiếp theo trước khi bộ nhớ đệm phát trùng lặp với mục tiếp theo.

Quá trình tải trước chỉ bắt đầu khi không có nội dung nghe nhìn nào đang được tải cho quá trình phát đang diễn ra. Điều này giúp ngăn quá trình tải trước cạnh tranh băng thông với quá trình phát chính.

Nếu bạn vẫn không chắc liệu mình có cần tải trước hay không, thì API này là một lựa chọn tuyệt vời và dễ dàng để bạn dùng thử!

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

Với PreloadConfiguration ở trên, trình phát sẽ cố gắng tải trước 5 giây nội dung nghe nhìn cho mục tiếp theo trong danh sách phát.

Sau khi chọn sử dụng, bạn có thể tắt tính năng tải trước danh sách phát bằng cách dùng biểu tượng PreloadConfiguration.DEFAULT để tắt tính năng này:

  player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. Tải trước danh sách động bằng PreloadManager

Đối với giao diện người dùng động như nguồn cấp dữ liệu dọc hoặc băng chuyền, trong đó mục "tiếp theo" được xác định bằng lượt tương tác của người dùng, thì API PreloadManager sẽ phù hợp. Đây là một thành phần độc lập, mạnh mẽ mới trong thư viện Media3 ExoPlayer, được thiết kế riêng để chủ động tải trước. Thư viện này quản lý một tập hợp các MediaSource tiềm năng, ưu tiên các MediaSource này dựa trên khoảng cách đến vị trí hiện tại của người dùng và cung cấp chế độ kiểm soát chi tiết đối với nội dung cần tải trước, phù hợp với các trường hợp phức tạp như nguồn cấp dữ liệu động của video ngắn.

Thiết lập PreloadManager

DefaultPreloadManager là phương thức triển khai chính tắc cho PreloadManager.

Trình tạo DefaultPreloadManager có thể tạo cả DefaultPreloadManager và mọi thực thể ExoPlayer sẽ phát nội dung được tải trước. Để tạo DefaultPreloadManager, bạn sẽ cần truyền một TargetPreloadStatusControl mà trình quản lý tải trước có thể truy vấn để biết cần tải bao nhiêu cho một mục. Chúng tôi sẽ giải thích và xác định một ví dụ về TargetPreloadStatusControl trong phần bên dưới.

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

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

Bạn cần sử dụng cùng một trình tạo cho cả ExoPlayer và DefaultPreloadManager. Điều này đảm bảo rằng các thành phần bên dưới của chúng được chia sẻ đúng cách.

Chỉ vậy thôi! Giờ đây, bạn đã có một người quản lý sẵn sàng nhận chỉ thị.

Định cấu hình thời lượng và thứ hạng bằng TargetPreloadStatusControl

Điều gì sẽ xảy ra nếu bạn muốn tải trước, chẳng hạn như 10 giây video? Bạn có thể cung cấp vị trí của các mục nội dung nghe nhìn trong băng chuyền và DefaultPreloadManager sẽ ưu tiên tải các mục dựa trên mức độ gần với mục mà người dùng hiện đang phát.

Nếu muốn kiểm soát thời lượng tải trước của mục, bạn có thể cho biết thời lượng đó bằng DefaultPreloadManager.PreloadStatus mà bạn trả về.

Ví dụ:

  • Mục "A" có mức độ ưu tiên cao nhất, tải 5 giây video.
  • Mục 'B' có mức độ ưu tiên trung bình, nhưng khi bạn chuyển đến mục này, hãy tải 3 giây video.
  • Mục "C" có mức độ ưu tiên thấp hơn, chỉ tải các bản nhạc.
  • Mục "D" có mức độ ưu tiên thấp hơn nữa, chỉ cần chuẩn bị.
  • Mọi mục khác đều ở xa, không tải trước bất cứ thứ gì.

Khả năng kiểm soát chi tiết này có thể giúp bạn tối ưu hoá việc sử dụng tài nguyên, đây là điều nên làm để có trải nghiệm phát liền mạch.

  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
            }
}

Lưu ý: PreloadManager có thể giữ cả mục trước và mục tiếp theo được tải trước, trong khi PreloadConfiguration sẽ chỉ xem trước các mục tiếp theo.

Quản lý các mục tải trước

Sau khi tạo người quản lý, bạn có thể bắt đầu yêu cầu người quản lý thực hiện công việc. Khi người dùng cuộn qua một nguồn cấp dữ liệu, bạn sẽ xác định các video sắp tới và thêm chúng vào trình quản lý. Tương tác với PreloadManager là một cuộc trò chuyện dựa trên trạng thái giữa giao diện người dùng và công cụ tải trước.

1. Thêm mục nội dung nghe nhìn

Khi điền sẵn thông tin vào nguồn cấp dữ liệu, bạn phải thông báo cho trình quản lý về nội dung nghe nhìn mà trình quản lý cần theo dõi. Nếu mới bắt đầu, bạn có thể thêm toàn bộ danh sách mà bạn muốn tải trước. Sau đó, bạn có thể tiếp tục thêm từng mục vào danh sách khi cần. Bạn có toàn quyền kiểm soát những mục trong danh sách tải trước, tức là bạn cũng phải quản lý những mục được thêm và xoá khỏi trình quản lý.

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

Bây giờ, trình quản lý sẽ bắt đầu tìm nạp dữ liệu cho MediaItem này ở chế độ nền.

Sau khi thêm, hãy yêu cầu trình quản lý đánh giá lại danh sách mới (gợi ý rằng đã có thay đổi, chẳng hạn như thêm/ xoá một mục hoặc người dùng chuyển sang phát một mục mới).

  preloadManager.invalidate()

2. Truy xuất và phát một mục

Đây là logic phát chính. Khi người dùng quyết định phát video đó, bạn không cần tạo một MediaSource mới. Thay vào đó, bạn yêu cầu PreloadManager cung cấp thông tin mà nó đã chuẩn bị. Bạn có thể truy xuất MediaSource từ Trình quản lý tải trước bằng MediaItem.

Nếu mục được truy xuất từ PreloadManager là giá trị rỗng, tức là mediaItem chưa được tải trước hoặc thêm vào PreloadMamager, nên bạn chọn đặt trực tiếp 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()

Bằng cách chuẩn bị MediaSource được truy xuất từ PreloadManager, bạn có thể chuyển đổi liền mạch từ giai đoạn tải trước sang giai đoạn phát, bằng cách sử dụng dữ liệu đã có trong bộ nhớ. Điều này giúp thời gian bắt đầu nhanh hơn.

3. Đồng bộ hoá chỉ mục hiện tại với giao diện người dùng

Vì nguồn cấp dữ liệu / danh sách của chúng tôi có thể thay đổi, nên bạn cần thông báo cho PreloadManager về chỉ mục đang phát hiện tại để PreloadManager luôn có thể ưu tiên những mục gần nhất với chỉ mục hiện tại của bạn để tải trước.

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

4. Xoá một mục

Để duy trì hiệu quả của trình quản lý, bạn nên xoá những mục mà trình quản lý không còn cần theo dõi, chẳng hạn như những mục ở xa vị trí hiện tại của người dùng.

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

Nếu cần xoá tất cả các mục cùng một lúc, bạn có thể gọi preloadManager.reset().

5. Giải phóng Manager

Khi không cần PreloadManager nữa (ví dụ: khi giao diện người dùng của bạn bị huỷ), bạn phải giải phóng PreloadManager để giải phóng tài nguyên của nó. Nơi phù hợp để làm việc này là nơi bạn đang phát hành tài nguyên của Người chơi. Bạn nên phát hành trình quản lý trước trình phát vì trình phát có thể tiếp tục phát nếu bạn không cần tải trước thêm nữa.

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

Phần minh hoạ

Hãy xem cách tính năng này hoạt động trong thực tế 👍

Trong bản minh hoạ bên dưới, chúng ta thấy tác động của PreloadManager ở phía bên phải, có thời gian tải nhanh hơn, trong khi phía bên trái cho thấy trải nghiệm hiện tại. Bạn cũng có thể xem mẫu mã cho bản minh hoạ. (Điểm cộng: Công cụ này cũng hiển thị độ trễ khởi động của từng video)

Demo-PreloadManager_2.webp

Tiếp theo là gì?

Vậy là hết phần 1! Giờ đây, bạn đã có các công cụ để tạo một hệ thống tải trước động. Bạn có thể sử dụng PreloadConfiguration để tải trước mục tiếp theo của một danh sách phát trong ExoPlayer hoặc thiết lập DefaultPreloadManager, thêm và xoá các mục ngay lập tức, định cấu hình trạng thái tải trước mục tiêu và truy xuất chính xác nội dung đã tải trước để phát.

Trong Phần 2, chúng ta sẽ tìm hiểu kỹ hơn về DefaultPreloadManager. Chúng ta sẽ khám phá cách theo dõi các sự kiện tải trước, thảo luận về các phương pháp hay nhất như sử dụng cửa sổ trượt để tránh các vấn đề về bộ nhớ, đồng thời xem xét các thành phần dùng chung tuỳ chỉnh của ExoPlayer và DefaultPreloadManager.

Bạn có ý kiến phản hồi nào muốn chia sẻ không? Chúng tôi rất mong nhận được ý kiến của bạn.

Hãy theo dõi và bắt đầu tăng tốc ứng dụng của bạn! 🚀

Tác giả:

Tiếp tục đọc