ExoPlayer از فرمت های تصویر زیر پشتیبانی می کند. برای نحوه ادغام با کتابخانه های خارجی که ممکن است از مجموعه فرمت های متفاوتی پشتیبانی کنند، به کتابخانه های بارگیری تصویر مراجعه کنید.
فرمت تصویر | پشتیبانی می شود | یادداشت ها |
---|---|---|
BMP | بله | |
GIF | نه | بدون پشتیبانی Extractor |
JPEG | بله | |
عکس حرکت JPEG | بله | تصویر ثابت و ویدیو پشتیبانی می شود |
JPEG Ultra HDR | بله | قبل از Android 14 یا در نمایشگرهای غیر HDR به SDR برمی گردد |
PNG | بله | |
وب پی | بله | |
HEIF/HEIC | بله | |
عکس حرکت HEIC | تا حدی | فقط تصویر ثابت پشتیبانی می شود* |
AVIF (خط پایه) | بله | فقط در Android 14+ رمزگشایی شده است |
* بخش ویدیویی عکس های حرکتی HEIC را می توان با MetadataRetriever دریافت کرد و به عنوان یک فایل مستقل پخش کرد.
با استفاده از MediaItem
برای پخش یک تصویر به عنوان بخشی از یک لیست پخش، یک MediaItem
با URI تصویر ایجاد کنید و آن را به پخش کننده ارسال کنید. MediaItem
باید دارای یک imageDurationMs
باشد تا مشخص کند چه مدت تصویر باید نمایش داده شود.
کاتلین
// 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
مستقیماً به پخشکننده ارسال کنید.
کاتلین
// 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 یا Coil .
ادغام این کتابخانه ها در خط لوله پخش به 3 مرحله نیاز دارد:
- یک
MediaItem
با نوع MIMEAPPLICATION_EXTERNALLY_LOADED_IMAGE
تعریف کنید. - یک رمزگشای تصویر برای بازیابی
Bitmap
از کتابخانه بارگذاری تصویر تهیه کنید. - یک لودر خارجی برای راهاندازی ذخیرهسازی و بارگذاری اولیه ارائه کنید.
MediaItem با نوع MIME تصویر بارگذاری شده خارجی
MediaItem
اضافه شده به Player
باید نوع MIME APPLICATION_EXTERNALLY_LOADED_IMAGE
را به صراحت برای استفاده از مسیرهای کد کتابخانه بارگیری تصویر تعریف کند:
کاتلین
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 تصویر استفاده می کند. این رمزگشا را می توان برای استفاده از کتابخانه بارگذاری تصویر خارجی نوشت، همانطور که در مثال زیر با Glide نشان داده شده است:
کاتلین
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 استفاده می کند تا اطمینان حاصل کند که تصویر درخواستی از قبل روی دیسک بارگذاری شده است:
کاتلین
val glidePreloader = ExternalLoader { request: LoadRequest -> GlideFutures.submit( Glide.with(context) .asFile() .apply( RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.DATA) .priority(Priority.HIGH) .skipMemoryCache(true) ) .load(request.uri) ) }
جاوا
ExternalLoader glidePreloader = request -> GlideFutures.submit( Glide.with(context) .asFile() .apply( diskCacheStrategyOf(DiskCacheStrategy.DATA) .priority(Priority.HIGH) .skipMemoryCache(true)) .load(request.uri));