Fix setDataSourceFactory handling in DefaultMediaSourceFactory

The call doesn't currently reset the already loaded suppliers and
factories. Also fix the supplier loading code to use a local copy
of the current dataSourceFactory to avoid leaking an updated
instance to a later invocation.

Issue: androidx/media#116

#minor-release

PiperOrigin-RevId: 460721541
This commit is contained in:
tonihei 2022-07-13 15:27:55 +00:00 committed by Rohit Singh
parent 9a616c0cee
commit adc50515e9
6 changed files with 173 additions and 6 deletions

View File

@ -17,6 +17,9 @@
* Use `SingleThreadExecutor` for releasing `AudioTrack` instances to avoid * Use `SingleThreadExecutor` for releasing `AudioTrack` instances to avoid
OutOfMemory errors when releasing multiple players at the same time OutOfMemory errors when releasing multiple players at the same time
([#10057](https://github.com/google/ExoPlayer/issues/10057)). ([#10057](https://github.com/google/ExoPlayer/issues/10057)).
* Fix implementation of `setDataSourceFactory` in
`DefaultMediaSourceFactory`, which was non-functional in some cases
([#116](https://github.com/androidx/media/issues/116)).
* Extractors: * Extractors:
* Add support for AVI * Add support for AVI
([#2092](https://github.com/google/ExoPlayer/issues/2092)). ([#2092](https://github.com/google/ExoPlayer/issues/2092)).

View File

@ -282,6 +282,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
*/ */
public DefaultMediaSourceFactory setDataSourceFactory(DataSource.Factory dataSourceFactory) { public DefaultMediaSourceFactory setDataSourceFactory(DataSource.Factory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
delegateFactoryLoader.setDataSourceFactory(dataSourceFactory);
return this; return this;
} }
@ -594,6 +595,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
// TODO(b/233577470): Call MediaSource.Factory.setDataSourceFactory on each value when it // TODO(b/233577470): Call MediaSource.Factory.setDataSourceFactory on each value when it
// exists on the interface. // exists on the interface.
mediaSourceFactorySuppliers.clear();
mediaSourceFactories.clear(); mediaSourceFactories.clear();
} }
} }
@ -627,6 +629,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
} }
@Nullable Supplier<MediaSource.Factory> mediaSourceFactorySupplier = null; @Nullable Supplier<MediaSource.Factory> mediaSourceFactorySupplier = null;
DataSource.Factory dataSourceFactory = checkNotNull(this.dataSourceFactory);
try { try {
Class<? extends MediaSource.Factory> clazz; Class<? extends MediaSource.Factory> clazz;
switch (contentType) { switch (contentType) {
@ -634,19 +637,19 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
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, checkNotNull(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, checkNotNull(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, checkNotNull(dataSourceFactory)); mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
break; break;
case C.CONTENT_TYPE_RTSP: case C.CONTENT_TYPE_RTSP:
clazz = clazz =
@ -656,9 +659,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
break; break;
case C.CONTENT_TYPE_OTHER: case C.CONTENT_TYPE_OTHER:
mediaSourceFactorySupplier = mediaSourceFactorySupplier =
() -> () -> new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
new ProgressiveMediaSource.Factory(
checkNotNull(dataSourceFactory), extractorsFactory);
break; break;
default: default:
// Do nothing. // Do nothing.

View File

@ -15,16 +15,21 @@
*/ */
package androidx.media3.exoplayer.dash; package androidx.media3.exoplayer.dash;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.test.utils.FakeDataSource;
import androidx.media3.test.utils.robolectric.RobolectricUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -82,4 +87,53 @@ public class DefaultMediaSourceFactoryTest {
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_DASH); assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_DASH);
} }
@Test
public void createMediaSource_withSetDataSourceFactory_usesDataSourceFactory() throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext())
.setDataSourceFactory(() -> fakeDataSource);
prepareDashUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
@Test
public void
createMediaSource_usingDefaultDataSourceFactoryAndSetDataSourceFactory_usesUpdatesDataSourceFactory()
throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext());
// Use default DataSource.Factory first.
prepareDashUrlAndWaitForPrepareError(defaultMediaSourceFactory);
defaultMediaSourceFactory.setDataSourceFactory(() -> fakeDataSource);
prepareDashUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
private static void prepareDashUrlAndWaitForPrepareError(
DefaultMediaSourceFactory defaultMediaSourceFactory) throws Exception {
MediaSource mediaSource =
defaultMediaSourceFactory.createMediaSource(MediaItem.fromUri(URI_MEDIA + "/file.mpd"));
getInstrumentation()
.runOnMainSync(
() ->
mediaSource.prepareSource(
(source, timeline) -> {}, /* mediaTransferListener= */ null, PlayerId.UNSET));
// We don't expect this to prepare successfully.
RobolectricUtil.runMainLooperUntil(
() -> {
try {
mediaSource.maybeThrowSourceInfoRefreshError();
return false;
} catch (IOException e) {
return true;
}
});
}
} }

View File

@ -15,16 +15,21 @@
*/ */
package androidx.media3.exoplayer.hls; package androidx.media3.exoplayer.hls;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.test.utils.FakeDataSource;
import androidx.media3.test.utils.robolectric.RobolectricUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -82,4 +87,53 @@ public class DefaultMediaSourceFactoryTest {
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_HLS); assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_HLS);
} }
@Test
public void createMediaSource_withSetDataSourceFactory_usesDataSourceFactory() throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext())
.setDataSourceFactory(() -> fakeDataSource);
prepareHlsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
@Test
public void
createMediaSource_usingDefaultDataSourceFactoryAndSetDataSourceFactory_usesUpdatesDataSourceFactory()
throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext());
// Use default DataSource.Factory first.
prepareHlsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
defaultMediaSourceFactory.setDataSourceFactory(() -> fakeDataSource);
prepareHlsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
private static void prepareHlsUrlAndWaitForPrepareError(
DefaultMediaSourceFactory defaultMediaSourceFactory) throws Exception {
MediaSource mediaSource =
defaultMediaSourceFactory.createMediaSource(MediaItem.fromUri(URI_MEDIA + "/file.m3u8"));
getInstrumentation()
.runOnMainSync(
() ->
mediaSource.prepareSource(
(source, timeline) -> {}, /* mediaTransferListener= */ null, PlayerId.UNSET));
// We don't expect this to prepare successfully.
RobolectricUtil.runMainLooperUntil(
() -> {
try {
mediaSource.maybeThrowSourceInfoRefreshError();
return false;
} catch (IOException e) {
return true;
}
});
}
} }

View File

@ -29,6 +29,7 @@ dependencies {
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
testImplementation project(modulePrefix + 'test-utils-robolectric')
testImplementation project(modulePrefix + 'test-utils') testImplementation project(modulePrefix + 'test-utils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion testImplementation 'org.robolectric:robolectric:' + robolectricVersion
} }

View File

@ -15,16 +15,21 @@
*/ */
package androidx.media3.exoplayer.smoothstreaming; package androidx.media3.exoplayer.smoothstreaming;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.test.utils.FakeDataSource;
import androidx.media3.test.utils.robolectric.RobolectricUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -93,4 +98,53 @@ public class DefaultMediaSourceFactoryTest {
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_SS); assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_SS);
} }
@Test
public void createMediaSource_withSetDataSourceFactory_usesDataSourceFactory() throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext())
.setDataSourceFactory(() -> fakeDataSource);
prepareSsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
@Test
public void
createMediaSource_usingDefaultDataSourceFactoryAndSetDataSourceFactory_usesUpdatesDataSourceFactory()
throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext());
// Use default DataSource.Factory first.
prepareSsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
defaultMediaSourceFactory.setDataSourceFactory(() -> fakeDataSource);
prepareSsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
private static void prepareSsUrlAndWaitForPrepareError(
DefaultMediaSourceFactory defaultMediaSourceFactory) throws Exception {
MediaSource mediaSource =
defaultMediaSourceFactory.createMediaSource(MediaItem.fromUri(URI_MEDIA + "/file.ism"));
getInstrumentation()
.runOnMainSync(
() ->
mediaSource.prepareSource(
(source, timeline) -> {}, /* mediaTransferListener= */ null, PlayerId.UNSET));
// We don't expect this to prepare successfully.
RobolectricUtil.runMainLooperUntil(
() -> {
try {
mediaSource.maybeThrowSourceInfoRefreshError();
return false;
} catch (IOException e) {
return true;
}
});
}
} }