mirror of
https://github.com/samsonjs/media.git
synced 2026-03-29 10:05:48 +00:00
Support customized loading in ExternallyLoadedMediaSource
PiperOrigin-RevId: 572299971
This commit is contained in:
parent
66fa591959
commit
8f2161c43d
6 changed files with 235 additions and 33 deletions
|
|
@ -116,6 +116,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||
|
||||
private DataSource.Factory dataSourceFactory;
|
||||
@Nullable private MediaSource.Factory serverSideAdInsertionMediaSourceFactory;
|
||||
@Nullable private ExternalLoader externalImageLoader;
|
||||
@Nullable private AdsLoader.Provider adsLoaderProvider;
|
||||
@Nullable private AdViewProvider adViewProvider;
|
||||
@Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||
|
|
@ -315,6 +316,26 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ExternalLoader} to be called when loading starts in {@link
|
||||
* ExternallyLoadedMediaSource} when loading images in an external Image Management Framework (for
|
||||
* example, Glide).
|
||||
*
|
||||
* <p>This loader is only used when the {@link MediaItem.LocalConfiguration#mimeType} is set to
|
||||
* {@link MimeTypes#APPLICATION_EXTERNALLY_LOADED_IMAGE}.
|
||||
*
|
||||
* @param externalImageLoader The {@link ExternalLoader} to load the media or {@code null} to
|
||||
* remove a previously set {@link ExternalLoader}.
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
@UnstableApi
|
||||
public DefaultMediaSourceFactory setExternalImageLoader(
|
||||
@Nullable ExternalLoader externalImageLoader) {
|
||||
this.externalImageLoader = externalImageLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the target live offset for live streams, in milliseconds.
|
||||
*
|
||||
|
|
@ -440,7 +461,8 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||
if (Objects.equals(
|
||||
mediaItem.localConfiguration.mimeType, MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE)) {
|
||||
return new ExternallyLoadedMediaSource.Factory(
|
||||
msToUs(mediaItem.localConfiguration.imageDurationMs))
|
||||
msToUs(mediaItem.localConfiguration.imageDurationMs),
|
||||
checkNotNull(externalImageLoader))
|
||||
.createMediaSource(mediaItem);
|
||||
}
|
||||
@C.ContentType
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.exoplayer.source;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/** An object for loading media outside of ExoPlayer's loading mechanism. */
|
||||
@UnstableApi
|
||||
public interface ExternalLoader {
|
||||
|
||||
/** A data class providing information associated with the load event. */
|
||||
final class LoadRequest {
|
||||
|
||||
/** The {@link Uri} stored in the load request object. */
|
||||
public final Uri uri;
|
||||
|
||||
/** Creates an instance. */
|
||||
public LoadRequest(Uri uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the external media.
|
||||
*
|
||||
* @param loadRequest The load request.
|
||||
* @return The {@link ListenableFuture} tracking the completion of the loading work.
|
||||
*/
|
||||
ListenableFuture<@Nullable ?> load(LoadRequest loadRequest);
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
package androidx.media3.exoplayer.source;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
|
|
@ -24,8 +25,17 @@ import androidx.media3.decoder.DecoderInputBuffer;
|
|||
import androidx.media3.exoplayer.FormatHolder;
|
||||
import androidx.media3.exoplayer.LoadingInfo;
|
||||
import androidx.media3.exoplayer.SeekParameters;
|
||||
import androidx.media3.exoplayer.source.ExternalLoader.LoadRequest;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* A {@link MediaPeriod} that puts a {@link Charsets#UTF_8}-encoded {@link Uri} into the sample
|
||||
|
|
@ -33,24 +43,42 @@ import com.google.common.base.Charsets;
|
|||
*/
|
||||
/* package */ final class ExternallyLoadedMediaPeriod implements MediaPeriod {
|
||||
|
||||
private final Format format;
|
||||
private final Uri uri;
|
||||
private final ExternalLoader externalLoader;
|
||||
private final TrackGroupArray tracks;
|
||||
private final byte[] sampleData;
|
||||
private final AtomicBoolean loadingFinished;
|
||||
private final AtomicReference<Throwable> loadingThrowable;
|
||||
private @MonotonicNonNull ListenableFuture<?> loadingFuture;
|
||||
|
||||
// TODO: b/303375301 - Removing this variable (replacing it with static returns in the methods
|
||||
// that
|
||||
// use it) causes playback to hang.
|
||||
private boolean loadingFinished;
|
||||
|
||||
public ExternallyLoadedMediaPeriod(Uri uri, String mimeType) {
|
||||
this.format = new Format.Builder().setSampleMimeType(mimeType).build();
|
||||
public ExternallyLoadedMediaPeriod(Uri uri, String mimeType, ExternalLoader externalLoader) {
|
||||
this.uri = uri;
|
||||
Format format = new Format.Builder().setSampleMimeType(mimeType).build();
|
||||
this.externalLoader = externalLoader;
|
||||
tracks = new TrackGroupArray(new TrackGroup(format));
|
||||
sampleData = uri.toString().getBytes(Charsets.UTF_8);
|
||||
loadingFinished = new AtomicBoolean();
|
||||
loadingThrowable = new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback, long positionUs) {
|
||||
callback.onPrepared(this);
|
||||
loadingFuture = externalLoader.load(new LoadRequest(uri));
|
||||
Futures.addCallback(
|
||||
loadingFuture,
|
||||
new FutureCallback<@NullableType Object>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Object result) {
|
||||
loadingFinished.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
loadingThrowable.set(t);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -105,26 +133,22 @@ import com.google.common.base.Charsets;
|
|||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
return loadingFinished ? C.TIME_END_OF_SOURCE : 0;
|
||||
return loadingFinished.get() ? C.TIME_END_OF_SOURCE : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextLoadPositionUs() {
|
||||
return loadingFinished ? C.TIME_END_OF_SOURCE : 0;
|
||||
return loadingFinished.get() ? C.TIME_END_OF_SOURCE : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(LoadingInfo loadingInfo) {
|
||||
if (loadingFinished) {
|
||||
return false;
|
||||
}
|
||||
loadingFinished = true;
|
||||
return true;
|
||||
return !loadingFinished.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
return !loadingFinished;
|
||||
return !loadingFinished.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -132,8 +156,13 @@ import com.google.common.base.Charsets;
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
private final class SampleStreamImpl implements SampleStream {
|
||||
public void releasePeriod() {
|
||||
if (loadingFuture != null) {
|
||||
loadingFuture.cancel(/* mayInterruptIfRunning= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
private final class SampleStreamImpl implements SampleStream {
|
||||
private static final int STREAM_STATE_SEND_FORMAT = 0;
|
||||
private static final int STREAM_STATE_SEND_SAMPLE = 1;
|
||||
private static final int STREAM_STATE_END_OF_STREAM = 2;
|
||||
|
|
@ -146,13 +175,16 @@ import com.google.common.base.Charsets;
|
|||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return loadingFinished;
|
||||
return loadingFinished.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowError() {
|
||||
// Do nothing.
|
||||
|
||||
public void maybeThrowError() throws IOException {
|
||||
@Nullable
|
||||
Throwable loadingThrowable = ExternallyLoadedMediaPeriod.this.loadingThrowable.get();
|
||||
if (loadingThrowable != null) {
|
||||
throw new IOException(loadingThrowable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -170,6 +202,10 @@ import com.google.common.base.Charsets;
|
|||
return C.RESULT_FORMAT_READ;
|
||||
}
|
||||
|
||||
if (!loadingFinished.get()) {
|
||||
return C.RESULT_NOTHING_READ;
|
||||
}
|
||||
|
||||
int sampleSize = sampleData.length;
|
||||
buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME);
|
||||
buffer.timeUs = 0;
|
||||
|
|
|
|||
|
|
@ -43,18 +43,24 @@ import java.util.Objects;
|
|||
@UnstableApi
|
||||
public final class ExternallyLoadedMediaSource extends BaseMediaSource {
|
||||
|
||||
private final ExternalLoader externalLoader;
|
||||
|
||||
/** Factory for {@link ExternallyLoadedMediaSource}. */
|
||||
public static final class Factory implements MediaSource.Factory {
|
||||
|
||||
private final long timelineDurationUs;
|
||||
private final ExternalLoader externalLoader;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param timelineDurationUs The duration of the {@link SinglePeriodTimeline} created.
|
||||
* @param timelineDurationUs The duration of the {@link SinglePeriodTimeline} created, in
|
||||
* microseconds.
|
||||
* @param externalLoader The {@link ExternalLoader} to load the media in preparation for
|
||||
* playback.
|
||||
*/
|
||||
Factory(long timelineDurationUs) {
|
||||
public Factory(long timelineDurationUs, ExternalLoader externalLoader) {
|
||||
this.timelineDurationUs = timelineDurationUs;
|
||||
this.externalLoader = externalLoader;
|
||||
}
|
||||
|
||||
/** Does nothing. {@link ExternallyLoadedMediaSource} does not support DRM. */
|
||||
|
|
@ -80,7 +86,7 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
|
|||
|
||||
@Override
|
||||
public ExternallyLoadedMediaSource createMediaSource(MediaItem mediaItem) {
|
||||
return new ExternallyLoadedMediaSource(mediaItem, timelineDurationUs);
|
||||
return new ExternallyLoadedMediaSource(mediaItem, timelineDurationUs, externalLoader);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,9 +95,11 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
|
|||
@GuardedBy("this")
|
||||
private MediaItem mediaItem;
|
||||
|
||||
private ExternallyLoadedMediaSource(MediaItem mediaItem, long timelineDurationUs) {
|
||||
private ExternallyLoadedMediaSource(
|
||||
MediaItem mediaItem, long timelineDurationUs, ExternalLoader externalLoader) {
|
||||
this.mediaItem = mediaItem;
|
||||
this.timelineDurationUs = timelineDurationUs;
|
||||
this.externalLoader = externalLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -118,7 +126,7 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean canUpdateMediaItem(MediaItem mediaItem) {
|
||||
public boolean canUpdateMediaItem(MediaItem mediaItem) {
|
||||
@Nullable MediaItem.LocalConfiguration newConfiguration = mediaItem.localConfiguration;
|
||||
MediaItem.LocalConfiguration oldConfiguration = checkNotNull(getMediaItem().localConfiguration);
|
||||
return newConfiguration != null
|
||||
|
|
@ -145,11 +153,11 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
|
|||
checkNotNull(
|
||||
mediaItem.localConfiguration.mimeType, "Externally loaded mediaItems require a MIME type.");
|
||||
return new ExternallyLoadedMediaPeriod(
|
||||
mediaItem.localConfiguration.uri, mediaItem.localConfiguration.mimeType);
|
||||
mediaItem.localConfiguration.uri, mediaItem.localConfiguration.mimeType, externalLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
// Do nothing.
|
||||
((ExternallyLoadedMediaPeriod) mediaPeriod).releasePeriod();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,18 @@ import androidx.media3.common.MediaItem;
|
|||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.datasource.AssetDataSource;
|
||||
import androidx.media3.datasource.DataSourceUtil;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.RendererCapabilities;
|
||||
import androidx.media3.exoplayer.image.BitmapFactoryImageDecoder;
|
||||
import androidx.media3.exoplayer.image.ImageDecoder;
|
||||
import androidx.media3.exoplayer.image.ImageDecoderException;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.test.utils.CapturingRenderersFactory;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
|
|
@ -45,7 +49,11 @@ import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
|
|||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.annotation.GraphicsMode;
|
||||
|
|
@ -58,14 +66,26 @@ public final class ExternallyLoadedImagePlaybackTest {
|
|||
private static final String INPUT_FILE = "png/non-motion-photo-shortened.png";
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
public void imagePlayback_validExternalLoader_callsLoadOnceAndPlaysSuccessfully()
|
||||
throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory renderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext, /* addImageRenderer= */ true)
|
||||
.setImageDecoderFactory(new CustomImageDecoderFactory());
|
||||
Clock clock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||
AtomicInteger externalLoaderCallCount = new AtomicInteger();
|
||||
ListeningExecutorService listeningExecutorService =
|
||||
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.setExternalImageLoader(
|
||||
unused ->
|
||||
listeningExecutorService.submit(externalLoaderCallCount::getAndIncrement));
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, renderersFactory).setClock(clock).build();
|
||||
new ExoPlayer.Builder(applicationContext, renderersFactory)
|
||||
.setClock(clock)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
|
||||
long durationMs = 5 * C.MILLIS_PER_SECOND;
|
||||
player.setMediaItem(
|
||||
|
|
@ -83,11 +103,80 @@ public final class ExternallyLoadedImagePlaybackTest {
|
|||
long playbackDurationMs = clock.elapsedRealtime() - playerStartedMs;
|
||||
player.release();
|
||||
|
||||
assertThat(externalLoaderCallCount.get()).isEqualTo(1);
|
||||
assertThat(playbackDurationMs).isAtLeast(durationMs);
|
||||
DumpFileAsserts.assertOutput(
|
||||
applicationContext, playbackOutput, "playbackdumps/" + INPUT_FILE + ".dump");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void imagePlayback_externalLoaderFutureFails_propagatesFailure() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory renderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext, /* addImageRenderer= */ true)
|
||||
.setImageDecoderFactory(new CustomImageDecoderFactory());
|
||||
ListeningExecutorService listeningExecutorService =
|
||||
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.setExternalImageLoader(
|
||||
unused ->
|
||||
listeningExecutorService.submit(
|
||||
() -> {
|
||||
throw new RuntimeException("My Exception");
|
||||
}));
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, renderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
player.setMediaItem(
|
||||
new MediaItem.Builder()
|
||||
.setUri("asset:///media/" + INPUT_FILE)
|
||||
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
|
||||
.setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE)
|
||||
.build());
|
||||
player.prepare();
|
||||
|
||||
ExoPlaybackException error = TestPlayerRunHelper.runUntilError(player);
|
||||
assertThat(error).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void imagePlayback_loadingCompletedWhenFutureCompletes() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory renderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext, /* addImageRenderer= */ true)
|
||||
.setImageDecoderFactory(new CustomImageDecoderFactory());
|
||||
ListeningExecutorService listeningExecutorService =
|
||||
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
||||
ConditionVariable loadingComplete = new ConditionVariable();
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.setExternalImageLoader(
|
||||
unused -> listeningExecutorService.submit(loadingComplete::blockUninterruptible));
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, renderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
player.setMediaItem(
|
||||
new MediaItem.Builder()
|
||||
.setUri("asset:///media/" + INPUT_FILE)
|
||||
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
|
||||
.setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE)
|
||||
.build());
|
||||
player.prepare();
|
||||
|
||||
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player);
|
||||
|
||||
assertThat(player.isLoading()).isTrue();
|
||||
|
||||
loadingComplete.open();
|
||||
// Assert the player stops loading.
|
||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
||||
}
|
||||
|
||||
private static final class CustomImageDecoderFactory implements ImageDecoder.Factory {
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.media3.exoplayer.analytics.PlayerId;
|
|||
import androidx.media3.test.utils.TestUtil;
|
||||
import androidx.media3.test.utils.robolectric.RobolectricUtil;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
@ -128,7 +129,7 @@ public class ExternallyLoadedMediaSourceTest {
|
|||
|
||||
private static MediaSource buildMediaSource(MediaItem mediaItem) {
|
||||
return new ExternallyLoadedMediaSource.Factory(
|
||||
msToUs(mediaItem.localConfiguration.imageDurationMs))
|
||||
msToUs(mediaItem.localConfiguration.imageDurationMs), unused -> SettableFuture.create())
|
||||
.createMediaSource(mediaItem);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue