ExoPlayer 라이브러리의 핵심은 Player
인터페이스입니다. Player
는 미디어 버퍼링, 재생, 일시중지, 탐색 기능과 같은 기존의 고급 미디어 플레이어 기능을 노출합니다. 기본 구현 ExoPlayer
은 재생되는 미디어 유형, 저장된 방식과 위치, 렌더링 방식에 관해 가정을 거의 하지 않도록 (따라서 제한을 거의 두지 않도록) 설계되었습니다. ExoPlayer
구현은 미디어 로드 및 렌더링을 직접 구현하는 대신 플레이어가 생성되거나 새 미디어 소스가 플레이어에 전달될 때 삽입되는 구성요소에 이 작업을 위임합니다.
모든 ExoPlayer
구현에 공통적인 구성요소는 다음과 같습니다.
- 재생할 미디어를 정의하고, 미디어를 로드하며, 로드된 미디어를 읽을 수 있는
MediaSource
인스턴스MediaSource
인스턴스는 플레이어 내의MediaSource.Factory
에 의해MediaItem
에서 생성됩니다. 미디어 소스 기반 재생목록 API를 사용하여 플레이어에 직접 전달할 수도 있습니다. MediaItem
을MediaSource
로 변환하는MediaSource.Factory
인스턴스입니다.MediaSource.Factory
는 플레이어가 생성될 때 삽입됩니다.- 미디어의 개별 구성요소를 렌더링하는
Renderer
인스턴스 이러한 종속 항목은 플레이어가 생성될 때 삽입됩니다. - 사용 가능한 각
Renderer
에서 소비할MediaSource
에서 제공하는 트랙을 선택하는TrackSelector
플레이어가 생성되면TrackSelector
가 삽입됩니다. MediaSource
이 더 많은 미디어를 버퍼링하는 시점과 버퍼링되는 미디어의 양을 제어하는LoadControl
플레이어가 생성되면LoadControl
이 삽입됩니다.- 플레이어가 구성된 라이브 오프셋에 가까이 유지되도록 라이브 재생 중 재생 속도를 제어하는
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: 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();
렌더러를 직접 인스턴스화하는 경우 new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous()
를 MediaCodecVideoRenderer
및 MediaCodecAudioRenderer
생성자에 전달합니다.
ForwardingSimpleBasePlayer
로 작업 맞춤설정
ForwardingSimpleBasePlayer
의 하위 클래스로 래핑하여 Player
인스턴스의 일부 동작을 맞춤설정할 수 있습니다. 이 클래스를 사용하면 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
이 no-op인지 확인) 다음을 실행하세요.
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
를 구현하면 앱 개발자가 사용 가능한 각Renderer
에서 소비하도록MediaSource
에 의해 노출된 트랙이 선택되는 방식을 변경할 수 있습니다.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
을 사용하여 구성요소에 전송하여 구성 변경사항을 전달해야 합니다. 재생 스레드에서 전송되는 메시지는 플레이어에서 실행되는 다른 작업과 순서대로 실행됩니다.