MediaSession을 사용하여 재생 제어 및 광고

오디오 또는 동영상과 상호작용하는 보편적인 방법을 제공하는 미디어 세션 있습니다. Media3에서 기본 플레이어는 ExoPlayer 클래스이며 Player 인터페이스 미디어 세션을 플레이어에 연결하면 앱이 허용됨 외부 IP 주소로부터 미디어 재생을 광고하고 외부 소스.

명령은 컴퓨터의 재생 버튼과 같은 실제 버튼에서 시작될 수 있습니다. 헤드셋이나 TV 리모컨을 사용하지 마세요. 또한 미디어 컨트롤러(예: '일시중지' 명령) Google 어시스턴트에 초대합니다. 미디어 세션은 미디어 앱의 플레이어에 이러한 명령어를 위임합니다.

미디어 세션을 선택해야 하는 경우

MediaSession를 구현하면 사용자가 재생을 제어할 수 있습니다.

  • 헤드폰을 통해 화면이나 링크에서 버튼이나 터치 상호작용이 사용자는 헤드폰에서 수행하여 미디어를 재생 또는 일시중지하거나 다음 또는 이전 트랙으로 이동합니다.
  • Google 어시스턴트와 대화 일반적인 패턴은 다음과 같이 말합니다. "OK Google, 일시중지'를 눌러 현재 기기에서 재생 중인 미디어를 모두 일시중지합니다.
  • Wear OS 시계를 통해 이렇게 하면 가장 일반적인 일반적인 재생 컨트롤이 있습니다.
  • 미디어 컨트롤을 통해: 이 캐러셀은 각 항목의 컨트롤을 표시합니다. 미디어 세션을 실행합니다.
  • TV 실제 재생 버튼, 플랫폼 재생으로 작업을 허용합니다. 제어 및 전원 관리 (예: TV, 사운드바 또는 A/V 수신기)를 꺼져 있거나 입력이 전환되면 앱에서 재생이 중지되어야 합니다.
  • 재생에 영향을 주어야 하는 다른 모든 외부 프로세스.

많은 사용 사례에 매우 유용합니다. 특히 다음과 같은 특성을 고려해야 합니다. 다음의 경우 MediaSession 사용:

  • 영화 또는 라이브 TV와 같은 긴 형식의 동영상 콘텐츠를 스트리밍하고 있습니다.
  • 팟캐스트 또는 음악과 같은 긴 형식의 오디오 콘텐츠를 스트리밍하고 있습니다. 있습니다.
  • TV 앱을 빌드하고 있습니다.

그러나 모든 사용 사례가 MediaSession에 잘 맞는 것은 아닙니다. 다음과 같은 작업을 할 수 있습니다. 다음과 같은 경우에는 Player만 사용하세요.

  • 사용자 참여 및 상호작용이 있는 짧은 형식 콘텐츠를 표시합니다. 매우 중요합니다
  • 사용자가 목록을 스크롤하는 등 활성 동영상이 하나도 없습니다. 여러 동영상이 동시에 화면에 표시됩니다.
  • 일회성 소개 또는 설명 동영상을 사용자가 적극적으로 시청할 것이라고 예상할 수 있습니다.
  • 콘텐츠가 개인 정보 보호에 민감하며 외부 절차의 영향을 받고 싶지 않은 경우 미디어 메타데이터 액세스 (예: 브라우저의 시크릿 모드)

사용 사례가 위에 나열된 사례에 해당하지 않는 경우 사용자가 적극적으로 참여하지 않더라도 앱이 계속 재생해도 괜찮습니다. 추가할 수 있습니다. 답이 '예'라면 MediaSession 그렇지 않다면 Player 하세요.

미디어 세션 만들기

미디어 세션은 관리하는 플레이어와 함께 움직입니다. 사용자는 ContextPlayer 객체가 있는 미디어 세션 가상 머신 인스턴스를 만들고 onStart() 또는 Activity, Fragment 또는 onCreate()onResume() 수명 주기 메서드 미디어 세션 및 관련 플레이어를 소유하는 Service의 메서드입니다.

미디어 세션을 만들려면 Player를 초기화하고 다음에 제공합니다. MediaSession.Builder에 다음과 같이 표시됩니다.

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

자바

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

자동 상태 처리

Media3 라이브러리는 반환합니다. 따라서 있습니다.

이는 컨테이너 이미지를 만들고 유지관리해야 했던 기존 접근 방식에서 벗어나 플레이어 자체와는 별개로 PlaybackStateCompat를 사용해야 합니다. 예를 들면 다음과 같습니다. 오류가 있는지 확인하세요.

고유 세션 ID

기본적으로 MediaSession.Builder는 세션 ID입니다. 이는 앱에서 단일 호출만 생성하려는 경우에 충분합니다. 세션 인스턴스를 사용할 수 있는데, 이것이 가장 일반적인 사례입니다.

앱이 여러 세션 인스턴스를 동시에 관리하려는 경우 각 세션의 세션 ID가 고유해야 합니다. 세션 ID는 MediaSession.Builder.setId(String id)로 세션을 빌드할 때 설정해야 합니다.

오류로 인해 앱이 비정상 종료되는 IllegalStateException가 표시되는 경우 IllegalStateException: Session ID must be unique. ID=에 메시지를 보냅니다. 이전에 생성되기 전에 예기치 않게 생성되었을 가능성이 높음 동일한 ID의 인스턴스가 해제된 경우 특정 IP 주소에 의해 세션이 유출되는 것을 방지하기 위해 이러한 경우는 탐지 및 알림 전송을 통해 예외가 인정됩니다.

다른 클라이언트에 제어 권한 부여

미디어 세션은 재생 제어의 핵심입니다. 이를 사용하면 플레이어에 전송하는 명령어를 있습니다. 이러한 소스는 기기의 재생 버튼과 같은 물리적 버튼일 수 있고 헤드셋이나 TV 리모컨, 간접 명령(예: "일시중지"라고 지시) Google 어시스턴트에 초대합니다. 마찬가지로 Android 기기에 액세스 권한을 부여할 수 있습니다. Wear OS에 알림 및 잠금 화면 제어를 용이하게 하는 시스템 시계 모드에서 재생을 제어할 수 있습니다. 외부 고객은 미디어 컨트롤러를 사용하여 미디어 앱에 재생 명령을 실행합니다. 이 두 가지는 미디어 세션에서 수신되어 궁극적으로는 표시됩니다.

<ph type="x-smartling-placeholder">
</ph> MediaSession과 MediaController 간의 상호작용을 보여주는 다이어그램
그림 1: 미디어 컨트롤러는 명령어를 외부 소스에서 미디어 세션에 전송합니다.

컨트롤러가 미디어 세션에 연결하려고 하면 onConnect() 메서드가 호출됩니다. 제공된 ControllerInfo 동의 여부를 또는 거부 요청을 처리합니다 연결 요청을 수락하는 예는 Declare 사용 가능한 명령어 섹션을 참조하세요.

연결 후 컨트롤러는 세션으로 재생 명령어를 전송할 수 있습니다. 이 세션에서 이러한 명령을 플레이어에게 위임합니다. 재생 및 재생목록 Player 인터페이스에 정의된 명령어는 세션입니다.

다른 콜백 메서드를 사용하면 예를 들어 맞춤 재생 명령재생목록 수정) 이러한 콜백은 마찬가지로 ControllerInfo 객체를 포함하므로 각 요청에 어떻게 응답하는지를 정의하게 됩니다.

재생목록 수정

미디어 세션은 다음에 설명된 대로 플레이어의 재생목록을 직접 수정할 수 있습니다. 재생목록용 ExoPlayer 가이드 컨트롤러는 다음 중 하나에 해당하는 경우 재생목록을 수정할 수도 있습니다. COMMAND_SET_MEDIA_ITEM 또는 COMMAND_CHANGE_MEDIA_ITEMS 사용 가능합니다.

재생목록에 새 항목을 추가할 때 플레이어에는 일반적으로 MediaItem이 필요합니다. 인코더-디코더에 정의된 URI 만들 수 있습니다. 기본적으로 새로 추가된 항목은 자동으로 전달됩니다. player.addMediaItem 같은 플레이어 메서드에 전달할 수 있습니다.

플레이어에 추가된 MediaItem 인스턴스를 맞춤설정하려면 다음 안내를 따르세요. 재정의하다 onAddMediaItems() 이 단계는 미디어를 요청하는 컨트롤러를 지원하려는 경우에 필요합니다. 할 수 있습니다. 대신 MediaItem는 일반적으로 다음과 같습니다. 다음 필드 중 하나 이상이 요청된 미디어를 설명하도록 설정됩니다.

  • MediaItem.id: 미디어를 식별하는 일반 ID입니다.
  • MediaItem.RequestMetadata.mediaUri: 커스텀 플레이어가 직접 플레이할 수 있는 것은 아닙니다.
  • MediaItem.RequestMetadata.searchQuery: 텍스트 검색어입니다. 예: Google 어시스턴트
  • MediaItem.MediaMetadata: 구조화된 메타데이터(예: 'title') '아티스트'로 표현할 수 있습니다.

완전히 새로운 재생목록을 위한 더 많은 맞춤설정 옵션을 사용하려면 추가로 재정의 onSetMediaItems() 재생목록의 시작 항목과 위치를 정의할 수 있습니다. 예를 들어 요청한 단일 항목을 전체 재생목록으로 확장하고 원래 요청된 항목의 색인에서 시작합니다. 가 onSetMediaItems()의 샘플 구현 세션 데모 앱에서 확인할 수 있습니다

맞춤 레이아웃 및 맞춤 명령 관리

다음 섹션에서는 맞춤 레이아웃의 맞춤 레이아웃을 홍보하는 방법을 설명합니다. 명령 버튼을 클라이언트 앱에 보내고 컨트롤러가 커스텀 명령어와 함께 사용하면 됩니다

세션의 맞춤 레이아웃 정의

어떤 재생 컨트롤을 세션의 맞춤 레이아웃을 설정합니다. 앱의 onCreate() 메서드에서 MediaSession를 빌드할 때 있습니다.

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

자바

@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();
}

사용 가능한 플레이어 및 맞춤 명령어 선언

미디어 애플리케이션은 커스텀 명령어를 정의할 수 있는데, 예를 들어 미디어 애플리케이션은 맞춤 레이아웃을 만들 수 있습니다. 예를 들어 사용자가 즐겨찾는 항목 목록에 미디어 항목을 저장할 수 있습니다. MediaController 맞춤 명령어를 전송하고 MediaSession.Callback가 이를 수신합니다.

GCP 콘솔에서 사용할 수 있는 커스텀 세션 명령어를 미디어 세션에 연결되면 MediaController입니다. 다음을 통해 이를 달성합니다. MediaSession.Callback.onConnect()를 재정의합니다. 구성 및 반환 서버로부터 연결 요청을 수락할 때 사용 가능한 명령 집합을 onConnect 콜백 메서드에서 MediaController를 호출합니다.

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

자바

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();
  }
}

MediaController에서 커스텀 명령어 요청을 수신하려면 다음을 재정의합니다. CallbackonCustomCommand() 메서드

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

자바

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)
      );
    }
    ...
  }
}

어떤 미디어 컨트롤러가 요청을 생성하는지 추적하려면 MediaSession.ControllerInfo 객체의 packageName 속성 Callback 메서드에 전달됩니다. 이렇게 하면 앱에 가장 적합한 특정 명령에 대한 응답으로, 시스템에서 비롯된 경우, 자체 앱 또는 기타 클라이언트 앱이 포함됩니다.

사용자 상호작용 후 맞춤 레이아웃 업데이트

사용자 지정 명령이나 플레이어와의 기타 상호작용을 처리한 후에는 컨트롤러 UI에 표시된 레이아웃을 업데이트하는 것이 좋습니다. 일반적인 예 연결된 작업을 트리거한 후 아이콘을 변경하는 전환 버튼입니다. 이 버튼을 누르세요. 레이아웃을 업데이트하려면 다음을 사용하면 됩니다. 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))

자바

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));

재생 명령어 동작 맞춤설정

다음과 같이 Player 인터페이스에 정의된 명령어의 동작을 맞춤설정합니다. play() 또는 seekToNext()PlayerForwardingPlayer에 래핑합니다.

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()

자바

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();

ForwardingPlayer에 관한 자세한 내용은 ExoPlayer 가이드를 참고하세요. 맞춤설정.

플레이어 명령어의 요청 컨트롤러 식별

Player 메서드 호출이 MediaController에서 시작된 경우 다음을 할 수 있습니다. MediaSession.controllerForCurrentRequest로 출처 소스 식별 현재 요청의 ControllerInfo를 가져옵니다.

Kotlin

class CallerAwareForwardingPlayer(player: Player) :
  ForwardingPlayer(player) {

  override fun seekToNext() {
    Log.d(
      "caller",
      "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}"
    )
    super.seekToNext()
  }
}

자바

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();
  }
}

미디어 버튼에 응답

미디어 버튼은 Android 기기 및 기타 주변기기에서 볼 수 있는 하드웨어 버튼입니다. (예: 블루투스 헤드셋의 재생/일시중지 버튼) Media3 핸들 사용자가 세션에 도착하여 세션 플레이어에서 적절한 Player 메서드를 호출합니다.

앱은 기본 동작을 재정의하여 MediaSession.Callback.onMediaButtonEvent(Intent) 이 경우 앱은 모든 API 사양을 자체적으로 처리할 수 있어야 합니다

오류 처리 및 보고

세션에서 발생되어 컨트롤러에 보고하는 두 가지 유형의 오류가 있습니다. 심각한 오류는 세션의 기술적 재생 실패를 보고합니다. 재생을 방해하는 플레이어입니다. 치명적인 오류가 컨트롤러에 보고됨 자동으로 백업됩니다. 치명적이지 않은 오류는 비기술적 오류 또는 정책적 오류입니다. 오류를 발생시키지 않으며, 현재 재생 중인 디스플레이 네트워크에서 애플리케이션을 수동으로 실행합니다.

치명적인 재생 오류

플레이어가 치명적인 재생 오류를 세션에 보고한 다음 컨트롤러에 보고되어 Player.Listener.onPlayerError(PlaybackException)Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)입니다.

이 경우 재생 상태가 STATE_IDLE로 전환되고 MediaController.getPlaybackError()는 다음을 발생시킨 PlaybackException를 반환합니다. 확인할 수 있습니다. 컨트롤러는 PlayerException.errorCode를 검사하여 오류 원인에 대한 정보

상호 운용성을 위해 치명적인 오류는 PlaybackStateCompat 상태를 STATE_ERROR로 전환하고 플랫폼 세션의 PlaybackException에 따른 오류 코드 및 메시지를 표시합니다.

치명적인 오류 맞춤설정

오류 코드 치명적인 재생 오류에 대한 추가 오류 메시지는 세션을 빌드할 때 ForwardingPlayer를 사용합니다.

Kotlin

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

자바

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

전달 플레이어가 Player.Listener를 실제 플레이어에 등록합니다. 오류를 보고하는 콜백을 가로챕니다. 맞춤형 그러면 PlaybackException가 전달 플레이어에 등록됩니다. 이 기능을 사용하려면 전달 플레이어가 Player.addListenerPlayer.removeListener를 재정의하여 맞춤설정된 오류 코드, 메시지 또는 추가 항목을 전송할 리스너:

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

자바

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

심각하지 않은 오류

기술적 예외로 인해 발생하지 않은 치명적이지 않은 오류는 전송할 수 있습니다. 전체 또는 특정 컨트롤러에 전송:

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

자바

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

미디어 알림 컨트롤러로 전송된 치명적이지 않은 오류는 플랫폼 세션의 PlaybackStateCompat입니다. 이에 따라 오류 코드와 오류 메시지는 적절하게 PlaybackStateCompat로 설정되지만, PlaybackStateCompat.stateSTATE_ERROR로 변경되지 않습니다.

심각하지 않은 오류 수신

MediaController는 다음을 구현하여 치명적이지 않은 오류를 수신합니다. 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.
              }
            });