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:
jbibik 2024-05-10 12:16:09 -07:00 committed by Copybara-Service
parent e1d8044ccc
commit 8fa72714db
2 changed files with 90 additions and 62 deletions

View File

@ -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,15 +714,27 @@ 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:
@ -763,15 +766,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
() -> new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
break;
default:
// Do nothing.
}
} catch (ClassNotFoundException e) {
// Expected if the app was built without the specific module.
throw new IllegalArgumentException("Unrecognized contentType: " + contentType);
}
mediaSourceFactorySuppliers.put(contentType, mediaSourceFactorySupplier);
if (mediaSourceFactorySupplier != null) {
supportedTypes.add(contentType);
}
return mediaSourceFactorySupplier;
}
}

View File

@ -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() {