라이브 스트리밍

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

적응형 라이브 스트림은 현재 실시간에 맞춰 정기적으로 업데이트되는 사용 가능한 미디어 창을 제공합니다. 즉, 재생 위치는 항상 이 창에 있으며 대부분의 경우 스트림이 생성되는 현재 실시간에 가깝습니다. 현재 실시간 위치와 재생 위치의 차이를 라이브 오프셋이라고 합니다.

라이브 재생 감지 및 모니터링

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

라이브 창

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

라이브 스트림에서 탐색하기

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

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

라이브 재생 UI

ExoPlayer의 기본 UI 구성요소에는 라이브 윈도우의 지속 시간과 그 안의 현재 재생 위치가 표시됩니다. 즉, 라이브 창이 업데이트될 때마다 위치가 뒤로 점프하는 것처럼 보입니다. 유닉스 시간이나 현재 라이브 오프셋을 표시하는 등 다른 동작이 필요한 경우 PlayerControlView를 포크하고 필요에 맞게 수정하면 됩니다.

라이브 재생 매개변수 구성

ExoPlayer는 일부 매개변수를 사용하여 라이브 에지에서 재생 위치의 오프셋과 이 오프셋을 조정하는 데 사용할 수 있는 재생 속도 범위를 제어합니다.

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

  • MediaItem.Builder.setLiveConfiguration에 전달된 MediaItem
  • 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)

자바

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

자바

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