mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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;
|
package androidx.media3.exoplayer.source;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
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.castNonNull;
|
||||||
import static androidx.media3.common.util.Util.msToUs;
|
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.MimeTypes;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.NullableType;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
@ -59,11 +57,9 @@ import com.google.common.primitives.Ints;
|
|||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -475,11 +471,13 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
if (mediaItem.localConfiguration.imageDurationMs != C.TIME_UNSET) {
|
if (mediaItem.localConfiguration.imageDurationMs != C.TIME_UNSET) {
|
||||||
delegateFactoryLoader.setJpegExtractorFlags(JpegExtractor.FLAG_READ_IMAGE);
|
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.Builder liveConfigurationBuilder =
|
||||||
mediaItem.liveConfiguration.buildUpon();
|
mediaItem.liveConfiguration.buildUpon();
|
||||||
if (mediaItem.liveConfiguration.targetOffsetMs == C.TIME_UNSET) {
|
if (mediaItem.liveConfiguration.targetOffsetMs == C.TIME_UNSET) {
|
||||||
@ -606,9 +604,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
/** Loads media source factories lazily. */
|
/** Loads media source factories lazily. */
|
||||||
private static final class DelegateFactoryLoader {
|
private static final class DelegateFactoryLoader {
|
||||||
private final ExtractorsFactory extractorsFactory;
|
private final ExtractorsFactory extractorsFactory;
|
||||||
private final Map<Integer, @NullableType Supplier<MediaSource.Factory>>
|
private final Map<Integer, Supplier<MediaSource.Factory>> mediaSourceFactorySuppliers;
|
||||||
mediaSourceFactorySuppliers;
|
|
||||||
private final Set<Integer> supportedTypes;
|
|
||||||
private final Map<Integer, MediaSource.Factory> mediaSourceFactories;
|
private final Map<Integer, MediaSource.Factory> mediaSourceFactories;
|
||||||
|
|
||||||
private DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
private DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||||
@ -623,27 +619,22 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
this.extractorsFactory = extractorsFactory;
|
this.extractorsFactory = extractorsFactory;
|
||||||
this.subtitleParserFactory = subtitleParserFactory;
|
this.subtitleParserFactory = subtitleParserFactory;
|
||||||
mediaSourceFactorySuppliers = new HashMap<>();
|
mediaSourceFactorySuppliers = new HashMap<>();
|
||||||
supportedTypes = new HashSet<>();
|
|
||||||
mediaSourceFactories = new HashMap<>();
|
mediaSourceFactories = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @C.ContentType int[] getSupportedTypes() {
|
public @C.ContentType int[] getSupportedTypes() {
|
||||||
ensureAllSuppliersAreLoaded();
|
ensureAllSuppliersAreLoaded();
|
||||||
return Ints.toArray(supportedTypes);
|
return Ints.toArray(mediaSourceFactorySuppliers.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated methods.
|
@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);
|
@Nullable MediaSource.Factory mediaSourceFactory = mediaSourceFactories.get(contentType);
|
||||||
if (mediaSourceFactory != null) {
|
if (mediaSourceFactory != null) {
|
||||||
return mediaSourceFactory;
|
return mediaSourceFactory;
|
||||||
}
|
}
|
||||||
@Nullable
|
Supplier<MediaSource.Factory> mediaSourceFactorySupplier = loadSupplier(contentType);
|
||||||
Supplier<MediaSource.Factory> mediaSourceFactorySupplier = maybeLoadSupplier(contentType);
|
|
||||||
if (mediaSourceFactorySupplier == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaSourceFactory = mediaSourceFactorySupplier.get();
|
mediaSourceFactory = mediaSourceFactorySupplier.get();
|
||||||
if (cmcdConfigurationFactory != null) {
|
if (cmcdConfigurationFactory != null) {
|
||||||
@ -723,55 +714,61 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
maybeLoadSupplier(C.CONTENT_TYPE_OTHER);
|
maybeLoadSupplier(C.CONTENT_TYPE_OTHER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CanIgnoreReturnValue
|
||||||
@Nullable
|
@Nullable
|
||||||
private Supplier<MediaSource.Factory> maybeLoadSupplier(@C.ContentType int contentType) {
|
private Supplier<MediaSource.Factory> maybeLoadSupplier(@C.ContentType int contentType) {
|
||||||
if (mediaSourceFactorySuppliers.containsKey(contentType)) {
|
try {
|
||||||
return mediaSourceFactorySuppliers.get(contentType);
|
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);
|
DataSource.Factory dataSourceFactory = checkNotNull(this.dataSourceFactory);
|
||||||
try {
|
Class<? extends MediaSource.Factory> clazz;
|
||||||
Class<? extends MediaSource.Factory> clazz;
|
switch (contentType) {
|
||||||
switch (contentType) {
|
case C.CONTENT_TYPE_DASH:
|
||||||
case C.CONTENT_TYPE_DASH:
|
clazz =
|
||||||
clazz =
|
Class.forName("androidx.media3.exoplayer.dash.DashMediaSource$Factory")
|
||||||
Class.forName("androidx.media3.exoplayer.dash.DashMediaSource$Factory")
|
.asSubclass(MediaSource.Factory.class);
|
||||||
.asSubclass(MediaSource.Factory.class);
|
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
break;
|
||||||
break;
|
case C.CONTENT_TYPE_SS:
|
||||||
case C.CONTENT_TYPE_SS:
|
clazz =
|
||||||
clazz =
|
Class.forName("androidx.media3.exoplayer.smoothstreaming.SsMediaSource$Factory")
|
||||||
Class.forName("androidx.media3.exoplayer.smoothstreaming.SsMediaSource$Factory")
|
.asSubclass(MediaSource.Factory.class);
|
||||||
.asSubclass(MediaSource.Factory.class);
|
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
break;
|
||||||
break;
|
case C.CONTENT_TYPE_HLS:
|
||||||
case C.CONTENT_TYPE_HLS:
|
clazz =
|
||||||
clazz =
|
Class.forName("androidx.media3.exoplayer.hls.HlsMediaSource$Factory")
|
||||||
Class.forName("androidx.media3.exoplayer.hls.HlsMediaSource$Factory")
|
.asSubclass(MediaSource.Factory.class);
|
||||||
.asSubclass(MediaSource.Factory.class);
|
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
||||||
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
|
break;
|
||||||
break;
|
case C.CONTENT_TYPE_RTSP:
|
||||||
case C.CONTENT_TYPE_RTSP:
|
clazz =
|
||||||
clazz =
|
Class.forName("androidx.media3.exoplayer.rtsp.RtspMediaSource$Factory")
|
||||||
Class.forName("androidx.media3.exoplayer.rtsp.RtspMediaSource$Factory")
|
.asSubclass(MediaSource.Factory.class);
|
||||||
.asSubclass(MediaSource.Factory.class);
|
mediaSourceFactorySupplier = () -> newInstance(clazz);
|
||||||
mediaSourceFactorySupplier = () -> newInstance(clazz);
|
break;
|
||||||
break;
|
case C.CONTENT_TYPE_OTHER:
|
||||||
case C.CONTENT_TYPE_OTHER:
|
mediaSourceFactorySupplier =
|
||||||
mediaSourceFactorySupplier =
|
() -> new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
|
||||||
() -> new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
throw new IllegalArgumentException("Unrecognized contentType: " + contentType);
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
// Expected if the app was built without the specific module.
|
|
||||||
}
|
}
|
||||||
mediaSourceFactorySuppliers.put(contentType, mediaSourceFactorySupplier);
|
mediaSourceFactorySuppliers.put(contentType, mediaSourceFactorySupplier);
|
||||||
if (mediaSourceFactorySupplier != null) {
|
|
||||||
supportedTypes.add(contentType);
|
|
||||||
}
|
|
||||||
return mediaSourceFactorySupplier;
|
return mediaSourceFactorySupplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package androidx.media3.exoplayer.source;
|
package androidx.media3.exoplayer.source;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -176,6 +177,36 @@ public final class DefaultMediaSourceFactoryTest {
|
|||||||
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER);
|
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.
|
@SuppressWarnings("deprecation") // Testing deprecated setters.
|
||||||
@Test
|
@Test
|
||||||
public void createMediaSource_withDeprecatedAdsConfiguration_callsAdsLoader() {
|
public void createMediaSource_withDeprecatedAdsConfiguration_callsAdsLoader() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user