Tin tức về sản phẩm
Nâng cao khả năng phát nội dung nghe nhìn: Nghiên cứu chuyên sâu về PreloadManager của Media3 – Phần 2
Đọc trong 9 phút
Chào mừng bạn đến với phần thứ hai trong loạt bài gồm 3 phần của chúng tôi về tính năng tải trước nội dung nghe nhìn bằng Media3. Loạt bài này được thiết kế để hướng dẫn bạn quy trình xây dựng trải nghiệm nội dung nghe nhìn có độ phản hồi cao và độ trễ thấp trong các ứng dụng Android.
- Phần 1: Giới thiệu về tính năng tải trước bằng Media3 đã đề cập đến những kiến thức cơ bản. Chúng tôi đã khám phá sự khác biệt giữa PreloadConfiguration cho danh sách phát đơn giản và DefaultPreloadManager mạnh mẽ hơn cho giao diện người dùng động. Bạn đã tìm hiểu cách triển khai vòng đời API cơ bản: thêm nội dung nghe nhìn bằng add(), truy xuất MediaSource đã chuẩn bị bằng getMediaSource(), quản lý mức độ ưu tiên bằng setCurrentPlayingIndex() và invalidate(), đồng thời giải phóng tài nguyên bằng remove() và release().
- Phần 2 (Bài đăng này): Trong blog này, chúng ta sẽ khám phá các tính năng nâng cao của DefaultPreloadManager. Chúng ta sẽ đề cập đến cách thu thập thông tin chi tiết bằng PreloadManagerListener, triển khai các phương pháp hay nhất sẵn sàng cho quá trình phát hành công khai như chia sẻ các thành phần cốt lõi với ExoPlayer và nắm vững mẫu cửa sổ trượt để quản lý bộ nhớ một cách hiệu quả.
- Phần 3: Phần cuối cùng của loạt bài này sẽ đi sâu vào việc tích hợp PreloadManager với bộ nhớ đệm của ổ đĩa liên tục, cho phép bạn giảm mức tiêu thụ dữ liệu bằng tính năng quản lý tài nguyên và mang đến trải nghiệm liền mạch.
Nếu bạn mới làm quen với tính năng tải trước trong Media3, bạn nên đọc Phần 1 trước khi tiếp tục. Đối với những người đã sẵn sàng tìm hiểu thêm, hãy khám phá cách nâng cao việc triển khai tính năng phát nội dung nghe nhìn.
Lắng nghe: Tìm nạp dữ liệu phân tích bằng PreloadManagerListener
Khi muốn ra mắt một tính năng trong quá trình phát hành, với tư cách là nhà phát triển ứng dụng, bạn cũng muốn hiểu và thu thập dữ liệu phân tích đằng sau tính năng đó. Làm thế nào để bạn chắc chắn rằng chiến lược tải trước của mình có hiệu quả trong môi trường thực tế? Để trả lời câu hỏi này, bạn cần có dữ liệu về tỷ lệ thành công, lỗi và hiệu suất. Giao diện PreloadManagerListener là cơ chế chính để thu thập dữ liệu này.
PreloadManagerListener cung cấp 2 lệnh gọi lại thiết yếu giúp bạn nắm được thông tin chi tiết quan trọng về quy trình và trạng thái tải trước.
- onCompleted(MediaItem mediaItem): Lệnh gọi lại này được gọi khi hoàn tất thành công một yêu cầu tải trước, như được xác định bởi TargetPreloadStatusControl.
- onError(PreloadException error): Lệnh gọi lại này có thể hữu ích cho việc gỡ lỗi và giám sát. Lệnh gọi lại này được gọi khi quá trình tải trước không thành công, cung cấp ngoại lệ được liên kết.
Bạn có thể đăng ký một trình nghe bằng một lệnh gọi phương thức duy nhất như trong mã ví dụ sau:
val preloadManagerListener = object : PreloadManagerListener {
override fun onCompleted(mediaItem: MediaItem) {
// Log success for analytics.
Log.d("PreloadAnalytics", "Preload completed for $mediaItem")
}
override fun onError( preloadError: PreloadException) {
// Log the specific error for debugging and monitoring.
Log.e("PreloadAnalytics", "Preload error ", preloadError)
}
}
preloadManager.addListener(preloadManagerListener)
Trích xuất thông tin chi tiết từ trình nghe
Bạn có thể kết nối các lệnh gọi lại của trình nghe này với quy trình phân tích. Bằng cách chuyển tiếp các sự kiện này đến công cụ phân tích, bạn có thể trả lời các câu hỏi quan trọng như:
- Tỷ lệ thành công khi tải trước là bao nhiêu? (tỷ lệ sự kiện onCompleted trên tổng số lần thử tải trước)
- CDN hoặc định dạng video nào có tỷ lệ lỗi cao nhất? (Bằng cách phân tích cú pháp các ngoại lệ từ onError)
- Tỷ lệ lỗi khi tải trước là bao nhiêu? (tỷ lệ sự kiện onError trên tổng số lần thử tải trước)
Dữ liệu này có thể cung cấp cho bạn thông tin phản hồi định lượng về chiến lược tải trước, cho phép kiểm thử A/B và cải thiện trải nghiệm người dùng dựa trên dữ liệu. Dữ liệu này có thể giúp bạn tinh chỉnh một cách thông minh thời lượng tải trước và số lượng video bạn muốn tải trước, cũng như các vùng đệm mà bạn phân bổ.
Ngoài việc gỡ lỗi: Sử dụng onError để dự phòng giao diện người dùng một cách linh hoạt
Quá trình tải trước không thành công là một dấu hiệu mạnh mẽ cho thấy sự kiện lưu vào bộ đệm sắp tới đối với người dùng. Lệnh gọi lại onError cho phép bạn phản hồi một cách chủ động. Thay vì chỉ ghi lại lỗi, bạn có thể điều chỉnh giao diện người dùng. Ví dụ: nếu video sắp tới không tải trước được, ứng dụng của bạn có thể tắt tính năng tự động phát cho lần vuốt tiếp theo, yêu cầu người dùng nhấn để bắt đầu phát.
Ngoài ra, bằng cách kiểm tra loại PreloadException, bạn có thể xác định chiến lược thử lại thông minh hơn. Ứng dụng có thể chọn xoá ngay nguồn không thành công khỏi trình quản lý dựa trên thông báo lỗi hoặc mã trạng thái HTTP. Bạn cần xoá mục đó khỏi luồng giao diện người dùng cho phù hợp để không làm rò rỉ các vấn đề về tải vào trải nghiệm người dùng. Bạn cũng có thể lấy dữ liệu thật chi tiết hơn từ PreloadException như HttpDataSourceException để tìm hiểu sâu hơn về các lỗi. Đọc thêm về cách khắc phục sự cố ExoPlayer.
Hệ thống hỗ trợ: Tại sao cần chia sẻ các thành phần với ExoPlayer?
DefaultPreloadManager và ExoPlayer được thiết kế để hoạt động cùng nhau. Để đảm bảo tính ổn định và hiệu quả, chúng phải chia sẻ một số thành phần cốt lõi components. Nếu hoạt động với các thành phần riêng biệt, không phối hợp, thì điều này có thể ảnh hưởng đến tính an toàn của luồng và khả năng sử dụng các bản nhạc được tải trước trên trình phát, vì chúng ta cần đảm bảo rằng các bản nhạc được tải trước phải được phát trên trình phát chính xác. Các thành phần riêng biệt cũng có thể cạnh tranh để giành các tài nguyên hạn chế như băng thông mạng và bộ nhớ, điều này có thể dẫn đến hiệu suất giảm sút. Một phần quan trọng của vòng đời là xử lý việc huỷ bỏ thích hợp. Thứ tự huỷ bỏ được đề xuất là giải phóng PreloadManager trước, sau đó là ExoPlayer.
DefaultPreloadManager.Builder được thiết kế để tạo điều kiện thuận lợi cho việc chia sẻ này và có các API để tạo thực thể cho cả PreloadManager và thực thể trình phát được liên kết. Hãy xem lý do tại sao các thành phần như BandwidthMeter, LoadControl, TrackSelector, Looper phải được chia sẻ. Kiểm tra hình ảnh minh hoạ về cách các thành phần này tương tác với tính năng phát lại ExoPlayer.
Ngăn chặn xung đột băng thông bằng BandwidthMeter được chia sẻ
BandwidthMeter cung cấp thông tin ước tính về băng thông mạng có sẵn dựa trên tốc độ truyền trước đây. Nếu PreloadManager và trình phát sử dụng các thực thể riêng biệt, thì chúng sẽ không biết về hoạt động mạng của nhau, điều này có thể dẫn đến các tình huống lỗi. Ví dụ: hãy xem xét tình huống người dùng đang xem video, kết nối mạng của họ bị giảm và MediaSource tải trước đồng thời bắt đầu tải xuống mạnh mẽ cho một video trong tương lai. Hoạt động của MediaSource tải trước sẽ tiêu thụ băng thông mà trình phát đang hoạt động cần, khiến video hiện tại bị gián đoạn. Việc gián đoạn trong quá trình phát lại là một lỗi nghiêm trọng về trải nghiệm người dùng.
Bằng cách chia sẻ một BandwidthMeter duy nhất, TrackSelector có thể chọn các bản nhạc có chất lượng cao nhất dựa trên điều kiện mạng hiện tại và trạng thái của bộ đệm, trong quá trình tải trước hoặc phát lại. Sau đó, công cụ này có thể đưa ra các quyết định thông minh để bảo vệ phiên phát lại đang hoạt động và đảm bảo trải nghiệm mượt mà.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
Đảm bảo tính nhất quán với các thành phần LoadControl, TrackSelector, Renderer được chia sẻ của ExoPlayer
- LoadControl: Thành phần này quy định chính sách lưu vào bộ đệm, chẳng hạn như lượng dữ liệu cần lưu vào bộ đệm trước khi bắt đầu phát lại và thời điểm bắt đầu hoặc dừng tải thêm dữ liệu. Việc chia sẻ LoadControl đảm bảo rằng mức tiêu thụ bộ nhớ của trình phát và PreloadManager được hướng dẫn bởi một chiến lược lưu vào bộ đệm phối hợp duy nhất trên cả nội dung nghe nhìn được tải trước và đang phát, ngăn chặn tình trạng tranh giành tài nguyên. Bạn sẽ phải phân bổ dung lượng bộ nhớ đệm một cách thông minh, phối hợp với số lượng mục bạn đang tải trước và thời lượng, để đảm bảo tính nhất quán. Trong thời gian tranh giành, trình phát sẽ ưu tiên phát lại mục hiện tại hiển thị trên màn hình. Với LoadControl được chia sẻ, trình quản lý tải trước sẽ tiếp tục tải trước miễn là số byte bộ đệm mục tiêu được phân bổ cho quá trình tải trước chưa đạt đến giới hạn trên, trình quản lý này không đợi cho đến khi quá trình tải để phát lại hoàn tất.
Lưu ý: Việc chia sẻ LoadControl trong phiên bản mới nhất của Media3 (1.8) đảm bảo rằng Allocator của phiên bản này có thể được chia sẻ chính xác với PreloadManager và trình phát. Sử dụng LoadControl để kiểm soát hiệu quả quá trình tải trước là một tính năng sẽ có trong bản phát hành Media3 1.9 sắp tới.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector: Thành phần này chịu trách nhiệm chọn các bản nhạc (ví dụ: video có độ phân giải nhất định, âm thanh bằng một ngôn ngữ cụ thể) để tải và phát. Việc chia sẻ đảm bảo rằng các bản nhạc được chọn trong quá trình tải trước là các bản nhạc mà trình phát sẽ sử dụng. Điều này giúp tránh tình huống lãng phí khi một bản nhạc video 480p được tải trước, chỉ để trình phát loại bỏ ngay lập tức và tìm nạp bản nhạc 720p khi phát lại.< br /> Trình quản lý tải trước KHÔNG được chia sẻ cùng một thực thể của TrackSelector với trình phát. Thay vào đó, chúng nên sử dụng thực thể TrackSelector khác nhưng có cùng cách triển khai. Đó là lý do chúng tôi đặt TrackSelectorFactory thay vì TrackSelector trong DefaultPreloadManager.Builder.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- Renderer: Thành phần này chịu trách nhiệm hiểu các khả năng của trình phát mà không cần tạo trình kết xuất đầy đủ. Thành phần này kiểm tra bản thiết kế này để xem trình phát cuối cùng sẽ hỗ trợ định dạng video, âm thanh và văn bản nào. Điều này cho phép thành phần này chọn và tải xuống một cách thông minh chỉ bản nhạc nội dung nghe nhìn tương thích và ngăn chặn việc lãng phí băng thông cho nội dung mà trình phát không thể phát.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
Đọc thêm về các thành phần Exoplayer.
Quy tắc vàng: Một Looper phát lại chung để kiểm soát tất cả
Bạn có thể chỉ định rõ ràng luồng mà thực thể ExoPlayer có thể truy cập bằng cách truyền một Looper khi tạo trình phát. Bạn có thể truy vấn Looper của luồng mà trình phát phải truy cập bằng Player.getApplicationLooper. Bằng cách duy trì một trình lặp được chia sẻ giữa trình phát và PreloadManager, bạn có thể đảm bảo rằng tất cả các thao tác trên các đối tượng nội dung nghe nhìn được chia sẻ này đều được chuyển đổi tuần tự vào hàng đợi thông báo của một luồng duy nhất. Điều này có thể làm giảm các lỗi đồng thời.
Tất cả các lượt tương tác giữa PreloadManager và trình phát với các nguồn nội dung nghe nhìn cần tải hoặc tải trước đều phải diễn ra trên cùng một luồng phát lại. Việc chia sẻ Looper là điều bắt buộc để đảm bảo tính an toàn của luồng, do đó, chúng ta phải chia sẻ PlaybackLooper giữa PreloadManager và trình phát.
PreloadManager chuẩn bị một đối tượng MediaSource có trạng thái ở chế độ nền. Khi mã giao diện người dùng của bạn gọi player.setMediaSource(mediaSource), bạn đang thực hiện việc chuyển giao đối tượng phức tạp, có trạng thái này từ MediaSource tải trước sang trình phát. Trong trường hợp này, toàn bộ PreloadMediaSource sẽ được chuyển từ trình quản lý sang trình phát. Tất cả các lượt tương tác và chuyển giao này phải diễn ra trên cùng một PlaybackLooper.
Nếu PreloadManager và ExoPlayer hoạt động trên các luồng khác nhau, thì có thể xảy ra tình huống tương tranh. Luồng của PreloadManager có thể đang sửa đổi trạng thái nội bộ của MediaSource (ví dụ: ghi dữ liệu mới vào vùng đệm) ngay tại thời điểm luồng của trình phát đang cố gắng đọc từ đó. Điều này dẫn đến hành vi không thể đoán trước, IllegalStateException khó gỡ lỗi.
preloadManagerBuilder.setPreloadLooper(playbackLooper)
Hãy xem cách bạn có thể chia sẻ tất cả các thành phần ở trên giữa ExoPlayer và DefaultPreloadManager trong chính quá trình thiết lập.
val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
// Optional - Share components between ExoPlayer and DefaultPreloadManager
preloadManagerBuilder
.setBandwidthMeter(customBandwidthMeter)
.setLoadControl(customLoadControl)
.setMediaSourceFactory(customMediaSourceFactory)
.setTrackSelectorFactory(customTrackSelectorFactory)
.setRenderersFactory(customRenderersFactory)
.setPreloadLooper(playbackLooper)
val preloadManager = val preloadManagerBuilder.build()
Mẹo: Nếu bạn sử dụng các thành phần Mặc định trong ExoPlayer như DefaultLoadControl, v.v., bạn không cần chia sẻ rõ ràng các thành phần này với DefaultPreloadManager. Khi bạn tạo thực thể ExoPlayer thông qua buildExoPlayer của DefaultPreloadManager.Builder, các thành phần này sẽ tự động tham chiếu lẫn nhau nếu bạn sử dụng các cách triển khai mặc định với cấu hình mặc định. Tuy nhiên, nếu bạn sử dụng các thành phần tuỳ chỉnh hoặc cấu hình tuỳ chỉnh, bạn nên thông báo cho DefaultPreloadManager về các thành phần này một cách rõ ràng thông qua các API ở trên.
Tải trước sẵn sàng cho quá trình phát hành công khai: Mẫu cửa sổ trượt
Trong nguồn cấp dữ liệu động, người dùng có thể cuộn qua một lượng nội dung gần như vô hạn. Nếu liên tục thêm video vào DefaultPreloadManager mà không có chiến lược xoá tương ứng, bạn chắc chắn sẽ gây ra lỗi OutOfMemoryError. Mỗi MediaSource được tải trước đều giữ lại một SampleQueue, phân bổ các vùng đệm bộ nhớ. Khi các vùng đệm này tích luỹ, chúng có thể làm cạn kiệt không gian vùng nhớ heap của ứng dụng. Giải pháp là một thuật toán mà bạn có thể đã quen thuộc, được gọi là cửa sổ trượt. Mẫu cửa sổ trượt duy trì một tập hợp nhỏ, dễ quản lý các mục trong bộ nhớ, các mục này được đặt cạnh nhau một cách logic với vị trí hiện tại của người dùng trong nguồn cấp dữ liệu. Khi người dùng cuộn, "cửa sổ" các mục được quản lý này sẽ trượt theo họ, thêm các mục mới xuất hiện trong chế độ xem và cũng xoá các mục hiện ở xa.
Triển khai mẫu cửa sổ trượt
Bạn cần hiểu rằng PreloadManager không cung cấp phương thức setWindowSize() tích hợp sẵn. Cửa sổ trượt là một mẫu thiết kế mà bạn, nhà phát triển, chịu trách nhiệm triển khai bằng cách sử dụng các phương thức add() và remove() nguyên thuỷ. Logic ứng dụng của bạn phải kết nối các sự kiện giao diện người dùng, chẳng hạn như thao tác cuộn hoặc thay đổi trang, với các lệnh gọi API này. Nếu bạn muốn tham khảo mã cho việc này, chúng tôi có mẫu cửa sổ trượt này được triển khai trong mẫu socialite, mẫu này cũng bao gồm một PreloadManagerWrapper mô phỏng một cửa sổ trượt.
Đừng quên thêm preloadManager.remove(mediaItem) vào quá trình triển khai khi mục đó không còn có khả năng xuất hiện sớm trong chế độ xem của người dùng. Việc không xoá các mục không còn gần người dùng là nguyên nhân chính gây ra các vấn đề về bộ nhớ trong quá trình triển khai tính năng tải trước. Lệnh gọi remove() đảm bảo giải phóng các tài nguyên giúp bạn duy trì mức sử dụng bộ nhớ của ứng dụng ở mức ổn định và có giới hạn.
Tinh chỉnh chiến lược tải trước được phân loại bằng TargetPreloadStatusControl
Sau khi xác định được nội dung cần tải trước (các mục trong cửa sổ của chúng ta), chúng ta có thể áp dụng một chiến lược được xác định rõ ràng về lượng nội dung cần tải trước cho mỗi mục. Chúng ta đã thấy cách đạt được mức độ chi tiết này bằng cách thiết lập TargetPreloadStatusControl trong Phần 1.
Để nhớ lại, một mục ở vị trí +/- 1 có thể có xác suất được phát cao hơn một mục ở vị trí +/- 4. Bạn có thể phân bổ thêm tài nguyên (mạng, CPU, bộ nhớ) cho các mục mà người dùng có nhiều khả năng xem tiếp theo. Điều này tạo ra một chiến lược "tải trước" dựa trên khoảng cách, đây là chìa khoá để cân bằng việc phát lại ngay lập tức với việc sử dụng tài nguyên hiệu quả.
Bạn có thể sử dụng dữ liệu phân tích thông qua PreloadManagerListener như đã thảo luận trong các phần trước để quyết định chiến lược thời lượng tải trước.
Kết luận và các bước tiếp theo
Giờ đây, bạn đã được trang bị kiến thức nâng cao để xây dựng nguồn cấp dữ liệu nội dung nghe nhìn nhanh, ổn định và tiết kiệm tài nguyên bằng DefaultPreloadManager của Media3.
Hãy tóm tắt những điểm chính cần ghi nhớ:
- Sử dụng PreloadManagerListener để thu thập thông tin chi tiết về dữ liệu phân tích và triển khai tính năng xử lý lỗi mạnh mẽ.
- Luôn sử dụng một DefaultPreloadManager.Builder duy nhất để tạo cả thực thể trình quản lý và trình phát nhằm đảm bảo các thành phần quan trọng được chia sẻ.
- Triển khai mẫu cửa sổ trượt bằng cách chủ động quản lý các lệnh gọi add() và remove() để ngăn chặn lỗi OutOfMemoryError.
- Sử dụng TargetPreloadStatusControl để tạo chiến lược tải trước thông minh, theo cấp bậc, cân bằng hiệu suất và mức tiêu thụ tài nguyên.
Nội dung tiếp theo trong Phần 3: Lưu vào bộ nhớ đệm bằng nội dung nghe nhìn được tải trước
Việc tải trước dữ liệu vào bộ nhớ mang lại lợi ích về hiệu suất ngay lập tức, nhưng có thể đi kèm với những đánh đổi. Sau khi đóng ứng dụng hoặc xoá nội dung nghe nhìn được tải trước khỏi trình quản lý, dữ liệu sẽ biến mất. Để đạt được mức tối ưu hoá bền bỉ hơn, chúng ta có thể kết hợp tính năng tải trước với tính năng lưu vào bộ nhớ đệm trên đĩa. Tính năng này đang được tích cực phát triển và sẽ ra mắt trong vài tháng tới.
Bạn có ý kiến phản hồi gì 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 độ phát video! 🚀
Tiếp tục đọc
-
Tin tức về sản phẩm
Trong các ứng dụng tập trung vào nội dung nghe nhìn ngày nay, việc mang đến trải nghiệm phát lại mượt mà, không bị gián đoạn là chìa khoá để có được 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 mạch mà không bị tạm dừng.
Mayuri Khinvasara Khabya • Đọc trong 8 phút
-
Tin tức về sản phẩm
Android Studio Panda 4 hiện đã ổn định và sẵn sàng để bạn sử dụng trong quá trình phát hành. Bản phát hành này mang đến Chế độ lập kế hoạch, tính năng Dự đoán lần chỉnh sửa tiếp theo và nhiều tính năng khác, giúp bạn dễ dàng hơn bao giờ hết trong việc xây dựng các ứng dụng Android chất lượng cao.
Matt Dyor • Đọc trong 5 phút
-
Tin tức về sản phẩm
Nếu bạn là nhà phát triển Android đang tìm cách triển khai các tính năng AI cải tiến vào ứng dụng của mình, thì chúng tôi mới ra mắt các bản cập nhật mới mạnh mẽ.
Thomas Ezan • Đọc trong 3 phút
Nhận thông tin cập nhật
Nhận thông tin chi tiết mới nhất về quá trình phát triển Android được gửi đến hộp thư đến của bạn hằng tuần.