Phiên phát nội dung nghe nhìn là cách thức tương tác phổ biến với trình phát âm thanh hoặc trình phát video. Trong Media3, trình phát mặc định là lớp ExoPlayer
, lớp này triển khai giao diện Player
. Việc kết nối phiên phát nội dung đa phương tiện với trình phát cho phép ứng dụng quảng cáo việc phát nội dung đa phương tiện bên ngoài và nhận lệnh phát từ các nguồn bên ngoài.
Lệnh có thể bắt nguồn từ các nút vật lý như nút phát trên tai nghe hoặc điều khiển từ xa của TV. Các quảng cáo này cũng có thể đến từ các ứng dụng khách có trình điều khiển nội dung nghe nhìn, chẳng hạn như hướng dẫn "tạm dừng" cho Trợ lý Google. Phiên phát nội dung đa phương tiện uỷ quyền các lệnh này cho trình phát của ứng dụng đa phương tiện.
Thời điểm chọn một phiên phát nội dung nghe nhìn
Khi triển khai MediaSession
, bạn cho phép người dùng kiểm soát chế độ phát:
- Qua tai nghe. Thường thì người dùng có thể thực hiện các thao tác nhấn nút hoặc chạm trên tai nghe để phát hoặc tạm dừng nội dung nghe nhìn hoặc chuyển sang bản nhạc tiếp theo hoặc trước đó.
- Bằng cách nói chuyện với Trợ lý Google. Một mẫu phổ biến là nói "Ok Google, tạm dừng" để tạm dừng mọi nội dung nghe nhìn hiện đang phát trên thiết bị.
- Thông qua đồng hồ Wear OS. Điều này giúp người dùng dễ dàng truy cập vào các chế độ điều khiển phát phổ biến nhất khi phát trên điện thoại.
- Thông qua Các nút điều khiển nội dung nghe nhìn. Băng chuyền này hiển thị các nút điều khiển cho từng phiên phát nội dung nghe nhìn đang chạy.
- Trên TV. Cho phép các thao tác bằng nút phát thực, điều khiển phát trên nền tảng và quản lý nguồn (ví dụ: nếu TV, loa thanh hoặc bộ thu A/V tắt hoặc đầu vào bị chuyển đổi, thì quá trình phát sẽ dừng trong ứng dụng).
- Và mọi quy trình bên ngoài khác cần ảnh hưởng đến quá trình phát.
Điều này rất phù hợp với nhiều trường hợp sử dụng. Cụ thể, bạn nên cân nhắc sử dụng MediaSession
khi:
- Bạn đang phát trực tiếp nội dung video dài, chẳng hạn như phim hoặc chương trình truyền hình trực tuyến.
- Bạn đang phát trực tuyến nội dung âm thanh dài, chẳng hạn như podcast hoặc danh sách phát nhạc.
- Bạn đang xây dựng một ứng dụng cho TV.
Tuy nhiên, không phải trường hợp sử dụng nào cũng phù hợp với MediaSession
. Bạn có thể chỉ muốn sử dụng Player
trong các trường hợp sau:
- Bạn đang hiển thị nội dung dạng ngắn, trong đó mức độ tương tác và tương tác của người dùng là yếu tố quan trọng.
- Không có một video nào đang hoạt động, chẳng hạn như người dùng đang cuộn qua một danh sách và nhiều video hiển thị trên màn hình cùng một lúc.
- Bạn đang phát video giới thiệu hoặc giải thích một lần mà bạn mong đợi người dùng sẽ chủ động xem.
- Nội dung của bạn nhạy cảm về quyền riêng tư và bạn không muốn các quy trình bên ngoài truy cập vào siêu dữ liệu của nội dung nghe nhìn (ví dụ: chế độ ẩn danh trong trình duyệt)
Nếu trường hợp sử dụng của bạn không phù hợp với bất kỳ trường hợp nào nêu trên, hãy cân nhắc xem bạn có đồng ý với việc ứng dụng tiếp tục phát khi người dùng không chủ động tương tác với nội dung hay không. Nếu câu trả lời là có, bạn nên chọn MediaSession
. Nếu câu trả lời là không, bạn có thể muốn sử dụng Player
.
Tạo phiên phát nội dung nghe nhìn
Phiên nội dung nghe nhìn tồn tại cùng với trình phát mà phiên đó quản lý. Bạn có thể tạo một phiên phát nội dung nghe nhìn bằng đối tượng Context
và Player
. Bạn nên tạo và khởi chạy phiên phát nội dung nghe nhìn khi cần, chẳng hạn như phương thức vòng đời onStart()
hoặc onResume()
của Activity
hoặc Fragment
, hoặc phương thức onCreate()
của Service
sở hữu phiên phát nội dung nghe nhìn và trình phát liên kết với phiên phát đó.
Để tạo một phiên phát nội dung đa phương tiện, hãy khởi chạy Player
và cung cấp phiên đó cho MediaSession.Builder
như sau:
Kotlin
val player = ExoPlayer.Builder(context).build() val mediaSession = MediaSession.Builder(context, player).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); MediaSession mediaSession = new MediaSession.Builder(context, player).build();
Xử lý trạng thái tự động
Thư viện Media3 tự động cập nhật phiên phát nội dung đa phương tiện bằng trạng thái của trình phát. Do đó, bạn không cần phải xử lý việc liên kết từ người chơi đến phiên theo cách thủ công.
Đây là điểm khác biệt so với phương pháp cũ, trong đó bạn cần tạo và duy trì một PlaybackStateCompat
độc lập với chính trình phát, chẳng hạn như để cho biết mọi lỗi.
Mã phiên hoạt động duy nhất
Theo mặc định, MediaSession.Builder
tạo một phiên có chuỗi trống làm mã phiên. Điều này là đủ nếu ứng dụng chỉ có ý định tạo một phiên bản thể hiện duy nhất, đây là trường hợp phổ biến nhất.
Nếu muốn quản lý nhiều phiên bản phiên cùng một lúc, ứng dụng phải đảm bảo rằng mã phiên của mỗi phiên là duy nhất. Bạn có thể đặt mã phiên khi tạo phiên bằng MediaSession.Builder.setId(String id)
.
Nếu bạn thấy IllegalStateException
gặp sự cố với ứng dụng của mình kèm theo thông báo lỗi IllegalStateException: Session ID must be unique. ID=
, thì có thể một phiên đã được tạo ngoài dự kiến trước khi một thực thể được tạo trước đó có cùng mã nhận dạng được phát hành. Để tránh các phiên bị rò rỉ do lỗi lập trình, các trường hợp như vậy sẽ được phát hiện và thông báo bằng cách gửi một ngoại lệ.
Cấp quyền kiểm soát cho các ứng dụng khác
Phiên phát nội dung đa phương tiện là chìa khoá để điều khiển quá trình phát. Lớp này cho phép bạn định tuyến các lệnh từ các nguồn bên ngoài đến trình phát thực hiện việc phát nội dung đa phương tiện. Các nguồn này có thể là các nút vật lý như nút phát trên tai nghe hoặc điều khiển từ xa của TV, hoặc các lệnh gián tiếp như hướng dẫn "tạm dừng" cho Trợ lý Google. Tương tự, bạn nên cấp quyền truy cập vào hệ thống Android để hỗ trợ các chế độ điều khiển thông báo và màn hình khoá hoặc cho đồng hồ Wear OS để có thể điều khiển chế độ phát trên mặt đồng hồ. Các ứng dụng bên ngoài có thể sử dụng trình điều khiển nội dung đa phương tiện để đưa ra lệnh phát cho ứng dụng đa phương tiện của bạn. Phiên phát nội dung đa phương tiện sẽ nhận các lệnh này qua phiên phát nội dung đa phương tiện, cuối cùng sẽ uỷ quyền các lệnh cho trình phát nội dung đa phương tiện.
Khi một tay điều khiển sắp kết nối với phiên phát nội dung nghe nhìn, phương thức onConnect()
sẽ được gọi. Bạn có thể sử dụng ControllerInfo
được cung cấp để quyết định chấp nhận hay từ chối yêu cầu. Xem ví dụ về cách chấp nhận yêu cầu kết nối trong phần Khai báo các lệnh có sẵn.
Sau khi kết nối, tay điều khiển có thể gửi lệnh phát đến phiên. Sau đó, phiên hoạt động sẽ uỷ quyền các lệnh đó cho người chơi. Các lệnh phát và danh sách phát được xác định trong giao diện Player
sẽ được phiên tự động xử lý.
Các phương thức gọi lại khác cho phép bạn xử lý, ví dụ: các yêu cầu về lệnh phát tuỳ chỉnh và sửa đổi danh sách phát).
Các lệnh gọi lại này cũng bao gồm đối tượng ControllerInfo
để bạn có thể sửa đổi cách phản hồi từng yêu cầu trên cơ sở từng tay điều khiển.
Sửa đổi danh sách phát
Một phiên phát nội dung nghe nhìn có thể trực tiếp sửa đổi danh sách phát của trình phát như được giải thích trong hướng dẫn về danh sách phát của ExoPlayer.
Bộ điều khiển cũng có thể sửa đổi danh sách phát nếu COMMAND_SET_MEDIA_ITEM
hoặc COMMAND_CHANGE_MEDIA_ITEMS
có sẵn cho bộ điều khiển.
Khi thêm các mục mới vào danh sách phát, trình phát thường yêu cầu các thực thể MediaItem
có URI đã xác định để có thể phát các mục đó. Theo mặc định, các mục mới thêm sẽ tự động được chuyển tiếp đến các phương thức trình phát như player.addMediaItem
nếu các mục đó có URI được xác định.
Nếu muốn tuỳ chỉnh các thực thể MediaItem
được thêm vào trình phát, bạn có thể ghi đè onAddMediaItems()
.
Bạn cần thực hiện bước này khi muốn hỗ trợ các tay điều khiển yêu cầu nội dung nghe nhìn mà không có URI được xác định. Thay vào đó, MediaItem
thường có một hoặc nhiều trường sau đây được đặt để mô tả nội dung nghe nhìn được yêu cầu:
MediaItem.id
: Mã nhận dạng chung xác định nội dung nghe nhìn.MediaItem.RequestMetadata.mediaUri
: URI yêu cầu có thể sử dụng giản đồ tuỳ chỉnh và không nhất thiết phải do người chơi phát trực tiếp.MediaItem.RequestMetadata.searchQuery
: Cụm từ tìm kiếm dạng văn bản, ví dụ: từ Trợ lý Google.MediaItem.MediaMetadata
: Siêu dữ liệu có cấu trúc như "tiêu đề" hoặc "nghệ sĩ".
Để có thêm các tuỳ chọn tuỳ chỉnh cho danh sách phát hoàn toàn mới, bạn có thể ghi đè thêm onSetMediaItems()
để xác định mục bắt đầu và vị trí trong danh sách phát. Ví dụ: bạn có thể mở rộng một mục đã yêu cầu thành toàn bộ danh sách phát và hướng dẫn trình phát bắt đầu tại chỉ mục của mục đã yêu cầu ban đầu. Bạn có thể xem cách triển khai onSetMediaItems()
bằng tính năng này trong ứng dụng minh hoạ phiên.
Quản lý bố cục tuỳ chỉnh và lệnh tuỳ chỉnh
Các phần sau đây mô tả cách quảng cáo bố cục tuỳ chỉnh của các nút lệnh tuỳ chỉnh cho ứng dụng khách và uỷ quyền cho tay điều khiển gửi các lệnh tuỳ chỉnh.
Xác định bố cục tuỳ chỉnh của phiên
Để cho ứng dụng khách biết bạn muốn hiển thị cho người dùng những chế độ điều khiển phát nào, hãy đặt bố cục tuỳ chỉnh của phiên khi tạo MediaSession
trong phương thức onCreate()
của dịch vụ.
Kotlin
override fun onCreate() { super.onCreate() val likeButton = CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build() val favoriteButton = CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle())) .build() session = MediaSession.Builder(this, player) .setCallback(CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build() }
Java
@Override public void onCreate() { super.onCreate(); CommandButton likeButton = new CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build(); CommandButton favoriteButton = new CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); Player player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player) .setCallback(new CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build(); }
Khai báo trình phát có sẵn và lệnh tuỳ chỉnh
Các ứng dụng đa phương tiện có thể xác định các lệnh tuỳ chỉnh, chẳng hạn như có thể được sử dụng trong bố cục tuỳ chỉnh. Ví dụ: bạn có thể muốn triển khai các nút cho phép người dùng lưu một mục nội dung nghe nhìn vào danh sách các mục yêu thích. MediaController
gửi các lệnh tuỳ chỉnh và MediaSession.Callback
nhận các lệnh đó.
Bạn có thể xác định lệnh phiên tuỳ chỉnh nào có sẵn cho MediaController
khi lệnh này kết nối với phiên phát nội dung đa phương tiện. Bạn có thể thực hiện việc này bằng cách ghi đè MediaSession.Callback.onConnect()
. Định cấu hình và trả về tập hợp các lệnh có sẵn khi chấp nhận yêu cầu kết nối từ MediaController
trong phương thức gọi lại onConnect
:
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): MediaSession.ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ConnectionResult onConnect( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } }
Để nhận yêu cầu lệnh tuỳ chỉnh từ MediaController
, hãy ghi đè phương thức onCustomCommand()
trong Callback
.
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { ... override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture<SessionResult> { if (customCommand.customAction == SAVE_TO_FAVORITES) { // Do custom logic here saveToFavorites(session.player.currentMediaItem) return Futures.immediateFuture( SessionResult(SessionResult.RESULT_SUCCESS) ) } ... } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { ... @Override public ListenableFuture<SessionResult> onCustomCommand( MediaSession session, ControllerInfo controller, SessionCommand customCommand, Bundle args ) { if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) { // Do custom logic here saveToFavorites(session.getPlayer().getCurrentMediaItem()); return Futures.immediateFuture( new SessionResult(SessionResult.RESULT_SUCCESS) ); } ... } }
Bạn có thể theo dõi trình điều khiển nội dung đa phương tiện nào đang đưa ra yêu cầu bằng cách sử dụng thuộc tính packageName
của đối tượng MediaSession.ControllerInfo
được truyền vào các phương thức Callback
. Điều này cho phép bạn điều chỉnh hành vi của ứng dụng để phản hồi một lệnh nhất định nếu lệnh đó bắt nguồn từ hệ thống, ứng dụng của riêng bạn hoặc các ứng dụng khách khác.
Cập nhật bố cục tuỳ chỉnh sau khi người dùng tương tác
Sau khi xử lý một lệnh tuỳ chỉnh hoặc bất kỳ hoạt động tương tác nào khác với người chơi, bạn có thể muốn cập nhật bố cục hiển thị trong giao diện người dùng của tay điều khiển. Một ví dụ điển hình là nút bật tắt thay đổi biểu tượng sau khi kích hoạt thao tác liên kết với nút này. Để cập nhật bố cục, bạn có thể sử dụng MediaSession.setCustomLayout
:
Kotlin
val removeFromFavoritesButton = CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle())) .build() mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
CommandButton removeFromFavoritesButton = new CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle())) .build(); mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));
Tuỳ chỉnh hành vi của lệnh phát
Để tuỳ chỉnh hành vi của một lệnh được xác định trong giao diện Player
, chẳng hạn như play()
hoặc seekToNext()
, hãy gói Player
trong ForwardingPlayer
.
Kotlin
val player = ExoPlayer.Builder(context).build() val forwardingPlayer = object : ForwardingPlayer(player) { override fun play() { // Add custom logic super.play() } override fun setPlayWhenReady(playWhenReady: Boolean) { // Add custom logic super.setPlayWhenReady(playWhenReady) } } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) { @Override public void play() { // Add custom logic super.play(); } @Override public void setPlayWhenReady(boolean playWhenReady) { // Add custom logic super.setPlayWhenReady(playWhenReady); } }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
Để biết thêm thông tin về ForwardingPlayer
, hãy xem hướng dẫn của ExoPlayer về Tuỳ chỉnh.
Xác định trình điều khiển yêu cầu của một lệnh người chơi
Khi lệnh gọi đến phương thức Player
bắt nguồn từ MediaController
, bạn có thể xác định nguồn gốc bằng MediaSession.controllerForCurrentRequest
và lấy ControllerInfo
cho yêu cầu hiện tại:
Kotlin
class CallerAwareForwardingPlayer(player: Player) : ForwardingPlayer(player) { override fun seekToNext() { Log.d( "caller", "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}" ) super.seekToNext() } }
Java
public class CallerAwareForwardingPlayer extends ForwardingPlayer { public CallerAwareForwardingPlayer(Player player) { super(player); } @Override public void seekToNext() { Log.d( "caller", "seekToNext called from package: " + session.getControllerForCurrentRequest().getPackageName()); super.seekToNext(); } }
Phản hồi với nút phát nội dung nghe nhìn
Nút phát là các nút phần cứng có trên thiết bị Android và các thiết bị ngoại vi khác, chẳng hạn như nút phát/tạm dừng trên tai nghe Bluetooth. Media3 xử lý các sự kiện nút nội dung nghe nhìn cho bạn khi các sự kiện đó đến phiên và gọi phương thức Player
thích hợp trên trình phát phiên.
Ứng dụng có thể ghi đè hành vi mặc định bằng cách ghi đè MediaSession.Callback.onMediaButtonEvent(Intent)
. Trong trường hợp như vậy, ứng dụng có thể/cần tự xử lý tất cả các thông tin cụ thể về API.
Xử lý và báo cáo lỗi
Có hai loại lỗi mà một phiên phát ra và báo cáo cho bộ điều khiển. Lỗi nghiêm trọng báo cáo lỗi phát kỹ thuật của trình phát phiên, khiến quá trình phát bị gián đoạn. Lỗi nghiêm trọng sẽ được tự động báo cáo cho trình điều khiển khi xảy ra. Lỗi không nghiêm trọng là lỗi không liên quan đến kỹ thuật hoặc lỗi chính sách không làm gián đoạn quá trình phát và được ứng dụng gửi đến tay điều khiển theo cách thủ công.
Lỗi phát lại nghiêm trọng
Trình phát sẽ báo cáo lỗi phát nghiêm trọng cho phiên, sau đó báo cáo cho bộ điều khiển để gọi thông qua Player.Listener.onPlayerError(PlaybackException)
và Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)
.
Trong trường hợp như vậy, trạng thái phát sẽ chuyển đổi thành STATE_IDLE
và MediaController.getPlaybackError()
trả về PlaybackException
gây ra quá trình chuyển đổi. Trình điều khiển có thể kiểm tra PlayerException.errorCode
để lấy thông tin về lý do gây ra lỗi.
Để có khả năng tương tác, lỗi nghiêm trọng sẽ được sao chép vào PlaybackStateCompat
của phiên nền tảng bằng cách chuyển trạng thái của phiên đó thành STATE_ERROR
và đặt mã lỗi và thông báo theo PlaybackException
.
Tuỳ chỉnh lỗi nghiêm trọng
Để cung cấp thông tin được bản địa hoá và có ý nghĩa cho người dùng, bạn có thể tuỳ chỉnh mã lỗi, thông báo lỗi và thông tin bổ sung về lỗi của lỗi phát nghiêm trọng bằng cách sử dụng ForwardingPlayer
khi tạo phiên:
Kotlin
val forwardingPlayer = ErrorForwardingPlayer(player) val session = MediaSession.Builder(context, forwardingPlayer).build()
Java
Player forwardingPlayer = new ErrorForwardingPlayer(player); MediaSession session = new MediaSession.Builder(context, forwardingPlayer).build();
Trình phát chuyển tiếp sẽ đăng ký Player.Listener
cho trình phát thực tế và chặn các lệnh gọi lại báo cáo lỗi. Sau đó, PlaybackException
tuỳ chỉnh sẽ được uỷ quyền cho các trình nghe đã được đăng ký trên trình phát chuyển tiếp. Để tính năng này hoạt động, trình phát chuyển tiếp sẽ ghi đè Player.addListener
và Player.removeListener
để có quyền truy cập vào trình nghe nhằm gửi mã lỗi, thông báo hoặc thông tin bổ sung tuỳ chỉnh:
Kotlin
class ErrorForwardingPlayer(private val context: Context, player: Player) : ForwardingPlayer(player) { private val listeners: MutableList<Player.Listener> = mutableListOf() private var customizedPlaybackException: PlaybackException? = null init { player.addListener(ErrorCustomizationListener()) } override fun addListener(listener: Player.Listener) { listeners.add(listener) } override fun removeListener(listener: Player.Listener) { listeners.remove(listener) } override fun getPlayerError(): PlaybackException? { return customizedPlaybackException } private inner class ErrorCustomizationListener : Player.Listener { override fun onPlayerErrorChanged(error: PlaybackException?) { customizedPlaybackException = error?.let { customizePlaybackException(it) } listeners.forEach { it.onPlayerErrorChanged(customizedPlaybackException) } } override fun onPlayerError(error: PlaybackException) { listeners.forEach { it.onPlayerError(customizedPlaybackException!!) } } private fun customizePlaybackException( error: PlaybackException, ): PlaybackException { val buttonLabel: String val errorMessage: String when (error.errorCode) { PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> { buttonLabel = context.getString(R.string.err_button_label_restart_stream) errorMessage = context.getString(R.string.err_msg_behind_live_window) } // Apps can customize further error messages by adding more branches. else -> { buttonLabel = context.getString(R.string.err_button_label_ok) errorMessage = context.getString(R.string.err_message_default) } } val extras = Bundle() extras.putString("button_label", buttonLabel) return PlaybackException(errorMessage, error.cause, error.errorCode, extras) } override fun onEvents(player: Player, events: Player.Events) { listeners.forEach { it.onEvents(player, events) } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
Java
private static class ErrorForwardingPlayer extends ForwardingPlayer { private final Context context; private List<Player.Listener> listeners; @Nullable private PlaybackException customizedPlaybackException; public ErrorForwardingPlayer(Context context, Player player) { super(player); this.context = context; listeners = new ArrayList<>(); player.addListener(new ErrorCustomizationListener()); } @Override public void addListener(Player.Listener listener) { listeners.add(listener); } @Override public void removeListener(Player.Listener listener) { listeners.remove(listener); } @Nullable @Override public PlaybackException getPlayerError() { return customizedPlaybackException; } private class ErrorCustomizationListener implements Listener { @Override public void onPlayerErrorChanged(@Nullable PlaybackException error) { customizedPlaybackException = error != null ? customizePlaybackException(error, context) : null; for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerErrorChanged(customizedPlaybackException); } } @Override public void onPlayerError(PlaybackException error) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerError(checkNotNull(customizedPlaybackException)); } } private PlaybackException customizePlaybackException( PlaybackException error, Context context) { String buttonLabel; String errorMessage; switch (error.errorCode) { case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW: buttonLabel = context.getString(R.string.err_button_label_restart_stream); errorMessage = context.getString(R.string.err_msg_behind_live_window); break; // Apps can customize further error messages by adding more case statements. default: buttonLabel = context.getString(R.string.err_button_label_ok); errorMessage = context.getString(R.string.err_message_default); break; } Bundle extras = new Bundle(); extras.putString("button_label", buttonLabel); return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras); } @Override public void onEvents(Player player, Events events) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onEvents(player, events); } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
Lỗi không nghiêm trọng
Ứng dụng có thể gửi các lỗi không nghiêm trọng không bắt nguồn từ một trường hợp ngoại lệ về kỹ thuật cho tất cả hoặc cho một đơn vị kiểm soát cụ thể:
Kotlin
val sessionError = SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired), ) // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError) // Interoperability: Sending a nonfatal error to the media notification controller to set the // error code and error message in the playback state of the platform media session. mediaSession.mediaNotificationControllerInfo?.let { mediaSession.sendError(it, sessionError) }
Java
SessionError sessionError = new SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired)); // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError); // Interoperability: Sending a nonfatal error to the media notification controller to set the // error code and error message in the playback state of the platform media session. ControllerInfo mediaNotificationControllerInfo = mediaSession.getMediaNotificationControllerInfo(); if (mediaNotificationControllerInfo != null) { mediaSession.sendError(mediaNotificationControllerInfo, sessionError); }
Lỗi không nghiêm trọng được gửi đến trình điều khiển thông báo đa phương tiện sẽ được sao chép vào PlaybackStateCompat
của phiên nền tảng. Do đó, chỉ có mã lỗi và thông báo lỗi được đặt thành PlaybackStateCompat
tương ứng, còn PlaybackStateCompat.state
không thay đổi thành STATE_ERROR
.
Nhận lỗi không nghiêm trọng
MediaController
nhận được lỗi không nghiêm trọng bằng cách triển khai MediaController.Listener.onError
:
Kotlin
val future = MediaController.Builder(context, sessionToken) .setListener(object : MediaController.Listener { override fun onError(controller: MediaController, sessionError: SessionError) { // Handle nonfatal error. } }) .buildAsync()
Java
MediaController.Builder future = new MediaController.Builder(context, sessionToken) .setListener( new MediaController.Listener() { @Override public void onError(MediaController controller, SessionError sessionError) { // Handle nonfatal error. } });