Add MediaDataSourceAdapter

Added a new data source which acts an adapter to read media data from platform `MediaDataSource`. This enables adding the `setDataSource(MediaDataSource)` API to `MediaExtractorCompat`.

PiperOrigin-RevId: 657564901
This commit is contained in:
rohks 2024-07-30 06:13:02 -07:00 committed by Copybara-Service
parent 867e9ea2da
commit 8b7b1b51a9
3 changed files with 249 additions and 0 deletions

View File

@ -0,0 +1,102 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.datasource;
import static java.lang.Math.min;
import android.media.MediaDataSource;
import android.net.Uri;
import androidx.media3.common.C;
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 org.junit.runner.RunWith;
/** {@link DataSource} contract tests for {@link MediaDataSourceAdapter}. */
@RunWith(AndroidJUnit4.class)
public class MediaDataSourceAdapterContractTest extends DataSourceContractTest {
private static final byte[] DATA = TestUtil.buildTestData(20);
@Override
protected DataSource createDataSource() {
MediaDataSource mediaDataSource =
new MediaDataSource() {
@Override
public int readAt(long position, byte[] buffer, int offset, int size) {
if (size == 0) {
return 0;
}
if (position > getSize()) {
return C.RESULT_END_OF_INPUT;
}
size = min(size, (int) (getSize() - position));
System.arraycopy(DATA, (int) position, buffer, offset, size);
return size;
}
@Override
public long getSize() {
return DATA.length;
}
@Override
public void close() {}
};
return new MediaDataSourceAdapter(mediaDataSource, /* isNetwork= */ false);
}
@Override
protected ImmutableList<TestResource> getTestResources() {
return ImmutableList.of(
new TestResource.Builder()
.setName("simple")
.setUri(Uri.EMPTY)
.setExpectedBytes(DATA)
.build());
}
@Override
protected Uri getNotFoundUri() {
throw new UnsupportedOperationException();
}
@Override
@Test
@Ignore
public void resourceNotFound() {}
@Override
@Test
@Ignore
public void resourceNotFound_transferListenerCallbacks() {}
@Override
@Test
@Ignore
public void getUri_resourceNotFound_returnsNullIfNotOpened() {}
@Override
@Test
@Ignore
public void getResponseHeaders_resourceNotFound_isEmptyWhileNotOpen() {}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.datasource;
import static java.lang.Math.min;
import android.media.MediaDataSource;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.UnstableApi;
import java.io.IOException;
/**
* A {@link DataSource} for reading from a {@link MediaDataSource}.
*
* <p>An adapter that allows to read media data supplied by an implementation of {@link
* MediaDataSource}.
*/
@RequiresApi(23)
@UnstableApi
public class MediaDataSourceAdapter extends BaseDataSource {
private final MediaDataSource mediaDataSource;
@Nullable private Uri uri;
private long position;
private long bytesRemaining;
private boolean opened;
/**
* Creates an instance.
*
* @param mediaDataSource The {@link MediaDataSource} from which to read.
* @param isNetwork Whether the data source loads data through a network.
*/
public MediaDataSourceAdapter(MediaDataSource mediaDataSource, boolean isNetwork) {
super(isNetwork);
this.mediaDataSource = mediaDataSource;
}
@Override
public long open(DataSpec dataSpec) throws IOException {
uri = dataSpec.uri;
position = dataSpec.position;
transferInitializing(dataSpec);
if (mediaDataSource.getSize() != C.LENGTH_UNSET && position > mediaDataSource.getSize()) {
throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
}
if (mediaDataSource.getSize() == C.LENGTH_UNSET) {
bytesRemaining = C.LENGTH_UNSET;
} else {
bytesRemaining = mediaDataSource.getSize() - position;
}
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining =
bytesRemaining == C.LENGTH_UNSET ? dataSpec.length : min(bytesRemaining, dataSpec.length);
}
opened = true;
transferStarted(dataSpec);
return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining;
}
@Override
public int read(byte[] buffer, int offset, int length) throws DataSourceException {
if (length == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? length : (int) min(bytesRemaining, length);
int bytesRead;
try {
bytesRead = mediaDataSource.readAt(position, buffer, offset, bytesToRead);
} catch (IOException e) {
throw new DataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
}
if (bytesRead == -1) {
return C.RESULT_END_OF_INPUT;
}
position += bytesRead;
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
bytesTransferred(bytesRead);
return bytesRead;
}
@Nullable
@Override
public Uri getUri() {
return uri;
}
@Override
public void close() throws IOException {
uri = null;
if (opened) {
opened = false;
transferEnded();
}
}
}

View File

@ -24,12 +24,14 @@ import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.media.MediaDataSource;
import android.media.MediaExtractor; import android.media.MediaExtractor;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.net.Uri; import android.net.Uri;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
@ -44,6 +46,7 @@ import androidx.media3.datasource.DataSourceUtil;
import androidx.media3.datasource.DataSpec; import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.FileDescriptorDataSource; import androidx.media3.datasource.FileDescriptorDataSource;
import androidx.media3.datasource.MediaDataSourceAdapter;
import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import androidx.media3.exoplayer.source.SampleQueue; import androidx.media3.exoplayer.source.SampleQueue;
@ -321,6 +324,24 @@ public final class MediaExtractorCompat {
dataSourceFactory.createDataSource(), buildDataSpec(Uri.parse(path), /* position= */ 0)); dataSourceFactory.createDataSource(), buildDataSpec(Uri.parse(path), /* position= */ 0));
} }
/**
* Sets the data source using the media stream obtained from the given {@link MediaDataSource}.
*
* @param mediaDataSource The {@link MediaDataSource} to extract media from.
* @throws IOException If an error occurs while extracting the media.
* @throws UnrecognizedInputFormatException If none of the available extractors successfully
* sniffs the input.
* @throws IllegalStateException If this method is called twice on the same instance.
*/
@RequiresApi(23)
public void setDataSource(MediaDataSource mediaDataSource) throws IOException {
// MediaDataSourceAdapter is created privately here, so TransferListeners cannot be registered.
// Therefore, the isNetwork parameter is hardcoded to false as it has no effect.
MediaDataSourceAdapter mediaDataSourceAdapter =
new MediaDataSourceAdapter(mediaDataSource, /* isNetwork= */ false);
prepareDataSource(mediaDataSourceAdapter, buildDataSpec(Uri.EMPTY, /* position= */ 0));
}
private void prepareDataSource(DataSource dataSource, DataSpec dataSpec) throws IOException { private void prepareDataSource(DataSource dataSource, DataSpec dataSpec) throws IOException {
// Assert that this instance is not being re-prepared, which is not currently supported. // Assert that this instance is not being re-prepared, which is not currently supported.
Assertions.checkState(!hasBeenPrepared); Assertions.checkState(!hasBeenPrepared);