تصاویر

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 مرحله نیاز دارد:

  1. یک MediaItem با نوع MIME APPLICATION_EXTERNALLY_LOADED_IMAGE تعریف کنید.
  2. یک رمزگشای تصویر برای بازیابی Bitmap از کتابخانه بارگذاری تصویر تهیه کنید.
  3. یک لودر خارجی برای راه‌اندازی ذخیره‌سازی و بارگذاری اولیه ارائه کنید.

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));