ExoPlayer hỗ trợ các định dạng hình ảnh sau. Xem Thư viện tải hình ảnh về cách tích hợp với các thư viện bên ngoài có thể hỗ trợ cho các định dạng khác nhau.
Định dạng hình ảnh | Có thể làm | Ghi chú |
---|---|---|
BMP | CÓ | |
GIF | KHÔNG | Không hỗ trợ Trình trích xuất |
JPEG | CÓ | |
Ảnh chuyển động JPEG | CÓ | Hỗ trợ hình ảnh và video tĩnh |
JPEG Ultra HDR | CÓ | Quay lại SDR trước phiên bản Android 14 trở lên màn hình không hỗ trợ HDR |
PNG | CÓ | |
WebP | CÓ | |
HEIF/HEIC | CÓ | |
Ảnh chuyển động HEIC | Một phần | Chỉ hỗ trợ ảnh tĩnh* |
AVIF (cơ sở) | CÓ | Chỉ được giải mã trên Android 14 trở lên |
* Phần video của ảnh chuyển động HEIC có thể thu được bằng MetadataRetriever và phát dưới dạng tệp độc lập.
Sử dụng MediaItem
Để phát một hình ảnh trong danh sách phát, hãy tạo MediaItem
bằng URI hình ảnh
rồi truyền quảng cáo đó đến người chơi. MediaItem
phải có imageDurationMs
để
chỉ định khoảng thời gian hiển thị hình ảnh.
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()
Java
// 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();
Ảnh chuyển động
Ảnh chuyển động là các tệp kết hợp một hình ảnh tĩnh với một video ngắn.
- Nếu thời lượng hình ảnh được xác định bằng
setImageDuration
, thì ảnh động sẽ được được hiển thị trong thời lượng khai báo dưới dạng hình ảnh tĩnh. - Nếu thời lượng hình ảnh không xác định, ảnh chuyển động sẽ được phát dưới dạng video.
Sử dụng ProgressiveMediaSource
Để có thêm lựa chọn tuỳ chỉnh, bạn có thể tạo một ProgressiveMediaSource
và
truyền trực tiếp đến trình phát thay vì 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()
Java
// 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();
Tuỳ chỉnh chế độ phát
ExoPlayer cung cấp nhiều cách để bạn điều chỉnh trải nghiệm phát cho phù hợp với của ứng dụng. Xem trang Tuỳ chỉnh để biết các ví dụ.
Thư viện tải hình ảnh
Hình ảnh thường do các thư viện tải hình ảnh bên ngoài quản lý, ví dụ: Glide hoặc Vòng quay.
Bạn cần thực hiện 3 bước để tích hợp các thư viện này vào quy trình phát:
- Xác định
MediaItem
có loại MIMEAPPLICATION_EXTERNALLY_LOADED_IMAGE
. - Cung cấp bộ giải mã hình ảnh để truy xuất
Bitmap
từ quá trình tải hình ảnh thư viện của bạn. - Cung cấp một trình tải bên ngoài để kích hoạt chức năng lưu vào bộ nhớ đệm và tải trước.
MediaItem có loại MIME hình ảnh được tải bên ngoài
MediaItem
được thêm vào Player
phải xác định
APPLICATION_EXTERNALLY_LOADED_IMAGE
loại MIME để sử dụng hình ảnh một cách rõ ràng
đang tải đường dẫn mã thư viện:
Kotlin
val mediaItem = MediaItem.Builder() .setUri(imageUri) .setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE) .build()
Java
MediaItem mediaItem = new MediaItem.Builder() .setUri(imageUri) .setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE) .build();
Bộ giải mã hình ảnh sử dụng thư viện tải hình ảnh
ImageRenderer
sử dụng các thực thể ImageDecoder
để truy xuất Bitmap
cho
URI hình ảnh. Bộ giải mã này có thể được ghi để sử dụng tính năng tải hình ảnh bên ngoài
thư viện, như minh hoạ trong ví dụ sau bằng Glide:
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()
Java
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();
Tải trước hình ảnh bằng thư viện tải hình ảnh
Trong khi phát, trình phát yêu cầu tải trước hình ảnh tiếp theo sau khi hình ảnh trước đó
mục trong danh sách phát đã được tải đầy đủ. Khi tải hình ảnh từ bên ngoài
thư viện, bạn phải chỉ định ExternalLoader
để kích hoạt tính năng tải trước này. Nếu không
có thể hoặc bắt buộc phải tải trước, bạn vẫn cần cung cấp trình tải này, nhưng
không thể làm gì cả.
Ví dụ sau đây sử dụng Glide để đảm bảo tải trước hình ảnh theo yêu cầu vào đĩa:
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));