맞춤설정

ExoPlayer 라이브러리의 핵심에는 Player 인터페이스가 있습니다. Player는 미디어 버퍼링, 재생, 일시중지, 탐색 기능과 같은 기존의 고급 미디어 플레이어 기능을 노출합니다. 기본 구현 ExoPlayer는 재생되는 미디어의 유형, 저장 방법 및 위치, 렌더링 방법에 관해 몇 가지 가정을 하지 않고 이에 관한 제한을 거의 두지 않도록 설계되었습니다. ExoPlayer 구현은 미디어의 로드 및 렌더링을 직접 구현하는 대신 플레이어가 생성되거나 새 미디어 소스가 플레이어에 전달될 때 삽입되는 구성요소에 이 작업을 위임합니다. 모든 ExoPlayer 구현에 공통으로 적용되는 구성요소는 다음과 같습니다.

  • 재생할 미디어를 정의하고, 미디어를 로드하고, 로드된 미디어를 읽을 수 있는 MediaSource 인스턴스 MediaSource 인스턴스는 플레이어 내부의 MediaSource.Factory에 의해 MediaItem에서 생성됩니다. 미디어 소스 기반 재생목록 API를 사용하여 플레이어에 직접 전달할 수도 있습니다.
  • MediaItemMediaSource로 변환하는 MediaSource.Factory 인스턴스 MediaSource.Factory는 플레이어가 생성될 때 삽입됩니다.
  • 미디어의 개별 구성요소를 렌더링하는 Renderer 인스턴스 이는 플레이어가 생성될 때 삽입됩니다.
  • 사용 가능한 각 Renderer에서 사용할 트랙을 MediaSource에서 제공하는 트랙을 선택하는 TrackSelector TrackSelector는 플레이어가 생성될 때 삽입됩니다.
  • MediaSource에서 더 많은 미디어를 버퍼링하는 시기와 버퍼링되는 미디어의 양을 제어하는 LoadControl입니다. LoadControl는 플레이어가 생성될 때 삽입됩니다.
  • 플레이어가 구성된 실시간 오프셋에 가깝게 유지될 수 있도록 실시간 재생 중 재생 속도를 제어하는 LivePlaybackSpeedControl입니다. LivePlaybackSpeedControl는 플레이어가 생성될 때 삽입됩니다.

플레이어 기능의 일부를 구현하는 구성요소 삽입이라는 개념은 라이브러리 전체에 존재합니다. 일부 구성요소의 기본 구현은 추가로 삽입된 구성요소에 작업을 위임합니다. 이렇게 하면 많은 하위 구성요소를 맞춤 방식으로 구성된 구현으로 개별적으로 대체할 수 있습니다.

플레이어 맞춤설정

구성요소를 삽입하여 플레이어를 맞춤설정하는 일반적인 예는 아래에 설명되어 있습니다.

네트워크 스택 구성

ExoPlayer에서 사용하는 네트워크 스택 맞춤설정에 관한 페이지가 있습니다.

네트워크에서 로드된 데이터 캐싱

임시 즉석 캐싱미디어 다운로드 가이드를 참고하세요.

서버 상호작용 맞춤설정

일부 앱에서는 HTTP 요청 및 응답을 가로채야 할 수 있습니다. 맞춤 요청 헤더를 삽입하거나 서버의 응답 헤더를 읽거나 요청의 URI를 수정하는 등의 작업을 할 수 있습니다. 예를 들어 앱은 미디어 세그먼트를 요청할 때 토큰을 헤더로 삽입하여 자체적으로 인증할 수 있습니다.

다음 예는 맞춤 DataSource.FactoryDefaultMediaSourceFactory에 삽입하여 이러한 동작을 구현하는 방법을 보여줍니다.

Kotlin

val dataSourceFactory =
  DataSource.Factory {
    val dataSource = httpDataSourceFactory.createDataSource()
    // Set a custom authentication request header.
    dataSource.setRequestProperty("Header", "Value")
    dataSource
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

DataSource.Factory dataSourceFactory =
    () -> {
      HttpDataSource dataSource = httpDataSourceFactory.createDataSource();
      // Set a custom authentication request header.
      dataSource.setRequestProperty("Header", "Value");
      return dataSource;
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

위의 코드 스니펫에서 삽입된 HttpDataSource는 모든 HTTP 요청에 "Header: Value" 헤더를 포함합니다. 이 동작은 HTTP 소스와의 모든 상호작용에서 수정됩니다.

보다 세부적인 접근 방식을 위해 ResolvingDataSource를 사용하여 적시 동작을 삽입할 수 있습니다. 다음 코드 스니펫은 HTTP 소스와 상호작용하기 직전에 요청 헤더를 삽입하는 방법을 보여줍니다.

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time request headers.
    dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri))
  }

Java

    DataSource.Factory dataSourceFactory =
        new ResolvingDataSource.Factory(
            httpDataSourceFactory,
            // Provide just-in-time request headers.
            dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));

다음 스니펫과 같이 ResolvingDataSource를 사용하여 URI의 적시 수정을 실행할 수도 있습니다.

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time URI resolution logic.
    dataSpec.withUri(resolveUri(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time URI resolution logic.
        dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));

오류 처리 맞춤설정

맞춤 LoadErrorHandlingPolicy를 구현하면 앱에서 ExoPlayer가 로드 오류에 반응하는 방식을 맞춤설정할 수 있습니다. 예를 들어 앱에서 여러 번 재시도하는 대신 빠르게 실패하거나 각 재시도 사이에 플레이어가 대기하는 시간을 제어하는 백오프 로직을 맞춤설정할 수 있습니다. 다음 스니펫은 맞춤 백오프 로직을 구현하는 방법을 보여줍니다.

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long {
      // Implement custom back-off logic here.
      return 0
    }
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
    )
    .build()

Java

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

LoadErrorInfo 인수에는 실패한 로드에 대한 추가 정보가 포함되어 오류 유형 또는 실패한 요청에 따라 로직을 맞춤설정할 수 있습니다.

추출기 플래그 맞춤설정

추출기 플래그를 사용하면 프로그레시브 미디어에서 개별 형식이 추출되는 방식을 맞춤설정할 수 있습니다. DefaultMediaSourceFactory에 제공된 DefaultExtractorsFactory에서 설정할 수 있습니다. 다음 예에서는 MP3 스트림의 색인 기반 탐색을 사용 설정하는 플래그를 전달합니다.

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
    .build()

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING);

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

상수 비트 전송률 탐색 사용 설정

MP3, ADTS, AMR 스트림의 경우 FLAG_ENABLE_CONSTANT_BITRATE_SEEKING 플래그와 함께 일정한 비트 전송률 가정을 사용하여 근사치 탐색을 사용 설정할 수 있습니다. 이러한 플래그는 위에서 설명한 대로 개별 DefaultExtractorsFactory.setXyzExtractorFlags 메서드를 사용하여 개별 추출기에 설정할 수 있습니다. 이를 지원하는 모든 추출기에 상수 비트 전송률 탐색을 사용 설정하려면 DefaultExtractorsFactory.setConstantBitrateSeekingEnabled를 사용하세요.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

그런 다음 위에서 추출기 플래그를 맞춤설정하기 위해 설명한 대로 DefaultMediaSourceFactory를 통해 ExtractorsFactory를 삽입할 수 있습니다.

비동기 버퍼 큐 사용 설정

비동기 버퍼 큐는 ExoPlayer의 렌더링 파이프라인을 개선한 것으로, 비동기 모드에서 MediaCodec 인스턴스를 작동하고 추가 스레드를 사용하여 데이터의 디코딩 및 렌더링을 예약합니다. 사용 설정하면 드롭된 프레임과 오디오 언더런을 줄일 수 있습니다.

비동기 버퍼 큐는 Android 12(API 수준 31) 이상을 실행하는 기기에서는 기본적으로 사용 설정되며 Android 6.0 (API 수준 23)부터 수동으로 사용 설정할 수 있습니다. 특히 DRM으로 보호되거나 높은 프레임 속도를 가진 콘텐츠를 재생할 때 드롭된 프레임 또는 오디오 언더런이 관찰되는 특정 기기에 이 기능을 사용 설정하는 것이 좋습니다.

가장 간단한 경우 다음과 같이 DefaultRenderersFactory를 플레이어에 삽입해야 합니다.

Kotlin

val renderersFactory = 
  DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing()
val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()

Java

DefaultRenderersFactory renderersFactory =
    new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();

렌더기를 직접 인스턴스화하는 경우 AsynchronousMediaCodecAdapter.FactoryMediaCodecVideoRendererMediaCodecAudioRenderer 생성자에 전달합니다.

ForwardingPlayer로 메서드 호출 가로채기

Player 인스턴스의 동작 중 일부를 맞춤설정할 수 있습니다. ForwardingPlayer의 서브클래스로 인스턴스를 래핑하고 메서드를 재정의하여 다음 작업을 할 수 있습니다.

  • 매개변수를 대리자 Player에 전달하기 전에 매개변수에 액세스합니다.
  • 반환하기 전에 대리자 Player에서 반환 값에 액세스합니다.
  • 메서드를 완전히 다시 구현합니다.

ForwardingPlayer 메서드를 재정의할 때는 특히 동일하거나 관련된 동작을 갖는 메서드를 처리할 때 구현이 자체 일관성을 유지하고 Player 인터페이스를 준수하도록 하는 것이 중요합니다. 예:

  • 모든 '재생' 작업을 재정의하려면 ForwardingPlayer.playForwardingPlayer.setPlayWhenReady를 모두 재정의해야 합니다. 호출자는 playWhenReady = true일 때 이러한 메서드의 동작이 동일할 것으로 예상하기 때문입니다.
  • 앞으로 탐색 증분을 변경하려면 맞춤설정된 증분으로 탐색 실행을 위해 ForwardingPlayer.seekForward와 올바른 맞춤설정된 증분을 호출자에게 다시 보고하기 위해 ForwardingPlayer.getSeekForwardIncrement를 모두 재정의해야 합니다.
  • 플레이어 인스턴스에서 알리는 Player.Commands를 제어하려면 Player.getAvailableCommands()Player.isCommandAvailable()를 모두 재정의해야 하며 Player.Listener.onAvailableCommandsChanged() 콜백을 수신 대기하여 기본 플레이어에서 발생하는 변경사항에 관한 알림을 받아야 합니다.

MediaSource 맞춤설정

위의 예에서는 플레이어에 전달되는 모든 MediaItem 객체를 재생하는 동안 사용할 맞춤설정된 구성요소를 삽입합니다. 세분화된 맞춤설정이 필요한 경우 맞춤설정된 구성요소를 개별 MediaSource 인스턴스에 삽입하여 플레이어에 직접 전달할 수 있습니다. 아래 예는 맞춤 DataSource.Factory, ExtractorsFactory, LoadErrorHandlingPolicy를 사용하도록 ProgressiveMediaSource를 맞춤설정하는 방법을 보여줍니다.

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
    .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
    .createMediaSource(MediaItem.fromUri(streamUri))

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
        .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
        .createMediaSource(MediaItem.fromUri(streamUri));

커스텀 구성요소 만들기

라이브러리는 일반적인 사용 사례를 위해 이 페이지 상단에 나열된 구성요소의 기본 구현을 제공합니다. ExoPlayer는 이러한 구성요소를 사용할 수 있지만 비표준 동작이 필요한 경우 맞춤 구현을 사용하도록 빌드할 수도 있습니다. 맞춤 구현의 몇 가지 사용 사례는 다음과 같습니다.

  • Renderer – 라이브러리에서 제공하는 기본 구현에서 지원하지 않는 미디어 유형을 처리하기 위해 맞춤 Renderer를 구현하는 것이 좋습니다.
  • TrackSelector: 맞춤 TrackSelector를 구현하면 앱 개발자가 MediaSource에서 노출된 트랙을 사용 가능한 각 Renderer에서 사용하기 위해 선택하는 방식을 변경할 수 있습니다.
  • LoadControl – 맞춤 LoadControl를 구현하면 앱 개발자가 플레이어의 버퍼링 정책을 변경할 수 있습니다.
  • Extractor – 현재 라이브러리에서 지원하지 않는 컨테이너 형식을 지원해야 하는 경우 맞춤 Extractor 클래스 구현을 고려해 보세요.
  • MediaSource – 맞춤 MediaSource 클래스 구현은 맞춤 방식으로 렌더기에 제공할 미디어 샘플을 가져오거나 맞춤 MediaSource 합성 동작을 구현하려는 경우에 적합할 수 있습니다.
  • MediaSource.Factory – 맞춤 MediaSource.Factory를 구현하면 애플리케이션이 MediaItem에서 MediaSource를 만드는 방식을 맞춤설정할 수 있습니다.
  • DataSource – ExoPlayer의 업스트림 패키지에는 이미 다양한 사용 사례를 위한 여러 DataSource 구현이 포함되어 있습니다. 커스텀 프로토콜, 커스텀 HTTP 스택 사용, 커스텀 영구 캐시 사용과 같은 다른 방식으로 데이터를 로드하기 위해 자체 DataSource 클래스를 구현할 수 있습니다.

맞춤 구성요소를 빌드할 때 권장사항은 다음과 같습니다.

  • 맞춤 구성요소에서 이벤트를 앱에 다시 보고해야 하는 경우 기존 ExoPlayer 구성요소와 동일한 모델을 사용하는 것이 좋습니다. 예를 들어 EventDispatcher 클래스를 사용하거나 Handler를 리스너와 함께 구성요소의 생성자에 전달합니다.
  • 맞춤 구성요소는 기존 ExoPlayer 구성요소와 동일한 모델을 사용하여 재생 중에 앱에서 재구성할 수 있도록 하는 것이 좋습니다. 이렇게 하려면 맞춤 구성요소가 PlayerMessage.Target를 구현하고 handleMessage 메서드에서 구성 변경사항을 수신해야 합니다. 애플리케이션 코드는 ExoPlayer의 createMessage 메서드를 호출하고 메시지를 구성한 후 PlayerMessage.send를 사용하여 구성요소에 이를 전송하여 구성 변경사항을 전달해야 합니다. 재생 스레드에서 전달할 메시지를 전송하면 플레이어에서 실행되는 다른 작업과 순서대로 메시지가 실행됩니다.