diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 56440638f5..215730473f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -145,6 +145,10 @@ * Add support for non-square DASH thumbnail grids ([#1300](https://github.com/androidx/media/pull/1300)). * Add support for AVIF for API 34+. +* DataSource: + * Allow `ByteArrayDataSource` to resolve a URI to a byte array during + `open()`, instead of being hard-coded at construction + ([#1405](https://github.com/androidx/media/issues/1405)). * DRM: * Allow setting a `LoadErrorHandlingPolicy` on `DefaultDrmSessionManagerProvider` diff --git a/libraries/datasource/src/main/java/androidx/media3/datasource/ByteArrayDataSource.java b/libraries/datasource/src/main/java/androidx/media3/datasource/ByteArrayDataSource.java index d62445a886..eff8e71d15 100644 --- a/libraries/datasource/src/main/java/androidx/media3/datasource/ByteArrayDataSource.java +++ b/libraries/datasource/src/main/java/androidx/media3/datasource/ByteArrayDataSource.java @@ -15,13 +15,15 @@ */ package androidx.media3.datasource; +import static androidx.media3.common.util.Assertions.checkArgument; +import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Assertions.checkStateNotNull; import static java.lang.Math.min; import android.net.Uri; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.PlaybackException; -import androidx.media3.common.util.Assertions; import androidx.media3.common.util.UnstableApi; import java.io.IOException; @@ -29,27 +31,55 @@ import java.io.IOException; @UnstableApi public final class ByteArrayDataSource extends BaseDataSource { - private final byte[] data; + /** Functional interface to resolve from {@link Uri} to {@link byte[]}. */ + public interface UriResolver { + /** + * Resolves a {@link Uri} to a {@link byte[]}. + * + *

Called during {@link DataSource#open(DataSpec)} from a loading thread, so can do blocking + * work. + * + * @return The resolved byte array. + * @throws IOException if the provided URI is not recognized, or an error occurs during + * resolution. + */ + byte[] resolve(Uri uri) throws IOException; + } + + private final UriResolver uriResolver; @Nullable private Uri uri; + @Nullable private byte[] data; private int readPosition; private int bytesRemaining; private boolean opened; /** + * Creates an instance. + * * @param data The data to be read. */ public ByteArrayDataSource(byte[] data) { + this(/* uriResolver= */ unusedUri -> data); + checkArgument(data.length > 0); + } + + /** + * Creates an instance. + * + * @param uriResolver Function to resolve from {@link Uri} to {@link byte[]} during {@link + * #open(DataSpec)}. + */ + public ByteArrayDataSource(UriResolver uriResolver) { super(/* isNetwork= */ false); - Assertions.checkNotNull(data); - Assertions.checkArgument(data.length > 0); - this.data = data; + this.uriResolver = checkNotNull(uriResolver); } @Override public long open(DataSpec dataSpec) throws IOException { - uri = dataSpec.uri; transferInitializing(dataSpec); + uri = dataSpec.uri; + data = uriResolver.resolve(uri); if (dataSpec.position > data.length) { throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } @@ -72,7 +102,7 @@ public final class ByteArrayDataSource extends BaseDataSource { } length = min(length, bytesRemaining); - System.arraycopy(data, readPosition, buffer, offset, length); + System.arraycopy(checkStateNotNull(data), readPosition, buffer, offset, length); readPosition += length; bytesRemaining -= length; bytesTransferred(length); @@ -92,5 +122,6 @@ public final class ByteArrayDataSource extends BaseDataSource { transferEnded(); } uri = null; + data = null; } } diff --git a/libraries/datasource/src/test/java/androidx/media3/datasource/ByteArrayDataSourceContractTest.java b/libraries/datasource/src/test/java/androidx/media3/datasource/ByteArrayDataSourceContractTest.java index ac26c8b703..09a0e7a7ff 100644 --- a/libraries/datasource/src/test/java/androidx/media3/datasource/ByteArrayDataSourceContractTest.java +++ b/libraries/datasource/src/test/java/androidx/media3/datasource/ByteArrayDataSourceContractTest.java @@ -20,53 +20,45 @@ import androidx.media3.test.utils.DataSourceContractTest; import androidx.media3.test.utils.TestUtil; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; -import org.junit.Ignore; -import org.junit.Test; +import java.io.IOException; import org.junit.runner.RunWith; /** {@link DataSource} contract tests for {@link ByteArrayDataSource}. */ @RunWith(AndroidJUnit4.class) public class ByteArrayDataSourceContractTest extends DataSourceContractTest { - private static final byte[] DATA = TestUtil.buildTestData(20); + private static final Uri URI_1 = Uri.parse("uri1"); + private static final byte[] DATA_1 = TestUtil.buildTestData(20); + private static final Uri URI_2 = Uri.parse("uri2"); + private static final byte[] DATA_2 = TestUtil.buildTestData(10); @Override protected ImmutableList getTestResources() { return ImmutableList.of( + new TestResource.Builder().setName("data-1").setUri(URI_1).setExpectedBytes(DATA_1).build(), new TestResource.Builder() - .setName("simple") - .setUri(Uri.EMPTY) - .setExpectedBytes(DATA) + .setName("data-2") + .setUri(URI_2) + .setExpectedBytes(DATA_2) .build()); } @Override protected Uri getNotFoundUri() { - throw new UnsupportedOperationException(); + return Uri.parse("not-found"); } @Override protected DataSource createDataSource() { - return new ByteArrayDataSource(DATA); + return new ByteArrayDataSource( + uri -> { + if (uri.equals(URI_1)) { + return DATA_1; + } else if (uri.equals(URI_2)) { + return DATA_2; + } else { + throw new IOException("Unrecognized URI: " + uri); + } + }); } - - @Override - @Test - @Ignore - public void resourceNotFound() {} - - @Override - @Test - @Ignore - public void resourceNotFound_transferListenerCallbacks() {} - - @Override - @Test - @Ignore - public void getUri_resourceNotFound_returnsNullIfNotOpened() throws Exception {} - - @Override - @Test - @Ignore - public void getResponseHeaders_resourceNotFound_isEmptyWhileNotOpen() throws Exception {} }