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

8 phút đọc
Xem hồ sơ của Mayuri Khinvasara Khabya
Mayuri Khinvasara Khabya Kỹ sư quan hệ 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 lại trải nghiệm phát mượt mà, không bị gián đoạn là yếu tố then chốt để mang lại trải nghiệm thú vị cho người dùng. Người dùng muốn video của họ bắt đầu ngay lập tức và phát liền mạch mà không bị tạm dừng.

Thử thách cốt lõi là độ trễ. Thông 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 vùng đệm) sau khi người dùng chọn một mục để phát. Cách tiếp cận mang tính ứng phó này không còn phù hợp với bối cảnh video ngắn hiện nay. Giải pháp là chủ động. Chúng ta cần dự đoán nội dung người dùng sẽ xem tiếp theo và chuẩn bị sẵn nội dung đó. Đâ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 tải vào vùng đệm: Bằng cách chủ động tải dữ liệu, quá trình phát sẽ ít bị gián đoạn hơn, chẳng hạn như do sự cố mạng.
  • ✨ Trải nghiệm người dùng mượt mà hơn: Sự kết hợp giữa thời gian bắt đầu nhanh hơn và thời gian tải vào bộ nhớ đệm ngắn hơn mang đến trải nghiệm tương tác mượt mà và liền mạch hơn cho người dùng.

Trong loạt bài gồm 3 phần này, chúng tôi sẽ giới thiệu và tìm hiểu sâu 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: hiểu rõ 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 tải trước các mục. Sau khi đọc xong 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 theo 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 môi trường thực tế 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 sâu về tính năng lưu vào bộ nhớ đệm của ổ đĩa bằng DefaultPreloadManager.

Tải trước để giải cứu! 🦸‍♀️

Ý 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 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 không đợi có đơn đặt món mới bắt đầu thái hành. 🧅 Họ chuẩn bị trước mọi thứ. Tải trước là công việc chuẩn bị cho trình phát video của bạn.

Khi được bật, tính năng tải trước có thể giúp giảm thiểu độ trễ khi tham gia 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. Giai đoạn đầ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 vùng đệm. Sau đó, khoảng thời gian được tải trước sẽ được đưa vào hàng đợi trong trình phát, trong đó các mẫu được lưu vào vùng đệm sẽ có sẵn ngay lập tức và sẵn sàng được đưa vào bộ mã hoá và giải mã để kết xuất.

Trong Media3, có hai 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 tuần tự, tuyến tính như danh sách phát có thứ tự phát có thể dự đoán được (như một loạt tập). Bạn cung cấp cho trình phát danh sách đầy đủ các mục nội dung nghe nhìn bằ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 trình tự như đã định cấu hình. API này cố gắng tối ưu hoá độ trễ khi tham gia khi người dùng chuyển sang mục tiếp theo trước khi vùng đệm phát đã chồng chéo vào 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 được tải cho quá trình phát đang diễn ra, nhờ đó tránh được việc cạnh tranh băng thông với quá trình phát chính.

Nếu bạn vẫn chưa chắc chắn 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, 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 sử dụng PreloadConfiguration.DEFAULT để tắt tính năng tải trước danh sách phát:

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 hoạt động tương tác của người dùng, thì API PreloadManager là lựa chọn 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. Lớp này quản lý một tập hợp các MediaSource tiềm năng, ưu tiên các nguồn này dựa trên mức độ gần với vị trí hiện tại của người dùng và cung cấp quyền kiểm soát từng phần đối với nội dung cần tải trước, phù hợp với các tình huống 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 chuẩn cho PreloadManager.

Trình tạo của 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 cần truyền một TargetPreloadStatusControl mà trình quản lý tải trước có thể truy vấn để tìm hiểu lượng dữ liệu cần tải 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 để đảm bảo các thành phần bên trong được chia sẻ chính xác.

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

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

Nếu bạn muốn tải trước 10 giây video thì sao? 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 của mục đó với mục mà người dùng đ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 điều đó 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" thậm chí còn ít được ưu tiên hơn, chỉ cần chuẩn bị.
  • Mọi mục khác đều ở xa, không tải trước bất kỳ mục nào.

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

Mẹo: 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 trình quản lý, bạn có thể bắt đầu cho trình quản lý biết cần làm gì. Khi người dùng di chuyể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ý. Hoạt động 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 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 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)
    )
}

Giờ đây, 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ó điều gì đó đã thay đổi 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

Sau đâ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 MediaSource mới. Thay vào đó, bạn yêu cầu PreloadManager cung cấp phiên bản 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à rỗng, thì tức là mediaItem chưa được tải trước hoặc chưa được thêm vào PreloadMamager, vì vậy, bạn chọn đặt mediaItem trực tiếp.

// 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ừ quá trình tải trước sang phát, sử dụng dữ liệu đã có trong bộ nhớ. Đây là yếu tố 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 ta có thể linh động, 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 các mục gần nhất với chỉ mục hiện tại để 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á các mục mà trình quản lý không cần theo dõi nữa, chẳng hạn như các 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 Trình quản lý

Khi không cần PreloadManager nữa (ví dụ: khi giao diện người dùng bị huỷ), bạn phải giải phóng để giải phóng tài nguyên của nó. Bạn nên thực hiện việc này ở nơi bạn đang phát hành tài nguyên của Trình phát. Bạn nên giải phóng 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 nữa.

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

Phần minh hoạ

Xem ví dụ thực tế 👍

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

Demo-PreloadManager_2.webp

Tiếp theo là gì?

Đó là tất cả nội dung của Phần 1! Giờ đây, bạn đã có các công cụ để xây dựng hệ thống tải trước linh động. Bạn có thể sử dụng PreloadConfiguration để tải trước mục tiếp theo của danh sách phát trong ExoPlayer hoặc thiết lập DefaultPreloadManager, thêm và xoá các mục một cách nhanh chóng, đị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 được tải trước để phát.

Trong Phần 2, chúng ta sẽ tìm hiểu sâu hơn về DefaultPreloadManager. Chúng ta sẽ tìm hiểu 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 tìm hiểu sâu hơn về 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à tăng tốc ứng dụng của bạn! 🚀

Tác giả:
Tiếp tục đọc