ExoPlayer mendukung format gambar berikut. Lihat Library Pemuatan Gambar mengenai cara mengintegrasikan dengan library eksternal yang dapat memberikan dukungan untuk kumpulan format yang berbeda.
Format gambar | Didukung | Catatan |
---|---|---|
BMP | YA | |
GIF | TIDAK | Tidak ada dukungan Ekstraktor |
JPEG | YA | |
Foto Motion JPEG | YA | Gambar diam dan video didukung |
JPEG Ultra HDR | YA | Kembali ke SDR sebelum Android 14 atau setelahnya layar non-HDR |
PNG | YA | |
WebP | YA | |
HEIF/HEIC | YA | |
Foto Gerakan HEIC | Sebagian | Hanya gambar diam yang didukung* |
AVIF (dasar pengukuran) | YA | Didekode di Android 14 dan yang lebih baru saja |
* Bagian video dari foto gerakan HEIC dapat diperoleh dengan MetadataRetriever dan diputar sebagai file mandiri.
Menggunakan MediaItem
Untuk memutar gambar sebagai bagian dari playlist, buat MediaItem
dengan URI gambar
dan meneruskannya ke pemain. MediaItem
harus memiliki imageDurationMs
untuk
menentukan berapa lama gambar akan ditampilkan.
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();
Foto Motion
Foto motion adalah file yang menggabungkan gambar diam dengan video pendek.
- Jika durasi gambar ditentukan dengan
setImageDuration
, foto motion akan ditampilkan selama durasi yang dinyatakan sebagai gambar diam. - Jika durasi gambar tidak ditentukan, foto motion akan diputar sebagai video.
Menggunakan ProgressiveMediaSource
Untuk opsi penyesuaian lainnya, Anda dapat membuat ProgressiveMediaSource
dan
meneruskannya langsung ke pemutar, bukan 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();
Menyesuaikan pemutaran
ExoPlayer menyediakan beberapa cara bagi Anda untuk menyesuaikan pengalaman pemutaran dengan aplikasi Anda. Lihat halaman Penyesuaian untuk mengetahui contohnya.
Library Pemuatan Gambar
Gambar sering dikelola oleh library pemuatan gambar eksternal, misalnya Glide atau Coil.
Perlu 3 langkah untuk mengintegrasikan library ini ke dalam pipeline pemutaran:
- Tentukan
MediaItem
dengan jenis MIMEAPPLICATION_EXTERNALLY_LOADED_IMAGE
. - Menyediakan decoder gambar untuk mengambil
Bitmap
dari pemuatan gambar library. - Menyediakan loader eksternal untuk memicu penyimpanan dalam cache dan pramuat.
MediaItem dengan jenis MIME gambar yang dimuat secara eksternal
MediaItem
yang ditambahkan ke Player
harus menentukan
APPLICATION_EXTERNALLY_LOADED_IMAGE
Jenis MIME secara eksplisit untuk menggunakan gambar
memuat jalur kode library:
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();
Decoder gambar menggunakan library pemuatan gambar
ImageRenderer
menggunakan instance ImageDecoder
untuk mengambil Bitmap
untuk
URI gambar. Decoder ini dapat ditulis untuk menggunakan pemuatan gambar eksternal
seperti yang ditunjukkan dalam contoh berikut dengan 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();
Pramuat gambar dengan library pemuatan gambar
Selama pemutaran, pemutar meminta untuk melakukan pramuat gambar berikutnya satu kali
item dalam playlist telah dimuat sepenuhnya. Saat menggunakan pemuatan gambar eksternal
library, Anda harus menentukan ExternalLoader
untuk memicu pramuat ini. Jika tidak
pramuat dapat dilakukan atau diperlukan, loader ini masih perlu disediakan, tetapi
tidak dapat melakukan apa pun.
Contoh berikut menggunakan Glide untuk memastikan gambar yang diminta sudah dipramuat ke {i>disk<i}:
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));