ExoPlayer 라이브러리의 핵심은 Player 인터페이스입니다. Player는 미디어 버퍼링, 재생, 일시중지, 탐색 기능과 같은 기존의 고급 미디어 플레이어 기능을 노출합니다. 기본 구현 ExoPlayer는 재생 중인 미디어 유형, 저장 방법 및 위치, 렌더링 방법에 관해 거의 가정하지 않도록 (따라서 거의 제한하지 않도록) 설계되었습니다. ExoPlayer 구현은 미디어의 로드 및 렌더링을 직접 구현하는 대신 플레이어가 생성되거나 새 미디어 소스가 플레이어에 전달될 때 삽입되는 구성요소에 이 작업을 위임합니다.
모든 ExoPlayer 구현에 공통적인 구성요소는 다음과 같습니다.
- 재생할 미디어를 정의하고 미디어를 로드하며 로드된 미디어를 읽을 수 있는
MediaSource인스턴스입니다.MediaSource인스턴스는 플레이어 내부의MediaSource.Factory에 의해MediaItem에서 생성됩니다. 미디어 소스 기반 재생목록 API를 사용하여 플레이어에 직접 전달할 수도 있습니다. MediaItem를MediaSource로 변환하는MediaSource.Factory인스턴스입니다.MediaSource.Factory는 플레이어가 생성될 때 삽입됩니다.- 미디어의 개별 구성요소를 렌더링하는
Renderer인스턴스입니다. 이러한 종속 항목은 플레이어가 생성될 때 삽입됩니다. MediaSource에서 제공하는 트랙을 선택하여 사용 가능한 각Renderer에서 사용할 수 있도록 하는TrackSelector입니다. 플레이어가 생성되면TrackSelector가 삽입됩니다.MediaSource가 더 많은 미디어를 버퍼링하는 시점과 버퍼링되는 미디어의 양을 제어하는LoadControlLoadControl는 플레이어가 생성될 때 삽입됩니다.- 플레이어가 구성된 실시간 오프셋에 가깝게 유지할 수 있도록 실시간 재생 중에 재생 속도를 제어하는
LivePlaybackSpeedControl입니다. 플레이어가 생성되면LivePlaybackSpeedControl가 삽입됩니다.
플레이어 기능의 일부를 구현하는 구성요소를 삽입하는 개념은 라이브러리 전반에 걸쳐 존재합니다. 일부 구성요소의 기본 구현은 추가로 삽입된 구성요소에 작업을 위임합니다. 이를 통해 많은 하위 구성요소를 맞춤 방식으로 구성된 구현으로 개별적으로 대체할 수 있습니다.
플레이어 맞춤설정
구성요소를 삽입하여 플레이어를 맞춤설정하는 몇 가지 일반적인 예는 다음 섹션에 설명되어 있습니다.
네트워크 스택 구성
ExoPlayer에서 사용하는 네트워크 스택 맞춤설정에 관한 페이지가 있습니다.
네트워크에서 로드된 데이터 캐싱
임시 즉석 캐싱 및 미디어 다운로드 가이드를 참고하세요.
서버 상호작용 맞춤설정
일부 앱은 HTTP 요청 및 응답을 가로채려고 할 수 있습니다. 커스텀 요청 헤더를 삽입하거나, 서버의 응답 헤더를 읽거나, 요청의 URI를 수정하는 등의 작업을 수행할 수 있습니다. 예를 들어 앱은 미디어 세그먼트를 요청할 때 토큰을 헤더로 삽입하여 자체적으로 인증할 수 있습니다.
다음 예는 맞춤 DataSource.Factory를 DefaultMediaSourceFactory에 삽입하여 이러한 동작을 구현하는 방법을 보여줍니다.
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: LoadErrorHandlingPolicy.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(LoadErrorHandlingPolicy.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();
렌더러를 직접 인스턴스화하는 경우 new
DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous()를 MediaCodecVideoRenderer 및 MediaCodecAudioRenderer 생성자에 전달합니다.
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) } }
자바
public static final 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. }
자바
public static final 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를 사용하여 구성요소에 전송하여 구성 변경사항을 전달해야 합니다. 재생 스레드에서 전달할 메시지를 전송하면 플레이어에서 실행 중인 다른 작업과 함께 순서대로 실행됩니다.