diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java index 94a668df8a..fb6a7548a8 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java @@ -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> - mediaSourceFactorySuppliers; - private final Set supportedTypes; + private final Map> mediaSourceFactorySuppliers; private final Map 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 mediaSourceFactorySupplier = maybeLoadSupplier(contentType); - if (mediaSourceFactorySupplier == null) { - return null; - } + Supplier 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 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 loadSupplier(@C.ContentType int contentType) + throws ClassNotFoundException { + @Nullable + Supplier mediaSourceFactorySupplier = + mediaSourceFactorySuppliers.get(contentType); + if (mediaSourceFactorySupplier != null) { + return mediaSourceFactorySupplier; } - @Nullable Supplier mediaSourceFactorySupplier = null; DataSource.Factory dataSourceFactory = checkNotNull(this.dataSourceFactory); - try { - Class 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 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; } } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactoryTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactoryTest.java index 7d3af7bb08..1ba6610026 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactoryTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactoryTest.java @@ -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() {