맞춤설정

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를 삽입할 수 있습니다.

비동기 버퍼 큐 사용 설정

비동기 버퍼 큐잉은 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()

자바

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

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

ForwardingSimpleBasePlayer를 사용하여 작업 맞춤설정

Player 인스턴스를 ForwardingSimpleBasePlayer의 서브클래스로 래핑하여 일부 동작을 맞춤설정할 수 있습니다. 이 클래스를 사용하면 Player 메서드를 직접 구현하지 않고도 특정 '작업'을 가로챌 수 있습니다. 이렇게 하면 예를 들어 play(), pause(), setPlayWhenReady(boolean)의 동작이 일관됩니다. 또한 모든 상태 변경사항이 등록된 Player.Listener 인스턴스에 올바르게 전파됩니다. 대부분의 맞춤설정 사용 사례에서는 이러한 일관성 보장으로 인해 오류가 더 발생하기 쉬운 ForwardingPlayer보다 ForwardingSimpleBasePlayer를 사용하는 것이 좋습니다.

예를 들어 재생이 시작되거나 중지될 때 맞춤 로직을 추가하려면 다음을 실행합니다.

Kotlin

class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady)
  }
}

자바

class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer {

  public PlayerWithCustomPlay(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady);
  }
}

또는 SEEK_TO_NEXT 명령어를 허용하지 않고 Player.seekToNext가 무작위 작업이 되도록 하려면 다음을 실행합니다.

Kotlin

class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun getState(): State {
    val state = super.getState()
    return state
      .buildUpon()
      .setAvailableCommands(
        state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()
      )
      .build()
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

자바

class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer {

  public PlayerWithoutSeekToNext(Player player) {
    super(player);
  }

  @Override
  protected State getState() {
    State state = super.getState();
    return state
        .buildUpon()
        .setAvailableCommands(
            state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build())
        .build();
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

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

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

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