라이브 스트리밍

ExoPlayer는 특별한 구성 없이 대부분의 적응형 실시간 스트림을 즉시 재생합니다. 자세한 내용은 지원되는 형식 페이지를 참고하세요.

적응형 실시간 스트림은 현재 실시간과 함께 이동할 수 있도록 일정한 간격으로 업데이트되는 사용 가능한 미디어 창을 제공합니다. 즉, 재생 위치는 항상 이 창의 어딘가에 있으며 대부분의 경우 스트림이 생성되고 있는 현재 실시간에 가깝습니다. 현재 실시간과 재생 위치 간의 차이를 실시간 오프셋이라고 합니다.

실시간 재생 감지 및 모니터링

실시간 기간이 업데이트될 때마다 등록된 Player.Listener 인스턴스가 onTimelineChanged 이벤트를 수신합니다. 아래 나열되고 다음 그림과 같이 다양한 PlayerTimeline.Window 메서드를 쿼리하여 현재 실시간 재생에 관한 세부정보를 검색할 수 있습니다.

실시간 창

  • Player.isCurrentWindowLive는 현재 재생 중인 미디어 항목이 실시간 스트림인지를 나타냅니다. 이 값은 실시간 스트림이 종료된 경우에도 여전히 true입니다.
  • Player.isCurrentWindowDynamic는 현재 재생 중인 미디어 항목이 아직 업데이트 중인지를 나타냅니다. 일반적으로 아직 종료되지 않은 라이브 스트림에 해당됩니다. 경우에 따라 실시간이 아닌 스트림에도 이 플래그가 적용됩니다.
  • Player.getCurrentLiveOffset은 현재 실시간과 재생 위치 사이의 오프셋을 반환합니다 (있는 경우).
  • Player.getDuration는 현재 실시간 창의 길이를 반환합니다.
  • Player.getCurrentPosition는 라이브 창의 시작을 기준으로 한 재생 위치를 반환합니다.
  • Player.getCurrentMediaItem는 현재 미디어 항목을 반환합니다. 여기서 MediaItem.liveConfiguration에는 타겟 실시간 오프셋과 실시간 오프셋 조정 매개변수에 관한 앱에서 제공하는 재정의가 포함되어 있습니다.
  • Player.getCurrentTimelineTimeline의 현재 미디어 구조를 반환합니다. 현재 Timeline.WindowPlayer.getCurrentWindowIndexTimeline.getWindow를 사용하여 Timeline에서 가져올 수 있습니다. Window에서 다음을 수행합니다.
    • Window.liveConfiguration에는 타겟 실시간 오프셋과 실시간 오프셋 조정 매개변수가 포함됩니다. 이러한 값은 미디어의 정보와 MediaItem.liveConfiguration에 설정된 앱 제공 재정의를 기반으로 합니다.
    • Window.windowStartTimeMs은 라이브 창이 시작되는 Unix 에포크 이후 시간입니다.
    • Window.getCurrentUnixTimeMs은 현재 실시간의 Unix 에포크 이후 시간입니다. 이 값은 서버와 클라이언트 간의 알려진 시계 차이로 수정할 수도 있습니다.
    • Window.getDefaultPositionMs은 라이브 창에서 플레이어가 기본적으로 재생을 시작하는 위치입니다.

라이브 스트림에서 탐색

Player.seekTo를 사용하여 라이브 창 내 어디로든 탐색할 수 있습니다. 전달된 탐색 위치는 라이브 창의 시작을 기준으로 합니다. 예를 들어 seekTo(0)는 라이브 창의 시작을 탐색합니다. 플레이어는 탐색 후 탐색한 위치와 동일한 실시간 오프셋을 유지하려고 합니다.

라이브 창에는 재생이 시작되어야 하는 기본 위치도 있습니다. 이 위치는 일반적으로 라이브 가장자리에 가까운 위치입니다. Player.seekToDefaultPosition를 호출하여 기본 위치를 탐색할 수 있습니다.

실시간 재생 UI

ExoPlayer의 기본 UI 구성요소는 실시간 창의 지속 시간과 창 내의 현재 재생 위치를 표시합니다. 즉, 라이브 창이 업데이트될 때마다 위치가 뒤로 이동하는 것처럼 보입니다. 다른 동작이 필요한 경우(예: Unix 시간이나 현재 라이브 오프셋을 표시해야 함) PlayerControlView를 포크하여 필요에 맞게 수정하면 됩니다.

실시간 재생 매개변수 구성

ExoPlayer는 일부 매개변수를 사용하여 실시간 가장자리로부터 재생 위치의 오프셋과 이 오프셋을 조정하는 데 사용할 수 있는 재생 속도 범위를 제어합니다.

ExoPlayer는 우선순위의 내림차순으로 세 위치에서 이러한 매개변수 값을 가져옵니다 (처음 발견된 값이 사용됨).

  • MediaItem당 값이 MediaItem.Builder.setLiveConfiguration에 전달됩니다.
  • DefaultMediaSourceFactory에 전역 기본값이 설정되었습니다.
  • 미디어에서 직접 값을 읽습니다.

Kotlin

// Global settings.
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context).setLiveTargetOffsetMs(5000))
    .build()

// Per MediaItem settings.
val mediaItem =
  MediaItem.Builder()
    .setUri(mediaUri)
    .setLiveConfiguration(
      MediaItem.LiveConfiguration.Builder().setMaxPlaybackSpeed(1.02f).build()
    )
    .build()
player.setMediaItem(mediaItem)

Java

// Global settings.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setLiveTargetOffsetMs(5000))
        .build();

// Per MediaItem settings.
MediaItem mediaItem =
    new MediaItem.Builder()
        .setUri(mediaUri)
        .setLiveConfiguration(
            new MediaItem.LiveConfiguration.Builder().setMaxPlaybackSpeed(1.02f).build())
        .build();
player.setMediaItem(mediaItem);

사용 가능한 구성 값은 다음과 같습니다.

  • targetOffsetMs: 타겟 실시간 오프셋입니다. 플레이어는 재생 중에 가능한 경우 이 실시간 오프셋에 근접하려고 시도합니다.
  • minOffsetMs: 허용되는 최소 실시간 오프셋입니다. 오프셋을 현재 네트워크 조건으로 조정하더라도 플레이어는 재생 중에 이 오프셋을 넘지 않도록 시도하지 않습니다.
  • maxOffsetMs: 허용되는 최대 실시간 오프셋입니다. 오프셋을 현재 네트워크 조건으로 조정하더라도 플레이어는 재생 중에 이 오프셋을 초과하려고 하지 않습니다.
  • minPlaybackSpeed: 플레이어가 타겟 실시간 오프셋에 도달하려고 할 때 대체하기 위해 사용할 수 있는 최소 재생 속도입니다.
  • maxPlaybackSpeed: 플레이어가 타겟 실시간 오프셋에 도달하려고 할 때 따라잡기 위해 사용할 수 있는 최대 재생 속도입니다.

재생 속도 조정

지연 시간이 짧은 실시간 스트림을 재생할 때 ExoPlayer는 재생 속도를 약간 변경하여 실시간 오프셋을 조정합니다. 플레이어는 미디어나 앱에서 제공하는 타겟 실시간 오프셋을 일치시키려고 시도하지만 네트워크 조건의 변화에도 반응하려고 합니다. 예를 들어 재생 중에 리버퍼링이 발생하면 플레이어는 실시간 에지에서 멀어지도록 재생 속도를 약간 늦춥니다. 그런 다음 네트워크가 다시 실시간 가장자리에 가깝게 재생되는 것을 지원할 수 있을 만큼 안정되면 플레이어는 재생 속도를 높여 타겟 실시간 오프셋으로 다시 이동합니다.

자동 재생 속도 조정을 사용하지 않으려면 minPlaybackSpeedmaxPlaybackSpeed 속성을 1.0f로 설정하여 사용 중지할 수 있습니다. 마찬가지로 지연 시간이 짧지 않은 실시간 스트림에도 명시적으로 1.0f 이외의 값으로 설정하여 사용 설정할 수 있습니다. 이러한 속성을 설정하는 방법에 관한 자세한 내용은 위의 구성 섹션을 참고하세요.

재생 속도 조정 알고리즘 맞춤설정

속도 조정이 사용 설정되면 LivePlaybackSpeedControl는 조정을 정의합니다. 맞춤 LivePlaybackSpeedControl를 구현하거나 기본 구현(DefaultLivePlaybackSpeedControl)을 맞춤설정할 수 있습니다. 두 경우 모두 플레이어를 빌드할 때 인스턴스를 설정할 수 있습니다.

Kotlin

val player =
  ExoPlayer.Builder(context)
    .setLivePlaybackSpeedControl(
      DefaultLivePlaybackSpeedControl.Builder().setFallbackMaxPlaybackSpeed(1.04f).build()
    )
    .build()

Java

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setLivePlaybackSpeedControl(
            new DefaultLivePlaybackSpeedControl.Builder()
                .setFallbackMaxPlaybackSpeed(1.04f)
                .build())
        .build();

DefaultLivePlaybackSpeedControl의 관련 맞춤설정 매개변수는 다음과 같습니다.

  • fallbackMinPlaybackSpeedfallbackMaxPlaybackSpeed: 미디어와 앱에서 제공한 MediaItem 중 어느 쪽도 제한을 정의하지 않는 경우 조정에 사용할 수 있는 최소 및 최대 재생 속도입니다.
  • proportionalControlFactor: 속도 조정의 매끄러운 정도를 제어합니다. 값이 클수록 더욱 갑작스럽고 반응성이 높아지지만 소리가 잘 들리기도 합니다. 값이 작을수록 속도 간에 더 원활하게 전환되지만 속도는 느려집니다.
  • targetLiveOffsetIncrementOnRebufferMs: 이 값은 더 조심스럽게 진행하기 위해 리버퍼링이 발생할 때마다 타겟 실시간 오프셋에 추가됩니다. 이 기능은 값을 0으로 설정하여 사용 중지할 수 있습니다.
  • minPossibleLiveOffsetSmoothingFactor: 현재 버퍼링된 미디어에 기반하여 가능한 최소 실시간 오프셋을 추적하는 데 사용되는 지수 평활 계수입니다. 값이 1에 매우 가까우면 추정이 더 주의하고 개선된 네트워크 조건에 적응하는 데 시간이 더 오래 걸릴 수 있음을 의미합니다. 반면 값이 작을수록 추정이 더 빠르게 조정되어 리버퍼에 잠길 위험이 커집니다.

BehindLiveWindowException 및 ERROR_CODE_BEHIND_LIVE_WINDOW

예를 들어 플레이어가 일시중지되거나 충분히 버퍼링된 경우 재생 위치가 실시간 창보다 뒤처질 수 있습니다. 그러면 재생이 실패하고 오류 코드 ERROR_CODE_BEHIND_LIVE_WINDOW의 예외가 Player.Listener.onPlayerError를 통해 보고됩니다. 애플리케이션 코드는 기본 위치에서 재생을 재개하여 이러한 오류를 처리하는 것이 좋습니다. 데모 앱의 PlayerActivity에서 이 방식을 잘 확인할 수 있습니다.

Kotlin

override fun onPlayerError(error: PlaybackException) {
  if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
    // Re-initialize player at the live edge.
    player.seekToDefaultPosition()
    player.prepare()
  } else {
    // Handle other errors
  }
}

Java

@Override
public void onPlayerError(PlaybackException error) {
  if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
    // Re-initialize player at the live edge.
    player.seekToDefaultPosition();
    player.prepare();
  } else {
    // Handle other errors
  }
}