ExoPlayer は、次の画像形式をサポートしています。詳しくは、 画像読み込みライブラリ のサポートを提供する外部ライブラリとの統合方法については、 使用できます。
イメージの形式 | サポート対象 | 注 |
---|---|---|
BMP | はい | |
GIF | いいえ | extractor のサポートなし |
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()
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();
モーション フォト
モーション フォトは、静止画像とショート動画を組み合わせたファイルです。
- 画像の長さを
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()
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();
再生のカスタマイズ
ExoPlayer は、ユーザーの視聴環境に合わせて再生エクスペリエンスを調整するための複数の方法を提供しています。 提供します。例については、カスタマイズのページをご覧ください。
画像読み込みライブラリ
多くの場合、画像は外部の画像読み込みライブラリによって管理されます。 グライドまたは コイル。
これらのライブラリを再生パイプラインに統合するには、次の 3 つのステップが必要です。
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()
Java
MediaItem mediaItem = new MediaItem.Builder() .setUri(imageUri) .setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE) .build();
画像読み込みライブラリを使用した画像デコーダ
ImageRenderer
は、ImageDecoder
インスタンスを使用して、インスタンスの Bitmap
を取得します。
イメージ URI。このデコーダは、外部の画像の読み込み
実装する方法を説明します。
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();
画像読み込みライブラリを使用した画像のプリロード
再生中、プレーヤーは前の画像が 1 つ前の画像をプリロードし、
再生リストのアイテムが完全に読み込まれました。外部画像の読み込みを使用する場合
このプリロードをトリガーするには、ExternalLoader
を指定する必要があります。「いいえ」の場合
プリロードが可能または必要な場合は、このローダも指定する必要がありますが、
何もできません。
次の例では、Glide を使用して、リクエストされた画像がプリロードされるようにしています to disk:
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));