Fail MediaSourceFactory creation if the right module is not added
Due to loading a MediaSourceFactory via reflection: Before this change * the content type was logged as an integer, rather than a human-readable string * `ClassNotFoundException` was swallowed silently by `maybeLoadSupplier` without telling the user what module they were missing After: * ClassNotFoundException is swallowed silently ONLY when determining supported types * ClassNotFoundException is bubbled up when we are trying to play media without the corresponding module properly loaded PiperOrigin-RevId: 632568989
This commit is contained in:
parent
e1d8044ccc
commit
8fa72714db
@ -16,7 +16,6 @@
|
||||
package androidx.media3.exoplayer.source;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.castNonNull;
|
||||
import static androidx.media3.common.util.Util.msToUs;
|
||||
|
||||
@ -30,7 +29,6 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
@ -59,11 +57,9 @@ import com.google.common.primitives.Ints;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
@ -475,11 +471,13 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
if (mediaItem.localConfiguration.imageDurationMs != C.TIME_UNSET) {
|
||||
delegateFactoryLoader.setJpegExtractorFlags(JpegExtractor.FLAG_READ_IMAGE);
|
||||
}
|
||||
@Nullable
|
||||
MediaSource.Factory mediaSourceFactory = delegateFactoryLoader.getMediaSourceFactory(type);
|
||||
checkStateNotNull(
|
||||
mediaSourceFactory, "No suitable media source factory found for content type: " + type);
|
||||
|
||||
MediaSource.Factory mediaSourceFactory;
|
||||
try {
|
||||
mediaSourceFactory = delegateFactoryLoader.getMediaSourceFactory(type);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
MediaItem.LiveConfiguration.Builder liveConfigurationBuilder =
|
||||
mediaItem.liveConfiguration.buildUpon();
|
||||
if (mediaItem.liveConfiguration.targetOffsetMs == C.TIME_UNSET) {
|
||||
@ -606,9 +604,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
/** Loads media source factories lazily. */
|
||||
private static final class DelegateFactoryLoader {
|
||||
private final ExtractorsFactory extractorsFactory;
|
||||
private final Map<Integer, @NullableType Supplier<MediaSource.Factory>>
|
||||
mediaSourceFactorySuppliers;
|
||||
private final Set<Integer> supportedTypes;
|
||||
private final Map<Integer, Supplier<MediaSource.Factory>> mediaSourceFactorySuppliers;
|
||||
private final Map<Integer, MediaSource.Factory> mediaSourceFactories;
|
||||
|
||||
private DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||
@ -623,27 +619,22 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
this.extractorsFactory = extractorsFactory;
|
||||
this.subtitleParserFactory = subtitleParserFactory;
|
||||
mediaSourceFactorySuppliers = new HashMap<>();
|
||||
supportedTypes = new HashSet<>();
|
||||
mediaSourceFactories = new HashMap<>();
|
||||
}
|
||||
|
||||
public @C.ContentType int[] getSupportedTypes() {
|
||||
ensureAllSuppliersAreLoaded();
|
||||
return Ints.toArray(supportedTypes);
|
||||
return Ints.toArray(mediaSourceFactorySuppliers.keySet());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Forwarding to deprecated methods.
|
||||
@Nullable
|
||||
public MediaSource.Factory getMediaSourceFactory(@C.ContentType int contentType) {
|
||||
public MediaSource.Factory getMediaSourceFactory(@C.ContentType int contentType)
|
||||
throws ClassNotFoundException {
|
||||
@Nullable MediaSource.Factory mediaSourceFactory = mediaSourceFactories.get(contentType);
|
||||
if (mediaSourceFactory != null) {
|
||||
return mediaSourceFactory;
|
||||
}
|
||||
@Nullable
|
||||
Supplier<MediaSource.Factory> mediaSourceFactorySupplier = maybeLoadSupplier(contentType);
|
||||
if (mediaSourceFactorySupplier == null) {
|
||||
return null;
|
||||
}
|
||||
Supplier<MediaSource.Factory> mediaSourceFactorySupplier = loadSupplier(contentType);
|
||||
|
||||
mediaSourceFactory = mediaSourceFactorySupplier.get();
|
||||
if (cmcdConfigurationFactory != null) {
|
||||
@ -723,55 +714,61 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
maybeLoadSupplier(C.CONTENT_TYPE_OTHER);
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
@Nullable
|
||||
private Supplier<MediaSource.Factory> maybeLoadSupplier(@C.ContentType int contentType) {
|
||||
if (mediaSourceFactorySuppliers.containsKey(contentType)) {
|
||||
return mediaSourceFactorySuppliers.get(contentType);
|
||||
try {
|
||||
return loadSupplier(contentType);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the specific module
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<MediaSource.Factory> loadSupplier(@C.ContentType int contentType)
|
||||
throws ClassNotFoundException {
|
||||
@Nullable
|
||||
Supplier<MediaSource.Factory> mediaSourceFactorySupplier =
|
||||
mediaSourceFactorySuppliers.get(contentType);
|
||||
if (mediaSourceFactorySupplier != null) {
|
||||
return mediaSourceFactorySupplier;
|
||||
}
|
||||
|
||||
@Nullable Supplier<MediaSource.Factory> mediaSourceFactorySupplier = null;
|
||||
DataSource.Factory dataSourceFactory = checkNotNull(this.dataSourceFactory);
|
||||
try {
|
||||
Class<? extends MediaSource.Factory> clazz;
|
||||
switch (contentType) {
|
||||
case C.CONTENT_TYPE_DASH:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.dash.DashMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_SS:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.smoothstreaming.SsMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_HLS:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.hls.HlsMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_RTSP:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.rtsp.RtspMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz);
|
||||
break;
|
||||
case C.CONTENT_TYPE_OTHER:
|
||||
mediaSourceFactorySupplier =
|
||||
() -> new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
|
||||
break;
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the specific module.
|
||||
Class<? extends MediaSource.Factory> clazz;
|
||||
switch (contentType) {
|
||||
case C.CONTENT_TYPE_DASH:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.dash.DashMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_SS:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.smoothstreaming.SsMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_HLS:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.hls.HlsMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_RTSP:
|
||||
clazz =
|
||||
Class.forName("androidx.media3.exoplayer.rtsp.RtspMediaSource$Factory")
|
||||
.asSubclass(MediaSource.Factory.class);
|
||||
mediaSourceFactorySupplier = () -> newInstance(clazz);
|
||||
break;
|
||||
case C.CONTENT_TYPE_OTHER:
|
||||
mediaSourceFactorySupplier =
|
||||
() -> new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized contentType: " + contentType);
|
||||
}
|
||||
mediaSourceFactorySuppliers.put(contentType, mediaSourceFactorySupplier);
|
||||
if (mediaSourceFactorySupplier != null) {
|
||||
supportedTypes.add(contentType);
|
||||
}
|
||||
return mediaSourceFactorySupplier;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.exoplayer.source;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.content.Context;
|
||||
@ -176,6 +177,36 @@ public final class DefaultMediaSourceFactoryTest {
|
||||
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaSource_withUnsupportedMimeType_throwsException() {
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext());
|
||||
|
||||
MediaItem dashMediaItem =
|
||||
new MediaItem.Builder().setUri(URI_MEDIA).setMimeType(MimeTypes.APPLICATION_MPD).build();
|
||||
IllegalStateException e1 =
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> defaultMediaSourceFactory.createMediaSource(dashMediaItem));
|
||||
assertThat(e1).hasCauseThat().isInstanceOf(ClassNotFoundException.class);
|
||||
assertThat(e1)
|
||||
.hasCauseThat()
|
||||
.hasMessageThat()
|
||||
.contains("androidx.media3.exoplayer.dash.DashMediaSource$Factory");
|
||||
|
||||
MediaItem hlsMediaItem =
|
||||
new MediaItem.Builder().setUri(URI_MEDIA).setMimeType(MimeTypes.APPLICATION_M3U8).build();
|
||||
IllegalStateException e2 =
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> defaultMediaSourceFactory.createMediaSource(hlsMediaItem));
|
||||
assertThat(e2).hasCauseThat().isInstanceOf(ClassNotFoundException.class);
|
||||
assertThat(e2)
|
||||
.hasCauseThat()
|
||||
.hasMessageThat()
|
||||
.contains("androidx.media3.exoplayer.hls.HlsMediaSource$Factory");
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated setters.
|
||||
@Test
|
||||
public void createMediaSource_withDeprecatedAdsConfiguration_callsAdsLoader() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user