Implement HttpEngineDataSource, a HttpDataSource using the [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine) API.

HttpEngine was added in Android SDK 34. This DataSource is preferable to the DefaultHttpDataSource if supported as it offers better performance and more modern features.

PiperOrigin-RevId: 574594553
This commit is contained in:
okunhardt 2023-10-18 14:15:42 -07:00 committed by Copybara-Service
parent c0759a4e62
commit fa8dc4f49a
12 changed files with 3241 additions and 0 deletions

View File

@ -45,6 +45,10 @@
* Downloads:
* OkHttp Extension:
* Cronet Extension:
* HttpEngine Extension:
* Implement `HttpEngineDataSource`, an `HttpDataSource` using the
[HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine)
API.
* RTMP Extension:
* HLS Extension:
* Refresh the HLS live playlist with an interval calculated from the last

View File

@ -0,0 +1,56 @@
# HttpEngine DataSource module
This module provides an [HttpDataSource][] implementation that uses
[HttpEngine][].
HttpEngine uses best HTTP stack available on the current platform. HttpEngine
was added in API level 34.
[HttpDataSource]: ../datasource/src/main/java/androidx/media3/datasource/HttpDataSource.java
[HttpEngine]: https://developer.android.com/reference/android/net/http/HttpEngine
## Getting the module
The easiest way to get the module is to add it as a gradle dependency:
```gradle
implementation 'androidx.media3:media3-datasource-httpengine:1.X.X'
```
where `1.X.X` is the version, which must match the version of the other media
modules being used.
Alternatively, you can clone this GitHub project and depend on the module
locally. Instructions for doing this can be found in the [top level README][].
[top level README]: ../../README.md
## Using the module
Media components request data through `DataSource` instances. These instances
are obtained from instances of `DataSource.Factory`, which are instantiated and
injected from application code.
If your application only needs to play http(s) content and the device is running
at least API level 34, using the HttpEngine extension is as simple as updating
`DataSource.Factory` instantiations in your application code to use
`HttpEngineDataSource.Factory`. If your application also needs to play
non-http(s) content such as local files, use:
```
new DefaultDataSource.Factory(
...
/* baseDataSourceFactory= */ new HttpEngineDataSource.Factory(...) );
```
## Cronet implementations
To instantiate an `HttpEngineDataSource.Factory` you'll need an `HttpEngine`. A
`HttpEngine` can be obtained from the platform in API level 34 or greater. It's
recommended that an application should only have a single `HttpEngine` instance.
## Links
* [Javadoc][]
[Javadoc]: https://developer.android.com/reference/androidx/media3/datasource/httpengine/package-summary

View File

@ -0,0 +1,50 @@
// Copyright (C) 2023 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.
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
android {
namespace 'androidx.media3.datasource.httpengine'
compileSdk 34
defaultConfig {
minSdk 34
}
publishing {
singleVariant('release') {
withSourcesJar()
}
}
}
dependencies {
implementation project(modulePrefix + 'lib-common')
implementation project(modulePrefix + 'lib-datasource')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'com.google.errorprone:error_prone_annotations:' + errorProneVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestImplementation project(modulePrefix + 'test-utils')
testImplementation project(modulePrefix + 'test-utils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
ext {
releaseArtifactId = 'media3-datasource-httpengine'
releaseName = 'Media3 HttpEngine DataSource module'
}
apply from: '../../publish.gradle'

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.media3.datasource.httpengine.test">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk/>
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:usesCleartextTraffic="true"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode"/>
<instrumentation
android:targetPackage="androidx.media3.datasource.httpengine.test"
android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest>

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 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.httpengine;
import android.net.Uri;
import android.net.http.HttpEngine;
import androidx.media3.datasource.DataSource;
import androidx.media3.test.utils.DataSourceContractTest;
import androidx.media3.test.utils.HttpDataSourceTestEnv;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.After;
import org.junit.Rule;
import org.junit.runner.RunWith;
/** {@link DataSource} contract tests for {@link HttpEngineDataSource}. */
@RunWith(AndroidJUnit4.class)
public class HttpEngineDataSourceContractTest extends DataSourceContractTest {
@Rule public HttpDataSourceTestEnv httpDataSourceTestEnv = new HttpDataSourceTestEnv();
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
@After
public void tearDown() {
executorService.shutdown();
}
@Override
protected DataSource createDataSource() {
HttpEngine httpEngine =
new HttpEngine.Builder(ApplicationProvider.getApplicationContext()).build();
return new HttpEngineDataSource.Factory(httpEngine, executorService).createDataSource();
}
@Override
protected ImmutableList<TestResource> getTestResources() {
return httpDataSourceTestEnv.getServedResources();
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
}
}

View File

@ -0,0 +1,22 @@
<!-- Copyright (C) 2023 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.media3.datasource.httpengine">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk />
</manifest>

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 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.httpengine;
import static java.lang.Math.min;
import android.net.http.UploadDataProvider;
import android.net.http.UploadDataSink;
import androidx.annotation.RequiresApi;
import java.io.IOException;
import java.nio.ByteBuffer;
/** A {@link UploadDataProvider} implementation that provides data from a {@code byte[]}. */
@RequiresApi(34)
/* package */ final class ByteArrayUploadDataProvider extends UploadDataProvider {
private final byte[] data;
private int position;
public ByteArrayUploadDataProvider(byte[] data) {
this.data = data;
}
@Override
public long getLength() {
return data.length;
}
@Override
public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException {
int readLength = min(byteBuffer.remaining(), data.length - position);
byteBuffer.put(data, position, readLength);
position += readLength;
uploadDataSink.onReadSucceeded(false);
}
@Override
public void rewind(UploadDataSink uploadDataSink) throws IOException {
position = 0;
uploadDataSink.onRewindSucceeded();
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2023 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.
*/
@NonNullApi
package androidx.media3.datasource.httpengine;
import androidx.media3.common.util.NonNullApi;

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<manifest package="androidx.media3.datasource.httpengine.test">
<uses-sdk/>
</manifest>

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2023 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.httpengine;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.net.http.UploadDataSink;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
/** Tests for {@link ByteArrayUploadDataProvider}. */
@RunWith(AndroidJUnit4.class)
@Config(sdk = 34)
public final class ByteArrayUploadDataProviderTest {
private static final byte[] TEST_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
@Mock private UploadDataSink mockUploadDataSink;
private ByteBuffer byteBuffer;
private ByteArrayUploadDataProvider byteArrayUploadDataProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
byteBuffer = ByteBuffer.allocate(TEST_DATA.length);
byteArrayUploadDataProvider = new ByteArrayUploadDataProvider(TEST_DATA);
}
@Test
public void getLength() {
assertThat(byteArrayUploadDataProvider.getLength()).isEqualTo(TEST_DATA.length);
}
@Test
public void readFullBuffer() throws IOException {
byteArrayUploadDataProvider.read(mockUploadDataSink, byteBuffer);
assertThat(byteBuffer.array()).isEqualTo(TEST_DATA);
}
@Test
public void readPartialBuffer() throws IOException {
byte[] firstHalf = Arrays.copyOf(TEST_DATA, TEST_DATA.length / 2);
byte[] secondHalf = Arrays.copyOfRange(TEST_DATA, TEST_DATA.length / 2, TEST_DATA.length);
byteBuffer = ByteBuffer.allocate(TEST_DATA.length / 2);
// Read half of the data.
byteArrayUploadDataProvider.read(mockUploadDataSink, byteBuffer);
assertThat(byteBuffer.array()).isEqualTo(firstHalf);
// Read the second half of the data.
byteBuffer.rewind();
byteArrayUploadDataProvider.read(mockUploadDataSink, byteBuffer);
assertThat(byteBuffer.array()).isEqualTo(secondHalf);
verify(mockUploadDataSink, times(2)).onReadSucceeded(false);
}
@Test
public void rewind() throws IOException {
// Read all the data.
byteArrayUploadDataProvider.read(mockUploadDataSink, byteBuffer);
assertThat(byteBuffer.array()).isEqualTo(TEST_DATA);
// Rewind and make sure it can be read again.
byteBuffer.clear();
byteArrayUploadDataProvider.rewind(mockUploadDataSink);
byteArrayUploadDataProvider.read(mockUploadDataSink, byteBuffer);
assertThat(byteBuffer.array()).isEqualTo(TEST_DATA);
verify(mockUploadDataSink).onRewindSucceeded();
}
}