맞춤설정

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를 수정하는 등의 작업을 할 수 있습니다. 예를 들어 앱은 미디어 세그먼트를 요청할 때 토큰을 헤더로 삽입하여 자체 인증할 수 있습니다.

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

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

자바

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

자바

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

자바

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

자바

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

자바

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)

자바

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

그런 다음 위에서 설명한 대로 DefaultMediaSourceFactory를 통해 ExtractorsFactory를 삽입할 수 있습니다.

비동기 버퍼 큐 사용 설정

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

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

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

Kotlin

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

자바

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

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

ForwardingPlayer로 메서드 호출 가로채기

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

  • 매개변수를 위임받은 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))

자바

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 구현이 포함되어 있습니다. 자체 DataSource 클래스를 구현하여 다른 방식으로(예: 커스텀 프로토콜을 통해, 커스텀 HTTP 스택을 사용하거나, 커스텀 영구 캐시에서) 데이터를 로드할 수 있습니다.

맞춤 구성요소를 빌드할 때는 다음을 권장합니다.

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