From d88accd49e834dc62f5abfe3c4a163d8a3311354 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 11 Nov 2021 14:06:25 +0000 Subject: [PATCH 1/9] Merge #9678: Fix typo in Hello world documentation. PiperOrigin-RevId: 409129177 --- docs/hello-world.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello-world.md b/docs/hello-world.md index 9ad62fb617..5b71f150ff 100644 --- a/docs/hello-world.md +++ b/docs/hello-world.md @@ -126,7 +126,7 @@ may result in unexpected or obscure errors. It will be removed in ExoPlayer 2.16. {:.info} -For more information about ExoPlayer's treading model, see the +For more information about ExoPlayer's threading model, see the ["Threading model" section of the ExoPlayer Javadoc][]. ## Attaching the player to a view ## From 793e675de85345c57bf256cf9b189399e6696d7b Mon Sep 17 00:00:00 2001 From: samrobinson Date: Thu, 11 Nov 2021 15:27:55 +0000 Subject: [PATCH 2/9] Setup the initial instrumentation tests for transformer. Due to sharding, each test should be in a separate class. PiperOrigin-RevId: 409142436 --- library/transformer/build.gradle | 20 +++- .../src/androidTest/AndroidManifest.xml | 37 ++++++++ .../transformer/AndroidTestUtil.java | 91 +++++++++++++++++++ .../RemoveAudioTransformationTest.java | 37 ++++++++ .../RemoveVideoTransformationTest.java | 37 ++++++++ .../transformer/SefTransformationTest.java | 37 ++++++++ .../transformer/TransformationTest.java | 36 ++++++++ 7 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 library/transformer/src/androidTest/AndroidManifest.xml create mode 100644 library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java create mode 100644 library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveAudioTransformationTest.java create mode 100644 library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveVideoTransformationTest.java create mode 100644 library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/SefTransformationTest.java create mode 100644 library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformationTest.java diff --git a/library/transformer/build.gradle b/library/transformer/build.gradle index 32fa7695d5..28742d1f6f 100644 --- a/library/transformer/build.gradle +++ b/library/transformer/build.gradle @@ -12,15 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" - android { + + defaultConfig { + // The following argument makes the Android Test Orchestrator run its + // "pm clear" command after each test invocation. This command ensures + // that the app's state is completely cleared between tests. + testInstrumentationRunnerArguments clearPackageData: 'true' + multiDexEnabled true + } + buildTypes { debug { testCoverageEnabled = true } } - sourceSets.test.assets.srcDir '../../testdata/src/test/assets/' + sourceSets { + androidTest.assets.srcDir '../test_data/src/test/assets/' //copybara:media3-only + androidTest.assets.srcDir '../../testdata/src/test/assets/' + test.assets.srcDir '../../testdata/src/test/assets/' + } } dependencies { @@ -33,6 +45,10 @@ dependencies { testImplementation project(modulePrefix + 'testutils') testImplementation project(modulePrefix + 'testdata') testImplementation 'org.robolectric:robolectric:' + robolectricVersion + testImplementation 'com.google.truth:truth:' + truthVersion + androidTestImplementation 'junit:junit:' + junitVersion + androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion + androidTestImplementation project(modulePrefix + 'testutils') } ext { diff --git a/library/transformer/src/androidTest/AndroidManifest.xml b/library/transformer/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..c1afec1685 --- /dev/null +++ b/library/transformer/src/androidTest/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java new file mode 100644 index 0000000000..1afb81f667 --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021 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 com.google.android.exoplayer2.transformer; + +import android.content.Context; +import android.net.Uri; +import androidx.annotation.Nullable; +import androidx.test.platform.app.InstrumentationRegistry; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.util.Assertions; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import org.checkerframework.checker.nullness.compatqual.NullableType; + +/** Utility methods for instrumentation tests. */ +/* package */ final class AndroidTestUtil { + public static final String MP4_ASSET_URI = "asset:///media/mp4/sample.mp4"; + public static final String SEF_ASSET_URI = "asset:///media/mp4/sample_sef_slow_motion.mp4"; + + /** Transforms the {@code uriString} with the {@link Transformer}. */ + public static void runTransformer(Context context, Transformer transformer, String uriString) + throws Exception { + AtomicReference<@NullableType Exception> exceptionReference = new AtomicReference<>(); + CountDownLatch countDownLatch = new CountDownLatch(1); + + Transformer testTransformer = + transformer + .buildUpon() + .setListener( + new Transformer.Listener() { + @Override + public void onTransformationCompleted(MediaItem inputMediaItem) { + countDownLatch.countDown(); + } + + @Override + public void onTransformationError(MediaItem inputMediaItem, Exception exception) { + exceptionReference.set(exception); + countDownLatch.countDown(); + } + }) + .build(); + + Uri uri = Uri.parse(uriString); + File externalCacheFile = createExternalCacheFile(uri, context); + try { + InstrumentationRegistry.getInstrumentation() + .runOnMainSync( + () -> { + try { + testTransformer.startTransformation( + MediaItem.fromUri(uri), externalCacheFile.getAbsolutePath()); + } catch (IOException e) { + exceptionReference.set(e); + } + }); + countDownLatch.await(); + @Nullable Exception exception = exceptionReference.get(); + if (exception != null) { + throw exception; + } + } finally { + externalCacheFile.delete(); + } + } + + private static File createExternalCacheFile(Uri uri, Context context) throws IOException { + File file = new File(context.getExternalCacheDir(), "transformer-" + uri.hashCode()); + Assertions.checkState( + !file.exists() || file.delete(), "Could not delete the previous transformer output file"); + Assertions.checkState(file.createNewFile(), "Could not create the transformer output file"); + return file; + } + + private AndroidTestUtil() {} +} diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveAudioTransformationTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveAudioTransformationTest.java new file mode 100644 index 0000000000..b8c7bd923b --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveAudioTransformationTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 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 com.google.android.exoplayer2.transformer; + +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI; +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer; + +import android.content.Context; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** {@link Transformer} instrumentation test for removing audio. */ +@RunWith(AndroidJUnit4.class) +public class RemoveAudioTransformationTest { + @Test + public void removeAudioTransform() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Transformer transformer = + new Transformer.Builder().setContext(context).setRemoveAudio(true).build(); + runTransformer(context, transformer, MP4_ASSET_URI); + } +} diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveVideoTransformationTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveVideoTransformationTest.java new file mode 100644 index 0000000000..7a27887c93 --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/RemoveVideoTransformationTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 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 com.google.android.exoplayer2.transformer; + +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI; +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer; + +import android.content.Context; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** {@link Transformer} instrumentation test for removing video. */ +@RunWith(AndroidJUnit4.class) +public class RemoveVideoTransformationTest { + @Test + public void removeVideoTransform() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Transformer transformer = + new Transformer.Builder().setContext(context).setRemoveVideo(true).build(); + runTransformer(context, transformer, MP4_ASSET_URI); + } +} diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/SefTransformationTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/SefTransformationTest.java new file mode 100644 index 0000000000..5b311443b4 --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/SefTransformationTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 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 com.google.android.exoplayer2.transformer; + +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.SEF_ASSET_URI; +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer; + +import android.content.Context; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** {@link Transformer} instrumentation test for SEF. */ +@RunWith(AndroidJUnit4.class) +public class SefTransformationTest { + @Test + public void sefTransform() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Transformer transformer = + new Transformer.Builder().setContext(context).setFlattenForSlowMotion(true).build(); + runTransformer(context, transformer, SEF_ASSET_URI); + } +} diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformationTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformationTest.java new file mode 100644 index 0000000000..20fe021ff2 --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformationTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021 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 com.google.android.exoplayer2.transformer; + +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI; +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer; + +import android.content.Context; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** {@link Transformer} instrumentation test. */ +@RunWith(AndroidJUnit4.class) +public class TransformationTest { + @Test + public void transform() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Transformer transformer = new Transformer.Builder().setContext(context).build(); + runTransformer(context, transformer, MP4_ASSET_URI); + } +} From 12c0aa2ec9f27842f915d48d3b1c57ef0e9966da Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 11 Nov 2021 17:29:12 +0000 Subject: [PATCH 3/9] Update dependency versions Note: Updating androidxTestTruthVersion is required tot arget API level 31. PiperOrigin-RevId: 409167744 --- constants.gradle | 35 +++++++++++++++++----------------- extensions/cronet/build.gradle | 2 +- extensions/ima/build.gradle | 2 +- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/constants.gradle b/constants.gradle index bd4a545c4c..2e40d62bb9 100644 --- a/constants.gradle +++ b/constants.gradle @@ -26,33 +26,32 @@ project.ext { // Use the same Guava version as the Android repo: // https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA guavaVersion = '27.1-android' - mockitoVersion = '3.4.0' - mockWebServerVersion = '3.12.0' + mockitoVersion = '3.12.4' robolectricVersion = '4.6.1' // Keep this in sync with Google's internal Checker Framework version. - checkerframeworkVersion = '3.5.0' - checkerframeworkCompatVersion = '2.5.0' - errorProneVersion = '2.9.0' + checkerframeworkVersion = '3.13.0' + checkerframeworkCompatVersion = '2.5.5' + errorProneVersion = '2.10.0' jsr305Version = '3.0.2' - kotlinAnnotationsVersion = '1.5.20' - androidxAnnotationVersion = '1.2.0' - androidxAppCompatVersion = '1.3.0' + kotlinAnnotationsVersion = '1.5.31' + androidxAnnotationVersion = '1.3.0' + androidxAppCompatVersion = '1.3.1' androidxCollectionVersion = '1.1.0' - androidxCoreVersion = '1.6.0' + androidxCoreVersion = '1.7.0' androidxFuturesVersion = '1.1.0' androidxMediaVersion = '1.4.3' - androidxMedia2Version = '1.1.3' + androidxMedia2Version = '1.2.0' androidxMultidexVersion = '2.0.1' androidxRecyclerViewVersion = '1.2.1' - androidxMaterialVersion = '1.3.0' - androidxTestCoreVersion = '1.3.0' - androidxTestJUnitVersion = '1.1.2' - androidxTestRunnerVersion = '1.3.0' - androidxTestRulesVersion = '1.3.0' - androidxTestServicesStorageVersion = '1.3.0' - androidxTestTruthVersion = '1.3.0' + androidxMaterialVersion = '1.4.0' + androidxTestCoreVersion = '1.4.0' + androidxTestJUnitVersion = '1.1.3' + androidxTestRunnerVersion = '1.4.0' + androidxTestRulesVersion = '1.4.0' + androidxTestServicesStorageVersion = '1.4.0' + androidxTestTruthVersion = '1.4.0' truthVersion = '1.1.3' - okhttpVersion = '4.9.1' + okhttpVersion = '4.9.2' modulePrefix = ':' if (gradle.ext.has('exoplayerModulePrefix')) { modulePrefix += gradle.ext.exoplayerModulePrefix diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index 6fdba05663..09211ca4cb 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -32,7 +32,7 @@ dependencies { androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion // Instrumentation tests assume that an app-packaged version of cronet is // available. - androidTestImplementation 'org.chromium.net:cronet-embedded:94.4606.61' + androidTestImplementation 'org.chromium.net:cronet-embedded:95.4638.50' androidTestImplementation project(modulePrefix + 'testutils') testImplementation project(modulePrefix + 'testutils') testImplementation 'com.squareup.okhttp3:mockwebserver:' + okhttpVersion diff --git a/extensions/ima/build.gradle b/extensions/ima/build.gradle index 26e87025e1..4a40f0afc9 100644 --- a/extensions/ima/build.gradle +++ b/extensions/ima/build.gradle @@ -25,7 +25,7 @@ android { } dependencies { - api 'com.google.ads.interactivemedia.v3:interactivemedia:3.24.0' + api 'com.google.ads.interactivemedia.v3:interactivemedia:3.25.1' implementation project(modulePrefix + 'library-core') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion From 67e14999565efb2e2ac8e20fc8befb5dbd082117 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Nov 2021 10:18:26 +0000 Subject: [PATCH 4/9] Fixes for some minor misc issues - Unnecessary deprecation suppressions - Dead code - Broken Javadoc PiperOrigin-RevId: 409357884 --- .../com/google/android/exoplayer2/ext/cast/CastPlayerTest.java | 1 - .../android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java | 2 -- .../src/main/java/com/google/android/exoplayer2/text/Cue.java | 1 - .../java/com/google/android/exoplayer2/DefaultLoadControl.java | 1 - .../java/com/google/android/exoplayer2/SimpleExoPlayer.java | 2 -- .../com/google/android/exoplayer2/ui/PlayerControlView.java | 1 - 6 files changed, 8 deletions(-) diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java index 516a6fdde4..917d25aba0 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java @@ -392,7 +392,6 @@ public class CastPlayerTest { assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(uri2); } - @SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly. @Test public void setMediaItems_replaceExistingPlaylist_notifiesMediaItemTransition() { List firstPlaylist = new ArrayList<>(); diff --git a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java index bd70d14394..61c3673e5e 100644 --- a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java +++ b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java @@ -134,8 +134,6 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab return player.getPlaybackState() == Player.STATE_IDLE ? -1 : player.getCurrentPosition(); } - // Calls deprecated method to provide backwards compatibility. - @SuppressWarnings("deprecation") @Override public void play() { if (player.getPlaybackState() == Player.STATE_IDLE) { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java index a386580f3d..436bea7048 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -362,7 +362,6 @@ public final class Cue implements Bundleable { * @param textSize See {@link #textSize}. * @deprecated Use {@link Builder}. */ - @SuppressWarnings("deprecation") @Deprecated public Cue( CharSequence text, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index d2ea709e76..65c00a8a59 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -262,7 +262,6 @@ public class DefaultLoadControl implements LoadControl { private boolean isLoading; /** Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. */ - @SuppressWarnings("deprecation") public DefaultLoadControl() { this( new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 83067a500c..fdc819078b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -396,7 +396,6 @@ public class SimpleExoPlayer extends BasePlayer /** @deprecated Use the {@link ExoPlayer.Builder}. */ @Deprecated - @SuppressWarnings("deprecation") protected SimpleExoPlayer( Context context, RenderersFactory renderersFactory, @@ -428,7 +427,6 @@ public class SimpleExoPlayer extends BasePlayer } /** @param builder The {@link ExoPlayer.Builder} to obtain all construction parameters. */ - @SuppressWarnings("deprecation") /* package */ SimpleExoPlayer(ExoPlayer.Builder builder) { constructorFinished = new ConditionVariable(); try { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java index a7838377bf..e880b0f254 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java @@ -1222,7 +1222,6 @@ public class PlayerControlView extends FrameLayout { } } - @SuppressWarnings("deprecation") private void dispatchPlay(Player player) { @State int state = player.getPlaybackState(); if (state == Player.STATE_IDLE) { From 68000e001299edc8f08e585e3a0f42f62986ba1c Mon Sep 17 00:00:00 2001 From: hschlueter Date: Mon, 15 Nov 2021 11:07:20 +0000 Subject: [PATCH 5/9] Move OpenGL usage from VideoSamplePipeline to new OpenGlFrameEditor. The decoder writes to `OpenGlFrameEditor`'s input `Surface` and the `OpenGlFrameEditor` writes to the encoder's input `Surface`. PiperOrigin-RevId: 409931796 --- .../transformer/OpenGlFrameEditor.java | 180 +++++++++++++ .../transformer/VideoSamplePipeline.java | 249 ++++-------------- 2 files changed, 224 insertions(+), 205 deletions(-) create mode 100644 library/transformer/src/main/java/com/google/android/exoplayer2/transformer/OpenGlFrameEditor.java diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/OpenGlFrameEditor.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/OpenGlFrameEditor.java new file mode 100644 index 0000000000..deab55d692 --- /dev/null +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/OpenGlFrameEditor.java @@ -0,0 +1,180 @@ +/* + * Copyright 2021 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 com.google.android.exoplayer2.transformer; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; + +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.opengl.GLES20; +import android.view.Surface; +import androidx.annotation.RequiresApi; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.util.GlUtil; +import java.io.IOException; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** Applies OpenGL transformations to video frames. */ +@RequiresApi(18) +/* package */ final class OpenGlFrameEditor { + + static { + GlUtil.glAssertionsEnabled = true; + } + + public static OpenGlFrameEditor create( + Context context, Format inputFormat, Surface outputSurface) { + EGLDisplay eglDisplay = GlUtil.createEglDisplay(); + EGLContext eglContext; + try { + eglContext = GlUtil.createEglContext(eglDisplay); + } catch (GlUtil.UnsupportedEglVersionException e) { + throw new IllegalStateException("EGL version is unsupported", e); + } + EGLSurface eglSurface = GlUtil.getEglSurface(eglDisplay, outputSurface); + GlUtil.focusSurface(eglDisplay, eglContext, eglSurface, inputFormat.width, inputFormat.height); + int textureId = GlUtil.createExternalTexture(); + GlUtil.Program copyProgram; + try { + copyProgram = new GlUtil.Program(context, VERTEX_SHADER_FILE_PATH, FRAGMENT_SHADER_FILE_PATH); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + copyProgram.use(); + GlUtil.Attribute[] copyAttributes = copyProgram.getAttributes(); + checkState( + copyAttributes.length == EXPECTED_NUMBER_OF_ATTRIBUTES, + "Expected program to have " + EXPECTED_NUMBER_OF_ATTRIBUTES + " vertex attributes."); + for (GlUtil.Attribute copyAttribute : copyAttributes) { + if (copyAttribute.name.equals("a_position")) { + copyAttribute.setBuffer( + new float[] { + -1.0f, -1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, + }, + /* size= */ 4); + } else if (copyAttribute.name.equals("a_texcoord")) { + copyAttribute.setBuffer( + new float[] { + 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, + }, + /* size= */ 4); + } else { + throw new IllegalStateException("Unexpected attribute name."); + } + copyAttribute.bind(); + } + GlUtil.Uniform[] copyUniforms = copyProgram.getUniforms(); + checkState( + copyUniforms.length == EXPECTED_NUMBER_OF_UNIFORMS, + "Expected program to have " + EXPECTED_NUMBER_OF_UNIFORMS + " uniforms."); + GlUtil.@MonotonicNonNull Uniform textureTransformUniform = null; + for (GlUtil.Uniform copyUniform : copyUniforms) { + if (copyUniform.name.equals("tex_sampler")) { + copyUniform.setSamplerTexId(textureId, 0); + copyUniform.bind(); + } else if (copyUniform.name.equals("tex_transform")) { + textureTransformUniform = copyUniform; + } else { + throw new IllegalStateException("Unexpected uniform name."); + } + } + + return new OpenGlFrameEditor( + eglDisplay, eglContext, eglSurface, textureId, checkNotNull(textureTransformUniform)); + } + + // Predefined shader values. + private static final String VERTEX_SHADER_FILE_PATH = "shaders/blit_vertex_shader.glsl"; + private static final String FRAGMENT_SHADER_FILE_PATH = + "shaders/copy_external_fragment_shader.glsl"; + private static final int EXPECTED_NUMBER_OF_ATTRIBUTES = 2; + private static final int EXPECTED_NUMBER_OF_UNIFORMS = 2; + + private final float[] textureTransformMatrix; + private final EGLDisplay eglDisplay; + private final EGLContext eglContext; + private final EGLSurface eglSurface; + private final int textureId; + private final SurfaceTexture inputSurfaceTexture; + private final Surface inputSurface; + private final GlUtil.Uniform textureTransformUniform; + + private volatile boolean hasInputData; + + private OpenGlFrameEditor( + EGLDisplay eglDisplay, + EGLContext eglContext, + EGLSurface eglSurface, + int textureId, + GlUtil.Uniform textureTransformUniform) { + this.eglDisplay = eglDisplay; + this.eglContext = eglContext; + this.eglSurface = eglSurface; + this.textureId = textureId; + this.textureTransformUniform = textureTransformUniform; + textureTransformMatrix = new float[16]; + inputSurfaceTexture = new SurfaceTexture(textureId); + inputSurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> hasInputData = true); + inputSurface = new Surface(inputSurfaceTexture); + } + + /** Releases all resources. */ + public void release() { + GlUtil.destroyEglContext(eglDisplay, eglContext); + GlUtil.deleteTexture(textureId); + inputSurfaceTexture.release(); + inputSurface.release(); + } + + /** Informs the editor that there is new input data available for it to process asynchronously. */ + public void processData() { + inputSurfaceTexture.updateTexImage(); + inputSurfaceTexture.getTransformMatrix(textureTransformMatrix); + textureTransformUniform.setFloats(textureTransformMatrix); + textureTransformUniform.bind(); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + long surfaceTextureTimestampNs = inputSurfaceTexture.getTimestamp(); + EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, surfaceTextureTimestampNs); + EGL14.eglSwapBuffers(eglDisplay, eglSurface); + hasInputData = false; + } + + /** + * Returns the input {@link Surface} after configuring the editor if it has not previously been + * configured. + */ + public Surface getInputSurface() { + return inputSurface; + } + + public boolean hasInputData() { + return hasInputData; + } +} diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoSamplePipeline.java index 004331404d..f6a0d4a433 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoSamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoSamplePipeline.java @@ -17,19 +17,9 @@ package com.google.android.exoplayer2.transformer; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import static com.google.android.exoplayer2.util.Assertions.checkState; -import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import android.content.Context; -import android.graphics.SurfaceTexture; import android.media.MediaCodec; -import android.opengl.EGL14; -import android.opengl.EGLContext; -import android.opengl.EGLDisplay; -import android.opengl.EGLExt; -import android.opengl.EGLSurface; -import android.opengl.GLES20; -import android.view.Surface; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; @@ -37,13 +27,8 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.util.GlUtil; import com.google.common.collect.ImmutableMap; import java.io.IOException; -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * Pipeline to decode video samples, apply transformations on the raw samples, and re-encode them. @@ -51,52 +36,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @RequiresApi(18) /* package */ final class VideoSamplePipeline implements SamplePipeline { - static { - GlUtil.glAssertionsEnabled = true; - } - private static final String TAG = "VideoSamplePipeline"; - // Predefined shader values. - private static final String VERTEX_SHADER_FILE_PATH = "shaders/blit_vertex_shader.glsl"; - private static final String FRAGMENT_SHADER_FILE_PATH = - "shaders/copy_external_fragment_shader.glsl"; - private static final int EXPECTED_NUMBER_OF_ATTRIBUTES = 2; - private static final int EXPECTED_NUMBER_OF_UNIFORMS = 2; - - private final Context context; - private final int rendererIndex; - private final MediaCodecAdapterWrapper encoder; private final DecoderInputBuffer encoderOutputBuffer; private final DecoderInputBuffer decoderInputBuffer; - private final float[] decoderTextureTransformMatrix; - private final Format decoderInputFormat; + private final MediaCodecAdapterWrapper decoder; - private @MonotonicNonNull EGLDisplay eglDisplay; - private @MonotonicNonNull EGLContext eglContext; - private @MonotonicNonNull EGLSurface eglSurface; + private final OpenGlFrameEditor openGlFrameEditor; - private int decoderTextureId; - private @MonotonicNonNull SurfaceTexture decoderSurfaceTexture; - private @MonotonicNonNull Surface decoderSurface; - private @MonotonicNonNull MediaCodecAdapterWrapper decoder; - private volatile boolean isDecoderSurfacePopulated; private boolean waitingForPopulatedDecoderSurface; - private GlUtil.@MonotonicNonNull Uniform decoderTextureTransformUniform; public VideoSamplePipeline( Context context, Format decoderInputFormat, Transformation transformation, int rendererIndex) throws ExoPlaybackException { - this.decoderInputFormat = decoderInputFormat; - this.rendererIndex = rendererIndex; - this.context = context; decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); - decoderTextureTransformMatrix = new float[16]; - decoderTextureId = GlUtil.TEXTURE_ID_UNSET; encoderOutputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); @@ -114,34 +71,57 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ImmutableMap.of()); } catch (IOException e) { // TODO (internal b/184262323): Assign an adequate error code. - throw ExoPlaybackException.createForRenderer( - e, - TAG, - rendererIndex, - decoderInputFormat, - /* rendererFormatSupport= */ C.FORMAT_HANDLED, - /* isRecoverable= */ false, - PlaybackException.ERROR_CODE_UNSPECIFIED); + throw createRendererException( + e, rendererIndex, decoderInputFormat, PlaybackException.ERROR_CODE_UNSPECIFIED); + } + openGlFrameEditor = + OpenGlFrameEditor.create( + context, + decoderInputFormat, + /* outputSurface= */ checkNotNull(encoder.getInputSurface())); + try { + decoder = + MediaCodecAdapterWrapper.createForVideoDecoding( + decoderInputFormat, openGlFrameEditor.getInputSurface()); + } catch (IOException e) { + throw createRendererException( + e, rendererIndex, decoderInputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); } } @Override - public boolean processData() throws ExoPlaybackException { - ensureOpenGlConfigured(); - return !ensureDecoderConfigured() || feedEncoderFromDecoder(); + public boolean processData() { + if (decoder.isEnded()) { + return false; + } + + if (!openGlFrameEditor.hasInputData()) { + if (!waitingForPopulatedDecoderSurface) { + if (decoder.getOutputBufferInfo() != null) { + decoder.releaseOutputBuffer(/* render= */ true); + waitingForPopulatedDecoderSurface = true; + } + if (decoder.isEnded()) { + encoder.signalEndOfInputStream(); + } + } + return false; + } + + waitingForPopulatedDecoderSurface = false; + openGlFrameEditor.processData(); + return true; } @Override @Nullable public DecoderInputBuffer dequeueInputBuffer() { - return decoder != null && decoder.maybeDequeueInputBuffer(decoderInputBuffer) - ? decoderInputBuffer - : null; + return decoder.maybeDequeueInputBuffer(decoderInputBuffer) ? decoderInputBuffer : null; } @Override public void queueInputBuffer() { - checkStateNotNull(decoder).queueInputBuffer(decoderInputBuffer); + decoder.queueInputBuffer(decoderInputBuffer); } @Override @@ -175,154 +155,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @Override public void release() { - GlUtil.destroyEglContext(eglDisplay, eglContext); - if (decoderTextureId != GlUtil.TEXTURE_ID_UNSET) { - GlUtil.deleteTexture(decoderTextureId); - } - if (decoderSurfaceTexture != null) { - decoderSurfaceTexture.release(); - } - if (decoderSurface != null) { - decoderSurface.release(); - } - if (decoder != null) { - decoder.release(); - } + openGlFrameEditor.release(); + decoder.release(); encoder.release(); } - @EnsuresNonNull({"eglDisplay", "eglContext", "eglSurface", "decoderTextureTransformUniform"}) - private void ensureOpenGlConfigured() { - if (eglDisplay != null - && eglContext != null - && eglSurface != null - && decoderTextureTransformUniform != null) { - return; - } - - eglDisplay = GlUtil.createEglDisplay(); - try { - eglContext = GlUtil.createEglContext(eglDisplay); - } catch (GlUtil.UnsupportedEglVersionException e) { - throw new IllegalStateException("EGL version is unsupported", e); - } - eglSurface = GlUtil.getEglSurface(eglDisplay, checkNotNull(encoder.getInputSurface())); - GlUtil.focusSurface( - eglDisplay, eglContext, eglSurface, decoderInputFormat.width, decoderInputFormat.height); - decoderTextureId = GlUtil.createExternalTexture(); - GlUtil.Program copyProgram; - try { - copyProgram = new GlUtil.Program(context, VERTEX_SHADER_FILE_PATH, FRAGMENT_SHADER_FILE_PATH); - } catch (IOException e) { - throw new IllegalStateException(e); - } - - copyProgram.use(); - GlUtil.Attribute[] copyAttributes = copyProgram.getAttributes(); - checkState( - copyAttributes.length == EXPECTED_NUMBER_OF_ATTRIBUTES, - "Expected program to have " + EXPECTED_NUMBER_OF_ATTRIBUTES + " vertex attributes."); - for (GlUtil.Attribute copyAttribute : copyAttributes) { - if (copyAttribute.name.equals("a_position")) { - copyAttribute.setBuffer( - new float[] { - -1.0f, -1.0f, 0.0f, 1.0f, - 1.0f, -1.0f, 0.0f, 1.0f, - -1.0f, 1.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 0.0f, 1.0f, - }, - /* size= */ 4); - } else if (copyAttribute.name.equals("a_texcoord")) { - copyAttribute.setBuffer( - new float[] { - 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 0.0f, 0.0f, 1.0f, - 0.0f, 1.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 0.0f, 1.0f, - }, - /* size= */ 4); - } else { - throw new IllegalStateException("Unexpected attribute name."); - } - copyAttribute.bind(); - } - GlUtil.Uniform[] copyUniforms = copyProgram.getUniforms(); - checkState( - copyUniforms.length == EXPECTED_NUMBER_OF_UNIFORMS, - "Expected program to have " + EXPECTED_NUMBER_OF_UNIFORMS + " uniforms."); - for (GlUtil.Uniform copyUniform : copyUniforms) { - if (copyUniform.name.equals("tex_sampler")) { - copyUniform.setSamplerTexId(decoderTextureId, 0); - copyUniform.bind(); - } else if (copyUniform.name.equals("tex_transform")) { - decoderTextureTransformUniform = copyUniform; - } else { - throw new IllegalStateException("Unexpected uniform name."); - } - } - checkNotNull(decoderTextureTransformUniform); - } - - @EnsuresNonNullIf( - expression = {"decoder", "decoderSurfaceTexture"}, - result = true) - private boolean ensureDecoderConfigured() throws ExoPlaybackException { - if (decoder != null && decoderSurfaceTexture != null) { - return true; - } - - checkState(decoderTextureId != GlUtil.TEXTURE_ID_UNSET); - decoderSurfaceTexture = new SurfaceTexture(decoderTextureId); - decoderSurfaceTexture.setOnFrameAvailableListener( - surfaceTexture -> isDecoderSurfacePopulated = true); - decoderSurface = new Surface(decoderSurfaceTexture); - try { - decoder = MediaCodecAdapterWrapper.createForVideoDecoding(decoderInputFormat, decoderSurface); - } catch (IOException e) { - throw createRendererException(e, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); - } - return true; - } - - @RequiresNonNull({ - "decoder", - "decoderSurfaceTexture", - "decoderTextureTransformUniform", - "eglDisplay", - "eglSurface" - }) - private boolean feedEncoderFromDecoder() { - if (decoder.isEnded()) { - return false; - } - - if (!isDecoderSurfacePopulated) { - if (!waitingForPopulatedDecoderSurface) { - if (decoder.getOutputBufferInfo() != null) { - decoder.releaseOutputBuffer(/* render= */ true); - waitingForPopulatedDecoderSurface = true; - } - if (decoder.isEnded()) { - encoder.signalEndOfInputStream(); - } - } - return false; - } - - waitingForPopulatedDecoderSurface = false; - decoderSurfaceTexture.updateTexImage(); - decoderSurfaceTexture.getTransformMatrix(decoderTextureTransformMatrix); - decoderTextureTransformUniform.setFloats(decoderTextureTransformMatrix); - decoderTextureTransformUniform.bind(); - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - long decoderSurfaceTextureTimestampNs = decoderSurfaceTexture.getTimestamp(); - EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, decoderSurfaceTextureTimestampNs); - EGL14.eglSwapBuffers(eglDisplay, eglSurface); - isDecoderSurfacePopulated = false; - return true; - } - - private ExoPlaybackException createRendererException(Throwable cause, int errorCode) { + private static ExoPlaybackException createRendererException( + Throwable cause, int rendererIndex, Format decoderInputFormat, int errorCode) { return ExoPlaybackException.createForRenderer( cause, TAG, From 1d81d7e501e6a4c07ad25614e1a451469f989edd Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 15 Nov 2021 11:19:57 +0000 Subject: [PATCH 6/9] Use buildUpon instead of creating new overrides in UI ControlView. Creating a new set of overrides removes previously set overrides that should be kept. Issue: google/ExoPlayer#9690 PiperOrigin-RevId: 409933541 --- .../google/android/exoplayer2/ui/StyledPlayerControlView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index cd33ecc9d9..4c1f0aa74c 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -2165,7 +2165,9 @@ public class StyledPlayerControlView extends FrameLayout { TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); TrackSelectionOverrides overrides = - new TrackSelectionOverrides.Builder() + trackSelectionParameters + .trackSelectionOverrides + .buildUpon() .setOverrideForType( new TrackSelectionOverride( track.trackGroup, ImmutableList.of(track.trackIndex))) From 1564d5314c07a76b5f44bb46ef9b78f05bce8f1b Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 15 Nov 2021 13:16:47 +0000 Subject: [PATCH 7/9] Add additional documentation regarding resource acquisition. The main point of the IDLE state is that the player is not holding resources. Clarify this in the documentation of STATE_IDLE, prepare and stop. PiperOrigin-RevId: 409950785 --- docs/listening-to-player-events.md | 3 ++- .../com/google/android/exoplayer2/Player.java | 21 +++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/listening-to-player-events.md b/docs/listening-to-player-events.md index 99af0b6678..3de8bde96b 100644 --- a/docs/listening-to-player-events.md +++ b/docs/listening-to-player-events.md @@ -31,7 +31,8 @@ Changes in player state can be received by implementing `Player.Listener`. The player can be in one of four playback states: * `Player.STATE_IDLE`: This is the initial state, the state when the player is - stopped, and when playback failed. + stopped, and when playback failed. The player will hold only limited resources + in this state. * `Player.STATE_BUFFERING`: The player is not able to immediately play from its current position. This mostly happens because more data needs to be loaded. * `Player.STATE_READY`: The player is able to immediately play from its current diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 406398e39f..28aa61057d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -1087,7 +1087,10 @@ public interface Player { @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED}) @interface State {} - /** The player is idle, and must be {@link #prepare() prepared} before it will play the media. */ + /** + * The player is idle, meaning it holds only limited resources. The player must be {@link + * #prepare() prepared} before it will play the media. + */ int STATE_IDLE = 1; /** * The player is not able to immediately play the media, but is doing work toward being able to do @@ -1669,7 +1672,12 @@ public interface Player { */ Commands getAvailableCommands(); - /** Prepares the player. */ + /** + * Prepares the player. + * + *

This will move the player out of {@link #STATE_IDLE idle state} and the player will start + * loading media and acquire resources needed for playback. + */ void prepare(); /** @@ -2001,12 +2009,13 @@ public interface Player { PlaybackParameters getPlaybackParameters(); /** - * Stops playback without resetting the player. Use {@link #pause()} rather than this method if + * Stops playback without resetting the playlist. Use {@link #pause()} rather than this method if * the intention is to pause playback. * - *

Calling this method will cause the playback state to transition to {@link #STATE_IDLE}. The - * player instance can still be used, and {@link #release()} must still be called on the player if - * it's no longer required. + *

Calling this method will cause the playback state to transition to {@link #STATE_IDLE} and + * the player will release the loaded media and resources required for playback. The player + * instance can still be used by calling {@link #prepare()} again, and {@link #release()} must + * still be called on the player if it's no longer required. * *

Calling this method does not clear the playlist, reset the playback position or the playback * error. From cd857d86597f9a604c1eff24a35e6376b3dc9ba0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 15 Nov 2021 14:25:17 +0000 Subject: [PATCH 8/9] Add parsed essential/supplemental properties to the Representation. We already parse essential and supplemental properties from the Representation, but don't add them to our Representation class so that they can be accessed by users. Issue: google/ExoPlayer#9579 PiperOrigin-RevId: 409961990 --- RELEASENOTES.md | 3 + .../dash/manifest/DashManifestParser.java | 13 ++- .../source/dash/manifest/Representation.java | 92 +++++++++++++------ .../exoplayer2/source/dash/DashUtilTest.java | 4 + .../dash/manifest/DashManifestParserTest.java | 72 +++++++++++++++ ...mple_mpd_essential_supplemental_properties | 31 +++++++ 6 files changed, 187 insertions(+), 28 deletions(-) create mode 100644 testdata/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8c770912c4..0a16398d41 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -14,6 +14,9 @@ * Extractors: * WAV: Add support for RF64 streams ([#9543](https://github.com/google/ExoPlayer/issues/9543). +* DASH: + * Add parsed essential and supplemental properties to the `Representation` + ([#9579](https://github.com/google/ExoPlayer/issues/9579)). ### 2.16.0 (2021-11-04) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index d997187a9d..ebafbee31d 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -753,6 +753,8 @@ public class DashManifestParser extends DefaultHandler drmSchemeType, drmSchemeDatas, inbandEventStreams, + essentialProperties, + supplementalProperties, Representation.REVISION_ID_DEFAULT); } @@ -841,7 +843,10 @@ public class DashManifestParser extends DefaultHandler formatBuilder.build(), representationInfo.baseUrls, representationInfo.segmentBase, - inbandEventStreams); + inbandEventStreams, + representationInfo.essentialProperties, + representationInfo.supplementalProperties, + /* cacheKey= */ null); } // SegmentBase, SegmentList and SegmentTemplate parsing. @@ -1910,6 +1915,8 @@ public class DashManifestParser extends DefaultHandler public final ArrayList drmSchemeDatas; public final ArrayList inbandEventStreams; public final long revisionId; + public final List essentialProperties; + public final List supplementalProperties; public RepresentationInfo( Format format, @@ -1918,6 +1925,8 @@ public class DashManifestParser extends DefaultHandler @Nullable String drmSchemeType, ArrayList drmSchemeDatas, ArrayList inbandEventStreams, + List essentialProperties, + List supplementalProperties, long revisionId) { this.format = format; this.baseUrls = ImmutableList.copyOf(baseUrls); @@ -1925,6 +1934,8 @@ public class DashManifestParser extends DefaultHandler this.drmSchemeType = drmSchemeType; this.drmSchemeDatas = drmSchemeDatas; this.inbandEventStreams = inbandEventStreams; + this.essentialProperties = essentialProperties; + this.supplementalProperties = supplementalProperties; this.revisionId = revisionId; } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java index af9771ef14..1e02e9cd62 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java @@ -50,6 +50,10 @@ public abstract class Representation { public final long presentationTimeOffsetUs; /** The in-band event streams in the representation. May be empty. */ public final List inbandEventStreams; + /** Essential properties in the representation. May be empty. */ + public final List essentialProperties; + /** Supplemental properties in the adaptation set. May be empty. */ + public final List supplementalProperties; private final RangedUri initializationUri; @@ -64,27 +68,15 @@ public abstract class Representation { */ public static Representation newInstance( long revisionId, Format format, List baseUrls, SegmentBase segmentBase) { - return newInstance(revisionId, format, baseUrls, segmentBase, /* inbandEventStreams= */ null); - } - - /** - * Constructs a new instance. - * - * @param revisionId Identifies the revision of the content. - * @param format The format of the representation. - * @param baseUrls The list of base URLs of the representation. - * @param segmentBase A segment base element for the representation. - * @param inbandEventStreams The in-band event streams in the representation. May be null. - * @return The constructed instance. - */ - public static Representation newInstance( - long revisionId, - Format format, - List baseUrls, - SegmentBase segmentBase, - @Nullable List inbandEventStreams) { return newInstance( - revisionId, format, baseUrls, segmentBase, inbandEventStreams, /* cacheKey= */ null); + revisionId, + format, + baseUrls, + segmentBase, + /* inbandEventStreams= */ null, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), + /* cacheKey= */ null); } /** @@ -95,6 +87,8 @@ public abstract class Representation { * @param baseUrls The list of base URLs of the representation. * @param segmentBase A segment base element for the representation. * @param inbandEventStreams The in-band event streams in the representation. May be null. + * @param essentialProperties Essential properties in the representation. May be empty. + * @param supplementalProperties Supplemental properties in the representation. May be empty. * @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. This * parameter is ignored if {@code segmentBase} consists of multiple segments. * @return The constructed instance. @@ -105,6 +99,8 @@ public abstract class Representation { List baseUrls, SegmentBase segmentBase, @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties, @Nullable String cacheKey) { if (segmentBase instanceof SingleSegmentBase) { return new SingleSegmentRepresentation( @@ -113,11 +109,19 @@ public abstract class Representation { baseUrls, (SingleSegmentBase) segmentBase, inbandEventStreams, + essentialProperties, + supplementalProperties, cacheKey, - C.LENGTH_UNSET); + /* contentLength= */ C.LENGTH_UNSET); } else if (segmentBase instanceof MultiSegmentBase) { return new MultiSegmentRepresentation( - revisionId, format, baseUrls, (MultiSegmentBase) segmentBase, inbandEventStreams); + revisionId, + format, + baseUrls, + (MultiSegmentBase) segmentBase, + inbandEventStreams, + essentialProperties, + supplementalProperties); } else { throw new IllegalArgumentException( "segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase"); @@ -129,7 +133,9 @@ public abstract class Representation { Format format, List baseUrls, SegmentBase segmentBase, - @Nullable List inbandEventStreams) { + @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties) { checkArgument(!baseUrls.isEmpty()); this.revisionId = revisionId; this.format = format; @@ -138,6 +144,8 @@ public abstract class Representation { inbandEventStreams == null ? Collections.emptyList() : Collections.unmodifiableList(inbandEventStreams); + this.essentialProperties = essentialProperties; + this.supplementalProperties = supplementalProperties; initializationUri = segmentBase.getInitialization(this); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); } @@ -207,7 +215,15 @@ public abstract class Representation { new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1); List baseUrls = ImmutableList.of(new BaseUrl(uri)); return new SingleSegmentRepresentation( - revisionId, format, baseUrls, segmentBase, inbandEventStreams, cacheKey, contentLength); + revisionId, + format, + baseUrls, + segmentBase, + inbandEventStreams, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), + cacheKey, + contentLength); } /** @@ -216,6 +232,8 @@ public abstract class Representation { * @param baseUrls The base urls of the representation. * @param segmentBase The segment base underlying the representation. * @param inbandEventStreams The in-band event streams in the representation. May be null. + * @param essentialProperties Essential properties in the representation. May be empty. + * @param supplementalProperties Supplemental properties in the representation. May be empty. * @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. */ @@ -225,9 +243,18 @@ public abstract class Representation { List baseUrls, SingleSegmentBase segmentBase, @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties, @Nullable String cacheKey, long contentLength) { - super(revisionId, format, baseUrls, segmentBase, inbandEventStreams); + super( + revisionId, + format, + baseUrls, + segmentBase, + inbandEventStreams, + essentialProperties, + supplementalProperties); this.uri = Uri.parse(baseUrls.get(0).url); this.indexUri = segmentBase.getIndex(); this.cacheKey = cacheKey; @@ -271,14 +298,25 @@ public abstract class Representation { * @param baseUrls The base URLs of the representation. * @param segmentBase The segment base underlying the representation. * @param inbandEventStreams The in-band event streams in the representation. May be null. + * @param essentialProperties Essential properties in the representation. May be empty. + * @param supplementalProperties Supplemental properties in the representation. May be empty. */ public MultiSegmentRepresentation( long revisionId, Format format, List baseUrls, MultiSegmentBase segmentBase, - @Nullable List inbandEventStreams) { - super(revisionId, format, baseUrls, segmentBase, inbandEventStreams); + @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties) { + super( + revisionId, + format, + baseUrls, + segmentBase, + inbandEventStreams, + essentialProperties, + supplementalProperties); this.segmentBase = segmentBase; } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java index d0cb9dabdd..664ea0995d 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -79,6 +79,8 @@ public final class DashUtilTest { baseUrls, new SingleSegmentBase(), /* inbandEventStreams= */ null, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), /* cacheKey= */ null, /* contentLength= */ 1); RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1); @@ -99,6 +101,8 @@ public final class DashUtilTest { baseUrls, new SingleSegmentBase(), /* inbandEventStreams= */ null, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), "cacheKey", /* contentLength= */ 1); RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1); diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index ce2961dd2e..6926a05859 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -24,6 +24,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.metadata.emsg.EventMessage; +import com.google.android.exoplayer2.source.dash.manifest.Representation.MultiSegmentRepresentation; +import com.google.android.exoplayer2.source.dash.manifest.Representation.SingleSegmentRepresentation; import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentTimelineElement; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.MimeTypes; @@ -53,6 +55,8 @@ public class DashManifestParserTest { private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "media/mpd/sample_mpd_asset_identifier"; private static final String SAMPLE_MPD_TEXT = "media/mpd/sample_mpd_text"; private static final String SAMPLE_MPD_TRICK_PLAY = "media/mpd/sample_mpd_trick_play"; + private static final String SAMPLE_MPD_ESSENTIAL_SUPPLEMENTAL_PROPERTIES = + "media/mpd/sample_mpd_essential_supplemental_properties"; private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_BASE_URL = "media/mpd/sample_mpd_availabilityTimeOffset_baseUrl"; private static final String SAMPLE_MPD_MULTIPLE_BASE_URLS = @@ -504,6 +508,74 @@ public class DashManifestParserTest { assertThat(assetIdentifier.id).isEqualTo("uniqueId"); } + @Test + public void parseEssentialAndSupplementalProperties() throws IOException { + DashManifestParser parser = new DashManifestParser(); + DashManifest manifest = + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream( + ApplicationProvider.getApplicationContext(), + SAMPLE_MPD_ESSENTIAL_SUPPLEMENTAL_PROPERTIES)); + + // Verify test setup. + assertThat(manifest.getPeriodCount()).isEqualTo(1); + assertThat(manifest.getPeriod(0).adaptationSets).hasSize(1); + AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get(0); + assertThat(adaptationSet.representations).hasSize(2); + Representation representation0 = adaptationSet.representations.get(0); + Representation representation1 = adaptationSet.representations.get(1); + assertThat(representation0).isInstanceOf(SingleSegmentRepresentation.class); + assertThat(representation1).isInstanceOf(MultiSegmentRepresentation.class); + + // Verify parsed properties. + assertThat(adaptationSet.essentialProperties).hasSize(1); + assertThat(adaptationSet.essentialProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(adaptationSet.essentialProperties.get(0).value).isEqualTo("adaptationEssential"); + assertThat(adaptationSet.supplementalProperties).hasSize(1); + assertThat(adaptationSet.supplementalProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(adaptationSet.supplementalProperties.get(0).value) + .isEqualTo("adaptationSupplemental"); + + assertThat(representation0.essentialProperties).hasSize(2); + assertThat(representation0.essentialProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation0.essentialProperties.get(0).value).isEqualTo("adaptationEssential"); + assertThat(representation0.essentialProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation0.essentialProperties.get(1).value) + .isEqualTo("representationEssential"); + assertThat(representation0.supplementalProperties).hasSize(2); + assertThat(representation0.supplementalProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation0.supplementalProperties.get(0).value) + .isEqualTo("adaptationSupplemental"); + assertThat(representation0.supplementalProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation0.supplementalProperties.get(1).value) + .isEqualTo("representationSupplemental"); + + assertThat(representation1.essentialProperties).hasSize(2); + assertThat(representation0.essentialProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation0.essentialProperties.get(0).value).isEqualTo("adaptationEssential"); + assertThat(representation1.essentialProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation1.essentialProperties.get(1).value) + .isEqualTo("representationEssential"); + assertThat(representation1.supplementalProperties).hasSize(2); + assertThat(representation0.supplementalProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation0.supplementalProperties.get(0).value) + .isEqualTo("adaptationSupplemental"); + assertThat(representation1.supplementalProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation1.supplementalProperties.get(1).value) + .isEqualTo("representationSupplemental"); + } + @Test public void availabilityTimeOffset_staticManifest_setToTimeUnset() throws IOException { DashManifestParser parser = new DashManifestParser(); diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties b/testdata/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties new file mode 100644 index 0000000000..1ef8a7e7f1 --- /dev/null +++ b/testdata/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + From fde78bc3a74ba069a3f0e2e8a4eec8dc46c6f91e Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 15 Nov 2021 16:48:00 +0000 Subject: [PATCH 9/9] Bump version to 2.16.1 And make related release notes updates. PiperOrigin-RevId: 409991879 --- RELEASENOTES.md | 5 +++++ constants.gradle | 4 ++-- .../com/google/android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0a16398d41..091a5240e6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,12 @@ ### dev-v2 (not yet released) +### 2.16.1 (2021-11-11) + * Core Library: + * Fix track selection issue where overriding one track group did not + disable other track groups of the same type + ([#9675](https://github.com/google/ExoPlayer/issues/9675)). * Fix track selection issue where a mixture of non-empty and empty track overrides is not applied correctly ([#9649](https://github.com/google/ExoPlayer/issues/9649). diff --git a/constants.gradle b/constants.gradle index 2e40d62bb9..7fd89a86d6 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.16.0' - releaseVersionCode = 2016000 + releaseVersion = '2.16.1' + releaseVersionCode = 2016001 minSdkVersion = 16 appTargetSdkVersion = 29 // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 4784575659..60b170bb4c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -27,11 +27,11 @@ public final class ExoPlayerLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.16.0"; + public static final String VERSION = "2.16.1"; /** The version of the library expressed as {@code TAG + "/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.16.0"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.16.1"; /** * The version of the library expressed as an integer, for example 1002003. @@ -41,7 +41,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2016000; + public static final int VERSION_INT = 2016001; /** Whether the library was compiled with {@link Assertions} checks enabled. */ public static final boolean ASSERTIONS_ENABLED = true;