ExoPlayer는 다음 이미지 형식을 지원합니다. 자세한 내용은 이미지 로드 라이브러리 API에 대한 지원을 제공할 수 있는 외부 라이브러리와 통합하는 방법에 대해 사용할 수 있습니다.
이미지 형식 | 지원됨 | 참고 |
---|---|---|
BMP | 예 | |
GIF | 아니요 | 추출기 지원 안 됨 |
JPEG | 예 | |
JPEG 모션 사진 | 예 | 정지 이미지 및 동영상 지원됨 |
JPEG 울트라 HDR | 예 | Android 14 및 이후 버전에서 SDR로 대체됩니다. 비 HDR 디스플레이 |
PNG | 예 | |
WebP | 예 | |
HEIF/HEIC | 예 | |
HEIC 모션 포토 | 일부만 | 정지 이미지만 지원됨* |
AVIF (기준) | 예 | Android 14 이상에서만 디코딩됨 |
* HEIC 모션 사진의 동영상 부분은 MetadataRetriever 독립형 파일로 재생됩니다.
MediaItem 사용
이미지를 재생목록의 일부로 재생하려면 이미지 URI를 사용하여 MediaItem
를 만듭니다.
플레이어에 전달합니다. MediaItem
에 imageDurationMs
가 있어야 합니다.
이미지를 표시할 시간을 지정할 수 있습니다.
Kotlin
// Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the media item to be played with the desired duration. player.setMediaItem( MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build()) // Prepare the player. player.prepare()
자바
// Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the media item to be played with the desired duration. player.setMediaItem( new MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build()); // Prepare the player. player.prepare();
모션 사진
모션 사진은 정지 이미지와 짧은 동영상을 결합한 파일입니다.
- 이미지 길이가
setImageDuration
로 정의된 경우 모션 사진은 다음과 같습니다. 선언된 기간 동안 정지 이미지로 표시됩니다. - 이미지 길이가 정의되지 않은 경우 모션 사진이 동영상으로 재생됩니다.
ProgressiveMediaSource 사용
더 많은 맞춤설정 옵션을 보려면 ProgressiveMediaSource
를 만들고
MediaItem
대신 플레이어에 직접 전달합니다.
Kotlin
// Create a data source factory. val dataSourceFactory = DefaultHttpDataSource.Factory() // Create a media item with the image URI and the desired duration. val mediaItem = MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build() // Create a progressive media source for this media item. val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(mediaItem) // Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the media source to be played. player.setMediaSource(mediaSource) // Prepare the player. player.prepare()
자바
// Create a data source factory. DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory(); // Create a media item with the image URI and the desired duration. MediaItem mediaItem = new MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build(); // Create a progressive media source for this media item. MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(mediaItem); // Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the media source to be played. player.setMediaSource(mediaSource); // Prepare the player. player.prepare();
재생 맞춤설정
ExoPlayer는 사용자의 기기에 맞게 재생 환경을 맞춤설정하는 여러 가지 방법을 파악할 수 있습니다. 예시는 맞춤설정 페이지를 참고하세요.
이미지 로드 라이브러리
이미지는 종종 외부 이미지 로드 라이브러리에서 관리됩니다. 예를 들면 다음과 같습니다. Glide 또는 코일.
이러한 라이브러리를 재생 파이프라인에 통합하려면 다음 세 단계를 수행해야 합니다.
APPLICATION_EXTERNALLY_LOADED_IMAGE
MIME 유형으로MediaItem
를 정의합니다.- 이미지 로드에서
Bitmap
를 검색하는 이미지 디코더 제공 있습니다. - 캐싱 및 미리 로드를 트리거하는 외부 로더를 제공합니다.
외부에서 로드된 이미지 MIME 유형이 있는 MediaItem
Player
에 추가된 MediaItem
는
이미지를 사용하기 위한 명시적으로 APPLICATION_EXTERNALLY_LOADED_IMAGE
MIME 유형
라이브러리 코드 경로 로드 중:
Kotlin
val mediaItem = MediaItem.Builder() .setUri(imageUri) .setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE) .build()
자바
MediaItem mediaItem = new MediaItem.Builder() .setUri(imageUri) .setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE) .build();
이미지 로드 라이브러리를 사용하는 이미지 디코더
ImageRenderer
은 ImageDecoder
인스턴스를 사용하여 Bitmap
이미지 URI입니다 이 디코더는 Transformer 블록의 외부 이미지 로딩을
라이브러리에 코드를 바인딩합니다.
Kotlin
val glideImageDecoder: ImageDecoder = object : ImageDecoder { private val inputBuffer = DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL) private val outputBuffer: ImageOutputBuffer = object : ImageOutputBuffer() { override fun release() { clear() bitmap = null } } private var pendingDecode: AtomicBoolean? = null private var decodeError: ImageDecoderException? = null override fun dequeueInputBuffer(): DecoderInputBuffer? { return if (pendingDecode == null) inputBuffer else null } override fun queueInputBuffer(inputBuffer: DecoderInputBuffer) { if (inputBuffer.isEndOfStream) { outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM) inputBuffer.clear() return } val currentDecode = AtomicBoolean(true) pendingDecode = currentDecode val imageUri = Uri.parse( String( inputBuffer.data!!.array(), inputBuffer.data!!.position(), inputBuffer.data!!.limit() - inputBuffer.data!!.position(), Charsets.UTF_8, ) ) val imageTimeUs = inputBuffer.timeUs Glide.with(context) .asBitmap() .load(imageUri) .into( object : CustomTarget<Bitmap?>() { override fun onResourceReady( resource: Bitmap, transition: Transition<in Bitmap?>?, ) { if (currentDecode.get()) { outputBuffer.timeUs = imageTimeUs outputBuffer.bitmap = resource pendingDecode = null } } override fun onLoadFailed(errorDrawable: Drawable?) { if (currentDecode.get()) { decodeError = ImageDecoderException("Glide load failed") } } override fun onLoadCleared(placeholder: Drawable?) {} } ) inputBuffer.clear() } @Throws(ImageDecoderException::class) override fun dequeueOutputBuffer(): ImageOutputBuffer? { if (decodeError != null) { throw decodeError as ImageDecoderException } val hasOutput = (pendingDecode == null && (outputBuffer.bitmap != null || outputBuffer.isEndOfStream)) return if (hasOutput) outputBuffer else null } override fun getName(): String { return "glideDecoder" } override fun setOutputStartTimeUs(outputStartTimeUs: Long) {} override fun flush() { if (pendingDecode != null) { pendingDecode!!.set(false) pendingDecode = null } decodeError = null inputBuffer.clear() outputBuffer.release() } override fun release() { flush() } } val glideImageDecoderFactory: ImageDecoder.Factory = object : ImageDecoder.Factory { override fun supportsFormat(format: Format): @RendererCapabilities.Capabilities Int { val isExternalImageUrl = format.sampleMimeType != null && format.sampleMimeType == MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE return RendererCapabilities.create( if (isExternalImageUrl) C.FORMAT_HANDLED else C.FORMAT_UNSUPPORTED_TYPE ) } override fun createImageDecoder(): ImageDecoder { return glideImageDecoder } } val player: Player = ExoPlayer.Builder(context) .setRenderersFactory( object : DefaultRenderersFactory(context) { override fun buildImageRenderers(out: ArrayList<Renderer>) { out.add( ImageRenderer(glideImageDecoderFactory, /* imageOutput= */ null)) } } ) .build()
자바
ImageDecoder glideImageDecoder = new ImageDecoder() { private final DecoderInputBuffer inputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_NORMAL); private final ImageOutputBuffer outputBuffer = new ImageOutputBuffer() { @Override public void release() { clear(); bitmap = null; } }; @Nullable private AtomicBoolean pendingDecode; @Nullable private ImageDecoderException decodeError; @Nullable @Override public DecoderInputBuffer dequeueInputBuffer() { return pendingDecode == null ? inputBuffer : null; } @Override public void queueInputBuffer(DecoderInputBuffer inputBuffer) { if (inputBuffer.isEndOfStream()) { outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); inputBuffer.clear(); return; } AtomicBoolean currentDecode = new AtomicBoolean(true); pendingDecode = currentDecode; Uri imageUri = Uri.parse( new String( inputBuffer.data.array(), inputBuffer.data.position(), inputBuffer.data.limit() - inputBuffer.data.position(), Charsets.UTF_8)); long imageTimeUs = inputBuffer.timeUs; Glide.with(context) .asBitmap() .load(imageUri) .into( new CustomTarget<Bitmap>() { @Override public void onResourceReady( Bitmap resource, @Nullable Transition<? super Bitmap> transition) { if (currentDecode.get()) { outputBuffer.timeUs = imageTimeUs; outputBuffer.bitmap = resource; pendingDecode = null; } } @Override public void onLoadFailed(@Nullable Drawable errorDrawable) { if (currentDecode.get()) { decodeError = new ImageDecoderException("Glide load failed"); } } @Override public void onLoadCleared(@Nullable Drawable placeholder) {} }); inputBuffer.clear(); } @Nullable @Override public ImageOutputBuffer dequeueOutputBuffer() throws ImageDecoderException { if (decodeError != null) { throw decodeError; } boolean hasOutput = pendingDecode == null && (outputBuffer.bitmap != null || outputBuffer.isEndOfStream()); return hasOutput ? outputBuffer : null; } @Override public String getName() { return "glideDecoder"; } @Override public void setOutputStartTimeUs(long outputStartTimeUs) {} @Override public void flush() { if (pendingDecode != null) { pendingDecode.set(false); pendingDecode = null; } decodeError = null; inputBuffer.clear(); outputBuffer.release(); } @Override public void release() { flush(); } }; ImageDecoder.Factory glideImageDecoderFactory = new ImageDecoder.Factory() { @Override public @RendererCapabilities.Capabilities int supportsFormat( Format format) { boolean isExternalImageUrl = format.sampleMimeType != null && format.sampleMimeType.equals( MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE); return RendererCapabilities.create( isExternalImageUrl ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE); } @Override public ImageDecoder createImageDecoder() { return glideImageDecoder; } }; Player player = new ExoPlayer.Builder(context) .setRenderersFactory( new DefaultRenderersFactory(context) { @Override protected void buildImageRenderers(ArrayList<Renderer> out) { out.add( new ImageRenderer( glideImageDecoderFactory, /* imageOutput= */ null)); } }) .build();
이미지 로드 라이브러리를 사용하여 이미지 미리 로드
재생되는 동안 플레이어가 이전 이미지가 완료되면 다음 이미지를 미리 로드하도록 요청합니다.
항목이 완전히 로드되기 전에
확인할 수 있습니다 외부 이미지 로드를 사용하는 경우
라이브러리를 사용하려면 ExternalLoader
를 지정하여 이 미리 로드를 트리거해야 합니다. 답이 '아니요'인 경우
미리 로드가 가능하거나 필요한 경우 이 로더를 계속 제공해야 하지만
아무것도 할 수 없습니다.
다음 예에서는 Glide를 사용하여 요청된 이미지가 미리 로드되도록 합니다. 디스크에 저장:
Kotlin
val glidePreloader = ExternalLoader { request: LoadRequest -> GlideFutures.submit( Glide.with(context) .asFile() .apply( RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.DATA) .priority(Priority.HIGH) .skipMemoryCache(true) ) .load(request.uri) ) }
Java
ExternalLoader glidePreloader = request -> GlideFutures.submit( Glide.with(context) .asFile() .apply( diskCacheStrategyOf(DiskCacheStrategy.DATA) .priority(Priority.HIGH) .skipMemoryCache(true)) .load(request.uri));