Allow ByteArrayDataSource to resolve the byte array when opened

This is a relatively small change, and massively simplifies the work
needed for an app to consume Kotlin Multiplatform resources (without a
full `KmpResourceDataSource` implementation, which poses some
dependency challenges for now).

Issue: androidx/media#1405
PiperOrigin-RevId: 638991375
This commit is contained in:
ibaker 2024-05-31 04:31:04 -07:00 committed by Copybara-Service
parent ac34798344
commit 4dd8360693
3 changed files with 62 additions and 35 deletions

View File

@ -145,6 +145,10 @@
* Add support for non-square DASH thumbnail grids * Add support for non-square DASH thumbnail grids
([#1300](https://github.com/androidx/media/pull/1300)). ([#1300](https://github.com/androidx/media/pull/1300)).
* Add support for AVIF for API 34+. * 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: * DRM:
* Allow setting a `LoadErrorHandlingPolicy` on * Allow setting a `LoadErrorHandlingPolicy` on
`DefaultDrmSessionManagerProvider` `DefaultDrmSessionManagerProvider`

View File

@ -15,13 +15,15 @@
*/ */
package androidx.media3.datasource; 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 static java.lang.Math.min;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.io.IOException; import java.io.IOException;
@ -29,27 +31,55 @@ import java.io.IOException;
@UnstableApi @UnstableApi
public final class ByteArrayDataSource extends BaseDataSource { 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[]}.
*
* <p>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 Uri uri;
@Nullable private byte[] data;
private int readPosition; private int readPosition;
private int bytesRemaining; private int bytesRemaining;
private boolean opened; private boolean opened;
/** /**
* Creates an instance.
*
* @param data The data to be read. * @param data The data to be read.
*/ */
public ByteArrayDataSource(byte[] data) { 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); super(/* isNetwork= */ false);
Assertions.checkNotNull(data); this.uriResolver = checkNotNull(uriResolver);
Assertions.checkArgument(data.length > 0);
this.data = data;
} }
@Override @Override
public long open(DataSpec dataSpec) throws IOException { public long open(DataSpec dataSpec) throws IOException {
uri = dataSpec.uri;
transferInitializing(dataSpec); transferInitializing(dataSpec);
uri = dataSpec.uri;
data = uriResolver.resolve(uri);
if (dataSpec.position > data.length) { if (dataSpec.position > data.length) {
throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); 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); length = min(length, bytesRemaining);
System.arraycopy(data, readPosition, buffer, offset, length); System.arraycopy(checkStateNotNull(data), readPosition, buffer, offset, length);
readPosition += length; readPosition += length;
bytesRemaining -= length; bytesRemaining -= length;
bytesTransferred(length); bytesTransferred(length);
@ -92,5 +122,6 @@ public final class ByteArrayDataSource extends BaseDataSource {
transferEnded(); transferEnded();
} }
uri = null; uri = null;
data = null;
} }
} }

View File

@ -20,53 +20,45 @@ import androidx.media3.test.utils.DataSourceContractTest;
import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.TestUtil;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.Ignore; import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** {@link DataSource} contract tests for {@link ByteArrayDataSource}. */ /** {@link DataSource} contract tests for {@link ByteArrayDataSource}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class ByteArrayDataSourceContractTest extends DataSourceContractTest { 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 @Override
protected ImmutableList<TestResource> getTestResources() { protected ImmutableList<TestResource> getTestResources() {
return ImmutableList.of( return ImmutableList.of(
new TestResource.Builder().setName("data-1").setUri(URI_1).setExpectedBytes(DATA_1).build(),
new TestResource.Builder() new TestResource.Builder()
.setName("simple") .setName("data-2")
.setUri(Uri.EMPTY) .setUri(URI_2)
.setExpectedBytes(DATA) .setExpectedBytes(DATA_2)
.build()); .build());
} }
@Override @Override
protected Uri getNotFoundUri() { protected Uri getNotFoundUri() {
throw new UnsupportedOperationException(); return Uri.parse("not-found");
} }
@Override @Override
protected DataSource createDataSource() { 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 {}
} }