mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add APIs to set data source using content URI, file path or HTTP URL
Added three `setDataSource` APIs in `MediaExtractorCompat`: - `setDataSource(Context context, Uri uri, @Nullable Map<String, String> headers)` to set data source with a content URI and optional headers. - `setDataSource(String path)` to set data source using a file path or HTTP URL. - `setDataSource(String path, @Nullable Map<String, String> headers)` to set data source using a file path or HTTP URL with optional headers. PiperOrigin-RevId: 657563973
This commit is contained in:
parent
ca5a26a409
commit
867e9ea2da
@ -21,6 +21,7 @@ import static androidx.media3.exoplayer.source.SampleStream.FLAG_OMIT_SAMPLE_DAT
|
|||||||
import static androidx.media3.exoplayer.source.SampleStream.FLAG_PEEK;
|
import static androidx.media3.exoplayer.source.SampleStream.FLAG_PEEK;
|
||||||
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
||||||
|
|
||||||
|
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.MediaExtractor;
|
import android.media.MediaExtractor;
|
||||||
@ -70,6 +71,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -77,6 +79,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
|
|
||||||
@ -132,6 +135,7 @@ public final class MediaExtractorCompat {
|
|||||||
@Nullable private SeekMap seekMap;
|
@Nullable private SeekMap seekMap;
|
||||||
private boolean tracksEnded;
|
private boolean tracksEnded;
|
||||||
private int upstreamFormatsCount;
|
private int upstreamFormatsCount;
|
||||||
|
@Nullable private Map<String, String> httpRequestHeaders;
|
||||||
|
|
||||||
/** Creates a new instance. */
|
/** Creates a new instance. */
|
||||||
public MediaExtractorCompat(Context context) {
|
public MediaExtractorCompat(Context context) {
|
||||||
@ -151,6 +155,10 @@ public final class MediaExtractorCompat {
|
|||||||
* <li>{@link #setDataSource(FileDescriptor)}
|
* <li>{@link #setDataSource(FileDescriptor)}
|
||||||
* <li>{@link #setDataSource(FileDescriptor, long, long)}
|
* <li>{@link #setDataSource(FileDescriptor, long, long)}
|
||||||
* </ul>
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Note: The {@link DataSource.Factory} provided may not be used to generate {@link DataSource}
|
||||||
|
* when setting data source using method {@link #setDataSource(Context, Uri, Map)} as the behavior
|
||||||
|
* depends on the fallthrough logic related to the scheme of the provided URI.
|
||||||
*/
|
*/
|
||||||
public MediaExtractorCompat(
|
public MediaExtractorCompat(
|
||||||
ExtractorsFactory extractorsFactory, DataSource.Factory dataSourceFactory) {
|
ExtractorsFactory extractorsFactory, DataSource.Factory dataSourceFactory) {
|
||||||
@ -241,8 +249,76 @@ public final class MediaExtractorCompat {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
FileDescriptorDataSource fileDescriptorDataSource =
|
FileDescriptorDataSource fileDescriptorDataSource =
|
||||||
new FileDescriptorDataSource(fileDescriptor, offset, length);
|
new FileDescriptorDataSource(fileDescriptor, offset, length);
|
||||||
DataSpec dataSpec = new DataSpec(Uri.EMPTY);
|
prepareDataSource(fileDescriptorDataSource, buildDataSpec(Uri.EMPTY, /* position= */ 0));
|
||||||
prepareDataSource(fileDescriptorDataSource, dataSpec);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data source using the media stream obtained from the given {@linkplain Uri content
|
||||||
|
* URI} and optional HTTP request headers.
|
||||||
|
*
|
||||||
|
* @param context The {@link Context} used to resolve the {@link Uri}.
|
||||||
|
* @param uri The {@linkplain Uri content URI} of the media to extract from.
|
||||||
|
* @param headers An optional {@link Map} of HTTP request headers to include when fetching the
|
||||||
|
* data, or {@code null} if no headers are to be included.
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
public void setDataSource(Context context, Uri uri, @Nullable Map<String, String> headers)
|
||||||
|
throws IOException {
|
||||||
|
String scheme = uri.getScheme();
|
||||||
|
String path = uri.getPath();
|
||||||
|
if ((scheme == null || scheme.equals("file")) && path != null) {
|
||||||
|
// If the URI scheme is null or file, treat it as a local file path
|
||||||
|
setDataSource(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
try (AssetFileDescriptor assetFileDescriptor = resolver.openAssetFileDescriptor(uri, "r")) {
|
||||||
|
if (assetFileDescriptor != null) {
|
||||||
|
// If the URI points to a content provider resource, use the AssetFileDescriptor
|
||||||
|
setDataSource(assetFileDescriptor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (SecurityException | FileNotFoundException e) {
|
||||||
|
// Fall back to using the URI as a string if the file is not found or the mode is invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the URI is an HTTP URL and use it with optional headers
|
||||||
|
setDataSource(uri.toString(), headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data source using the media stream obtained from the given file path or HTTP URL.
|
||||||
|
*
|
||||||
|
* @param path The path of the file, or the HTTP URL, 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.
|
||||||
|
*/
|
||||||
|
public void setDataSource(String path) throws IOException {
|
||||||
|
setDataSource(path, /* headers= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data source using the media stream obtained from the given file path or HTTP URL, with
|
||||||
|
* optional HTTP request headers.
|
||||||
|
*
|
||||||
|
* @param path The path of the file, or the HTTP URL, to extract media from.
|
||||||
|
* @param headers An optional {@link Map} of HTTP request headers to include when fetching the
|
||||||
|
* data, or {@code null} if no headers are to be included.
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
public void setDataSource(String path, @Nullable Map<String, String> headers) throws IOException {
|
||||||
|
httpRequestHeaders = headers;
|
||||||
|
prepareDataSource(
|
||||||
|
dataSourceFactory.createDataSource(), buildDataSpec(Uri.parse(path), /* position= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareDataSource(DataSource dataSource, DataSpec dataSpec) throws IOException {
|
private void prepareDataSource(DataSource dataSource, DataSpec dataSpec) throws IOException {
|
||||||
@ -658,13 +734,18 @@ public final class MediaExtractorCompat {
|
|||||||
* <p>The created {@link DataSpec} disables caching if the content length cannot be resolved,
|
* <p>The created {@link DataSpec} disables caching if the content length cannot be resolved,
|
||||||
* since this is indicative of a progressive live stream.
|
* since this is indicative of a progressive live stream.
|
||||||
*/
|
*/
|
||||||
private static DataSpec buildDataSpec(Uri uri, long position) {
|
private DataSpec buildDataSpec(Uri uri, long position) {
|
||||||
return new DataSpec.Builder()
|
DataSpec.Builder dataSpec =
|
||||||
|
new DataSpec.Builder()
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
.setPosition(position)
|
.setPosition(position)
|
||||||
.setFlags(
|
.setFlags(
|
||||||
DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN | DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)
|
DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN
|
||||||
.build();
|
| DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION);
|
||||||
|
if (httpRequestHeaders != null) {
|
||||||
|
dataSpec.setHttpRequestHeaders(httpRequestHeaders);
|
||||||
|
}
|
||||||
|
return dataSpec.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ExtractorOutputImpl implements ExtractorOutput {
|
private final class ExtractorOutputImpl implements ExtractorOutput {
|
||||||
|
@ -14,6 +14,15 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<manifest package="androidx.media3.exoplayer.test">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" package="androidx.media3.exoplayer.test">
|
||||||
<uses-sdk/>
|
<uses-sdk/>
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
|
||||||
|
<provider
|
||||||
|
android:authorities="androidx.media3.test.utils.AssetContentProvider"
|
||||||
|
android:name="androidx.media3.test.utils.AssetContentProvider"/>
|
||||||
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -18,6 +18,7 @@ package androidx.media3.exoplayer;
|
|||||||
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.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
@ -36,23 +37,37 @@ import androidx.media3.extractor.SeekMap;
|
|||||||
import androidx.media3.extractor.SeekMap.SeekPoints;
|
import androidx.media3.extractor.SeekMap.SeekPoints;
|
||||||
import androidx.media3.extractor.SeekPoint;
|
import androidx.media3.extractor.SeekPoint;
|
||||||
import androidx.media3.extractor.TrackOutput;
|
import androidx.media3.extractor.TrackOutput;
|
||||||
|
import androidx.media3.test.utils.AssetContentProvider;
|
||||||
|
import androidx.media3.test.utils.TestUtil;
|
||||||
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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
|
import okio.Buffer;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/** Tests for {@link MediaExtractorCompat}. */
|
/** Tests for {@link MediaExtractorCompat}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class MediaExtractorCompatTest {
|
public class MediaExtractorCompatTest {
|
||||||
|
|
||||||
|
@Rule public final TemporaryFolder tempFolder = new TemporaryFolder();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Placeholder data URI which saves us from mocking the data source which MediaExtractorCompat
|
* Placeholder data URI which saves us from mocking the data source which MediaExtractorCompat
|
||||||
* uses.
|
* uses.
|
||||||
@ -484,6 +499,55 @@ public class MediaExtractorCompatTest {
|
|||||||
assertThat(mediaExtractorCompat.getSampleTime()).isEqualTo(7);
|
assertThat(mediaExtractorCompat.getSampleTime()).isEqualTo(7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
setDataSourceUsingMethodExpectingContentUri_useAbsoluteFilePathAsUri_setsTrackCountCorrectly()
|
||||||
|
throws IOException {
|
||||||
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
byte[] fileData = TestUtil.getByteArray(context, /* fileName= */ "media/mp4/sample.mp4");
|
||||||
|
File file = tempFolder.newFile();
|
||||||
|
Files.write(Paths.get(file.getAbsolutePath()), fileData);
|
||||||
|
MediaExtractorCompat mediaExtractorCompat = new MediaExtractorCompat(context);
|
||||||
|
|
||||||
|
mediaExtractorCompat.setDataSource(
|
||||||
|
context, Uri.parse(file.getAbsolutePath()), /* headers= */ null);
|
||||||
|
|
||||||
|
assertThat(mediaExtractorCompat.getTrackCount()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
setDataSourceUsingMethodExpectingContentUri_useHttpUri_setsTrackCountAndHeadersCorrectly()
|
||||||
|
throws Exception {
|
||||||
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
byte[] fileData = TestUtil.getByteArray(context, /* fileName= */ "media/mp4/sample.mp4");
|
||||||
|
try (MockWebServer mockWebServer = new MockWebServer()) {
|
||||||
|
mockWebServer.enqueue(new MockResponse().setBody(new Buffer().write(fileData)));
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
headers.put("k", "v");
|
||||||
|
MediaExtractorCompat mediaExtractorCompat = new MediaExtractorCompat(context);
|
||||||
|
|
||||||
|
mediaExtractorCompat.setDataSource(
|
||||||
|
context, Uri.parse(mockWebServer.url("/test-path").toString()), headers);
|
||||||
|
|
||||||
|
assertThat(mediaExtractorCompat.getTrackCount()).isEqualTo(2);
|
||||||
|
assertThat(mockWebServer.takeRequest().getHeaders().get("k")).isEqualTo("v");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDataSourceUsingMethodExpectingContentUri_useContentUri_setsTrackCountCorrectly()
|
||||||
|
throws IOException {
|
||||||
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
Uri contentUri =
|
||||||
|
AssetContentProvider.buildUri(/* filePath= */ "media/mp4/sample.mp4", /* pipeMode= */ true);
|
||||||
|
MediaExtractorCompat mediaExtractorCompat = new MediaExtractorCompat(context);
|
||||||
|
|
||||||
|
mediaExtractorCompat.setDataSource(context, contentUri, /* headers= */ null);
|
||||||
|
|
||||||
|
assertThat(mediaExtractorCompat.getTrackCount()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void assertReadSample(int trackIndex, long timeUs, byte... sampleData) {
|
private void assertReadSample(int trackIndex, long timeUs, byte... sampleData) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user