From 95e6db931a047775c1aa792c452be7996167a08f Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 30 Sep 2021 11:13:06 +0100 Subject: [PATCH 001/113] Add link to annual media developer survey. This will be removed after the survey has closed in ~1 month. PiperOrigin-RevId: 399890121 --- docs/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.md b/docs/index.md index b28743192f..4a512678e1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,10 @@ --- layout: article --- +The Android media team is interested in your experiences with the Android media +APIs and developer resources. Please provide your feedback by +[completing this short survey](https://goo.gle/media-survey-6). +{:.info} ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both From 701f343ee5fb65111afb7a87211ae9b03482cb9c Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Mon, 18 Oct 2021 17:04:29 -0700 Subject: [PATCH 002/113] Fixes issues with EXTINF duration conversion to microseconds The HLS Parser converts from a string decimal duration in seconds into long microseconds. Because the conversion passes through a java double type it can result in representation errors. For example: `#EXTINF:4.004` -> `Segment.durationUs` of 4003999 This matters because the first sample (which is the IDR) for a segment will be discarded following a seek because of the logic in the `SampleQueue`: ````java buffer.timeUs = timesUs[relativeReadIndex]; if (buffer.timeUs < startTimeUs) { buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); } ```` --- .../source/hls/playlist/HlsPlaylistParser.java | 10 ++++++++-- .../hls/playlist/HlsMediaPlaylistParserTest.java | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index ac8bf1e5a8..022b9f1b2b 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -48,6 +48,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.math.BigDecimal; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -759,8 +760,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = mediaPlaylist.segments; assertThat(segments).isNotNull(); - assertThat(segments).hasSize(5); + assertThat(segments).hasSize(6); Segment segment = segments.get(0); assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence) @@ -152,6 +155,9 @@ public class HlsMediaPlaylistParserTest { assertThat(segment.byteRangeLength).isEqualTo(C.LENGTH_UNSET); assertThat(segment.byteRangeOffset).isEqualTo(0); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts"); + + segment = segments.get(5); + assertThat(segment.durationUs).isEqualTo(2002000); } @Test From 2ee72076e506af1fd06df1b4e8796e31f534e9c5 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 21 Oct 2021 23:40:40 +0100 Subject: [PATCH 003/113] Add datasource module PiperOrigin-RevId: 404897119 --- core_settings.gradle | 2 + extensions/cronet/build.gradle | 1 + extensions/okhttp/build.gradle | 1 + extensions/rtmp/build.gradle | 1 + library/all/build.gradle | 1 + library/common/build.gradle | 9 --- library/common/proguard-rules.txt | 11 ---- library/core/build.gradle | 1 + library/datasource/README.md | 12 ++++ library/datasource/build.gradle | 65 +++++++++++++++++++ library/datasource/proguard-rules.txt | 12 ++++ .../src/androidTest/AndroidManifest.xml | 6 +- .../ContentDataSourceContractTest.java | 0 .../upstream/ContentDataSourceTest.java | 0 .../RawResourceDataSourceContractTest.java | 2 +- .../src/androidTest/res/raw/resource1 | 0 .../src/androidTest/res/raw/resource2 | 0 .../datasource/src/main/AndroidManifest.xml | 19 ++++++ .../upstream}/AesCipherDataSink.java | 4 +- .../upstream}/AesCipherDataSource.java | 5 +- .../upstream}/AesFlushingCipher.java | 2 +- .../exoplayer2/upstream/AssetDataSource.java | 0 .../exoplayer2/upstream/BaseDataSource.java | 0 .../upstream/ByteArrayDataSink.java | 0 .../upstream/ByteArrayDataSource.java | 0 .../upstream/ContentDataSource.java | 0 .../upstream/DataSchemeDataSource.java | 0 .../android/exoplayer2/upstream/DataSink.java | 0 .../exoplayer2/upstream/DataSource.java | 0 .../upstream/DataSourceException.java | 0 .../upstream/DataSourceInputStream.java | 0 .../exoplayer2/upstream/DataSourceUtil.java | 0 .../android/exoplayer2/upstream/DataSpec.java | 0 .../upstream/DefaultDataSource.java | 0 .../upstream/DefaultDataSourceFactory.java | 0 .../upstream/DefaultHttpDataSource.java | 0 .../exoplayer2/upstream/DummyDataSource.java | 0 .../exoplayer2/upstream/FileDataSource.java | 0 .../exoplayer2/upstream/HttpDataSource.java | 0 .../android/exoplayer2/upstream/HttpUtil.java | 0 .../upstream/PriorityDataSource.java | 0 .../upstream/PriorityDataSourceFactory.java | 0 .../upstream/RawResourceDataSource.java | 0 .../upstream/ResolvingDataSource.java | 0 .../exoplayer2/upstream/StatsDataSource.java | 0 .../exoplayer2/upstream/TeeDataSource.java | 0 .../exoplayer2/upstream/TransferListener.java | 0 .../exoplayer2/upstream/UdpDataSource.java | 0 .../exoplayer2/upstream/cache/Cache.java | 0 .../upstream/cache/CacheDataSink.java | 0 .../upstream/cache/CacheDataSource.java | 0 .../upstream/cache/CacheEvictor.java | 0 .../upstream/cache/CacheFileMetadata.java | 0 .../cache/CacheFileMetadataIndex.java | 0 .../upstream/cache/CacheKeyFactory.java | 0 .../exoplayer2/upstream/cache/CacheSpan.java | 0 .../upstream/cache/CacheWriter.java | 0 .../upstream/cache/CachedContent.java | 0 .../upstream/cache/CachedContentIndex.java | 0 .../upstream/cache/ContentMetadata.java | 0 .../cache/ContentMetadataMutations.java | 0 .../cache/DefaultContentMetadata.java | 0 .../cache/LeastRecentlyUsedCacheEvictor.java | 0 .../upstream/cache/NoOpCacheEvictor.java | 0 .../cache/ReusableBufferedOutputStream.java | 0 .../upstream/cache/SimpleCache.java | 0 .../upstream/cache/SimpleCacheSpan.java | 0 .../upstream/cache/package-info.java | 0 .../exoplayer2/upstream/package-info.java | 0 .../datasource/src/test/AndroidManifest.xml | 19 ++++++ .../upstream}/AesFlushingCipherTest.java | 2 +- .../upstream/AssetDataSourceContractTest.java | 0 .../upstream/AssetDataSourceTest.java | 0 .../upstream/BaseDataSourceTest.java | 0 .../ByteArrayDataSourceContractTest.java | 0 .../upstream/ByteArrayDataSourceTest.java | 0 .../DataSchemeDataSourceContractTest.java | 0 .../upstream/DataSchemeDataSourceTest.java | 0 .../upstream/DataSourceExceptionTest.java | 0 .../upstream/DataSourceInputStreamTest.java | 0 .../exoplayer2/upstream/DataSpecTest.java | 0 .../DefaultHttpDataSourceContractTest.java | 0 .../upstream/DefaultHttpDataSourceTest.java | 0 .../upstream/FileDataSourceContractTest.java | 0 .../exoplayer2/upstream/HttpUtilTest.java | 0 .../ResolvingDataSourceContractTest.java | 0 .../upstream/UdpDataSourceContractTest.java | 0 .../cache/CacheDataSourceContractTest.java | 0 .../upstream/cache/CacheDataSourceTest.java | 0 .../upstream/cache/CacheDataSourceTest2.java | 0 .../cache/CacheFileMetadataIndexTest.java | 0 .../upstream/cache/CacheKeyFactoryTest.java | 0 .../upstream/cache/CacheWriterTest.java | 0 .../cache/CachedContentIndexTest.java | 0 .../cache/DefaultContentMetadataTest.java | 0 .../LeastRecentlyUsedCacheEvictorTest.java | 0 .../ReusableBufferedOutputStreamTest.java | 0 .../upstream/cache/SimpleCacheSpanTest.java | 0 .../upstream/cache/SimpleCacheTest.java | 0 99 files changed, 142 insertions(+), 33 deletions(-) create mode 100644 library/datasource/README.md create mode 100644 library/datasource/build.gradle create mode 100644 library/datasource/proguard-rules.txt rename library/{common => datasource}/src/androidTest/AndroidManifest.xml (86%) rename library/{common => datasource}/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceContractTest.java (100%) rename library/{common => datasource}/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java (100%) rename library/{common => datasource}/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java (98%) rename library/{common => datasource}/src/androidTest/res/raw/resource1 (100%) rename library/{common => datasource}/src/androidTest/res/raw/resource2 (100%) create mode 100644 library/datasource/src/main/AndroidManifest.xml rename library/{common/src/main/java/com/google/android/exoplayer2/upstream/crypto => datasource/src/main/java/com/google/android/exoplayer2/upstream}/AesCipherDataSink.java (95%) rename library/{common/src/main/java/com/google/android/exoplayer2/upstream/crypto => datasource/src/main/java/com/google/android/exoplayer2/upstream}/AesCipherDataSource.java (91%) rename library/{common/src/main/java/com/google/android/exoplayer2/upstream/crypto => datasource/src/main/java/com/google/android/exoplayer2/upstream}/AesFlushingCipher.java (99%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DataSourceUtil.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/StatsDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadata.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndex.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStream.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/cache/package-info.java (100%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/package-info.java (100%) create mode 100644 library/datasource/src/test/AndroidManifest.xml rename library/{common/src/test/java/com/google/android/exoplayer2/upstream/crypto => datasource/src/test/java/com/google/android/exoplayer2/upstream}/AesFlushingCipherTest.java (99%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/BaseDataSourceTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/DataSourceExceptionTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceContractTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndexTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactoryTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStreamTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java (100%) rename library/{common => datasource}/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java (100%) diff --git a/core_settings.gradle b/core_settings.gradle index 3002d134fb..1f22196a33 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -51,6 +51,8 @@ project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui' include modulePrefix + 'extension-leanback' project(modulePrefix + 'extension-leanback').projectDir = new File(rootDir, 'extensions/leanback') +include modulePrefix + 'library-datasource' +project(modulePrefix + 'library-datasource').projectDir = new File(rootDir, 'library/datasource') include modulePrefix + 'extension-cronet' project(modulePrefix + 'extension-cronet').projectDir = new File(rootDir, 'extensions/cronet') include modulePrefix + 'extension-rtmp' diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index 5b806f6f38..6fdba05663 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -22,6 +22,7 @@ android { dependencies { api "com.google.android.gms:play-services-cronet:17.0.1" implementation project(modulePrefix + 'library-common') + implementation project(modulePrefix + 'library-datasource') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion diff --git a/extensions/okhttp/build.gradle b/extensions/okhttp/build.gradle index a8c3c622d2..ba2078e78d 100644 --- a/extensions/okhttp/build.gradle +++ b/extensions/okhttp/build.gradle @@ -17,6 +17,7 @@ android.defaultConfig.minSdkVersion 21 dependencies { implementation project(modulePrefix + 'library-common') + implementation project(modulePrefix + 'library-datasource') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion diff --git a/extensions/rtmp/build.gradle b/extensions/rtmp/build.gradle index 7b1e9d244c..b2ff46f80a 100644 --- a/extensions/rtmp/build.gradle +++ b/extensions/rtmp/build.gradle @@ -15,6 +15,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { implementation project(modulePrefix + 'library-common') + implementation project(modulePrefix + 'library-datasource') implementation 'net.butterflytv.utils:rtmp-client:3.1.0' implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion diff --git a/library/all/build.gradle b/library/all/build.gradle index 965cdc11c4..8daf11eea7 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -15,6 +15,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { api project(modulePrefix + 'library-common') + api project(modulePrefix + 'library-datasource') api project(modulePrefix + 'library-decoder') api project(modulePrefix + 'library-extractor') api project(modulePrefix + 'library-core') diff --git a/library/common/build.gradle b/library/common/build.gradle index 1a3217fabe..40d6c7c610 100644 --- a/library/common/build.gradle +++ b/library/common/build.gradle @@ -14,19 +14,11 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" android { - defaultConfig { - multiDexEnabled true - } - buildTypes { debug { testCoverageEnabled = true } } - - sourceSets { - androidTest.assets.srcDir '../../testdata/src/test/assets/' - } } dependencies { @@ -56,7 +48,6 @@ dependencies { testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion testImplementation 'junit:junit:' + junitVersion testImplementation 'com.google.truth:truth:' + truthVersion - testImplementation 'com.squareup.okhttp3:mockwebserver:' + okhttpVersion testImplementation 'org.robolectric:robolectric:' + robolectricVersion testImplementation project(modulePrefix + 'library-core') testImplementation project(modulePrefix + 'testutils') diff --git a/library/common/proguard-rules.txt b/library/common/proguard-rules.txt index 557df0f374..2eb313c45d 100644 --- a/library/common/proguard-rules.txt +++ b/library/common/proguard-rules.txt @@ -1,16 +1,5 @@ # Proguard rules specific to the common module. -# Constant folding for resource integers may mean that a resource passed to this method appears to be unused. Keep the method to prevent this from happening. --keepclassmembers class com.google.android.exoplayer2.upstream.RawResourceDataSource { - public static android.net.Uri buildRawResourceUri(int); -} - -# Constructors accessed via reflection in DefaultDataSource --dontnote com.google.android.exoplayer2.ext.rtmp.RtmpDataSource --keepclassmembers class com.google.android.exoplayer2.ext.rtmp.RtmpDataSource { - (); -} - # Don't warn about checkerframework and Kotlin annotations -dontwarn org.checkerframework.** -dontwarn kotlin.annotations.jvm.** diff --git a/library/core/build.gradle b/library/core/build.gradle index 7d848f9ab5..524948cc1e 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -37,6 +37,7 @@ android { dependencies { api project(modulePrefix + 'library-common') // TODO(b/203754886): Revisit which modules are exported as API dependencies. + api project(modulePrefix + 'library-datasource') api project(modulePrefix + 'library-decoder') api project(modulePrefix + 'library-extractor') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion diff --git a/library/datasource/README.md b/library/datasource/README.md new file mode 100644 index 0000000000..f87be557e9 --- /dev/null +++ b/library/datasource/README.md @@ -0,0 +1,12 @@ +# DataSource module + +Provides a `DataSource` abstraction and a number of concrete implementations for +reading data from different sources. Application code will not normally need to +depend on this module directly. + +## Links + +* [Javadoc][]: Classes matching `com.google.android.exoplayer2.upstream.*` belong to this + module. + +[Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/datasource/build.gradle b/library/datasource/build.gradle new file mode 100644 index 0000000000..98de23a80e --- /dev/null +++ b/library/datasource/build.gradle @@ -0,0 +1,65 @@ +// Copyright (C) 2020 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.exoplayerSettingsDir/common_library_config.gradle" + +android { + defaultConfig { + multiDexEnabled true + } + + buildTypes { + debug { + testCoverageEnabled = true + } + } + + sourceSets { + androidTest.assets.srcDir '../../testdata/src/test/assets/' + test.assets.srcDir '../../testdata/src/test/assets/' + } +} + +dependencies { + implementation project(modulePrefix + 'library-common') + implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion + compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version + compileOnly 'com.google.errorprone:error_prone_annotations:' + errorProneVersion + compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion + compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion + androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion + androidTestImplementation 'com.linkedin.dexmaker:dexmaker:' + dexmakerVersion + androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion + androidTestImplementation(project(modulePrefix + 'testutils')) { + exclude module: modulePrefix.substring(1) + 'library-core' + } + testImplementation 'org.mockito:mockito-core:' + mockitoVersion + testImplementation 'androidx.test:core:' + androidxTestCoreVersion + testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion + testImplementation 'com.google.truth:truth:' + truthVersion + testImplementation 'com.squareup.okhttp3:mockwebserver:' + okhttpVersion + testImplementation 'org.robolectric:robolectric:' + robolectricVersion + testImplementation project(modulePrefix + 'testutils') +} + +ext { + javadocTitle = 'DataSource module' +} +apply from: '../../javadoc_library.gradle' + +ext { + releaseArtifactId = 'exoplayer-datasource' + releaseDescription = 'The ExoPlayer library DataSource module.' +} +apply from: '../../publish.gradle' diff --git a/library/datasource/proguard-rules.txt b/library/datasource/proguard-rules.txt new file mode 100644 index 0000000000..ecdc535689 --- /dev/null +++ b/library/datasource/proguard-rules.txt @@ -0,0 +1,12 @@ +# Proguard rules specific to the DataSource module. + +# Constant folding for resource integers may mean that a resource passed to this method appears to be unused. Keep the method to prevent this from happening. +-keepclassmembers class com.google.android.exoplayer2.upstream.RawResourceDataSource { + public static android.net.Uri buildRawResourceUri(int); +} + +# Constructors accessed via reflection in DefaultDataSource +-dontnote com.google.android.exoplayer2.ext.rtmp.RtmpDataSource +-keepclassmembers class com.google.android.exoplayer2.ext.rtmp.RtmpDataSource { + (); +} diff --git a/library/common/src/androidTest/AndroidManifest.xml b/library/datasource/src/androidTest/AndroidManifest.xml similarity index 86% rename from library/common/src/androidTest/AndroidManifest.xml rename to library/datasource/src/androidTest/AndroidManifest.xml index 15db6a41e0..5edae1db96 100644 --- a/library/common/src/androidTest/AndroidManifest.xml +++ b/library/datasource/src/androidTest/AndroidManifest.xml @@ -1,5 +1,5 @@ - + + + + diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java similarity index 95% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java index 4e5b9f2b8e..5d84485e24 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream.crypto; +package com.google.android.exoplayer2.upstream; import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.min; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.upstream.DataSink; -import com.google.android.exoplayer2.upstream.DataSpec; import java.io.IOException; import javax.crypto.Cipher; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java similarity index 91% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java index 98ec914fa0..77126904bb 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream.crypto; +package com.google.android.exoplayer2.upstream; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; @@ -21,9 +21,6 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.TransferListener; import java.io.IOException; import java.util.List; import java.util.Map; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java similarity index 99% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java index 96cb13604e..5a60a985db 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream.crypto; +package com.google.android.exoplayer2.upstream; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Assertions; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceUtil.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSourceUtil.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceUtil.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSourceUtil.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/StatsDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/StatsDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/StatsDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/StatsDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadata.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadata.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadata.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadata.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndex.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndex.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndex.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndex.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStream.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStream.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStream.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStream.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/package-info.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/package-info.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/cache/package-info.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/cache/package-info.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/package-info.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/package-info.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/package-info.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/package-info.java diff --git a/library/datasource/src/test/AndroidManifest.xml b/library/datasource/src/test/AndroidManifest.xml new file mode 100644 index 0000000000..173af4332e --- /dev/null +++ b/library/datasource/src/test/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java similarity index 99% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java index 2811b0eada..b6cdb61ba0 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java +++ b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream.crypto; +package com.google.android.exoplayer2.upstream; import static com.google.common.truth.Truth.assertThat; import static java.lang.Math.min; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/BaseDataSourceTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/BaseDataSourceTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/BaseDataSourceTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/BaseDataSourceTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSourceExceptionTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSourceExceptionTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSourceExceptionTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSourceExceptionTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceContractTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceContractTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceContractTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceContractTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndexTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndexTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndexTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheFileMetadataIndexTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactoryTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactoryTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactoryTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactoryTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStreamTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStreamTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStreamTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/ReusableBufferedOutputStreamTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java From 1f3f22a7097d7e3d504cfc9eb846ed1a389b5ccd Mon Sep 17 00:00:00 2001 From: krocard Date: Mon, 25 Oct 2021 11:50:47 +0100 Subject: [PATCH 004/113] Encapsulate TrackSelectionOverrides in its own class The current API exposes an `ImmutableMap` of `TrackGroup` -> `TrackSelectionOverride`. This has several disadvantages: - A difficult to use API for mutation (`ImmutableMap.Builder` doesn't support key removal). - There is no track selection specific methods, how the generic map API mapps to the selection override is not complex but to obvious for a casual reader. - The internal data type is exposed, making internal refactor difficult. This was done to have the API ready as quick as possible. When transitioning the clients to the map API in , it became clear that the map API was too verbose and not mapping to the clients needs, so utility methods were added to make operations clearer and more concise. Nevertheless, having to use utility method to use easily and correctly an API is not the sign of a good API. This cl refactors the track selection API for several improvements: - Add a type `TrackSelectionParameters` that encapsulate the internal data structure (map currently). - For iteration, expose as a list. - Add a `Builder` for easy mutable operations. - Add track selection specific methods to avoid having utilities functions. - Those operations are the same as `DefaultTrackSelector.Parameters` for easier migration. (`setOverride` was renamed to `addOverride`) - Move `TrackSelection` classes outside of `TrackSelectionParameters` as their own top level classes. The migration of the client code is straightforward as most of it were already using the previously mentioned utility functions that are now native methods. The full migration has not been done yet, and is pending on this cl approval. PiperOrigin-RevId: 405362719 --- .../TrackSelectionOverrides.java | 301 ++++++++++++++++++ .../TrackSelectionParameters.java | 144 +-------- .../TrackSelectionOverridesTest.java | 176 ++++++++++ .../TrackSelectionParametersTest.java | 22 +- .../trackselection/DefaultTrackSelector.java | 18 +- .../trackselection/TrackSelectionUtil.java | 73 ----- .../DefaultTrackSelectorTest.java | 26 +- .../ui/StyledPlayerControlView.java | 105 ++++-- 8 files changed, 614 insertions(+), 251 deletions(-) create mode 100644 library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java create mode 100644 library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java new file mode 100644 index 0000000000..65c659cce8 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 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.trackselection; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList; +import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList; +import static java.util.Collections.max; +import static java.util.Collections.min; + +import android.os.Bundle; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.trackselection.C.TrackType; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.Ints; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Forces the selection of the specified tracks in {@link TrackGroup TrackGroups}. + * + *

Each {@link TrackSelectionOverride override} only affects the selection of tracks of that + * {@link TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO + * audio} {@link TrackGroup} will not affect the selection of {@link C#TRACK_TYPE_VIDEO video} or + * {@link C#TRACK_TYPE_TEXT text} tracks. + * + *

If multiple {@link TrackGroup TrackGroups} of the same {@link TrackType} are overridden, which + * tracks will be selected depend on the player capabilities. For example, by default {@code + * ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link TrackType}. + * + *

Overrides of {@link TrackGroup} that are not currently available are ignored. For example, + * when the player transitions to the next {@link MediaItem} in a playlist, any overrides of the + * previous {@link MediaItem} are ignored. + * + * @see TrackSelectionParameters#trackSelectionOverrides + */ +public final class TrackSelectionOverrides implements Bundleable { + + /** Builder for {@link TrackSelectionOverrides}. */ + public static final class Builder { + // Cannot use ImmutableMap.Builder as it doesn't support removing entries. + private final HashMap overrides; + + /** Creates an builder with no {@link TrackSelectionOverride}. */ + public Builder() { + overrides = new HashMap<>(); + } + + private Builder(Map overrides) { + this.overrides = new HashMap<>(overrides); + } + + /** Adds an override for the provided {@link TrackGroup}. */ + public Builder addOverride(TrackSelectionOverride override) { + overrides.put(override.trackGroup, override); + return this; + } + + /** Removes the override associated with the provided {@link TrackGroup} if present. */ + public Builder clearOverride(TrackGroup trackGroup) { + overrides.remove(trackGroup); + return this; + } + + /** Set the override for the type of the provided {@link TrackGroup}. */ + public Builder setOverrideForType(TrackSelectionOverride override) { + clearOverridesOfType(override.getTrackType()); + overrides.put(override.trackGroup, override); + return this; + } + + /** + * Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}. + */ + public Builder clearOverridesOfType(@TrackType int trackType) { + for (Iterator it = overrides.values().iterator(); it.hasNext(); ) { + TrackSelectionOverride trackSelectionOverride = it.next(); + if (trackSelectionOverride.getTrackType() == trackType) { + it.remove(); + } + } + return this; + } + + /** Returns a new {@link TrackSelectionOverrides} instance with the current builder values. */ + public TrackSelectionOverrides build() { + return new TrackSelectionOverrides(overrides); + } + } + + /** + * Forces the selection of {@link #trackIndexes} for a {@link TrackGroup}. + * + *

If multiple {link #tracks} are overridden, as many as possible will be selected depending on + * the player capabilities. + * + *

If a {@link TrackSelectionOverride} has no tracks ({@code tracks.isEmpty()}), no tracks will + * be played. This is similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it + * will only affect the playback of the associated {@link TrackGroup}. For example, if the only + * {@link C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play + * until the next video starts. + */ + public static final class TrackSelectionOverride implements Bundleable { + + /** The {@link TrackGroup} whose {@link #trackIndexes} are forced to be selected. */ + public final TrackGroup trackGroup; + /** The index of tracks in a {@link TrackGroup} to be selected. */ + public final ImmutableList trackIndexes; + + /** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */ + public TrackSelectionOverride(TrackGroup trackGroup) { + this.trackGroup = trackGroup; + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (int i = 0; i < trackGroup.length; i++) { + builder.add(i); + } + this.trackIndexes = builder.build(); + } + + /** + * Constructs an instance to force {@code trackIndexes} in {@code trackGroup} to be selected. + * + * @param trackGroup The {@link TrackGroup} for which to override the track selection. + * @param trackIndexes The indexes of the tracks in the {@link TrackGroup} to select. + */ + public TrackSelectionOverride(TrackGroup trackGroup, List trackIndexes) { + if (!trackIndexes.isEmpty()) { + if (min(trackIndexes) < 0 || max(trackIndexes) >= trackGroup.length) { + throw new IndexOutOfBoundsException(); + } + } + this.trackGroup = trackGroup; + this.trackIndexes = ImmutableList.copyOf(trackIndexes); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackSelectionOverride that = (TrackSelectionOverride) obj; + return trackGroup.equals(that.trackGroup) && trackIndexes.equals(that.trackIndexes); + } + + @Override + public int hashCode() { + return trackGroup.hashCode() + 31 * trackIndexes.hashCode(); + } + + private @TrackType int getTrackType() { + return MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType); + } + + // Bundleable implementation + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TRACK_GROUP, + FIELD_TRACKS, + }) + private @interface FieldNumber {} + + private static final int FIELD_TRACK_GROUP = 0; + private static final int FIELD_TRACKS = 1; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle()); + bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndexes)); + return bundle; + } + + /** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + @Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP)); + checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults. + TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle); + @Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS)); + if (tracks == null) { + return new TrackSelectionOverride(trackGroup); + } + return new TrackSelectionOverride(trackGroup, Ints.asList(tracks)); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } + } + + /** Empty {@code TrackSelectionOverrides}, where no track selection is overridden. */ + public static final TrackSelectionOverrides EMPTY = + new TrackSelectionOverrides(ImmutableMap.of()); + + private final ImmutableMap overrides; + + private TrackSelectionOverrides(Map overrides) { + this.overrides = ImmutableMap.copyOf(overrides); + } + + /** Returns a {@link Builder} initialized with the values of this instance. */ + public Builder buildUpon() { + return new Builder(overrides); + } + + /** Returns all {@link TrackSelectionOverride} contained. */ + public ImmutableList asList() { + return ImmutableList.copyOf(overrides.values()); + } + + /** + * Returns the {@link TrackSelectionOverride} of the provided {@link TrackGroup} or {@code null} + * if there is none. + */ + @Nullable + public TrackSelectionOverride getOverride(TrackGroup trackGroup) { + return overrides.get(trackGroup); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackSelectionOverrides that = (TrackSelectionOverrides) obj; + return overrides.equals(that.overrides); + } + + @Override + public int hashCode() { + return overrides.hashCode(); + } + + // Bundleable implementation + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_OVERRIDES, + }) + private @interface FieldNumber {} + + private static final int FIELD_OVERRIDES = 0; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + keyForField(FIELD_OVERRIDES), toBundleArrayList(overrides.values())); + return bundle; + } + + /** Object that can restore {@code TrackSelectionOverrides} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + List trackSelectionOverrides = + fromBundleNullableList( + TrackSelectionOverride.CREATOR, + bundle.getParcelableArrayList(keyForField(FIELD_OVERRIDES)), + ImmutableList.of()); + ImmutableMap.Builder builder = + new ImmutableMap.Builder<>(); + for (int i = 0; i < trackSelectionOverrides.size(); i++) { + TrackSelectionOverride trackSelectionOverride = trackSelectionOverrides.get(i); + builder.put(trackSelectionOverride.trackGroup, trackSelectionOverride); + } + return new TrackSelectionOverrides(builder.build()); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } +} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java index 57cdc3a970..738cf74e2b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java @@ -16,8 +16,7 @@ package com.google.android.exoplayer2.trackselection; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList; -import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList; +import static com.google.android.exoplayer2.util.BundleableUtil.fromNullableBundle; import static com.google.common.base.MoreObjects.firstNonNull; import android.content.Context; @@ -30,23 +29,17 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Ints; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; /** * Constraint parameters for track selection. @@ -100,7 +93,7 @@ public class TrackSelectionParameters implements Bundleable { // General private boolean forceLowestBitrate; private boolean forceHighestSupportedBitrate; - private ImmutableMap trackSelectionOverrides; + private TrackSelectionOverrides trackSelectionOverrides; private ImmutableSet<@C.TrackType Integer> disabledTrackTypes; /** @@ -131,7 +124,7 @@ public class TrackSelectionParameters implements Bundleable { // General forceLowestBitrate = false; forceHighestSupportedBitrate = false; - trackSelectionOverrides = ImmutableMap.of(); + trackSelectionOverrides = TrackSelectionOverrides.EMPTY; disabledTrackTypes = ImmutableSet.of(); } @@ -233,17 +226,11 @@ public class TrackSelectionParameters implements Bundleable { bundle.getBoolean( keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate); - List keys = - fromBundleNullableList( - TrackGroup.CREATOR, - bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)), - ImmutableList.of()); - List values = - fromBundleNullableList( - TrackSelectionOverride.CREATOR, - bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDE_VALUES)), - ImmutableList.of()); - trackSelectionOverrides = zipToMap(keys, values); + trackSelectionOverrides = + fromNullableBundle( + TrackSelectionOverrides.CREATOR, + bundle.getBundle(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)), + TrackSelectionOverrides.EMPTY); disabledTrackTypes = ImmutableSet.copyOf( Ints.asList( @@ -642,9 +629,8 @@ public class TrackSelectionParameters implements Bundleable { * @param trackSelectionOverrides The track selection overrides. * @return This builder. */ - public Builder setTrackSelectionOverrides( - Map trackSelectionOverrides) { - this.trackSelectionOverrides = ImmutableMap.copyOf(trackSelectionOverrides); + public Builder setTrackSelectionOverrides(TrackSelectionOverrides trackSelectionOverrides) { + this.trackSelectionOverrides = trackSelectionOverrides; return this; } @@ -692,83 +678,6 @@ public class TrackSelectionParameters implements Bundleable { } return listBuilder.build(); } - - private static ImmutableMap<@NonNull K, @NonNull V> zipToMap( - List<@NonNull K> keys, List<@NonNull V> values) { - ImmutableMap.Builder<@NonNull K, @NonNull V> builder = new ImmutableMap.Builder<>(); - for (int i = 0; i < keys.size(); i++) { - builder.put(keys.get(i), values.get(i)); - } - return builder.build(); - } - } - - /** - * Forces the selection of {@link #tracks} for a {@link TrackGroup}. - * - * @see #trackSelectionOverrides - */ - public static final class TrackSelectionOverride implements Bundleable { - /** Force the selection of the associated {@link TrackGroup}, but no track will be played. */ - public static final TrackSelectionOverride DISABLE = - new TrackSelectionOverride(ImmutableSet.of()); - - /** The index of tracks in a {@link TrackGroup} to be selected. */ - public final ImmutableSet tracks; - - /** Constructs an instance to force {@code tracks} to be selected. */ - public TrackSelectionOverride(ImmutableSet tracks) { - this.tracks = tracks; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TrackSelectionOverride that = (TrackSelectionOverride) obj; - return tracks.equals(that.tracks); - } - - @Override - public int hashCode() { - return tracks.hashCode(); - } - - // Bundleable implementation - - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - FIELD_TRACKS, - }) - private @interface FieldNumber {} - - private static final int FIELD_TRACKS = 0; - - @Override - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(tracks)); - return bundle; - } - - /** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */ - public static final Creator CREATOR = - bundle -> { - @Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS)); - if (tracks == null) { - return DISABLE; - } - return new TrackSelectionOverride(ImmutableSet.copyOf(Ints.asList(tracks))); - }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** @@ -921,29 +830,8 @@ public class TrackSelectionParameters implements Bundleable { */ public final boolean forceHighestSupportedBitrate; - /** - * For each {@link TrackGroup} in the map, forces the tracks associated with it to be selected for - * playback. - * - *

For example if {@code trackSelectionOverrides.equals(ImmutableMap.of(trackGroup, - * ImmutableSet.of(1, 2, 3)))}, the tracks 1, 2 and 3 of {@code trackGroup} will be selected. - * - *

If multiple of the current {@link TrackGroup}s of the same {@link C.TrackType} are - * overridden, it is undetermined which one(s) will be selected. For example if a {@link - * MediaItem} has 2 video track groups (for example 2 different angles), and both are overriden, - * it is undetermined which one will be selected. - * - *

If multiple tracks of the {@link TrackGroup} are overriden, all supported (see {@link - * C.FormatSupport}) will be selected. - * - *

If a {@link TrackGroup} is associated with an empty set of tracks, no tracks will be played. - * This is similar to {@link #disabledTrackTypes}, except it will only affect the playback of the - * associated {@link TrackGroup}. For example, if the {@link C#TRACK_TYPE_VIDEO} {@link - * TrackGroup} is associated with no tracks, no video will play until the next video starts. - * - *

The default value is that no {@link TrackGroup} selections are overridden (empty map). - */ - public final ImmutableMap trackSelectionOverrides; + /** Overrides to force tracks to be selected. */ + public final TrackSelectionOverrides trackSelectionOverrides; /** * The track types that are disabled. No track of a disabled type will be selected, thus no track * type contained in the set will be played. The default value is that no track type is disabled @@ -1159,12 +1047,8 @@ public class TrackSelectionParameters implements Bundleable { bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate); bundle.putBoolean( keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate); - bundle.putParcelableArrayList( - keyForField(FIELD_SELECTION_OVERRIDE_KEYS), - toBundleArrayList(trackSelectionOverrides.keySet())); - bundle.putParcelableArrayList( - keyForField(FIELD_SELECTION_OVERRIDE_VALUES), - toBundleArrayList(trackSelectionOverrides.values())); + bundle.putBundle( + keyForField(FIELD_SELECTION_OVERRIDE_KEYS), trackSelectionOverrides.toBundle()); bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes)); return bundle; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java new file mode 100644 index 0000000000..2d2a27134b --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 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.trackselection; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +// packages.bara.sky: import com.google.android.exoplayer2.util.MimeTypes; +// packages.bara.sky: import com.google.android.exoplayer2.Bundleable; +// packages.bara.sky: import com.google.android.exoplayer2.C; +// packages.bara.sky: import com.google.android.exoplayer2.Format; + +/** Unit tests for {@link TrackSelectionOverrides}. */ +@RunWith(AndroidJUnit4.class) +public final class TrackSelectionOverridesTest { + + public static final TrackGroup AAC_TRACK_GROUP = + new TrackGroup(new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build()); + + private static TrackGroup newTrackGroupWithIds(int... ids) { + return new TrackGroup( + Arrays.stream(ids) + .mapToObj(id -> new Format.Builder().setId(id).build()) + .toArray(Format[]::new)); + } + + @Test + public void newTrackSelectionOverride_withJustTrackGroup_selectsAllTracks() { + TrackSelectionOverride trackSelectionOverride = + new TrackSelectionOverride(newTrackGroupWithIds(1, 2)); + + assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2)); + assertThat(trackSelectionOverride.trackIndexes).containsExactly(0, 1).inOrder(); + } + + @Test + public void newTrackSelectionOverride_withTracks_selectsOnlySpecifiedTracks() { + TrackSelectionOverride trackSelectionOverride = + new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(1)); + + assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2)); + assertThat(trackSelectionOverride.trackIndexes).containsExactly(1); + } + + @Test + public void newTrackSelectionOverride_with0Tracks_selectsAllSpecifiedTracks() { + TrackSelectionOverride trackSelectionOverride = + new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of()); + + assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2)); + assertThat(trackSelectionOverride.trackIndexes).isEmpty(); + } + + @Test + public void newTrackSelectionOverride_withInvalidIndex_throws() { + assertThrows( + IndexOutOfBoundsException.class, + () -> new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(2))); + } + + @Test + public void roundTripViaBundle_withOverrides_yieldsEqualInstance() { + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .setOverrideForType( + new TrackSelectionOverride(newTrackGroupWithIds(3, 4), ImmutableList.of(1))) + .addOverride(new TrackSelectionOverride(newTrackGroupWithIds(5, 6))) + .build(); + + TrackSelectionOverrides fromBundle = + TrackSelectionOverrides.CREATOR.fromBundle(trackSelectionOverrides.toBundle()); + + assertThat(fromBundle).isEqualTo(trackSelectionOverrides); + assertThat(fromBundle.asList()).isEqualTo(trackSelectionOverrides.asList()); + } + + @Test + public void builder_byDefault_isEmpty() { + TrackSelectionOverrides trackSelectionOverrides = new TrackSelectionOverrides.Builder().build(); + + assertThat(trackSelectionOverrides.asList()).isEmpty(); + assertThat(trackSelectionOverrides).isEqualTo(TrackSelectionOverrides.EMPTY); + } + + @Test + public void addOverride_onDifferentGroups_addsOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2)); + + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder().addOverride(override1).addOverride(override2).build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override1, override2); + assertThat(trackSelectionOverrides.getOverride(override1.trackGroup)).isEqualTo(override1); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void addOverride_onSameGroup_replacesOverride() { + TrackGroup trackGroup = newTrackGroupWithIds(1, 2, 3); + TrackSelectionOverride override1 = + new TrackSelectionOverride(trackGroup, /* trackIndexes= */ ImmutableList.of(0)); + TrackSelectionOverride override2 = + new TrackSelectionOverride(trackGroup, /* trackIndexes= */ ImmutableList.of(1)); + + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder().addOverride(override1).addOverride(override2).build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override2); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void setOverrideForType_onSameType_replacesOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2)); + + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .setOverrideForType(override1) + .setOverrideForType(override2) + .build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override2); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void clearOverridesOfType_ofTypeAudio_removesAudioOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .addOverride(override1) + .addOverride(override2) + .clearOverridesOfType(C.TRACK_TYPE_AUDIO) + .build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override2); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void clearOverride_ofTypeGroup_removesOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .addOverride(override1) + .addOverride(override2) + .clearOverride(override2.trackGroup) + .build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override1); + assertThat(trackSelectionOverrides.getOverride(override1.trackGroup)).isEqualTo(override1); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java index 701d88d0e6..075898d4d0 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java @@ -23,16 +23,16 @@ import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.MimeTypes; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; /** Tests for {@link TrackSelectionParameters}. */ @RunWith(AndroidJUnit4.class) -public class TrackSelectionParametersTest { +public final class TrackSelectionParametersTest { @Test public void defaultValue_withoutChange_isAsExpected() { @@ -64,16 +64,22 @@ public class TrackSelectionParametersTest { // General assertThat(parameters.forceLowestBitrate).isFalse(); assertThat(parameters.forceHighestSupportedBitrate).isFalse(); - assertThat(parameters.trackSelectionOverrides).isEmpty(); + assertThat(parameters.trackSelectionOverrides.asList()).isEmpty(); assertThat(parameters.disabledTrackTypes).isEmpty(); } @Test public void parametersSet_fromDefault_isAsExpected() { - ImmutableMap trackSelectionOverrides = - ImmutableMap.of( - new TrackGroup(new Format.Builder().build()), - new TrackSelectionOverride(/* tracks= */ ImmutableSet.of(2, 3))); + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .addOverride(new TrackSelectionOverride(new TrackGroup(new Format.Builder().build()))) + .addOverride( + new TrackSelectionOverride( + new TrackGroup( + new Format.Builder().setId(4).build(), + new Format.Builder().setId(5).build()), + /* trackIndexes= */ ImmutableList.of(1))) + .build(); TrackSelectionParameters parameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT .buildUpon() diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 40814ceffd..3e0ed7e887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -39,7 +39,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.BundleableUtil; import com.google.android.exoplayer2.util.Util; @@ -611,7 +611,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { @Override public ParametersBuilder setTrackSelectionOverrides( - Map trackSelectionOverrides) { + TrackSelectionOverrides trackSelectionOverrides) { super.setTrackSelectionOverrides(trackSelectionOverrides); return this; } @@ -710,7 +710,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @param groups The {@link TrackGroupArray} for which the override should be applied. * @param override The override. * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder setSelectionOverride( int rendererIndex, TrackGroupArray groups, @Nullable SelectionOverride override) { Map overrides = @@ -733,7 +735,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @param rendererIndex The renderer index. * @param groups The {@link TrackGroupArray} for which the override should be cleared. * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder clearSelectionOverride( int rendererIndex, TrackGroupArray groups) { Map overrides = @@ -754,7 +758,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * * @param rendererIndex The renderer index. * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder clearSelectionOverrides(int rendererIndex) { Map overrides = selectionOverrides.get(rendererIndex); @@ -770,7 +776,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * Clears all track selection overrides for all renderers. * * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder clearSelectionOverrides() { if (selectionOverrides.size() == 0) { // Nothing to clear. @@ -1560,9 +1568,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int j = 0; j < rendererTrackGroups.length; j++) { TrackGroup trackGroup = rendererTrackGroups.get(j); @Nullable - TrackSelectionOverride overrideTracks = params.trackSelectionOverrides.get(trackGroup); + TrackSelectionOverride overrideTracks = + params.trackSelectionOverrides.getOverride(trackGroup); if (overrideTracks != null) { - return new ExoTrackSelection.Definition(trackGroup, Ints.toArray(overrideTracks.tracks)); + return new ExoTrackSelection.Definition( + trackGroup, Ints.toArray(overrideTracks.trackIndexes)); } } return currentDefinition; // No override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java index 42f0ca68a0..a06d15b5cf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -17,21 +17,11 @@ package com.google.android.exoplayer2.trackselection; import android.os.SystemClock; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.TracksInfo; -import com.google.android.exoplayer2.TracksInfo.TrackGroupInfo; -import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; -import com.google.android.exoplayer2.util.MimeTypes; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.util.Map; import org.checkerframework.checker.nullness.compatqual.NullableType; -import org.checkerframework.dataflow.qual.Pure; /** Track selection related utility methods. */ public final class TrackSelectionUtil { @@ -134,67 +124,4 @@ public final class TrackSelectionUtil { numberOfTracks, numberOfExcludedTracks); } - - /** - * Forces tracks in a {@link TrackGroup} to be the only ones selected for a {@link C.TrackType}. - * No other tracks of that type will be selectable. If the forced tracks are not supported, then - * no tracks of that type will be selected. - * - * @param trackSelectionOverrides The current {@link TrackSelectionOverride overrides}. - * @param tracksInfo The current {@link TracksInfo}. - * @param forcedTrackGroupIndex The index of the {@link TrackGroup} in {@code tracksInfo} that - * should have its track selected. - * @param forcedTrackSelectionOverride The tracks to force selection of. - * @return The updated {@link TrackSelectionOverride overrides}. - */ - @Pure - public static ImmutableMap forceTrackSelection( - ImmutableMap trackSelectionOverrides, - TracksInfo tracksInfo, - int forcedTrackGroupIndex, - TrackSelectionOverride forcedTrackSelectionOverride) { - @C.TrackType - int trackType = tracksInfo.getTrackGroupInfos().get(forcedTrackGroupIndex).getTrackType(); - ImmutableMap.Builder overridesBuilder = - new ImmutableMap.Builder<>(); - // Maintain overrides for the other track types. - for (Map.Entry entry : trackSelectionOverrides.entrySet()) { - if (MimeTypes.getTrackType(entry.getKey().getFormat(0).sampleMimeType) != trackType) { - overridesBuilder.put(entry); - } - } - ImmutableList trackGroupInfos = tracksInfo.getTrackGroupInfos(); - for (int i = 0; i < trackGroupInfos.size(); i++) { - TrackGroup trackGroup = trackGroupInfos.get(i).getTrackGroup(); - if (i == forcedTrackGroupIndex) { - overridesBuilder.put(trackGroup, forcedTrackSelectionOverride); - } else { - overridesBuilder.put(trackGroup, TrackSelectionOverride.DISABLE); - } - } - return overridesBuilder.build(); - } - - /** - * Removes all {@link TrackSelectionOverride overrides} associated with {@link TrackGroup - * TrackGroups} of type {@code trackType}. - * - * @param trackType The {@link C.TrackType} of all overrides to remove. - * @param trackSelectionOverrides The current {@link TrackSelectionOverride overrides}. - * @return The updated {@link TrackSelectionOverride overrides}. - */ - @Pure - public static ImmutableMap - clearTrackSelectionOverridesForType( - @C.TrackType int trackType, - ImmutableMap trackSelectionOverrides) { - ImmutableMap.Builder overridesBuilder = - ImmutableMap.builder(); - for (Map.Entry entry : trackSelectionOverrides.entrySet()) { - if (MimeTypes.getTrackType(entry.getKey().getFormat(0).sampleMimeType) != trackType) { - overridesBuilder.put(entry); - } - } - return overridesBuilder.build(); - } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index e04a0530a7..0a12b61a12 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -46,13 +46,12 @@ import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.HashMap; import java.util.Map; @@ -158,12 +157,15 @@ public final class DefaultTrackSelectorTest { /** Tests that an empty override clears a track selection. */ @Test - public void selectTracks_withNullOverride_clearsTrackSelection() throws ExoPlaybackException { + public void selectTracks_withOverrideWithoutTracks_clearsTrackSelection() + throws ExoPlaybackException { trackSelector.setParameters( trackSelector .buildUponParameters() .setTrackSelectionOverrides( - ImmutableMap.of(VIDEO_TRACK_GROUP, new TrackSelectionOverride(ImmutableSet.of())))); + new TrackSelectionOverrides.Builder() + .addOverride(new TrackSelectionOverride(VIDEO_TRACK_GROUP, ImmutableList.of())) + .build())); TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); @@ -210,8 +212,11 @@ public final class DefaultTrackSelectorTest { trackSelector .buildUponParameters() .setTrackSelectionOverrides( - ImmutableMap.of( - new TrackGroup(VIDEO_FORMAT, VIDEO_FORMAT), TrackSelectionOverride.DISABLE))); + new TrackSelectionOverrides.Builder() + .setOverrideForType( + new TrackSelectionOverride( + new TrackGroup(VIDEO_FORMAT, VIDEO_FORMAT), ImmutableList.of())) + .build())); TrackSelectorResult result = trackSelector.selectTracks( @@ -1874,9 +1879,12 @@ public final class DefaultTrackSelectorTest { .setRendererDisabled(3, true) .setRendererDisabled(5, false) .setTrackSelectionOverrides( - ImmutableMap.of( - AUDIO_TRACK_GROUP, - new TrackSelectionOverride(/* tracks= */ ImmutableSet.of(3, 4, 5)))) + new TrackSelectionOverrides.Builder() + .setOverrideForType( + new TrackSelectionOverride( + new TrackGroup(AUDIO_FORMAT, AUDIO_FORMAT, AUDIO_FORMAT, AUDIO_FORMAT), + /* trackIndexes= */ ImmutableList.of(0, 2, 3))) + .build()) .setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_AUDIO)) .build(); } 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 95dee700e5..30ca26f4f9 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 @@ -32,9 +32,8 @@ import static com.google.android.exoplayer2.Player.EVENT_SEEK_FORWARD_INCREMENT_ import static com.google.android.exoplayer2.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_TRACKS_CHANGED; -import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.clearTrackSelectionOverridesForType; -import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.forceTrackSelection; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Util.castNonNull; import android.annotation.SuppressLint; import android.content.Context; @@ -68,13 +67,13 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.TracksInfo.TrackGroupInfo; import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Arrays; @@ -82,8 +81,8 @@ import java.util.Collections; import java.util.Formatter; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import org.checkerframework.dataflow.qual.Pure; /** * A view for controlling {@link Player} instances. @@ -2032,14 +2031,8 @@ public class StyledPlayerControlView extends FrameLayout { // Audio track selection option includes "Auto" at the top. holder.textView.setText(R.string.exo_track_selection_auto); // hasSelectionOverride is true means there is an explicit track selection, not "Auto". - boolean hasSelectionOverride = false; TrackSelectionParameters parameters = checkNotNull(player).getTrackSelectionParameters(); - for (int i = 0; i < tracks.size(); i++) { - if (parameters.trackSelectionOverrides.containsKey(tracks.get(i).trackGroup)) { - hasSelectionOverride = true; - break; - } - } + boolean hasSelectionOverride = hasSelectionOverride(parameters.trackSelectionOverrides); holder.checkView.setVisibility(hasSelectionOverride ? INVISIBLE : VISIBLE); holder.itemView.setOnClickListener( v -> { @@ -2048,15 +2041,18 @@ public class StyledPlayerControlView extends FrameLayout { } TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); - // Remove all audio overrides. - ImmutableMap trackSelectionOverrides = - clearTrackSelectionOverridesForType( - C.TRACK_TYPE_AUDIO, trackSelectionParameters.trackSelectionOverrides); - player.setTrackSelectionParameters( + TrackSelectionOverrides trackSelectionOverrides = trackSelectionParameters + .trackSelectionOverrides .buildUpon() - .setTrackSelectionOverrides(trackSelectionOverrides) - .build()); + .clearOverridesOfType(C.TRACK_TYPE_AUDIO) + .build(); + castNonNull(player) + .setTrackSelectionParameters( + trackSelectionParameters + .buildUpon() + .setTrackSelectionOverrides(trackSelectionOverrides) + .build()); settingsAdapter.setSubTextAtPosition( SETTINGS_AUDIO_TRACK_SELECTION_POSITION, getResources().getString(R.string.exo_track_selection_auto)); @@ -2064,6 +2060,21 @@ public class StyledPlayerControlView extends FrameLayout { }); } + private boolean hasSelectionOverride(TrackSelectionOverrides trackSelectionOverrides) { + int previousTrackGroupIndex = C.INDEX_UNSET; + for (int i = 0; i < tracks.size(); i++) { + TrackInformation track = tracks.get(i); + if (track.trackGroupIndex == previousTrackGroupIndex) { + continue; + } + if (trackSelectionOverrides.getOverride(track.trackGroup) != null) { + return true; + } + previousTrackGroupIndex = track.trackGroupIndex; + } + return false; + } + @Override public void onTrackSelection(String subtext) { settingsAdapter.setSubTextAtPosition(SETTINGS_AUDIO_TRACK_SELECTION_POSITION, subtext); @@ -2075,9 +2086,10 @@ public class StyledPlayerControlView extends FrameLayout { boolean hasSelectionOverride = false; for (int i = 0; i < trackInformations.size(); i++) { if (checkNotNull(player) - .getTrackSelectionParameters() - .trackSelectionOverrides - .containsKey(trackInformations.get(i).trackGroup)) { + .getTrackSelectionParameters() + .trackSelectionOverrides + .getOverride(trackInformations.get(i).trackGroup) + != null) { hasSelectionOverride = true; break; } @@ -2140,9 +2152,10 @@ public class StyledPlayerControlView extends FrameLayout { TrackInformation track = tracks.get(position - 1); boolean explicitlySelected = checkNotNull(player) - .getTrackSelectionParameters() - .trackSelectionOverrides - .containsKey(track.trackGroup) + .getTrackSelectionParameters() + .trackSelectionOverrides + .getOverride(track.trackGroup) + != null && track.isSelected(); holder.textView.setText(track.trackName); holder.checkView.setVisibility(explicitlySelected ? VISIBLE : INVISIBLE); @@ -2153,12 +2166,13 @@ public class StyledPlayerControlView extends FrameLayout { } TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); - Map overrides = + TrackSelectionOverrides overrides = forceTrackSelection( trackSelectionParameters.trackSelectionOverrides, track.tracksInfo, track.trackGroupIndex, - new TrackSelectionOverride(ImmutableSet.of(track.trackIndex))); + new TrackSelectionOverride( + track.trackGroup, ImmutableList.of(track.trackIndex))); checkNotNull(player) .setTrackSelectionParameters( trackSelectionParameters @@ -2196,4 +2210,41 @@ public class StyledPlayerControlView extends FrameLayout { checkView = itemView.findViewById(R.id.exo_check); } } + + /** + * Forces tracks in a {@link TrackGroup} to be the only ones selected for a {@link C.TrackType}. + * No other tracks of that type will be selectable. If the forced tracks are not supported, then + * no tracks of that type will be selected. + * + * @param trackSelectionOverrides The current {@link TrackSelectionOverride overrides}. + * @param tracksInfo The current {@link TracksInfo}. + * @param forcedTrackGroupIndex The index of the {@link TrackGroup} in {@code tracksInfo} that + * should have its track selected. + * @param forcedTrackSelectionOverride The tracks to force selection of. + * @return The updated {@link TrackSelectionOverride overrides}. + */ + @Pure + private static TrackSelectionOverrides forceTrackSelection( + TrackSelectionOverrides trackSelectionOverrides, + TracksInfo tracksInfo, + int forcedTrackGroupIndex, + TrackSelectionOverride forcedTrackSelectionOverride) { + TrackSelectionOverrides.Builder overridesBuilder = trackSelectionOverrides.buildUpon(); + + @C.TrackType + int trackType = tracksInfo.getTrackGroupInfos().get(forcedTrackGroupIndex).getTrackType(); + overridesBuilder.setOverrideForType(forcedTrackSelectionOverride); + // TrackSelectionOverride doesn't currently guarantee that only overwritten track + // group of a given type are selected, so the others have to be explicitly disabled. + // This guarantee is provided in the following patch that removes the need for this method. + ImmutableList trackGroupInfos = tracksInfo.getTrackGroupInfos(); + for (int i = 0; i < trackGroupInfos.size(); i++) { + TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i); + if (i != forcedTrackGroupIndex && trackGroupInfo.getTrackType() == trackType) { + TrackGroup trackGroup = trackGroupInfo.getTrackGroup(); + overridesBuilder.addOverride(new TrackSelectionOverride(trackGroup, ImmutableList.of())); + } + } + return overridesBuilder.build(); + } } From 17d2f5a0b1f6268e9acdc602807c51f09096e166 Mon Sep 17 00:00:00 2001 From: kimvde Date: Mon, 25 Oct 2021 12:05:41 +0100 Subject: [PATCH 005/113] Transformer: avoid retrieving the video decoded bytes Decoded video frames can be large and there is no need to retrieve the corresponding ByteBuffer as we render the decoded frames on a surface for better performance. PiperOrigin-RevId: 405364950 --- .../transformer/TransformerTranscodingVideoRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index 88e04a0b70..79d28a208e 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -305,7 +305,7 @@ import java.nio.ByteBuffer; if (!isDecoderSurfacePopulated) { if (!waitingForPopulatedDecoderSurface) { - if (decoder.getOutputBuffer() != null) { + if (decoder.getOutputBufferInfo() != null) { decoder.releaseOutputBuffer(/* render= */ true); waitingForPopulatedDecoderSurface = true; } From a42d9f36b12033eb7accc3c08dbb365c66800e6f Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 12:28:46 +0100 Subject: [PATCH 006/113] Allow output audio MIME type to be set in TranscodingTransformer. This introduces a new option `setAudioMimeType` in `TranscodingTransformer.Builder` and a corresponding check whether the selected type is supported. This check is done using `supportsSampleMimeType` which is now part of the `Muxer.Factory` and `MuxerWrapper` rather than `Muxer`. A new field `audioMimeType` is added to `Transformation` and the `TransformerAudioRenderer` uses this instead of the input MIME type if requested. PiperOrigin-RevId: 405367817 --- .../transformer/FrameworkMuxer.java | 63 +++++++++---------- .../android/exoplayer2/transformer/Muxer.java | 20 +++--- .../exoplayer2/transformer/MuxerWrapper.java | 8 ++- .../transformer/TranscodingTransformer.java | 48 ++++++++++++-- .../transformer/Transformation.java | 7 ++- .../exoplayer2/transformer/Transformer.java | 16 +++-- .../transformer/TransformerAudioRenderer.java | 6 +- .../transformer/TransformerBaseRenderer.java | 8 ++- .../exoplayer2/transformer/TestMuxer.java | 21 +++---- .../transformer/TransformerTest.java | 18 +++++- 10 files changed, 144 insertions(+), 71 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java index 737e1285f5..fc5b65891d 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java @@ -42,7 +42,7 @@ import java.nio.ByteBuffer; @Override public FrameworkMuxer create(String path, String outputMimeType) throws IOException { MediaMuxer mediaMuxer = new MediaMuxer(path, mimeTypeToMuxerOutputFormat(outputMimeType)); - return new FrameworkMuxer(mediaMuxer, outputMimeType); + return new FrameworkMuxer(mediaMuxer); } @RequiresApi(26) @@ -53,7 +53,7 @@ import java.nio.ByteBuffer; new MediaMuxer( parcelFileDescriptor.getFileDescriptor(), mimeTypeToMuxerOutputFormat(outputMimeType)); - return new FrameworkMuxer(mediaMuxer, outputMimeType); + return new FrameworkMuxer(mediaMuxer); } @Override @@ -65,47 +65,46 @@ import java.nio.ByteBuffer; } return true; } + + @Override + public boolean supportsSampleMimeType( + @Nullable String sampleMimeType, String containerMimeType) { + // MediaMuxer supported sample formats are documented in MediaMuxer.addTrack(MediaFormat). + boolean isAudio = MimeTypes.isAudio(sampleMimeType); + boolean isVideo = MimeTypes.isVideo(sampleMimeType); + if (containerMimeType.equals(MimeTypes.VIDEO_MP4)) { + if (isVideo) { + return MimeTypes.VIDEO_H263.equals(sampleMimeType) + || MimeTypes.VIDEO_H264.equals(sampleMimeType) + || MimeTypes.VIDEO_MP4V.equals(sampleMimeType) + || (Util.SDK_INT >= 24 && MimeTypes.VIDEO_H265.equals(sampleMimeType)); + } else if (isAudio) { + return MimeTypes.AUDIO_AAC.equals(sampleMimeType) + || MimeTypes.AUDIO_AMR_NB.equals(sampleMimeType) + || MimeTypes.AUDIO_AMR_WB.equals(sampleMimeType); + } + } else if (containerMimeType.equals(MimeTypes.VIDEO_WEBM) && SDK_INT >= 21) { + if (isVideo) { + return MimeTypes.VIDEO_VP8.equals(sampleMimeType) + || (Util.SDK_INT >= 24 && MimeTypes.VIDEO_VP9.equals(sampleMimeType)); + } else if (isAudio) { + return MimeTypes.AUDIO_VORBIS.equals(sampleMimeType); + } + } + return false; + } } private final MediaMuxer mediaMuxer; - private final String outputMimeType; private final MediaCodec.BufferInfo bufferInfo; private boolean isStarted; - private FrameworkMuxer(MediaMuxer mediaMuxer, String outputMimeType) { + private FrameworkMuxer(MediaMuxer mediaMuxer) { this.mediaMuxer = mediaMuxer; - this.outputMimeType = outputMimeType; bufferInfo = new MediaCodec.BufferInfo(); } - @Override - public boolean supportsSampleMimeType(@Nullable String mimeType) { - // MediaMuxer supported sample formats are documented in MediaMuxer.addTrack(MediaFormat). - boolean isAudio = MimeTypes.isAudio(mimeType); - boolean isVideo = MimeTypes.isVideo(mimeType); - if (outputMimeType.equals(MimeTypes.VIDEO_MP4)) { - if (isVideo) { - return MimeTypes.VIDEO_H263.equals(mimeType) - || MimeTypes.VIDEO_H264.equals(mimeType) - || MimeTypes.VIDEO_MP4V.equals(mimeType) - || (Util.SDK_INT >= 24 && MimeTypes.VIDEO_H265.equals(mimeType)); - } else if (isAudio) { - return MimeTypes.AUDIO_AAC.equals(mimeType) - || MimeTypes.AUDIO_AMR_NB.equals(mimeType) - || MimeTypes.AUDIO_AMR_WB.equals(mimeType); - } - } else if (outputMimeType.equals(MimeTypes.VIDEO_WEBM) && SDK_INT >= 21) { - if (isVideo) { - return MimeTypes.VIDEO_VP8.equals(mimeType) - || (Util.SDK_INT >= 24 && MimeTypes.VIDEO_VP9.equals(mimeType)); - } else if (isAudio) { - return MimeTypes.AUDIO_VORBIS.equals(mimeType); - } - } - return false; - } - @Override public int addTrack(Format format) { String sampleMimeType = checkNotNull(format.sampleMimeType); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Muxer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Muxer.java index b00fbadfd5..20a868edb2 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Muxer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Muxer.java @@ -25,11 +25,12 @@ import java.nio.ByteBuffer; /** * Abstracts media muxing operations. * - *

Query whether {@link #supportsSampleMimeType(String) sample MIME types are supported} and - * {@link #addTrack(Format) add all tracks}, then {@link #writeSampleData(int, ByteBuffer, boolean, - * long) write sample data} to mux samples. Once any sample data has been written, it is not - * possible to add tracks. After writing all sample data, {@link #release(boolean) release} the - * instance to finish writing to the output and return any resources to the system. + *

Query whether {@link Factory#supportsOutputMimeType(String) container MIME type} and {@link + * Factory#supportsSampleMimeType(String, String) sample MIME types} are supported and {@link + * #addTrack(Format) add all tracks}, then {@link #writeSampleData(int, ByteBuffer, boolean, long) + * write sample data} to mux samples. Once any sample data has been written, it is not possible to + * add tracks. After writing all sample data, {@link #release(boolean) release} the instance to + * finish writing to the output and return any resources to the system. */ /* package */ interface Muxer { @@ -62,10 +63,13 @@ import java.nio.ByteBuffer; /** Returns whether the {@link MimeTypes MIME type} provided is a supported output format. */ boolean supportsOutputMimeType(String mimeType); - } - /** Returns whether the sample {@link MimeTypes MIME type} is supported. */ - boolean supportsSampleMimeType(@Nullable String mimeType); + /** + * Returns whether the sample {@link MimeTypes MIME type} is supported with the given container + * {@link MimeTypes MIME type}. + */ + boolean supportsSampleMimeType(@Nullable String sampleMimeType, String containerMimeType); + } /** * Adds a track with the specified format, and returns its index (to be passed in subsequent calls diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java index 6127fd567a..e8a217f645 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java @@ -45,8 +45,10 @@ import java.nio.ByteBuffer; private static final long MAX_TRACK_WRITE_AHEAD_US = C.msToUs(500); private final Muxer muxer; + private final Muxer.Factory muxerFactory; private final SparseIntArray trackTypeToIndex; private final SparseLongArray trackTypeToTimeUs; + private final String containerMimeType; private int trackCount; private int trackFormatCount; @@ -54,8 +56,10 @@ import java.nio.ByteBuffer; private @C.TrackType int previousTrackType; private long minTrackTimeUs; - public MuxerWrapper(Muxer muxer) { + public MuxerWrapper(Muxer muxer, Muxer.Factory muxerFactory, String containerMimeType) { this.muxer = muxer; + this.muxerFactory = muxerFactory; + this.containerMimeType = containerMimeType; trackTypeToIndex = new SparseIntArray(); trackTypeToTimeUs = new SparseLongArray(); previousTrackType = C.TRACK_TYPE_NONE; @@ -78,7 +82,7 @@ import java.nio.ByteBuffer; /** Returns whether the sample {@link MimeTypes MIME type} is supported. */ public boolean supportsSampleMimeType(@Nullable String mimeType) { - return muxer.supportsSampleMimeType(mimeType); + return muxerFactory.supportsSampleMimeType(mimeType, containerMimeType); } /** diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 02db36c6ad..61feaf5dd0 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -100,6 +100,7 @@ public final class TranscodingTransformer { private boolean removeVideo; private boolean flattenForSlowMotion; private String outputMimeType; + @Nullable private String audioMimeType; private TranscodingTransformer.Listener listener; private Looper looper; private Clock clock; @@ -122,6 +123,7 @@ public final class TranscodingTransformer { this.removeVideo = transcodingTransformer.transformation.removeVideo; this.flattenForSlowMotion = transcodingTransformer.transformation.flattenForSlowMotion; this.outputMimeType = transcodingTransformer.transformation.outputMimeType; + this.audioMimeType = transcodingTransformer.transformation.audioMimeType; this.listener = transcodingTransformer.listener; this.looper = transcodingTransformer.looper; this.clock = transcodingTransformer.clock; @@ -212,10 +214,8 @@ public final class TranscodingTransformer { } /** - * Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. The - * output MIME type should be supported by the {@link - * Muxer.Factory#supportsOutputMimeType(String) muxer}. Values supported by the default {@link - * FrameworkMuxer} are: + * Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported + * values are: * *

    *
  • {@link MimeTypes#VIDEO_MP4} @@ -230,6 +230,31 @@ public final class TranscodingTransformer { return this; } + /** + * Sets the audio MIME type of the output. The default value is to use the same MIME type as the + * input. Supported values are: + * + *
      + *
    • when the container MIME type is {@link MimeTypes#VIDEO_MP4}: + *
        + *
      • {@link MimeTypes#AUDIO_AAC} + *
      • {@link MimeTypes#AUDIO_AMR_NB} + *
      • {@link MimeTypes#AUDIO_AMR_WB} + *
      + *
    • when the container MIME type is {@link MimeTypes#VIDEO_WEBM}: + *
        + *
      • {@link MimeTypes#AUDIO_VORBIS} + *
      + *
    + * + * @param audioMimeType The MIME type of the audio samples in the output. + * @return This builder. + */ + public Builder setAudioMimeType(String audioMimeType) { + this.audioMimeType = audioMimeType; + return this; + } + /** * Sets the {@link TranscodingTransformer.Listener} to listen to the transformation events. * @@ -290,6 +315,7 @@ public final class TranscodingTransformer { * @throws IllegalStateException If both audio and video have been removed (otherwise the output * would not contain any samples). * @throws IllegalStateException If the muxer doesn't support the requested output MIME type. + * @throws IllegalStateException If the muxer doesn't support the requested audio MIME type. */ public TranscodingTransformer build() { checkStateNotNull(context); @@ -303,8 +329,17 @@ public final class TranscodingTransformer { checkState( muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); + if (audioMimeType != null) { + checkState( + muxerFactory.supportsSampleMimeType(audioMimeType, outputMimeType), + "Unsupported sample MIME type " + + audioMimeType + + " for container MIME type " + + outputMimeType); + } Transformation transformation = - new Transformation(removeAudio, removeVideo, flattenForSlowMotion, outputMimeType); + new Transformation( + removeAudio, removeVideo, flattenForSlowMotion, outputMimeType, audioMimeType); return new TranscodingTransformer( context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock); } @@ -469,7 +504,8 @@ public final class TranscodingTransformer { throw new IllegalStateException("There is already a transformation in progress."); } - MuxerWrapper muxerWrapper = new MuxerWrapper(muxer); + MuxerWrapper muxerWrapper = + new MuxerWrapper(muxer, muxerFactory, transformation.outputMimeType); this.muxerWrapper = muxerWrapper; DefaultTrackSelector trackSelector = new DefaultTrackSelector(context); trackSelector.setParameters( diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java index b0c9e8d2cc..a224fe3a93 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java @@ -16,6 +16,8 @@ package com.google.android.exoplayer2.transformer; +import androidx.annotation.Nullable; + /** A media transformation configuration. */ /* package */ final class Transformation { @@ -23,15 +25,18 @@ package com.google.android.exoplayer2.transformer; public final boolean removeVideo; public final boolean flattenForSlowMotion; public final String outputMimeType; + @Nullable public final String audioMimeType; public Transformation( boolean removeAudio, boolean removeVideo, boolean flattenForSlowMotion, - String outputMimeType) { + String outputMimeType, + @Nullable String audioMimeType) { this.removeAudio = removeAudio; this.removeVideo = removeVideo; this.flattenForSlowMotion = flattenForSlowMotion; this.outputMimeType = outputMimeType; + this.audioMimeType = audioMimeType; } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index f300f66aa3..34d10137b8 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -209,10 +209,8 @@ public final class Transformer { } /** - * Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. The - * output MIME type should be supported by the {@link - * Muxer.Factory#supportsOutputMimeType(String) muxer}. Values supported by the default {@link - * FrameworkMuxer} are: + * Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported + * values are: * *
      *
    • {@link MimeTypes#VIDEO_MP4} @@ -301,7 +299,12 @@ public final class Transformer { muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); Transformation transformation = - new Transformation(removeAudio, removeVideo, flattenForSlowMotion, outputMimeType); + new Transformation( + removeAudio, + removeVideo, + flattenForSlowMotion, + outputMimeType, + /* audioMimeType= */ null); return new Transformer( context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock); } @@ -464,7 +467,8 @@ public final class Transformer { throw new IllegalStateException("There is already a transformation in progress."); } - MuxerWrapper muxerWrapper = new MuxerWrapper(muxer); + MuxerWrapper muxerWrapper = + new MuxerWrapper(muxer, muxerFactory, transformation.outputMimeType); this.muxerWrapper = muxerWrapper; DefaultTrackSelector trackSelector = new DefaultTrackSelector(context); trackSelector.setParameters( diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 6b1d0f3b90..64dc53d58c 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -350,11 +350,15 @@ import java.nio.ByteBuffer; throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED); } } + String audioMimeType = + transformation.audioMimeType == null + ? checkNotNull(inputFormat).sampleMimeType + : transformation.audioMimeType; try { encoder = MediaCodecAdapterWrapper.createForAudioEncoding( new Format.Builder() - .setSampleMimeType(checkNotNull(inputFormat).sampleMimeType) + .setSampleMimeType(audioMimeType) .setSampleRate(outputAudioFormat.sampleRate) .setChannelCount(outputAudioFormat.channelCount) .setAverageBitrate(DEFAULT_ENCODER_BITRATE) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java index 724790b8c6..4f2243000d 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java @@ -52,7 +52,13 @@ import com.google.android.exoplayer2.util.MimeTypes; @Nullable String sampleMimeType = format.sampleMimeType; if (MimeTypes.getTrackType(sampleMimeType) != getTrackType()) { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); - } else if (muxerWrapper.supportsSampleMimeType(sampleMimeType)) { + } else if ((MimeTypes.isAudio(sampleMimeType) + && muxerWrapper.supportsSampleMimeType( + transformation.audioMimeType == null + ? sampleMimeType + : transformation.audioMimeType)) + || (MimeTypes.isVideo(sampleMimeType) + && muxerWrapper.supportsSampleMimeType(sampleMimeType))) { return RendererCapabilities.create(C.FORMAT_HANDLED); } else { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java index e4835274c2..d47dd0d32f 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java @@ -26,30 +26,27 @@ import java.util.List; /** * An implementation of {@link Muxer} that supports dumping information about all interactions (for - * testing purposes) and delegates the actual muxing operations to a {@link FrameworkMuxer}. + * testing purposes) and delegates the actual muxing operations to another {@link Muxer} created + * using the factory provided. */ public final class TestMuxer implements Muxer, Dumper.Dumpable { - private final Muxer frameworkMuxer; + private final Muxer muxer; private final List dumpables; /** Creates a new test muxer. */ - public TestMuxer(String path, String outputMimeType) throws IOException { - frameworkMuxer = new FrameworkMuxer.Factory().create(path, outputMimeType); + public TestMuxer(String path, String outputMimeType, Muxer.Factory muxerFactory) + throws IOException { + muxer = muxerFactory.create(path, outputMimeType); dumpables = new ArrayList<>(); dumpables.add(dumper -> dumper.add("containerMimeType", outputMimeType)); } // Muxer implementation. - @Override - public boolean supportsSampleMimeType(String mimeType) { - return frameworkMuxer.supportsSampleMimeType(mimeType); - } - @Override public int addTrack(Format format) { - int trackIndex = frameworkMuxer.addTrack(format); + int trackIndex = muxer.addTrack(format); dumpables.add(new DumpableFormat(format, trackIndex)); return trackIndex; } @@ -58,13 +55,13 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable { public void writeSampleData( int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) { dumpables.add(new DumpableSample(trackIndex, data, isKeyFrame, presentationTimeUs)); - frameworkMuxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs); + muxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs); } @Override public void release(boolean forCancellation) { dumpables.add(dumper -> dumper.add("released", true)); - frameworkMuxer.release(forCancellation); + muxer.release(forCancellation); } // Dumper.Dumpable implementation. diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java index a99b4a369b..690b6fe2bb 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java @@ -562,16 +562,25 @@ public final class TransformerTest { } private final class TestMuxerFactory implements Muxer.Factory { + + private final Muxer.Factory frameworkMuxerFactory; + + public TestMuxerFactory() { + frameworkMuxerFactory = new FrameworkMuxer.Factory(); + } + @Override public Muxer create(String path, String outputMimeType) throws IOException { - testMuxer = new TestMuxer(path, outputMimeType); + testMuxer = new TestMuxer(path, outputMimeType, frameworkMuxerFactory); return testMuxer; } @Override public Muxer create(ParcelFileDescriptor parcelFileDescriptor, String outputMimeType) throws IOException { - testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), outputMimeType); + testMuxer = + new TestMuxer( + "FD:" + parcelFileDescriptor.getFd(), outputMimeType, frameworkMuxerFactory); return testMuxer; } @@ -579,5 +588,10 @@ public final class TransformerTest { public boolean supportsOutputMimeType(String mimeType) { return true; } + + @Override + public boolean supportsSampleMimeType(String sampleMimeType, String outputMimeType) { + return frameworkMuxerFactory.supportsSampleMimeType(sampleMimeType, outputMimeType); + } } } From 2dc7ac38513a7c7eef8f4de9242ec7c5ed2b1029 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 13:23:53 +0100 Subject: [PATCH 007/113] Upgrade RTMP dependency and remove jcenter() PiperOrigin-RevId: 405375352 --- RELEASENOTES.md | 4 ++++ extensions/rtmp/build.gradle | 2 +- .../google/android/exoplayer2/ext/rtmp/RtmpDataSource.java | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2ace17dc48..1d82331ccb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,7 @@ `Player.removeListener(EventListener)` out of `Player` into subclasses. * Fix `mediaMetadata` being reset when media is repeated ([#9458](https://github.com/google/ExoPlayer/issues/9458)). + * Remove final dependency on `jcenter()`. * Video: * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a released `Surface` when playing without an app-provided `Surface` @@ -77,6 +78,9 @@ * Populate `Format.sampleMimeType`, `width` and `height` for image `AdaptationSet` elements ([#9500](https://github.com/google/ExoPlayer/issues/9500)). +* RTMP extension: + * Upgrade to `io.antmedia:rtmp_client`, which does not rely on `jcenter()` + ([#9591](https://github.com/google/ExoPlayer/issues/9591)). * Remove deprecated symbols: * Remove `Renderer.VIDEO_SCALING_MODE_*` constants. Use identically named constants in `C` instead. diff --git a/extensions/rtmp/build.gradle b/extensions/rtmp/build.gradle index b2ff46f80a..1d55cd0e32 100644 --- a/extensions/rtmp/build.gradle +++ b/extensions/rtmp/build.gradle @@ -16,7 +16,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { implementation project(modulePrefix + 'library-common') implementation project(modulePrefix + 'library-datasource') - implementation 'net.butterflytv.utils:rtmp-client:3.1.0' + implementation 'io.antmedia:rtmp-client:3.2.0' implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion testImplementation project(modulePrefix + 'library-core') diff --git a/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSource.java b/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSource.java index 8093b4d275..fdcaf25e67 100644 --- a/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSource.java +++ b/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSource.java @@ -25,9 +25,9 @@ import com.google.android.exoplayer2.upstream.BaseDataSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; +import io.antmedia.rtmp_client.RtmpClient; +import io.antmedia.rtmp_client.RtmpClient.RtmpIOException; import java.io.IOException; -import net.butterflytv.rtmp_client.RtmpClient; -import net.butterflytv.rtmp_client.RtmpClient.RtmpIOException; /** A Real-Time Messaging Protocol (RTMP) {@link DataSource}. */ public final class RtmpDataSource extends BaseDataSource { From 988a55db9d69fd66726a39daf698bf01e2256b66 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 13:44:13 +0100 Subject: [PATCH 008/113] Rm stray blank line PiperOrigin-RevId: 405377964 --- common_library_config.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/common_library_config.gradle b/common_library_config.gradle index 431a7ab14d..6164b35e15 100644 --- a/common_library_config.gradle +++ b/common_library_config.gradle @@ -11,7 +11,6 @@ // 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.exoplayerSettingsDir/constants.gradle" apply plugin: 'com.android.library' From 2b97455a8c78806b4f2a03695a8e8c5c98c1c2f2 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 13:56:56 +0100 Subject: [PATCH 009/113] Register newly split modules PiperOrigin-RevId: 405379511 --- .../com/google/android/exoplayer2/upstream/DataSpec.java | 5 +++++ .../android/exoplayer2/decoder/DecoderInputBuffer.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index dde0835517..a117a2e3c5 100644 --- a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -19,6 +19,7 @@ import android.net.Uri; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.util.Assertions; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -30,6 +31,10 @@ import java.util.Map; /** Defines a region of data in a resource. */ public final class DataSpec { + static { + ExoPlayerLibraryInfo.registerModule("goog.exo.datasource"); + } + /** * Builds {@link DataSpec} instances. * diff --git a/library/decoder/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/decoder/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index d10f24184c..5ddfb9cc81 100644 --- a/library/decoder/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/decoder/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.decoder; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Format; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -28,6 +29,10 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; /** Holds input for a decoder. */ public class DecoderInputBuffer extends Buffer { + static { + ExoPlayerLibraryInfo.registerModule("goog.exo.decoder"); + } + /** * Thrown when an attempt is made to write into a {@link DecoderInputBuffer} whose {@link * #bufferReplacementMode} is {@link #BUFFER_REPLACEMENT_MODE_DISABLED} and who {@link #data} From cd6c2e989f68323a5e7a04aea03b76ddd67ae0f6 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Mon, 25 Oct 2021 14:22:30 +0100 Subject: [PATCH 010/113] Change MediaMetadata update priority to favour MediaItem values. The static and dynamic metadata now build up in a list, such that when the MediaMetadata is built, they are applied in an event order. This means that newer/fresher values will overwrite older ones. The MediaItem values are then applied at the end, as they take priority over any other. #minor-release PiperOrigin-RevId: 405383177 --- RELEASENOTES.md | 3 + .../android/exoplayer2/MediaMetadata.java | 102 ++++++++++++++++++ .../com/google/android/exoplayer2/Player.java | 4 +- .../android/exoplayer2/MediaMetadataTest.java | 88 +++++++++------ .../android/exoplayer2/ExoPlayerImpl.java | 42 +++++++- .../android/exoplayer2/ExoPlayerTest.java | 40 +++++++ 6 files changed, 241 insertions(+), 38 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1d82331ccb..023d02d95f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -25,6 +25,9 @@ * Fix `mediaMetadata` being reset when media is repeated ([#9458](https://github.com/google/ExoPlayer/issues/9458)). * Remove final dependency on `jcenter()`. + * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values + input through the `MediaItem.MediaMetadata` are used above media + derived values. * Video: * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a released `Surface` when playing without an app-provided `Surface` diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index e76edee173..4dad2da4c3 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -383,6 +383,108 @@ public final class MediaMetadata implements Bundleable { return this; } + /** Populates all the fields from {@code mediaMetadata}, provided they are non-null. */ + public Builder populate(@Nullable MediaMetadata mediaMetadata) { + if (mediaMetadata == null) { + return this; + } + if (mediaMetadata.title != null) { + setTitle(mediaMetadata.title); + } + if (mediaMetadata.artist != null) { + setArtist(mediaMetadata.artist); + } + if (mediaMetadata.albumTitle != null) { + setAlbumTitle(mediaMetadata.albumTitle); + } + if (mediaMetadata.albumArtist != null) { + setAlbumArtist(mediaMetadata.albumArtist); + } + if (mediaMetadata.displayTitle != null) { + setDisplayTitle(mediaMetadata.displayTitle); + } + if (mediaMetadata.subtitle != null) { + setSubtitle(mediaMetadata.subtitle); + } + if (mediaMetadata.description != null) { + setDescription(mediaMetadata.description); + } + if (mediaMetadata.mediaUri != null) { + setMediaUri(mediaMetadata.mediaUri); + } + if (mediaMetadata.userRating != null) { + setUserRating(mediaMetadata.userRating); + } + if (mediaMetadata.overallRating != null) { + setOverallRating(mediaMetadata.overallRating); + } + if (mediaMetadata.artworkData != null) { + setArtworkData(mediaMetadata.artworkData, mediaMetadata.artworkDataType); + } + if (mediaMetadata.artworkUri != null) { + setArtworkUri(mediaMetadata.artworkUri); + } + if (mediaMetadata.trackNumber != null) { + setTrackNumber(mediaMetadata.trackNumber); + } + if (mediaMetadata.totalTrackCount != null) { + setTotalTrackCount(mediaMetadata.totalTrackCount); + } + if (mediaMetadata.folderType != null) { + setFolderType(mediaMetadata.folderType); + } + if (mediaMetadata.isPlayable != null) { + setIsPlayable(mediaMetadata.isPlayable); + } + if (mediaMetadata.year != null) { + setRecordingYear(mediaMetadata.year); + } + if (mediaMetadata.recordingYear != null) { + setRecordingYear(mediaMetadata.recordingYear); + } + if (mediaMetadata.recordingMonth != null) { + setRecordingMonth(mediaMetadata.recordingMonth); + } + if (mediaMetadata.recordingDay != null) { + setRecordingDay(mediaMetadata.recordingDay); + } + if (mediaMetadata.releaseYear != null) { + setReleaseYear(mediaMetadata.releaseYear); + } + if (mediaMetadata.releaseMonth != null) { + setReleaseMonth(mediaMetadata.releaseMonth); + } + if (mediaMetadata.releaseDay != null) { + setReleaseDay(mediaMetadata.releaseDay); + } + if (mediaMetadata.writer != null) { + setWriter(mediaMetadata.writer); + } + if (mediaMetadata.composer != null) { + setComposer(mediaMetadata.composer); + } + if (mediaMetadata.conductor != null) { + setConductor(mediaMetadata.conductor); + } + if (mediaMetadata.discNumber != null) { + setDiscNumber(mediaMetadata.discNumber); + } + if (mediaMetadata.totalDiscCount != null) { + setTotalDiscCount(mediaMetadata.totalDiscCount); + } + if (mediaMetadata.genre != null) { + setGenre(mediaMetadata.genre); + } + if (mediaMetadata.compilation != null) { + setCompilation(mediaMetadata.compilation); + } + if (mediaMetadata.extras != null) { + setExtras(mediaMetadata.extras); + } + + return this; + } + /** Returns a new {@link MediaMetadata} instance with the current builder values. */ public MediaMetadata build() { return new MediaMetadata(/* builder= */ this); 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 b2b80d0d81..34d94ede8b 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 @@ -2065,7 +2065,9 @@ public interface Player { * *

      This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the * static and dynamic metadata from the {@link TrackSelection#getFormat(int) track selections' - * formats} and {@link Listener#onMetadata(Metadata)}. + * formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in the {@link + * MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or + * dynamic metadata. */ MediaMetadata getMediaMetadata(); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java index 769758647e..9d2d78f48e 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java @@ -27,6 +27,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class MediaMetadataTest { + private static final String EXTRAS_KEY = "exampleKey"; + private static final String EXTRAS_VALUE = "exampleValue"; + @Test public void builder_minimal_correctDefaults() { MediaMetadata mediaMetadata = new MediaMetadata.Builder().build(); @@ -91,41 +94,62 @@ public class MediaMetadataTest { } @Test - public void roundTripViaBundle_yieldsEqualInstance() { - Bundle extras = new Bundle(); - extras.putString("exampleKey", "exampleValue"); + public void populate_populatesEveryField() { + MediaMetadata mediaMetadata = getFullyPopulatedMediaMetadata(); + MediaMetadata populated = new MediaMetadata.Builder().populate(mediaMetadata).build(); - MediaMetadata mediaMetadata = - new MediaMetadata.Builder() - .setTitle("title") - .setAlbumArtist("the artist") - .setMediaUri(Uri.parse("https://www.google.com")) - .setUserRating(new HeartRating(false)) - .setOverallRating(new PercentageRating(87.4f)) - .setArtworkData( - new byte[] {-88, 12, 3, 2, 124, -54, -33, 69}, MediaMetadata.PICTURE_TYPE_MEDIA) - .setTrackNumber(4) - .setTotalTrackCount(12) - .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) - .setIsPlayable(true) - .setRecordingYear(2000) - .setRecordingMonth(11) - .setRecordingDay(23) - .setReleaseYear(2001) - .setReleaseMonth(1) - .setReleaseDay(2) - .setComposer("Composer") - .setConductor("Conductor") - .setWriter("Writer") - .setDiscNumber(1) - .setTotalDiscCount(3) - .setGenre("Pop") - .setCompilation("Amazing songs.") - .setExtras(extras) // Extras is not implemented in MediaMetadata.equals(Object o). - .build(); + // If this assertion fails, it's likely that a field is not being updated in + // MediaMetadata.Builder#populate(MediaMetadata). + assertThat(populated).isEqualTo(mediaMetadata); + assertThat(populated.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE); + } + + @Test + public void roundTripViaBundle_yieldsEqualInstance() { + MediaMetadata mediaMetadata = getFullyPopulatedMediaMetadata(); MediaMetadata fromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle()); assertThat(fromBundle).isEqualTo(mediaMetadata); - assertThat(fromBundle.extras.getString("exampleKey")).isEqualTo("exampleValue"); + // Extras is not implemented in MediaMetadata.equals(Object o). + assertThat(fromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE); + } + + private static MediaMetadata getFullyPopulatedMediaMetadata() { + Bundle extras = new Bundle(); + extras.putString(EXTRAS_KEY, EXTRAS_VALUE); + + return new MediaMetadata.Builder() + .setTitle("title") + .setArtist("artist") + .setAlbumTitle("album title") + .setAlbumArtist("album artist") + .setDisplayTitle("display title") + .setSubtitle("subtitle") + .setDescription("description") + .setMediaUri(Uri.parse("https://www.google.com")) + .setUserRating(new HeartRating(false)) + .setOverallRating(new PercentageRating(87.4f)) + .setArtworkData( + new byte[] {-88, 12, 3, 2, 124, -54, -33, 69}, MediaMetadata.PICTURE_TYPE_MEDIA) + .setArtworkUri(Uri.parse("https://www.google.com")) + .setTrackNumber(4) + .setTotalTrackCount(12) + .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) + .setIsPlayable(true) + .setRecordingYear(2000) + .setRecordingMonth(11) + .setRecordingDay(23) + .setReleaseYear(2001) + .setReleaseMonth(1) + .setReleaseDay(2) + .setComposer("Composer") + .setConductor("Conductor") + .setWriter("Writer") + .setDiscNumber(1) + .setTotalDiscCount(3) + .setGenre("Pop") + .setCompilation("Amazing songs.") + .setExtras(extras) + .build(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index bee9a64ea6..78f232e9a5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -39,6 +39,7 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.ShuffleOrder; +import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; @@ -111,6 +112,10 @@ import java.util.concurrent.CopyOnWriteArraySet; private MediaMetadata mediaMetadata; private MediaMetadata playlistMetadata; + // MediaMetadata built from static (TrackGroup Format) and dynamic (onMetadata(Metadata)) metadata + // sources. + private MediaMetadata staticAndDynamicMediaMetadata; + // Playback information when there is no pending seek/set source operation. private PlaybackInfo playbackInfo; @@ -229,6 +234,7 @@ import java.util.concurrent.CopyOnWriteArraySet; .build(); mediaMetadata = MediaMetadata.EMPTY; playlistMetadata = MediaMetadata.EMPTY; + staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; maskingWindowIndex = C.INDEX_UNSET; playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null); playbackInfoUpdateListener = @@ -986,8 +992,11 @@ import java.util.concurrent.CopyOnWriteArraySet; } public void onMetadata(Metadata metadata) { - MediaMetadata newMediaMetadata = - mediaMetadata.buildUpon().populateFromMetadata(metadata).build(); + staticAndDynamicMediaMetadata = + staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build(); + + MediaMetadata newMediaMetadata = buildUpdatedMediaMetadata(); + if (newMediaMetadata.equals(mediaMetadata)) { return; } @@ -1235,12 +1244,17 @@ import java.util.concurrent.CopyOnWriteArraySet; .windowIndex; mediaItem = newPlaybackInfo.timeline.getWindow(windowIndex, window).mediaItem; } - newMediaMetadata = mediaItem != null ? mediaItem.mediaMetadata : MediaMetadata.EMPTY; + staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; } if (mediaItemTransitioned || !previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) { - newMediaMetadata = - newMediaMetadata.buildUpon().populateFromMetadata(newPlaybackInfo.staticMetadata).build(); + staticAndDynamicMediaMetadata = + staticAndDynamicMediaMetadata + .buildUpon() + .populateFromMetadata(newPlaybackInfo.staticMetadata) + .build(); + + newMediaMetadata = buildUpdatedMediaMetadata(); } boolean metadataChanged = !newMediaMetadata.equals(mediaMetadata); mediaMetadata = newMediaMetadata; @@ -1794,6 +1808,24 @@ import java.util.concurrent.CopyOnWriteArraySet; return positionUs; } + /** + * Builds a {@link MediaMetadata} from the main sources. + * + *

      {@link MediaItem} {@link MediaMetadata} is prioritized, with any gaps/missing fields + * populated by metadata from static ({@link TrackGroup} {@link Format}) and dynamic ({@link + * #onMetadata(Metadata)}) sources. + */ + private MediaMetadata buildUpdatedMediaMetadata() { + @Nullable MediaItem mediaItem = getCurrentMediaItem(); + + if (mediaItem == null) { + return staticAndDynamicMediaMetadata; + } + + // MediaItem metadata is prioritized over metadata within the media. + return staticAndDynamicMediaMetadata.buildUpon().populate(mediaItem.mediaMetadata).build(); + } + private static boolean isPlaying(PlaybackInfo playbackInfo) { return playbackInfo.playbackState == Player.STATE_READY && playbackInfo.playWhenReady diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index bde93310f1..07f61f4e5e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -11181,6 +11181,46 @@ public final class ExoPlayerTest { assertThat(videoRenderer.get().positionResetCount).isEqualTo(1); } + @Test + public void setMediaItem_withMediaMetadata_updatesMediaMetadata() { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("the title").build(); + + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.setMediaItem( + new MediaItem.Builder() + .setMediaId("id") + .setUri(Uri.EMPTY) + .setMediaMetadata(mediaMetadata) + .build()); + + assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata); + } + + @Test + public void playingMedia_withNoMetadata_doesNotUpdateMediaMetadata() throws Exception { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("the title").build(); + + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + MediaItem mediaItem = + new MediaItem.Builder() + .setMediaId("id") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .setMediaMetadata(mediaMetadata) + .build(); + player.setMediaItem(mediaItem); + + assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player.stop(); + + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { From 647d69b950d9bebb43401cef05f25f40f1ecc3d9 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 15:07:57 +0100 Subject: [PATCH 011/113] Remove jcenter() dependency PiperOrigin-RevId: 405391455 --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index da37ad7bf6..055e3cdaff 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,6 @@ allprojects { repositories { google() mavenCentral() - jcenter() } if (it.hasProperty('externalBuildDir')) { if (!new File(externalBuildDir).isAbsolute()) { From 4a8f2fc78723810403d69467e654cb8f9ed15e8b Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 25 Oct 2021 15:27:41 +0100 Subject: [PATCH 012/113] Update package name PiperOrigin-RevId: 405394994 --- .../android/exoplayer2/testutil/truth/SpannedSubject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java index c156d24779..47d4f57d93 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java @@ -52,7 +52,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** A Truth {@link Subject} for assertions on {@link Spanned} instances containing text styling. */ -// TODO: add support for more Spans i.e. all those used in com.google.android.exoplayer2.text. +// TODO: add support for more Spans i.e. all those used in androidx.media3.common.text. public final class SpannedSubject extends Subject { @Nullable private final Spanned actual; From 922e508213bb31d9b87fae93756b6abe07d6fd66 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 25 Oct 2021 15:36:59 +0100 Subject: [PATCH 013/113] Update import scrubbing PiperOrigin-RevId: 405396600 --- .../trackselection/TrackSelectionOverridesTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java index 2d2a27134b..ba04f73429 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java @@ -19,15 +19,14 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; -// packages.bara.sky: import com.google.android.exoplayer2.util.MimeTypes; -// packages.bara.sky: import com.google.android.exoplayer2.Bundleable; -// packages.bara.sky: import com.google.android.exoplayer2.C; -// packages.bara.sky: import com.google.android.exoplayer2.Format; /** Unit tests for {@link TrackSelectionOverrides}. */ @RunWith(AndroidJUnit4.class) From 2ab7f28ec387044caa1c0ebd94dff2d93de5d77a Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 16:38:58 +0100 Subject: [PATCH 014/113] Fix TrackSelectionOverrides imports PiperOrigin-RevId: 405408606 --- .../TrackSelectionOverrides.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java index 65c659cce8..239dd76b60 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java @@ -24,7 +24,11 @@ import static java.util.Collections.min; import android.os.Bundle; import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.trackselection.C.TrackType; +import com.google.android.exoplayer2.Bundleable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.Ints; @@ -40,13 +44,13 @@ import java.util.Map; * Forces the selection of the specified tracks in {@link TrackGroup TrackGroups}. * *

      Each {@link TrackSelectionOverride override} only affects the selection of tracks of that - * {@link TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO + * {@link C.TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO * audio} {@link TrackGroup} will not affect the selection of {@link C#TRACK_TYPE_VIDEO video} or * {@link C#TRACK_TYPE_TEXT text} tracks. * - *

      If multiple {@link TrackGroup TrackGroups} of the same {@link TrackType} are overridden, which - * tracks will be selected depend on the player capabilities. For example, by default {@code - * ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link TrackType}. + *

      If multiple {@link TrackGroup TrackGroups} of the same {@link C.TrackType} are overridden, + * which tracks will be selected depend on the player capabilities. For example, by default {@code + * ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link C.TrackType}. * *

      Overrides of {@link TrackGroup} that are not currently available are ignored. For example, * when the player transitions to the next {@link MediaItem} in a playlist, any overrides of the @@ -92,7 +96,7 @@ public final class TrackSelectionOverrides implements Bundleable { /** * Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}. */ - public Builder clearOverridesOfType(@TrackType int trackType) { + public Builder clearOverridesOfType(@C.TrackType int trackType) { for (Iterator it = overrides.values().iterator(); it.hasNext(); ) { TrackSelectionOverride trackSelectionOverride = it.next(); if (trackSelectionOverride.getTrackType() == trackType) { @@ -170,7 +174,7 @@ public final class TrackSelectionOverrides implements Bundleable { return trackGroup.hashCode() + 31 * trackIndexes.hashCode(); } - private @TrackType int getTrackType() { + private @C.TrackType int getTrackType() { return MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType); } From 101b94f8740e5f385bee7b75998de86a001a99c8 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 18:11:53 +0100 Subject: [PATCH 015/113] Remove dependency from opus module to extractor module PiperOrigin-RevId: 405429757 --- .../ext/opus/LibopusAudioRenderer.java | 3 +- .../exoplayer2/ext/opus/OpusDecoder.java | 65 ++++++++++++-- .../exoplayer2/ext/opus/OpusDecoderTest.java | 90 +++++++++++++++++++ .../android/exoplayer2/audio/OpusUtil.java | 37 -------- .../exoplayer2/audio/OpusUtilTest.java | 39 -------- 5 files changed, 150 insertions(+), 84 deletions(-) create mode 100644 extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java index 452a0411c1..1b92708573 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java @@ -24,7 +24,6 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.AudioSink.SinkFormatSupport; import com.google.android.exoplayer2.audio.DecoderAudioRenderer; -import com.google.android.exoplayer2.audio.OpusUtil; import com.google.android.exoplayer2.decoder.CryptoConfig; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; @@ -124,6 +123,6 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer { protected Format getOutputFormat(OpusDecoder decoder) { @C.PcmEncoding int pcmEncoding = decoder.outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT; - return Util.getPcmFormat(pcmEncoding, decoder.channelCount, OpusUtil.SAMPLE_RATE); + return Util.getPcmFormat(pcmEncoding, decoder.channelCount, OpusDecoder.SAMPLE_RATE); } } diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java index 56617b7d13..b6b7567d81 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java @@ -20,7 +20,6 @@ import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.audio.OpusUtil; import com.google.android.exoplayer2.decoder.CryptoConfig; import com.google.android.exoplayer2.decoder.CryptoException; import com.google.android.exoplayer2.decoder.CryptoInfo; @@ -30,6 +29,7 @@ import com.google.android.exoplayer2.decoder.SimpleDecoderOutputBuffer; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.List; /** Opus decoder. */ @@ -37,6 +37,12 @@ import java.util.List; public final class OpusDecoder extends SimpleDecoder { + /** Opus streams are always 48000 Hz. */ + /* package */ static final int SAMPLE_RATE = 48_000; + + private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; + private static final int FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT = 3; + private static final int NO_ERROR = 0; private static final int DECODE_ERROR = -1; private static final int DRM_ERROR = -2; @@ -89,14 +95,14 @@ public final class OpusDecoder && (initializationData.get(1).length != 8 || initializationData.get(2).length != 8)) { throw new OpusDecoderException("Invalid pre-skip or seek pre-roll"); } - preSkipSamples = OpusUtil.getPreSkipSamples(initializationData); - seekPreRollSamples = OpusUtil.getSeekPreRollSamples(initializationData); + preSkipSamples = getPreSkipSamples(initializationData); + seekPreRollSamples = getSeekPreRollSamples(initializationData); byte[] headerBytes = initializationData.get(0); if (headerBytes.length < 19) { throw new OpusDecoderException("Invalid header length"); } - channelCount = OpusUtil.getChannelCount(headerBytes); + channelCount = getChannelCount(headerBytes); if (channelCount > 8) { throw new OpusDecoderException("Invalid channel count: " + channelCount); } @@ -124,7 +130,7 @@ public final class OpusDecoder System.arraycopy(headerBytes, 21, streamMap, 0, channelCount); } nativeDecoderContext = - opusInit(OpusUtil.SAMPLE_RATE, channelCount, numStreams, numCoupled, gain, streamMap); + opusInit(SAMPLE_RATE, channelCount, numStreams, numCoupled, gain, streamMap); if (nativeDecoderContext == 0) { throw new OpusDecoderException("Failed to initialize decoder"); } @@ -176,7 +182,7 @@ public final class OpusDecoder inputData, inputData.limit(), outputBuffer, - OpusUtil.SAMPLE_RATE, + SAMPLE_RATE, cryptoConfig, cryptoInfo.mode, Assertions.checkNotNull(cryptoInfo.key), @@ -225,6 +231,53 @@ public final class OpusDecoder opusClose(nativeDecoderContext); } + /** + * Parses the channel count from an Opus Identification Header. + * + * @param header An Opus Identification Header, as defined by RFC 7845. + * @return The parsed channel count. + */ + @VisibleForTesting + /* package */ static int getChannelCount(byte[] header) { + return header[9] & 0xFF; + } + + /** + * Returns the number of pre-skip samples specified by the given Opus codec initialization data. + * + * @param initializationData The codec initialization data. + * @return The number of pre-skip samples. + */ + @VisibleForTesting + /* package */ static int getPreSkipSamples(List initializationData) { + if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { + long codecDelayNs = + ByteBuffer.wrap(initializationData.get(1)).order(ByteOrder.nativeOrder()).getLong(); + return (int) ((codecDelayNs * SAMPLE_RATE) / C.NANOS_PER_SECOND); + } + // Fall back to parsing directly from the Opus Identification header. + byte[] headerData = initializationData.get(0); + return ((headerData[11] & 0xFF) << 8) | (headerData[10] & 0xFF); + } + + /** + * Returns the number of seek per-roll samples specified by the given Opus codec initialization + * data. + * + * @param initializationData The codec initialization data. + * @return The number of seek pre-roll samples. + */ + @VisibleForTesting + /* package */ static int getSeekPreRollSamples(List initializationData) { + if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { + long seekPreRollNs = + ByteBuffer.wrap(initializationData.get(2)).order(ByteOrder.nativeOrder()).getLong(); + return (int) ((seekPreRollNs * SAMPLE_RATE) / C.NANOS_PER_SECOND); + } + // Fall back to returning the default seek pre-roll. + return DEFAULT_SEEK_PRE_ROLL_SAMPLES; + } + private static int readSignedLittleEndian16(byte[] input, int offset) { int value = input[offset] & 0xFF; value |= (input[offset + 1] & 0xFF) << 8; diff --git a/extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java b/extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java new file mode 100644 index 0000000000..f723237646 --- /dev/null +++ b/extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 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.ext.opus; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link OpusDecoder}. */ +@RunWith(AndroidJUnit4.class) +public final class OpusDecoderTest { + + private static final byte[] HEADER = + new byte[] {79, 112, 117, 115, 72, 101, 97, 100, 0, 2, 1, 56, 0, 0, -69, -128, 0, 0, 0}; + + private static final int HEADER_PRE_SKIP_SAMPLES = 14337; + + private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; + + private static final ImmutableList HEADER_ONLY_INITIALIZATION_DATA = + ImmutableList.of(HEADER); + + private static final long CUSTOM_PRE_SKIP_SAMPLES = 28674; + private static final byte[] CUSTOM_PRE_SKIP_BYTES = + buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES)); + + private static final long CUSTOM_SEEK_PRE_ROLL_SAMPLES = 7680; + private static final byte[] CUSTOM_SEEK_PRE_ROLL_BYTES = + buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_SEEK_PRE_ROLL_SAMPLES)); + + private static final ImmutableList FULL_INITIALIZATION_DATA = + ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES); + + @Test + public void getChannelCount() { + int channelCount = OpusDecoder.getChannelCount(HEADER); + assertThat(channelCount).isEqualTo(2); + } + + @Test + public void getPreSkipSamples_fullInitializationData_returnsOverrideValue() { + int preSkipSamples = OpusDecoder.getPreSkipSamples(FULL_INITIALIZATION_DATA); + assertThat(preSkipSamples).isEqualTo(CUSTOM_PRE_SKIP_SAMPLES); + } + + @Test + public void getPreSkipSamples_headerOnlyInitializationData_returnsHeaderValue() { + int preSkipSamples = OpusDecoder.getPreSkipSamples(HEADER_ONLY_INITIALIZATION_DATA); + assertThat(preSkipSamples).isEqualTo(HEADER_PRE_SKIP_SAMPLES); + } + + @Test + public void getSeekPreRollSamples_fullInitializationData_returnsInitializationDataValue() { + int seekPreRollSamples = OpusDecoder.getSeekPreRollSamples(FULL_INITIALIZATION_DATA); + assertThat(seekPreRollSamples).isEqualTo(CUSTOM_SEEK_PRE_ROLL_SAMPLES); + } + + @Test + public void getSeekPreRollSamples_headerOnlyInitializationData_returnsDefaultValue() { + int seekPreRollSamples = OpusDecoder.getSeekPreRollSamples(HEADER_ONLY_INITIALIZATION_DATA); + assertThat(seekPreRollSamples).isEqualTo(DEFAULT_SEEK_PRE_ROLL_SAMPLES); + } + + private static long sampleCountToNanoseconds(long sampleCount) { + return (sampleCount * C.NANOS_PER_SECOND) / OpusDecoder.SAMPLE_RATE; + } + + private static byte[] buildNativeOrderByteArray(long value) { + return ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(value).array(); + } +} diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java index 3e434bb7e8..70b1e96a8c 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java @@ -61,39 +61,6 @@ public class OpusUtil { return initializationData; } - /** - * Returns the number of pre-skip samples specified by the given Opus codec initialization data. - * - * @param initializationData The codec initialization data. - * @return The number of pre-skip samples. - */ - public static int getPreSkipSamples(List initializationData) { - if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { - long codecDelayNs = - ByteBuffer.wrap(initializationData.get(1)).order(ByteOrder.nativeOrder()).getLong(); - return (int) nanosecondsToSampleCount(codecDelayNs); - } - // Fall back to parsing directly from the Opus Identification header. - return getPreSkipSamples(initializationData.get(0)); - } - - /** - * Returns the number of seek per-roll samples specified by the given Opus codec initialization - * data. - * - * @param initializationData The codec initialization data. - * @return The number of seek pre-roll samples. - */ - public static int getSeekPreRollSamples(List initializationData) { - if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { - long seekPreRollNs = - ByteBuffer.wrap(initializationData.get(2)).order(ByteOrder.nativeOrder()).getLong(); - return (int) nanosecondsToSampleCount(seekPreRollNs); - } - // Fall back to returning the default seek pre-roll. - return DEFAULT_SEEK_PRE_ROLL_SAMPLES; - } - private static int getPreSkipSamples(byte[] header) { return ((header[11] & 0xFF) << 8) | (header[10] & 0xFF); } @@ -105,8 +72,4 @@ public class OpusUtil { private static long sampleCountToNanoseconds(long sampleCount) { return (sampleCount * C.NANOS_PER_SECOND) / SAMPLE_RATE; } - - private static long nanosecondsToSampleCount(long nanoseconds) { - return (nanoseconds * SAMPLE_RATE) / C.NANOS_PER_SECOND; - } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java index 4fe18aa4d0..d3026359c2 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; -import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.List; @@ -41,20 +40,6 @@ public final class OpusUtilTest { private static final byte[] DEFAULT_SEEK_PRE_ROLL_BYTES = buildNativeOrderByteArray(sampleCountToNanoseconds(DEFAULT_SEEK_PRE_ROLL_SAMPLES)); - private static final ImmutableList HEADER_ONLY_INITIALIZATION_DATA = - ImmutableList.of(HEADER); - - private static final long CUSTOM_PRE_SKIP_SAMPLES = 28674; - private static final byte[] CUSTOM_PRE_SKIP_BYTES = - buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES)); - - private static final long CUSTOM_SEEK_PRE_ROLL_SAMPLES = 7680; - private static final byte[] CUSTOM_SEEK_PRE_ROLL_BYTES = - buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_SEEK_PRE_ROLL_SAMPLES)); - - private static final ImmutableList FULL_INITIALIZATION_DATA = - ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES); - @Test public void buildInitializationData() { List initializationData = OpusUtil.buildInitializationData(HEADER); @@ -70,30 +55,6 @@ public final class OpusUtilTest { assertThat(channelCount).isEqualTo(2); } - @Test - public void getPreSkipSamples_fullInitializationData_returnsOverrideValue() { - int preSkipSamples = OpusUtil.getPreSkipSamples(FULL_INITIALIZATION_DATA); - assertThat(preSkipSamples).isEqualTo(CUSTOM_PRE_SKIP_SAMPLES); - } - - @Test - public void getPreSkipSamples_headerOnlyInitializationData_returnsHeaderValue() { - int preSkipSamples = OpusUtil.getPreSkipSamples(HEADER_ONLY_INITIALIZATION_DATA); - assertThat(preSkipSamples).isEqualTo(HEADER_PRE_SKIP_SAMPLES); - } - - @Test - public void getSeekPreRollSamples_fullInitializationData_returnsInitializationDataValue() { - int seekPreRollSamples = OpusUtil.getSeekPreRollSamples(FULL_INITIALIZATION_DATA); - assertThat(seekPreRollSamples).isEqualTo(CUSTOM_SEEK_PRE_ROLL_SAMPLES); - } - - @Test - public void getSeekPreRollSamples_headerOnlyInitializationData_returnsDefaultValue() { - int seekPreRollSamples = OpusUtil.getSeekPreRollSamples(HEADER_ONLY_INITIALIZATION_DATA); - assertThat(seekPreRollSamples).isEqualTo(DEFAULT_SEEK_PRE_ROLL_SAMPLES); - } - private static long sampleCountToNanoseconds(long sampleCount) { return (sampleCount * C.NANOS_PER_SECOND) / OpusUtil.SAMPLE_RATE; } From 23b46d2e0c5cc6bb2d598685feec828bf7c977c2 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 21:10:05 +0100 Subject: [PATCH 016/113] Move NAL unit utils to extractor module PiperOrigin-RevId: 405473686 --- .../java/com/google/android/exoplayer2/util/NalUnitUtil.java | 0 .../google/android/exoplayer2/util/ParsableNalUnitBitArray.java | 1 + .../java/com/google/android/exoplayer2/util/NalUnitUtilTest.java | 0 .../android/exoplayer2/util/ParsableNalUnitBitArrayTest.java | 0 4 files changed, 1 insertion(+) rename library/{common => extractor}/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java (100%) rename library/{common => extractor}/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java (99%) rename library/{common => extractor}/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java (100%) rename library/{common => extractor}/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java (100%) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java rename to library/extractor/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java b/library/extractor/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java similarity index 99% rename from library/common/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java rename to library/extractor/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java index 50810bc6a0..33db40b91d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.util; + /** * Wraps a byte array, providing methods that allow it to be read as a NAL unit bitstream. * diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java rename to library/extractor/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java rename to library/extractor/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java From 241409a888ced6db35ed14c5fa53faab05739fc7 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 26 Oct 2021 09:20:21 +0100 Subject: [PATCH 017/113] Remove resolved TODO This has been done for (almost) all span types. PiperOrigin-RevId: 405588294 --- .../google/android/exoplayer2/testutil/truth/SpannedSubject.java | 1 - 1 file changed, 1 deletion(-) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java index 47d4f57d93..2e81bff452 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java @@ -52,7 +52,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** A Truth {@link Subject} for assertions on {@link Spanned} instances containing text styling. */ -// TODO: add support for more Spans i.e. all those used in androidx.media3.common.text. public final class SpannedSubject extends Subject { @Nullable private final Spanned actual; From 0da494c613b8d2eb38a2cd8b8e126825b78031ce Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 26 Oct 2021 09:54:46 +0100 Subject: [PATCH 018/113] Fix main demo gradle task name PiperOrigin-RevId: 405592960 --- demos/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos/README.md b/demos/README.md index 2360e01137..1ec3a1f306 100644 --- a/demos/README.md +++ b/demos/README.md @@ -21,5 +21,5 @@ the demo project. Choose an install option from the `Install tasks` section. **Example**: -`./gradlew :demo:installNoExtensionsDebug` installs the main ExoPlayer demo app - in debug mode with no extensions. +`./gradlew :demo:installNoDecoderExtensionsDebug` installs the main ExoPlayer +demo app in debug mode with no decoder extensions. From 0ad1cdbfa16b762c322d8f5bb4348f5b9d29e027 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 26 Oct 2021 10:30:55 +0100 Subject: [PATCH 019/113] Tidy READMEs PiperOrigin-RevId: 405598530 --- extensions/av1/README.md | 3 +-- extensions/cast/README.md | 5 ++--- extensions/cronet/README.md | 3 +-- extensions/ffmpeg/README.md | 3 +-- extensions/flac/README.md | 3 +-- extensions/ima/README.md | 5 ++--- extensions/leanback/README.md | 3 +-- extensions/okhttp/README.md | 3 +-- extensions/opus/README.md | 3 +-- extensions/rtmp/README.md | 3 +-- extensions/vp9/README.md | 3 +-- extensions/workmanager/README.md | 3 +-- library/common/README.md | 2 +- library/core/README.md | 2 +- library/dash/README.md | 5 ++--- library/datasource/README.md | 3 +-- library/decoder/README.md | 3 +-- library/extractor/README.md | 3 +-- library/hls/README.md | 5 ++--- library/rtsp/README.md | 5 ++--- library/smoothstreaming/README.md | 6 ++---- library/transformer/README.md | 3 +-- library/ui/README.md | 4 ++-- robolectricutils/README.md | 3 +-- testutils/README.md | 3 +-- 25 files changed, 32 insertions(+), 55 deletions(-) diff --git a/extensions/av1/README.md b/extensions/av1/README.md index c7b59baca2..f2a31dcbf1 100644 --- a/extensions/av1/README.md +++ b/extensions/av1/README.md @@ -135,7 +135,6 @@ GL rendering mode has better performance, so should be preferred ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.av1.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/cast/README.md b/extensions/cast/README.md index 880636f154..f09e1450e7 100644 --- a/extensions/cast/README.md +++ b/extensions/cast/README.md @@ -3,7 +3,7 @@ This module provides a [Player][] implementation that controls a Cast receiver app. -[Player]: https://exoplayer.dev/doc/reference/index.html?com/google/android/exoplayer2/Player.html +[Player]: https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html ## Getting the module @@ -30,7 +30,6 @@ UI module. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.cast.*` belong to this - module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/cronet/README.md b/extensions/cronet/README.md index 7749d27d6b..fc22a77d33 100644 --- a/extensions/cronet/README.md +++ b/extensions/cronet/README.md @@ -121,7 +121,6 @@ application. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.cronet.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/ffmpeg/README.md b/extensions/ffmpeg/README.md index cb0fdf3f7c..f4d596dc57 100644 --- a/extensions/ffmpeg/README.md +++ b/extensions/ffmpeg/README.md @@ -128,8 +128,7 @@ To try out playback using the module in the [demo application][], see ## Links * [Troubleshooting using extensions][] -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ffmpeg.*` - belong to this module. +* [Javadoc][] [Troubleshooting using extensions]: https://exoplayer.dev/troubleshooting.html#how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/flac/README.md b/extensions/flac/README.md index aaa4e35191..0614678f92 100644 --- a/extensions/flac/README.md +++ b/extensions/flac/README.md @@ -106,7 +106,6 @@ To try out playback using the module in the [demo application][], see ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.flac.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/ima/README.md b/extensions/ima/README.md index 335f4bf8c5..cb9015fd56 100644 --- a/extensions/ima/README.md +++ b/extensions/ima/README.md @@ -5,7 +5,7 @@ The ExoPlayer IMA module provides an [AdsLoader][] implementation wrapping the content played using ExoPlayer. [IMA]: https://developers.google.com/interactive-media-ads/docs/sdks/android/ -[AdsLoader]: https://exoplayer.dev/doc/reference/index.html?com/google/android/exoplayer2/source/ads/AdsLoader.html +[AdsLoader]: https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/source/ads/AdsLoader.html ## Getting the module @@ -53,8 +53,7 @@ player position when backgrounded during ad playback. ## Links * [ExoPlayer documentation on ad insertion][] -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ima.*` - belong to this module. +* [Javadoc][] [ExoPlayer documentation on ad insertion]: https://exoplayer.dev/ad-insertion.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/leanback/README.md b/extensions/leanback/README.md index 19b1659c45..2f23762b97 100644 --- a/extensions/leanback/README.md +++ b/extensions/leanback/README.md @@ -25,7 +25,6 @@ locally. Instructions for doing this can be found in the [top level README][]. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.leanback.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/okhttp/README.md b/extensions/okhttp/README.md index 269b9a6100..020e3496c0 100644 --- a/extensions/okhttp/README.md +++ b/extensions/okhttp/README.md @@ -51,7 +51,6 @@ new DefaultDataSourceFactory( ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.okhttp.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/opus/README.md b/extensions/opus/README.md index e3242c3f32..e669344839 100644 --- a/extensions/opus/README.md +++ b/extensions/opus/README.md @@ -110,7 +110,6 @@ To try out playback using the module in the [demo application][], see ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.opus.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/rtmp/README.md b/extensions/rtmp/README.md index 3df7db9a56..f825b03367 100644 --- a/extensions/rtmp/README.md +++ b/extensions/rtmp/README.md @@ -48,7 +48,6 @@ doesn't need to handle any other protocols, you can update any ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.rtmp.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/vp9/README.md b/extensions/vp9/README.md index b24d089549..384064d68d 100644 --- a/extensions/vp9/README.md +++ b/extensions/vp9/README.md @@ -148,7 +148,6 @@ GL rendering mode has better performance, so should be preferred. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.vp9.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/workmanager/README.md b/extensions/workmanager/README.md index d69b47cae4..1cc28c7a37 100644 --- a/extensions/workmanager/README.md +++ b/extensions/workmanager/README.md @@ -22,7 +22,6 @@ locally. Instructions for doing this can be found in the [top level README][]. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.workmanager.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/common/README.md b/library/common/README.md index f2f8e2cca9..e9b59f7236 100644 --- a/library/common/README.md +++ b/library/common/README.md @@ -5,6 +5,6 @@ will not normally need to depend on this module directly. ## Links -* [Javadoc][]: Note that this Javadoc is combined with that of other modules. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/core/README.md b/library/core/README.md index a8c13c9e70..6eb4bad8b7 100644 --- a/library/core/README.md +++ b/library/core/README.md @@ -26,6 +26,6 @@ get started. ## Links -* [Javadoc][]: Note that this Javadoc is combined with that of other modules. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/dash/README.md b/library/dash/README.md index 0eb871a040..ea560e529a 100644 --- a/library/dash/README.md +++ b/library/dash/README.md @@ -36,9 +36,8 @@ instances and pass them directly to the player. For advanced download use cases, ## Links -* [Developer Guide][]. -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.dash.*` - belong to this module. +* [Developer Guide][] +* [Javadoc][] [Developer Guide]: https://exoplayer.dev/dash.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/datasource/README.md b/library/datasource/README.md index f87be557e9..ddd37c0e0d 100644 --- a/library/datasource/README.md +++ b/library/datasource/README.md @@ -6,7 +6,6 @@ depend on this module directly. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.upstream.*` belong to this - module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/decoder/README.md b/library/decoder/README.md index 7524b959d4..ef37e98884 100644 --- a/library/decoder/README.md +++ b/library/decoder/README.md @@ -5,7 +5,6 @@ depend on this module directly. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.decoder.*` belong to this - module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/extractor/README.md b/library/extractor/README.md index f7aaad1e4c..b1a08d9129 100644 --- a/library/extractor/README.md +++ b/library/extractor/README.md @@ -5,7 +5,6 @@ not normally need to depend on this module directly. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.extractor.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/hls/README.md b/library/hls/README.md index 93f776c25f..2168df1e80 100644 --- a/library/hls/README.md +++ b/library/hls/README.md @@ -35,9 +35,8 @@ instances and pass them directly to the player. For advanced download use cases, ## Links -* [Developer Guide][]. -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.hls.*` belong to - this module. +* [Developer Guide][] +* [Javadoc][] [Developer Guide]: https://exoplayer.dev/hls.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/rtsp/README.md b/library/rtsp/README.md index 08837189b0..c0a94bff5e 100644 --- a/library/rtsp/README.md +++ b/library/rtsp/README.md @@ -30,9 +30,8 @@ instances and pass them directly to the player. ## Links -* [Developer Guide][]. -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.rtsp.*` belong to - this module. +* [Developer Guide][] +* [Javadoc][] [Developer Guide]: https://exoplayer.dev/dash.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/smoothstreaming/README.md b/library/smoothstreaming/README.md index f187288666..1eceea29ef 100644 --- a/library/smoothstreaming/README.md +++ b/library/smoothstreaming/README.md @@ -35,10 +35,8 @@ instances and pass them directly to the player. For advanced download use cases, ## Links -* [Developer Guide][]. -* [Javadoc][]: Classes matching - `com.google.android.exoplayer2.source.smoothstreaming.*` belong to this - module. +* [Developer Guide][] +* [Javadoc][] [Developer Guide]: https://exoplayer.dev/smoothstreaming.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/transformer/README.md b/library/transformer/README.md index 0d7ba4f0d1..b9dc677531 100644 --- a/library/transformer/README.md +++ b/library/transformer/README.md @@ -25,7 +25,6 @@ Use of the Transformer module is documented in the ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.transformer.*` belong to this - module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/ui/README.md b/library/ui/README.md index b41072427a..8f1de86449 100644 --- a/library/ui/README.md +++ b/library/ui/README.md @@ -20,8 +20,8 @@ locally. Instructions for doing this can be found in the [top level README][]. ## Links -* [Developer Guide][]. -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ui.*` belong to this module. +* [Developer Guide][] +* [Javadoc][] [Developer Guide]: https://exoplayer.dev/ui-components.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/robolectricutils/README.md b/robolectricutils/README.md index 84f9139c5c..a461d21940 100644 --- a/robolectricutils/README.md +++ b/robolectricutils/README.md @@ -4,7 +4,6 @@ Provides test infrastructure for Robolectric-based media tests. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.robolectric.*` - belong to this module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/testutils/README.md b/testutils/README.md index 9f7f924261..2830e7c640 100644 --- a/testutils/README.md +++ b/testutils/README.md @@ -4,7 +4,6 @@ Provides utility classes for media unit and instrumentation tests. ## Links -* [Javadoc][]: Classes matching `com.google.android.exoplayer2.testutil.*` belong to this - module. +* [Javadoc][] [Javadoc]: https://exoplayer.dev/doc/reference/index.html From 74fb05cfbe41cb1a5b51aff2bfcaf91a51e6ed32 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Tue, 26 Oct 2021 12:35:36 +0100 Subject: [PATCH 020/113] Update MediaMetadata javadoc to clarify nuances. PiperOrigin-RevId: 405616711 --- .../src/main/java/com/google/android/exoplayer2/Player.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 34d94ede8b..7c00e1dfb9 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 @@ -141,7 +141,11 @@ public interface Player { * *

      The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} * and the static and dynamic metadata from the {@link TrackSelection#getFormat(int) track - * selections' formats} and {@link Listener#onMetadata(Metadata)}. + * selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in + * the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from + * static or dynamic metadata. + * + *

      This method may be called multiple times in quick succession. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. From a7aa674a2947c060a74d6b28faa7cd30fca9c975 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 26 Oct 2021 13:29:20 +0100 Subject: [PATCH 021/113] Removed unused link PiperOrigin-RevId: 405624136 --- CONTRIBUTING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94b349b217..388b2ed259 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,8 +19,6 @@ We will also consider high quality pull requests. These should normally merge into the `dev-v2` branch. Before a pull request can be accepted you must submit a Contributor License Agreement, as described below. -[dev]: https://github.com/google/ExoPlayer/tree/dev - ## Contributor license agreement ## Contributions to any Google project must be accompanied by a Contributor From f6051654302b28a40f49df2fa3821a1b41de11e0 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 26 Oct 2021 13:45:46 +0100 Subject: [PATCH 022/113] Add database module PiperOrigin-RevId: 405626096 --- core_settings.gradle | 3 ++ .../android/exoplayer2/demo/DemoUtil.java | 4 +- docs/downloading-media.md | 2 +- library/all/build.gradle | 1 + library/core/build.gradle | 1 + .../offline/ActionFileUpgradeUtilTest.java | 6 +-- .../offline/DefaultDownloadIndexTest.java | 10 ++--- library/database/README.md | 10 +++++ library/database/build.gradle | 44 +++++++++++++++++++ library/database/src/main/AndroidManifest.xml | 19 ++++++++ .../database/DatabaseIOException.java | 0 .../exoplayer2/database/DatabaseProvider.java | 0 .../database/DefaultDatabaseProvider.java | 0 .../database/ExoDatabaseProvider.java | 0 .../database/StandaloneDatabaseProvider.java | 0 .../exoplayer2/database/VersionTable.java | 5 +++ .../exoplayer2/database/package-info.java | 0 library/database/src/test/AndroidManifest.xml | 19 ++++++++ .../exoplayer2/database/VersionTableTest.java | 0 library/datasource/build.gradle | 1 + .../playbacktests/gts/DashDownloadTest.java | 6 ++- 21 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 library/database/README.md create mode 100644 library/database/build.gradle create mode 100644 library/database/src/main/AndroidManifest.xml rename library/{common => database}/src/main/java/com/google/android/exoplayer2/database/DatabaseIOException.java (100%) rename library/{common => database}/src/main/java/com/google/android/exoplayer2/database/DatabaseProvider.java (100%) rename library/{common => database}/src/main/java/com/google/android/exoplayer2/database/DefaultDatabaseProvider.java (100%) rename library/{common => database}/src/main/java/com/google/android/exoplayer2/database/ExoDatabaseProvider.java (100%) rename library/{common => database}/src/main/java/com/google/android/exoplayer2/database/StandaloneDatabaseProvider.java (100%) rename library/{common => database}/src/main/java/com/google/android/exoplayer2/database/VersionTable.java (97%) rename library/{common => database}/src/main/java/com/google/android/exoplayer2/database/package-info.java (100%) create mode 100644 library/database/src/test/AndroidManifest.xml rename library/{common => database}/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java (100%) diff --git a/core_settings.gradle b/core_settings.gradle index 1f22196a33..83ec79ef72 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -51,6 +51,9 @@ project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui' include modulePrefix + 'extension-leanback' project(modulePrefix + 'extension-leanback').projectDir = new File(rootDir, 'extensions/leanback') +include modulePrefix + 'library-database' +project(modulePrefix + 'library-database').projectDir = new File(rootDir, 'library/database') + include modulePrefix + 'library-datasource' project(modulePrefix + 'library-datasource').projectDir = new File(rootDir, 'library/datasource') include modulePrefix + 'extension-cronet' diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java index a4193184bc..62c3a5a005 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java @@ -19,7 +19,7 @@ import android.content.Context; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.database.DatabaseProvider; -import com.google.android.exoplayer2.database.ExoDatabaseProvider; +import com.google.android.exoplayer2.database.StandaloneDatabaseProvider; import com.google.android.exoplayer2.ext.cronet.CronetDataSource; import com.google.android.exoplayer2.ext.cronet.CronetUtil; import com.google.android.exoplayer2.offline.ActionFileUpgradeUtil; @@ -194,7 +194,7 @@ public final class DemoUtil { private static synchronized DatabaseProvider getDatabaseProvider(Context context) { if (databaseProvider == null) { - databaseProvider = new ExoDatabaseProvider(context); + databaseProvider = new StandaloneDatabaseProvider(context); } return databaseProvider; } diff --git a/docs/downloading-media.md b/docs/downloading-media.md index e724d0cd4a..6bef8a25d6 100644 --- a/docs/downloading-media.md +++ b/docs/downloading-media.md @@ -63,7 +63,7 @@ which can be returned by `getDownloadManager()` in your `DownloadService`: ~~~ // Note: This should be a singleton in your app. -databaseProvider = new ExoDatabaseProvider(context); +databaseProvider = new StandaloneDatabaseProvider(context); // A download cache should not evict media, so should use a NoopCacheEvictor. downloadCache = new SimpleCache( diff --git a/library/all/build.gradle b/library/all/build.gradle index 8daf11eea7..96c7ff8683 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -15,6 +15,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { api project(modulePrefix + 'library-common') + api project(modulePrefix + 'library-database') api project(modulePrefix + 'library-datasource') api project(modulePrefix + 'library-decoder') api project(modulePrefix + 'library-extractor') diff --git a/library/core/build.gradle b/library/core/build.gradle index 524948cc1e..1ee7c8a289 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -40,6 +40,7 @@ dependencies { api project(modulePrefix + 'library-datasource') api project(modulePrefix + 'library-decoder') api project(modulePrefix + 'library-extractor') + api project(modulePrefix + 'library-database') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.core:core:' + androidxCoreVersion compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java index 05c0bcc780..239762c57f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java @@ -20,7 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.net.Uri; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.database.ExoDatabaseProvider; +import com.google.android.exoplayer2.database.StandaloneDatabaseProvider; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -40,13 +40,13 @@ public class ActionFileUpgradeUtilTest { private static final long NOW_MS = 1234; private File tempFile; - private ExoDatabaseProvider databaseProvider; + private StandaloneDatabaseProvider databaseProvider; private DefaultDownloadIndex downloadIndex; @Before public void setUp() throws Exception { tempFile = Util.createTempFile(ApplicationProvider.getApplicationContext(), "ExoPlayerTest"); - databaseProvider = new ExoDatabaseProvider(ApplicationProvider.getApplicationContext()); + databaseProvider = new StandaloneDatabaseProvider(ApplicationProvider.getApplicationContext()); downloadIndex = new DefaultDownloadIndex(databaseProvider); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java index 988b5127ec..96bd6bbf76 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java @@ -29,7 +29,7 @@ import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.database.DatabaseIOException; -import com.google.android.exoplayer2.database.ExoDatabaseProvider; +import com.google.android.exoplayer2.database.StandaloneDatabaseProvider; import com.google.android.exoplayer2.database.VersionTable; import com.google.android.exoplayer2.testutil.DownloadBuilder; import com.google.android.exoplayer2.testutil.TestUtil; @@ -51,12 +51,12 @@ public class DefaultDownloadIndexTest { private static final String EMPTY_NAME = ""; - private ExoDatabaseProvider databaseProvider; + private StandaloneDatabaseProvider databaseProvider; private DefaultDownloadIndex downloadIndex; @Before public void setUp() { - databaseProvider = new ExoDatabaseProvider(ApplicationProvider.getApplicationContext()); + databaseProvider = new StandaloneDatabaseProvider(ApplicationProvider.getApplicationContext()); downloadIndex = new DefaultDownloadIndex(databaseProvider); } @@ -222,7 +222,7 @@ public class DefaultDownloadIndexTest { @Test public void downloadIndex_upgradesFromVersion2() throws IOException { Context context = ApplicationProvider.getApplicationContext(); - File databaseFile = context.getDatabasePath(ExoDatabaseProvider.DATABASE_NAME); + File databaseFile = context.getDatabasePath(StandaloneDatabaseProvider.DATABASE_NAME); try (FileOutputStream output = new FileOutputStream(databaseFile)) { output.write(TestUtil.getByteArray(context, "media/offline/exoplayer_internal_v2.db")); } @@ -251,7 +251,7 @@ public class DefaultDownloadIndexTest { ImmutableList.of(), /* customCacheKey= */ "customCacheKey"); - databaseProvider = new ExoDatabaseProvider(context); + databaseProvider = new StandaloneDatabaseProvider(context); downloadIndex = new DefaultDownloadIndex(databaseProvider); assertEqual(downloadIndex.getDownload("http://www.test.com/manifest.mpd"), dashDownload); diff --git a/library/database/README.md b/library/database/README.md new file mode 100644 index 0000000000..22afea406c --- /dev/null +++ b/library/database/README.md @@ -0,0 +1,10 @@ +# Common module + +Provides database functionality for use by other media modules. Application code +will not normally need to depend on this module directly. + +## Links + +* [Javadoc][] + +[Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/database/build.gradle b/library/database/build.gradle new file mode 100644 index 0000000000..9c73e24c68 --- /dev/null +++ b/library/database/build.gradle @@ -0,0 +1,44 @@ +// Copyright (C) 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. +apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" + +android { + buildTypes { + debug { + testCoverageEnabled = true + } + } +} + +dependencies { + implementation project(modulePrefix + 'library-common') + implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion + compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion + testImplementation 'androidx.test:core:' + androidxTestCoreVersion + testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion + testImplementation 'com.google.truth:truth:' + truthVersion + testImplementation 'org.robolectric:robolectric:' + robolectricVersion + testImplementation project(modulePrefix + 'testutils') +} + +ext { + javadocTitle = 'Database module' +} +apply from: '../../javadoc_library.gradle' + +ext { + releaseArtifactId = 'exoplayer-database' + releaseDescription = 'The ExoPlayer database module.' +} +apply from: '../../publish.gradle' diff --git a/library/database/src/main/AndroidManifest.xml b/library/database/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..482b3ea14d --- /dev/null +++ b/library/database/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/library/common/src/main/java/com/google/android/exoplayer2/database/DatabaseIOException.java b/library/database/src/main/java/com/google/android/exoplayer2/database/DatabaseIOException.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/database/DatabaseIOException.java rename to library/database/src/main/java/com/google/android/exoplayer2/database/DatabaseIOException.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/database/DatabaseProvider.java b/library/database/src/main/java/com/google/android/exoplayer2/database/DatabaseProvider.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/database/DatabaseProvider.java rename to library/database/src/main/java/com/google/android/exoplayer2/database/DatabaseProvider.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/database/DefaultDatabaseProvider.java b/library/database/src/main/java/com/google/android/exoplayer2/database/DefaultDatabaseProvider.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/database/DefaultDatabaseProvider.java rename to library/database/src/main/java/com/google/android/exoplayer2/database/DefaultDatabaseProvider.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/database/ExoDatabaseProvider.java b/library/database/src/main/java/com/google/android/exoplayer2/database/ExoDatabaseProvider.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/database/ExoDatabaseProvider.java rename to library/database/src/main/java/com/google/android/exoplayer2/database/ExoDatabaseProvider.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/database/StandaloneDatabaseProvider.java b/library/database/src/main/java/com/google/android/exoplayer2/database/StandaloneDatabaseProvider.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/database/StandaloneDatabaseProvider.java rename to library/database/src/main/java/com/google/android/exoplayer2/database/StandaloneDatabaseProvider.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/database/VersionTable.java b/library/database/src/main/java/com/google/android/exoplayer2/database/VersionTable.java similarity index 97% rename from library/common/src/main/java/com/google/android/exoplayer2/database/VersionTable.java rename to library/database/src/main/java/com/google/android/exoplayer2/database/VersionTable.java index d6d2a1cb7d..a55767bd3e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/database/VersionTable.java +++ b/library/database/src/main/java/com/google/android/exoplayer2/database/VersionTable.java @@ -20,6 +20,7 @@ import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import androidx.annotation.IntDef; +import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -31,6 +32,10 @@ import java.lang.annotation.RetentionPolicy; */ public final class VersionTable { + static { + ExoPlayerLibraryInfo.registerModule("goog.exo.database"); + } + /** Returned by {@link #getVersion(SQLiteDatabase, int, String)} if the version is unset. */ public static final int VERSION_UNSET = -1; /** Version of tables used for offline functionality. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/database/package-info.java b/library/database/src/main/java/com/google/android/exoplayer2/database/package-info.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/database/package-info.java rename to library/database/src/main/java/com/google/android/exoplayer2/database/package-info.java diff --git a/library/database/src/test/AndroidManifest.xml b/library/database/src/test/AndroidManifest.xml new file mode 100644 index 0000000000..482b3ea14d --- /dev/null +++ b/library/database/src/test/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/library/common/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java b/library/database/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java similarity index 100% rename from library/common/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java rename to library/database/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java diff --git a/library/datasource/build.gradle b/library/datasource/build.gradle index 98de23a80e..4bd29fb1c2 100644 --- a/library/datasource/build.gradle +++ b/library/datasource/build.gradle @@ -32,6 +32,7 @@ android { dependencies { implementation project(modulePrefix + 'library-common') + implementation project(modulePrefix + 'library-database') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version compileOnly 'com.google.errorprone:error_prone_annotations:' + errorProneVersion diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java index 0518fec094..9f54167f62 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java @@ -21,7 +21,7 @@ import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.rule.ActivityTestRule; import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.database.ExoDatabaseProvider; +import com.google.android.exoplayer2.database.StandaloneDatabaseProvider; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.dash.DashUtil; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; @@ -72,7 +72,9 @@ public final class DashDownloadTest { tempFolder = Util.createTempDirectory(testRule.getActivity(), "ExoPlayerTest"); cache = new SimpleCache( - tempFolder, new NoOpCacheEvictor(), new ExoDatabaseProvider(testRule.getActivity())); + tempFolder, + new NoOpCacheEvictor(), + new StandaloneDatabaseProvider(testRule.getActivity())); httpDataSourceFactory = new DefaultHttpDataSource.Factory(); offlineDataSourceFactory = new CacheDataSource.Factory().setCache(cache); } From 68729ecd490497f21e3d8334060c302d8333d135 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Tue, 26 Oct 2021 13:47:25 +0100 Subject: [PATCH 023/113] Remove unneeded release notes. PiperOrigin-RevId: 405626270 --- RELEASENOTES.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 023d02d95f..f5ed04a933 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,8 +16,6 @@ `com.google.android.exoplayer2.decoder.CryptoException`. * Move `com.google.android.exoplayer2.upstream.cache.CachedRegionTracker` to `com.google.android.exoplayer2.upstream.CachedRegionTracker`. - * Make `ExoPlayer.Builder` return a `SimpleExoPlayer` instance. - * Deprecate `SimpleExoPlayer.Builder`. Use `ExoPlayer.Builder` instead. * Remove `ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED`. Use `GlUtil.glAssertionsEnabled` instead. * Move `Player.addListener(EventListener)` and From 649fe702f2149ee761947ea921136d2252477e6c Mon Sep 17 00:00:00 2001 From: hschlueter Date: Tue, 26 Oct 2021 14:20:24 +0100 Subject: [PATCH 024/113] Deduce encoder video format from decoder format. When no encoder video MIME type is specified, the `TransformerTranscodingVideoRenderer` now uses the video MIME type of the input for the encoder format. The input format is now read in a new method `ensureInputFormatRead` which is called before the other configuration methods. This removes the logic for reading the input format from `ensureDecoderConfigured`, because it is now needed for both encoder and decoder configuration but the encoder needs to be configured before GL and GL needs to be configured before the decoder, so the decoder can't read the format. The width and height are now inferred from the input and the frame rate and bit rate are still hard-coded but set by the `MediaCodecAdapterWrapper` instead of `TranscodingTransformer`. PiperOrigin-RevId: 405631263 --- .../transformer/MediaCodecAdapterWrapper.java | 13 ++-- .../transformer/TranscodingTransformer.java | 18 ++--- .../transformer/Transformation.java | 5 +- .../exoplayer2/transformer/Transformer.java | 3 +- .../transformer/TransformerAudioRenderer.java | 14 ++-- .../TransformerTranscodingVideoRenderer.java | 67 +++++++++++-------- 6 files changed, 65 insertions(+), 55 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java index 4afa84dfd3..be05034d70 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java @@ -19,7 +19,6 @@ package com.google.android.exoplayer2.transformer; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; -import static java.lang.Math.ceil; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; @@ -202,9 +201,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * MediaCodecAdapter} video encoder. * * @param format The {@link Format} (of the output data) used to determine the underlying {@link - * MediaCodec} and its configuration values. {@link Format#width}, {@link Format#height}, - * {@link Format#frameRate} and {@link Format#averageBitrate} must be set to those of the - * desired output video format. + * MediaCodec} and its configuration values. {@link Format#sampleMimeType}, {@link + * Format#width} and {@link Format#height} must be set to those of the desired output video + * format. * @param additionalEncoderConfig A map of {@link MediaFormat}'s integer settings, where the keys * are from {@code MediaFormat.KEY_*} constants. Its values will override those in {@code * format}. @@ -215,8 +214,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; Format format, Map additionalEncoderConfig) throws IOException { checkArgument(format.width != Format.NO_VALUE); checkArgument(format.height != Format.NO_VALUE); - checkArgument(format.frameRate != Format.NO_VALUE); - checkArgument(format.averageBitrate != Format.NO_VALUE); @Nullable MediaCodecAdapter adapter = null; try { @@ -224,9 +221,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; MediaFormat.createVideoFormat( checkNotNull(format.sampleMimeType), format.width, format.height); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, CodecCapabilities.COLOR_FormatSurface); - mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, (int) ceil(format.frameRate)); + mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); - mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.averageBitrate); + mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 413_000); for (Map.Entry encoderSetting : additionalEncoderConfig.entrySet()) { mediaFormat.setInteger(encoderSetting.getKey(), encoderSetting.getValue()); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 61feaf5dd0..8e622c2a9a 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -38,7 +38,6 @@ import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; @@ -339,7 +338,12 @@ public final class TranscodingTransformer { } Transformation transformation = new Transformation( - removeAudio, removeVideo, flattenForSlowMotion, outputMimeType, audioMimeType); + removeAudio, + removeVideo, + flattenForSlowMotion, + outputMimeType, + audioMimeType, + /* videoMimeType= */ null); return new TranscodingTransformer( context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock); } @@ -639,17 +643,9 @@ public final class TranscodingTransformer { index++; } if (!transformation.removeVideo) { - Format encoderOutputFormat = - new Format.Builder() - .setSampleMimeType(MimeTypes.VIDEO_H264) - .setWidth(480) - .setHeight(360) - .setAverageBitrate(413_000) - .setFrameRate(30) - .build(); renderers[index] = new TransformerTranscodingVideoRenderer( - context, muxerWrapper, mediaClock, transformation, encoderOutputFormat); + context, muxerWrapper, mediaClock, transformation); index++; } return renderers; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java index a224fe3a93..e273c1fde5 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java @@ -26,17 +26,20 @@ import androidx.annotation.Nullable; public final boolean flattenForSlowMotion; public final String outputMimeType; @Nullable public final String audioMimeType; + @Nullable public final String videoMimeType; public Transformation( boolean removeAudio, boolean removeVideo, boolean flattenForSlowMotion, String outputMimeType, - @Nullable String audioMimeType) { + @Nullable String audioMimeType, + @Nullable String videoMimeType) { this.removeAudio = removeAudio; this.removeVideo = removeVideo; this.flattenForSlowMotion = flattenForSlowMotion; this.outputMimeType = outputMimeType; this.audioMimeType = audioMimeType; + this.videoMimeType = videoMimeType; } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 34d10137b8..1a79061f66 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -304,7 +304,8 @@ public final class Transformer { removeVideo, flattenForSlowMotion, outputMimeType, - /* audioMimeType= */ null); + /* audioMimeType= */ null, + /* videoMimeType= */ null); return new Transformer( context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 64dc53d58c..9a8da0ecf6 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -51,7 +51,7 @@ import java.nio.ByteBuffer; @Nullable private MediaCodecAdapterWrapper decoder; @Nullable private MediaCodecAdapterWrapper encoder; @Nullable private SpeedProvider speedProvider; - @Nullable private Format inputFormat; + @Nullable private Format decoderInputFormat; @Nullable private AudioFormat encoderInputAudioFormat; private ByteBuffer sonicOutputBuffer; @@ -100,7 +100,7 @@ import java.nio.ByteBuffer; encoder = null; } speedProvider = null; - inputFormat = null; + decoderInputFormat = null; encoderInputAudioFormat = null; sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER; nextEncoderInputBufferTimeUs = 0; @@ -352,7 +352,7 @@ import java.nio.ByteBuffer; } String audioMimeType = transformation.audioMimeType == null - ? checkNotNull(inputFormat).sampleMimeType + ? checkNotNull(decoderInputFormat).sampleMimeType : transformation.audioMimeType; try { encoder = @@ -385,14 +385,14 @@ import java.nio.ByteBuffer; if (result != C.RESULT_FORMAT_READ) { return false; } - inputFormat = checkNotNull(formatHolder.format); + decoderInputFormat = checkNotNull(formatHolder.format); try { - decoder = MediaCodecAdapterWrapper.createForAudioDecoding(inputFormat); + decoder = MediaCodecAdapterWrapper.createForAudioDecoding(decoderInputFormat); } catch (IOException e) { // TODO (internal b/184262323): Assign an adequate error code. throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED); } - speedProvider = new SegmentSpeedProvider(inputFormat); + speedProvider = new SegmentSpeedProvider(decoderInputFormat); currentSpeed = speedProvider.getSpeed(0); return true; } @@ -418,7 +418,7 @@ import java.nio.ByteBuffer; cause, TAG, getIndex(), - inputFormat, + decoderInputFormat, /* rendererFormatSupport= */ C.FORMAT_HANDLED, /* isRecoverable= */ false, errorCode); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index 79d28a208e..8c1e11df61 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -42,6 +42,7 @@ import com.google.android.exoplayer2.util.GlUtil; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.nio.ByteBuffer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @RequiresApi(18) /* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer { @@ -53,12 +54,12 @@ import java.nio.ByteBuffer; private static final String TAG = "TransformerTranscodingVideoRenderer"; private final Context context; - /** The format the encoder is configured to output, may differ from the actual output format. */ - private final Format encoderConfigurationOutputFormat; private final DecoderInputBuffer decoderInputBuffer; private final float[] decoderTextureTransformMatrix; + private @MonotonicNonNull Format decoderInputFormat; + @Nullable private EGLDisplay eglDisplay; @Nullable private EGLContext eglContext; @Nullable private EGLSurface eglSurface; @@ -81,11 +82,9 @@ import java.nio.ByteBuffer; Context context, MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, - Transformation transformation, - Format encoderConfigurationOutputFormat) { + Transformation transformation) { super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation); this.context = context; - this.encoderConfigurationOutputFormat = encoderConfigurationOutputFormat; decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); decoderTextureTransformMatrix = new float[16]; decoderTextureId = GlUtil.TEXTURE_ID_UNSET; @@ -97,15 +96,13 @@ import java.nio.ByteBuffer; } @Override - protected void onStarted() throws ExoPlaybackException { - super.onStarted(); + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (!isRendererStarted || isEnded() || !ensureInputFormatRead()) { + return; + } ensureEncoderConfigured(); ensureOpenGlConfigured(); - } - - @Override - public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - if (!isRendererStarted || isEnded() || !ensureDecoderConfigured()) { + if (!ensureDecoderConfigured()) { return; } @@ -153,6 +150,22 @@ import java.nio.ByteBuffer; muxerWrapperTrackEnded = false; } + private boolean ensureInputFormatRead() { + if (decoderInputFormat != null) { + return true; + } + FormatHolder formatHolder = getFormatHolder(); + @SampleStream.ReadDataResult + int result = + readSource( + formatHolder, decoderInputBuffer, /* readFlags= */ SampleStream.FLAG_REQUIRE_FORMAT); + if (result != C.RESULT_FORMAT_READ) { + return false; + } + decoderInputFormat = checkNotNull(formatHolder.format); + return true; + } + private void ensureEncoderConfigured() throws ExoPlaybackException { if (encoder != null) { return; @@ -161,7 +174,15 @@ import java.nio.ByteBuffer; try { encoder = MediaCodecAdapterWrapper.createForVideoEncoding( - encoderConfigurationOutputFormat, ImmutableMap.of()); + new Format.Builder() + .setWidth(checkNotNull(decoderInputFormat).width) + .setHeight(decoderInputFormat.height) + .setSampleMimeType( + transformation.videoMimeType != null + ? transformation.videoMimeType + : decoderInputFormat.sampleMimeType) + .build(), + ImmutableMap.of()); } catch (IOException e) { throw createRendererException( // TODO(claincly): should be "ENCODER_INIT_FAILED" @@ -190,8 +211,8 @@ import java.nio.ByteBuffer; eglDisplay, eglContext, eglSurface, - encoderConfigurationOutputFormat.width, - encoderConfigurationOutputFormat.height); + checkNotNull(decoderInputFormat).width, + decoderInputFormat.height); decoderTextureId = GlUtil.createExternalTexture(); String vertexShaderCode; String fragmentShaderCode; @@ -248,26 +269,18 @@ import java.nio.ByteBuffer; return true; } - FormatHolder formatHolder = getFormatHolder(); - @SampleStream.ReadDataResult - int result = - readSource( - formatHolder, decoderInputBuffer, /* readFlags= */ SampleStream.FLAG_REQUIRE_FORMAT); - if (result != C.RESULT_FORMAT_READ) { - return false; - } - - Format inputFormat = checkNotNull(formatHolder.format); checkState(decoderTextureId != GlUtil.TEXTURE_ID_UNSET); decoderSurfaceTexture = new SurfaceTexture(decoderTextureId); decoderSurfaceTexture.setOnFrameAvailableListener( surfaceTexture -> isDecoderSurfacePopulated = true); decoderSurface = new Surface(decoderSurfaceTexture); try { - decoder = MediaCodecAdapterWrapper.createForVideoDecoding(inputFormat, decoderSurface); + decoder = + MediaCodecAdapterWrapper.createForVideoDecoding( + checkNotNull(decoderInputFormat), decoderSurface); } catch (IOException e) { throw createRendererException( - e, formatHolder.format, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); + e, decoderInputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); } return true; } From 98200c2692ba007ba0b177d7b285b957dc08ff93 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 26 Oct 2021 14:31:23 +0100 Subject: [PATCH 025/113] Replace ExtractorsFactory with MediaSourceFactory in ExoPlayer.Builder This has a few benefits: * Aligns the Builder constructors with the setters (setRenderersFactory is missing, but can be easily added in a follow-up change). * Allows DefaultMediaSourceFactory to be stripped by R8 and makes the shrinking dev guide for the cases of providing a custom MediaSourceFactory or directly instantiating MediaSource instances less weird too. #minor-release PiperOrigin-RevId: 405632981 --- docs/shrinking.md | 49 ++++++---- .../google/android/exoplayer2/ExoPlayer.java | 91 ++++++++----------- .../android/exoplayer2/SimpleExoPlayer.java | 23 +++-- .../exoplayer2/source/MediaSourceFactory.java | 50 ++++++++++ 4 files changed, 136 insertions(+), 77 deletions(-) diff --git a/docs/shrinking.md b/docs/shrinking.md index 5506e742c2..7c97c9ea29 100644 --- a/docs/shrinking.md +++ b/docs/shrinking.md @@ -81,36 +81,49 @@ an app that only needs to play mp4 files can provide a factory like: ExtractorsFactory mp4ExtractorFactory = () -> new Extractor[] {new Mp4Extractor()}; ExoPlayer player = - new ExoPlayer.Builder(context, mp4ExtractorFactory).build(); + new ExoPlayer.Builder( + context, + new DefaultMediaSourceFactory(context, mp4ExtractorFactory)) + .build(); ~~~ {: .language-java} This will allow other `Extractor` implementations to be removed by code shrinking, which can result in a significant reduction in size. -You should pass `ExtractorsFactory.EMPTY` to the `ExoPlayer.Builder` -constructor, if your app is doing one of the following: - -* Not playing progressive media at all, for example because it only - plays DASH, HLS or SmoothStreaming content -* Providing a customized `DefaultMediaSourceFactory` -* Using `MediaSource`s directly instead of `MediaItem`s +If your app is not playing progressive content at all, you should pass +`ExtractorsFactory.EMPTY` to the `DefaultMediaSourceFactory` constructor, then +pass that `mediaSourceFactory` to the `ExoPlayer.Builder` constructor. ~~~ -// Only playing DASH, HLS or SmoothStreaming. ExoPlayer player = - new ExoPlayer.Builder(context, ExtractorsFactory.EMPTY).build(); + new ExoPlayer.Builder( + context, + new DefaultMediaSourceFactory(context, ExtractorsFactory.EMPTY)) + .build(); +~~~ +{: .language-java} -// Providing a customized `DefaultMediaSourceFactory` -ExoPlayer player = - new ExoPlayer.Builder(context, ExtractorsFactory.EMPTY) - .setMediaSourceFactory( - new DefaultMediaSourceFactory(context, customExtractorsFactory)) - .build(); +## Custom `MediaSource` instantiation ## -// Using a MediaSource directly. +If your app is using a custom `MediaSourceFactory` and you want +`DefaultMediaSourceFactory` to be removed by code stripping, you should pass +your `MediaSourceFactory` directly to the `ExoPlayer.Builder` constructor. + +~~~ ExoPlayer player = - new ExoPlayer.Builder(context, ExtractorsFactory.EMPTY).build(); + new ExoPlayer.Builder(context, customMediaSourceFactory).build(); +~~~ +{: .language-java} + +If your app is using `MediaSource`s directly instead of `MediaItem`s you should +pass `MediaSourceFactory.UNSUPPORTED` to the `ExoPlayer.Builder` constructor, to +ensure `DefaultMediaSourceFactory` and `DefaultExtractorsFactory` can be +stripped by code shrinking. + +~~~ +ExoPlayer player = + new ExoPlayer.Builder(context, MediaSourceFactory.UNSUPPORTED).build(); ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory( dataSourceFactory, customExtractorsFactory) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 1605a3e5a3..635a4051c9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -367,8 +367,8 @@ public interface ExoPlayer extends Player { /* package */ Clock clock; /* package */ long foregroundModeTimeoutMs; - /* package */ TrackSelector trackSelector; /* package */ MediaSourceFactory mediaSourceFactory; + /* package */ TrackSelector trackSelector; /* package */ LoadControl loadControl; /* package */ BandwidthMeter bandwidthMeter; /* package */ AnalyticsCollector analyticsCollector; @@ -395,10 +395,11 @@ public interface ExoPlayer extends Player { * Creates a builder. * *

      Use {@link #Builder(Context, RenderersFactory)}, {@link #Builder(Context, - * RenderersFactory)} or {@link #Builder(Context, RenderersFactory, ExtractorsFactory)} instead, - * if you intend to provide a custom {@link RenderersFactory} or a custom {@link - * ExtractorsFactory}. This is to ensure that ProGuard or R8 can remove ExoPlayer's {@link - * DefaultRenderersFactory} and {@link DefaultExtractorsFactory} from the APK. + * MediaSourceFactory)} or {@link #Builder(Context, RenderersFactory, MediaSourceFactory)} + * instead, if you intend to provide a custom {@link RenderersFactory}, {@link + * ExtractorsFactory} or {@link DefaultMediaSourceFactory}. This is to ensure that ProGuard or + * R8 can remove ExoPlayer's {@link DefaultRenderersFactory}, {@link DefaultExtractorsFactory} + * and {@link DefaultMediaSourceFactory} from the APK. * *

      The builder uses the following default values: * @@ -434,7 +435,10 @@ public interface ExoPlayer extends Player { * @param context A {@link Context}. */ public Builder(Context context) { - this(context, new DefaultRenderersFactory(context), new DefaultExtractorsFactory()); + this( + context, + new DefaultRenderersFactory(context), + new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory())); } /** @@ -447,40 +451,23 @@ public interface ExoPlayer extends Player { * player. */ public Builder(Context context, RenderersFactory renderersFactory) { - this(context, renderersFactory, new DefaultExtractorsFactory()); - } - - /** - * Creates a builder with a custom {@link ExtractorsFactory}. - * - *

      See {@link #Builder(Context)} for a list of default values. - * - * @param context A {@link Context}. - * @param extractorsFactory An {@link ExtractorsFactory} used to extract progressive media from - * its container. - */ - public Builder(Context context, ExtractorsFactory extractorsFactory) { - this(context, new DefaultRenderersFactory(context), extractorsFactory); - } - - /** - * Creates a builder with a custom {@link RenderersFactory} and {@link ExtractorsFactory}. - * - *

      See {@link #Builder(Context)} for a list of default values. - * - * @param context A {@link Context}. - * @param renderersFactory A factory for creating {@link Renderer Renderers} to be used by the - * player. - * @param extractorsFactory An {@link ExtractorsFactory} used to extract progressive media from - * its container. - */ - public Builder( - Context context, RenderersFactory renderersFactory, ExtractorsFactory extractorsFactory) { this( context, renderersFactory, + new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory())); + } + + public Builder(Context context, MediaSourceFactory mediaSourceFactory) { + this(context, new DefaultRenderersFactory(context), mediaSourceFactory); + } + + public Builder( + Context context, RenderersFactory renderersFactory, MediaSourceFactory mediaSourceFactory) { + this( + context, + renderersFactory, + mediaSourceFactory, new DefaultTrackSelector(context), - new DefaultMediaSourceFactory(context, extractorsFactory), new DefaultLoadControl(), DefaultBandwidthMeter.getSingletonInstance(context), new AnalyticsCollector(Clock.DEFAULT)); @@ -495,8 +482,8 @@ public interface ExoPlayer extends Player { * @param context A {@link Context}. * @param renderersFactory A factory for creating {@link Renderer Renderers} to be used by the * player. - * @param trackSelector A {@link TrackSelector}. * @param mediaSourceFactory A {@link MediaSourceFactory}. + * @param trackSelector A {@link TrackSelector}. * @param loadControl A {@link LoadControl}. * @param bandwidthMeter A {@link BandwidthMeter}. * @param analyticsCollector An {@link AnalyticsCollector}. @@ -504,15 +491,15 @@ public interface ExoPlayer extends Player { public Builder( Context context, RenderersFactory renderersFactory, - TrackSelector trackSelector, MediaSourceFactory mediaSourceFactory, + TrackSelector trackSelector, LoadControl loadControl, BandwidthMeter bandwidthMeter, AnalyticsCollector analyticsCollector) { this.context = context; this.renderersFactory = renderersFactory; - this.trackSelector = trackSelector; this.mediaSourceFactory = mediaSourceFactory; + this.trackSelector = trackSelector; this.loadControl = loadControl; this.bandwidthMeter = bandwidthMeter; this.analyticsCollector = analyticsCollector; @@ -546,19 +533,6 @@ public interface ExoPlayer extends Player { return this; } - /** - * Sets the {@link TrackSelector} that will be used by the player. - * - * @param trackSelector A {@link TrackSelector}. - * @return This builder. - * @throws IllegalStateException If {@link #build()} has already been called. - */ - public Builder setTrackSelector(TrackSelector trackSelector) { - checkState(!buildCalled); - this.trackSelector = trackSelector; - return this; - } - /** * Sets the {@link MediaSourceFactory} that will be used by the player. * @@ -572,6 +546,19 @@ public interface ExoPlayer extends Player { return this; } + /** + * Sets the {@link TrackSelector} that will be used by the player. + * + * @param trackSelector A {@link TrackSelector}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setTrackSelector(TrackSelector trackSelector) { + checkState(!buildCalled); + this.trackSelector = trackSelector; + return this; + } + /** * Sets the {@link LoadControl} that will be used by the player. * 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 f247b20ebe..c6fddec392 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 @@ -55,6 +55,7 @@ import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataOutput; +import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.ShuffleOrder; @@ -111,25 +112,33 @@ public class SimpleExoPlayer extends BasePlayer wrappedBuilder = new ExoPlayer.Builder(context, renderersFactory); } - /** @deprecated Use {@link ExoPlayer.Builder#Builder(Context, ExtractorsFactory)} instead. */ + /** + * @deprecated Use {@link ExoPlayer.Builder#Builder(Context, MediaSourceFactory)} and {@link + * DefaultMediaSourceFactory#DefaultMediaSourceFactory(Context, ExtractorsFactory)} instead. + */ @Deprecated public Builder(Context context, ExtractorsFactory extractorsFactory) { - wrappedBuilder = new ExoPlayer.Builder(context, extractorsFactory); + wrappedBuilder = + new ExoPlayer.Builder(context, new DefaultMediaSourceFactory(context, extractorsFactory)); } /** * @deprecated Use {@link ExoPlayer.Builder#Builder(Context, RenderersFactory, - * ExtractorsFactory)} instead. + * MediaSourceFactory)} and {@link + * DefaultMediaSourceFactory#DefaultMediaSourceFactory(Context, ExtractorsFactory)} instead. */ @Deprecated public Builder( Context context, RenderersFactory renderersFactory, ExtractorsFactory extractorsFactory) { - wrappedBuilder = new ExoPlayer.Builder(context, renderersFactory, extractorsFactory); + wrappedBuilder = + new ExoPlayer.Builder( + context, renderersFactory, new DefaultMediaSourceFactory(context, extractorsFactory)); } /** - * @deprecated Use {@link ExoPlayer.Builder#Builder(Context, RenderersFactory, TrackSelector, - * MediaSourceFactory, LoadControl, BandwidthMeter, AnalyticsCollector)} instead. + * @deprecated Use {@link ExoPlayer.Builder#Builder(Context, RenderersFactory, + * MediaSourceFactory, TrackSelector, LoadControl, BandwidthMeter, AnalyticsCollector)} + * instead. */ @Deprecated public Builder( @@ -144,8 +153,8 @@ public class SimpleExoPlayer extends BasePlayer new ExoPlayer.Builder( context, renderersFactory, - trackSelector, mediaSourceFactory, + trackSelector, loadControl, bandwidthMeter, analyticsCollector); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java index d4a5d6ca62..9a4896a95f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java @@ -34,6 +34,56 @@ import java.util.List; /** Factory for creating {@link MediaSource MediaSources} from {@link MediaItem MediaItems}. */ public interface MediaSourceFactory { + /** + * An instance that throws {@link UnsupportedOperationException} from {@link #createMediaSource} + * and {@link #getSupportedTypes()}. + */ + MediaSourceFactory UNSUPPORTED = + new MediaSourceFactory() { + @Override + public MediaSourceFactory setDrmSessionManagerProvider( + @Nullable DrmSessionManagerProvider drmSessionManagerProvider) { + return this; + } + + @Deprecated + @Override + public MediaSourceFactory setDrmSessionManager( + @Nullable DrmSessionManager drmSessionManager) { + return this; + } + + @Deprecated + @Override + public MediaSourceFactory setDrmHttpDataSourceFactory( + @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) { + return this; + } + + @Deprecated + @Override + public MediaSourceFactory setDrmUserAgent(@Nullable String userAgent) { + return this; + } + + @Override + public MediaSourceFactory setLoadErrorHandlingPolicy( + @Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) { + return this; + } + + @Override + @C.ContentType + public int[] getSupportedTypes() { + throw new UnsupportedOperationException(); + } + + @Override + public MediaSource createMediaSource(MediaItem mediaItem) { + throw new UnsupportedOperationException(); + } + }; + /** @deprecated Use {@link MediaItem.LocalConfiguration#streamKeys} instead. */ @Deprecated default MediaSourceFactory setStreamKeys(@Nullable List streamKeys) { From 8545a8b35f18360bf52ba2d33387a1dffc4806c6 Mon Sep 17 00:00:00 2001 From: hschlueter Date: Tue, 26 Oct 2021 15:44:37 +0100 Subject: [PATCH 026/113] Allow video MIME type to be set in TranscodingTransformer. Also check that the output video MIME type is supported with the given container MIME type in `TranscodingTransformer` and `TransformerBaseRenderer`. PiperOrigin-RevId: 405645362 --- .../transformer/TranscodingTransformer.java | 50 ++++++++++++++++--- .../transformer/TransformerBaseRenderer.java | 5 +- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 8e622c2a9a..d766987aa8 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -100,6 +100,7 @@ public final class TranscodingTransformer { private boolean flattenForSlowMotion; private String outputMimeType; @Nullable private String audioMimeType; + @Nullable private String videoMimeType; private TranscodingTransformer.Listener listener; private Looper looper; private Clock clock; @@ -123,6 +124,7 @@ public final class TranscodingTransformer { this.flattenForSlowMotion = transcodingTransformer.transformation.flattenForSlowMotion; this.outputMimeType = transcodingTransformer.transformation.outputMimeType; this.audioMimeType = transcodingTransformer.transformation.audioMimeType; + this.videoMimeType = transcodingTransformer.transformation.videoMimeType; this.listener = transcodingTransformer.listener; this.looper = transcodingTransformer.looper; this.clock = transcodingTransformer.clock; @@ -229,6 +231,33 @@ public final class TranscodingTransformer { return this; } + /** + * Sets the video MIME type of the output. The default value is to use the same MIME type as the + * input. Supported values are: + * + *

        + *
      • when the container MIME type is {@link MimeTypes#VIDEO_MP4}: + *
          + *
        • {@link MimeTypes#VIDEO_H263} + *
        • {@link MimeTypes#VIDEO_H264} + *
        • {@link MimeTypes#VIDEO_H265} from API level 24 + *
        • {@link MimeTypes#VIDEO_MP4V} + *
        + *
      • when the container MIME type is {@link MimeTypes#VIDEO_WEBM}: + *
          + *
        • {@link MimeTypes#VIDEO_VP8} + *
        • {@link MimeTypes#VIDEO_VP9} from API level 24 + *
        + *
      + * + * @param videoMimeType The MIME type of the video samples in the output. + * @return This builder. + */ + public Builder setVideoMimeType(String videoMimeType) { + this.videoMimeType = videoMimeType; + return this; + } + /** * Sets the audio MIME type of the output. The default value is to use the same MIME type as the * input. Supported values are: @@ -329,12 +358,10 @@ public final class TranscodingTransformer { muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); if (audioMimeType != null) { - checkState( - muxerFactory.supportsSampleMimeType(audioMimeType, outputMimeType), - "Unsupported sample MIME type " - + audioMimeType - + " for container MIME type " - + outputMimeType); + checkSampleMimeType(audioMimeType); + } + if (videoMimeType != null) { + checkSampleMimeType(videoMimeType); } Transformation transformation = new Transformation( @@ -343,10 +370,19 @@ public final class TranscodingTransformer { flattenForSlowMotion, outputMimeType, audioMimeType, - /* videoMimeType= */ null); + videoMimeType); return new TranscodingTransformer( context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock); } + + private void checkSampleMimeType(String sampleMimeType) { + checkState( + muxerFactory.supportsSampleMimeType(sampleMimeType, outputMimeType), + "Unsupported sample MIME type " + + sampleMimeType + + " for container MIME type " + + outputMimeType); + } } /** A listener for the transformation events. */ diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java index 4f2243000d..e0ceb945fc 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java @@ -58,7 +58,10 @@ import com.google.android.exoplayer2.util.MimeTypes; ? sampleMimeType : transformation.audioMimeType)) || (MimeTypes.isVideo(sampleMimeType) - && muxerWrapper.supportsSampleMimeType(sampleMimeType))) { + && muxerWrapper.supportsSampleMimeType( + transformation.videoMimeType == null + ? sampleMimeType + : transformation.videoMimeType))) { return RendererCapabilities.create(C.FORMAT_HANDLED); } else { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); From 383bad80ce2aabe8418073f7d81336a6d556c763 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 26 Oct 2021 16:45:30 +0100 Subject: [PATCH 027/113] Generalize findEsdsPosition to support other types - This CL does not introduce functional changes. - This change will allow searching for the clli box while parsing the mdcv box in order to construct the HDR static info contained in ColorInfo. #minor-release PiperOrigin-RevId: 405656499 --- .../exoplayer2/extractor/mp4/AtomParsers.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index b43eddf300..93b9c5b77a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -1435,7 +1435,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; int esdsAtomPosition = childAtomType == Atom.TYPE_esds ? childPosition - : findEsdsPosition(parent, childPosition, childAtomSize); + : findBoxPosition(parent, Atom.TYPE_esds, childPosition, childAtomSize); if (esdsAtomPosition != C.POSITION_UNSET) { Pair<@NullableType String, byte @NullableType []> mimeTypeAndInitializationData = parseEsdsFromParent(parent, esdsAtomPosition); @@ -1537,18 +1537,28 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } /** - * Returns the position of the esds box within a parent, or {@link C#POSITION_UNSET} if no esds - * box is found + * Returns the position of the first box with the given {@code boxType} within {@code parent}, or + * {@link C#POSITION_UNSET} if no such box is found. + * + * @param parent The {@link ParsableByteArray} to search. The search will start from the {@link + * ParsableByteArray#getPosition() current position}. + * @param boxType The box type to search for. + * @param parentBoxPosition The position in {@code parent} of the box we are searching. + * @param parentBoxSize The size of the parent box we are searching in bytes. + * @return The position of the first box with the given {@code boxType} within {@code parent}, or + * {@link C#POSITION_UNSET} if no such box is found. */ - private static int findEsdsPosition(ParsableByteArray parent, int position, int size) + private static int findBoxPosition( + ParsableByteArray parent, int boxType, int parentBoxPosition, int parentBoxSize) throws ParserException { int childAtomPosition = parent.getPosition(); - while (childAtomPosition - position < size) { + ExtractorUtil.checkContainerInput(childAtomPosition >= parentBoxPosition, /* message= */ null); + while (childAtomPosition - parentBoxPosition < parentBoxSize) { parent.setPosition(childAtomPosition); int childAtomSize = parent.readInt(); ExtractorUtil.checkContainerInput(childAtomSize > 0, "childAtomSize must be positive"); int childType = parent.readInt(); - if (childType == Atom.TYPE_esds) { + if (childType == boxType) { return childAtomPosition; } childAtomPosition += childAtomSize; From 310f268a626ee54b6a5de0b2d2f223d10db07894 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 26 Oct 2021 22:30:30 +0100 Subject: [PATCH 028/113] Set assumedVideoMinimumCodecOperatingRate for all playbacks PiperOrigin-RevId: 405736227 --- .../video/MediaCodecVideoRenderer.java | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index de6215e0b5..55b6359fef 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -205,7 +205,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { /* enableDecoderFallback= */ false, eventHandler, eventListener, - maxDroppedFramesToNotify); + maxDroppedFramesToNotify, + /* assumedMinimumCodecOperatingRate= */ 30); } /** @@ -238,12 +239,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { enableDecoderFallback, eventHandler, eventListener, - maxDroppedFramesToNotify); + maxDroppedFramesToNotify, + /* assumedMinimumCodecOperatingRate= */ 30); } /** - * Creates a new instance. - * * @param context A context. * @param codecAdapterFactory The {@link MediaCodecAdapter.Factory} used to create {@link * MediaCodecAdapter} instances. @@ -268,12 +268,56 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { + + this( + context, + codecAdapterFactory, + mediaCodecSelector, + allowedJoiningTimeMs, + enableDecoderFallback, + eventHandler, + eventListener, + maxDroppedFramesToNotify, + /* assumedMinimumCodecOperatingRate= */ 30); + } + + /** + * Creates a new instance. + * + * @param context A context. + * @param codecAdapterFactory The {@link MediaCodecAdapter.Factory} used to create {@link + * MediaCodecAdapter} instances. + * @param mediaCodecSelector A decoder selector. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. + * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder + * initialization fails. This may result in using a decoder that is slower/less efficient than + * the primary decoder. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between + * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. + * @param assumedMinimumCodecOperatingRate A codec operating rate that all codecs instantiated by + * this renderer are assumed to meet implicitly (i.e. without the operating rate being set + * explicitly using {@link MediaFormat#KEY_OPERATING_RATE}). + */ + public MediaCodecVideoRenderer( + Context context, + MediaCodecAdapter.Factory codecAdapterFactory, + MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs, + boolean enableDecoderFallback, + @Nullable Handler eventHandler, + @Nullable VideoRendererEventListener eventListener, + int maxDroppedFramesToNotify, + float assumedMinimumCodecOperatingRate) { super( C.TRACK_TYPE_VIDEO, codecAdapterFactory, mediaCodecSelector, enableDecoderFallback, - /* assumedMinimumCodecOperatingRate= */ 30); + assumedMinimumCodecOperatingRate); this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.context = context.getApplicationContext(); From 3bc0fae70881758ad9b19a845464c263404e2c82 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Wed, 27 Oct 2021 10:26:50 +0100 Subject: [PATCH 029/113] Migrate SegmentSpeedProviderTest off deprecated method. PiperOrigin-RevId: 405841397 --- .../transformer/SegmentSpeedProviderTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/SegmentSpeedProviderTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/SegmentSpeedProviderTest.java index 616443e963..5fa2880e7c 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/SegmentSpeedProviderTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/SegmentSpeedProviderTest.java @@ -19,12 +19,12 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; 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.Metadata; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData.Segment; import com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import java.util.List; import org.junit.Test; @@ -60,17 +60,17 @@ public class SegmentSpeedProviderTest { .setMetadata(new Metadata(new SlowMotionData(segments), SMTA_SPEED_8)) .build()); - assertThat(provider.getSpeed(C.msToUs(0))).isEqualTo(8); - assertThat(provider.getSpeed(C.msToUs(500))).isEqualTo(1); - assertThat(provider.getSpeed(C.msToUs(800))).isEqualTo(1); - assertThat(provider.getSpeed(C.msToUs(1000))).isEqualTo(8); - assertThat(provider.getSpeed(C.msToUs(1250))).isEqualTo(8); - assertThat(provider.getSpeed(C.msToUs(1500))).isEqualTo(2); - assertThat(provider.getSpeed(C.msToUs(1650))).isEqualTo(2); - assertThat(provider.getSpeed(C.msToUs(2000))).isEqualTo(4); - assertThat(provider.getSpeed(C.msToUs(2400))).isEqualTo(4); - assertThat(provider.getSpeed(C.msToUs(2500))).isEqualTo(8); - assertThat(provider.getSpeed(C.msToUs(3000))).isEqualTo(8); + assertThat(provider.getSpeed(Util.msToUs(0))).isEqualTo(8); + assertThat(provider.getSpeed(Util.msToUs(500))).isEqualTo(1); + assertThat(provider.getSpeed(Util.msToUs(800))).isEqualTo(1); + assertThat(provider.getSpeed(Util.msToUs(1000))).isEqualTo(8); + assertThat(provider.getSpeed(Util.msToUs(1250))).isEqualTo(8); + assertThat(provider.getSpeed(Util.msToUs(1500))).isEqualTo(2); + assertThat(provider.getSpeed(Util.msToUs(1650))).isEqualTo(2); + assertThat(provider.getSpeed(Util.msToUs(2000))).isEqualTo(4); + assertThat(provider.getSpeed(Util.msToUs(2400))).isEqualTo(4); + assertThat(provider.getSpeed(Util.msToUs(2500))).isEqualTo(8); + assertThat(provider.getSpeed(Util.msToUs(3000))).isEqualTo(8); } @Test From 39639f8df0f6fe0b8acadf1fbec1b38ed6e20988 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 27 Oct 2021 10:34:03 +0100 Subject: [PATCH 030/113] Allow missing full_range_flag in colr box with type=nclx Test file produced with: $ MP4Box -add "sample.mp4#video:colr=nclc,1,1,1" -new sample_18byte_nclx_colr.mp4 And then manually changing the `nclc` bytes to `nclx`. This produces an 18-byte `colr` box with type `nclx`. The bitstream of this file does not contain HDR content, so the file itself is invalid for playback with a real decoder, but adding the box is enough to test the extractor change in this commit. (aside: MP4Box will let you pass `nclx`, but it requires 4 parameters, i.e. it requires the full_range_flag to be set, resulting in a valid 19-byte colr box) #minor-release Issue: #9332 PiperOrigin-RevId: 405842520 --- RELEASENOTES.md | 7 +- .../exoplayer2/extractor/mp4/AtomParsers.java | 11 +- .../extractor/mp4/Mp4ExtractorTest.java | 11 ++ .../mp4/sample_18byte_nclx_colr.mp4.0.dump | 148 ++++++++++++++++++ .../mp4/sample_18byte_nclx_colr.mp4.1.dump | 148 ++++++++++++++++++ .../mp4/sample_18byte_nclx_colr.mp4.2.dump | 148 ++++++++++++++++++ .../mp4/sample_18byte_nclx_colr.mp4.3.dump | 148 ++++++++++++++++++ ...e_18byte_nclx_colr.mp4.unknown_length.dump | 148 ++++++++++++++++++ .../media/mp4/sample_18byte_nclx_colr.mp4 | Bin 0 -> 91100 bytes 9 files changed, 764 insertions(+), 5 deletions(-) create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.0.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.1.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.2.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.3.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.unknown_length.dump create mode 100644 testdata/src/test/assets/media/mp4/sample_18byte_nclx_colr.mp4 diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f5ed04a933..bf708bfa32 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,8 +24,8 @@ ([#9458](https://github.com/google/ExoPlayer/issues/9458)). * Remove final dependency on `jcenter()`. * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values - input through the `MediaItem.MediaMetadata` are used above media - derived values. + input through the `MediaItem.MediaMetadata` are used above media derived + values. * Video: * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a released `Surface` when playing without an app-provided `Surface` @@ -61,6 +61,9 @@ * MP4: Correctly handle HEVC tracks with pixel aspect ratios other than 1. * MP4: Add support for Dolby TrueHD (only for unfragmented streams) ([#9496](https://github.com/google/ExoPlayer/issues/9496)). + * MP4: Avoid throwing `ArrayIndexOutOfBoundsException` when parsing + invalid `colr` boxes produced by some device cameras + ([#9332](https://github.com/google/ExoPlayer/issues/9332)). * TS: Correctly handle HEVC tracks with pixel aspect ratios other than 1. * TS: Map stream type 0x80 to H262 ([#9472](https://github.com/google/ExoPlayer/issues/9472)). diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 93b9c5b77a..bc5fa10fe3 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -1198,14 +1198,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } } else if (childAtomType == Atom.TYPE_colr) { int colorType = parent.readInt(); - boolean isNclx = colorType == TYPE_nclx; - if (isNclx || colorType == TYPE_nclc) { + if (colorType == TYPE_nclx || colorType == TYPE_nclc) { // For more info on syntax, see Section 8.5.2.2 in ISO/IEC 14496-12:2012(E) and // https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html. int colorPrimaries = parent.readUnsignedShort(); int transferCharacteristics = parent.readUnsignedShort(); parent.skipBytes(2); // matrix_coefficients. - boolean fullRangeFlag = isNclx && (parent.readUnsignedByte() & 0b10000000) != 0; + + // Only try and read full_range_flag if the box is long enough. It should be present in + // all colr boxes with type=nclx (Section 8.5.2.2 in ISO/IEC 14496-12:2012(E)) but some + // device cameras record videos with type=nclx without this final flag (and therefore + // size=18): https://github.com/google/ExoPlayer/issues/9332 + boolean fullRangeFlag = + childAtomSize == 19 && (parent.readUnsignedByte() & 0b10000000) != 0; colorInfo = new ColorInfo( ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries), diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java index 9c41d361ad..911f3f477e 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java @@ -103,6 +103,17 @@ public final class Mp4ExtractorTest { Mp4Extractor::new, "media/mp4/sample_with_color_info.mp4", simulationConfig); } + /** + * Test case for https://github.com/google/ExoPlayer/issues/9332. The file contains a colr box + * with size=18 and type=nclx. This is not valid according to the spec (size must be 19), but + * files like this exist in the wild. + */ + @Test + public void mp4Sample18ByteNclxColr() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_18byte_nclx_colr.mp4", simulationConfig); + } + @Test public void mp4SampleWithDolbyTrueHDTrack() throws Exception { ExtractorAsserts.assertBehavior( diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.0.dump new file mode 100644 index 0000000000..30748b0761 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.0.dump @@ -0,0 +1,148 @@ +seekMap: + isSeekable = true + duration = 1001000 + getPosition(0) = [[timeUs=0, position=1160]] + getPosition(1) = [[timeUs=0, position=1160]] + getPosition(500500) = [[timeUs=0, position=1160]] + getPosition(1001000) = [[timeUs=0, position=1160]] +numberOfTracks = 1 +track 0: + total output bytes = 89876 + sample count = 30 + format 0: + id = 1 + sampleMimeType = video/avc + codecs = avc1.64001F + maxInputSize = 36722 + width = 1080 + height = 720 + frameRate = 29.970028 + colorInfo: + colorSpace = 1 + colorRange = 2 + colorTransfer = 3 + hdrStaticInfo = length 0, hash 0 + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + sample 0: + time = 0 + flags = 1 + data = length 36692, hash D216076E + sample 1: + time = 66733 + flags = 0 + data = length 5312, hash D45D3CA0 + sample 2: + time = 33366 + flags = 0 + data = length 599, hash 1BE7812D + sample 3: + time = 200200 + flags = 0 + data = length 7735, hash 4490F110 + sample 4: + time = 133466 + flags = 0 + data = length 987, hash 560B5036 + sample 5: + time = 100100 + flags = 0 + data = length 673, hash ED7CD8C7 + sample 6: + time = 166833 + flags = 0 + data = length 523, hash 3020DF50 + sample 7: + time = 333666 + flags = 0 + data = length 6061, hash 736C72B2 + sample 8: + time = 266933 + flags = 0 + data = length 992, hash FE132F23 + sample 9: + time = 233566 + flags = 0 + data = length 623, hash 5B2C1816 + sample 10: + time = 300300 + flags = 0 + data = length 421, hash 742E69C1 + sample 11: + time = 433766 + flags = 0 + data = length 4899, hash F72F86A1 + sample 12: + time = 400400 + flags = 0 + data = length 568, hash 519A8E50 + sample 13: + time = 367033 + flags = 0 + data = length 620, hash 3990AA39 + sample 14: + time = 567233 + flags = 0 + data = length 5450, hash F06EC4AA + sample 15: + time = 500500 + flags = 0 + data = length 1051, hash 92DFA63A + sample 16: + time = 467133 + flags = 0 + data = length 874, hash 69587FB4 + sample 17: + time = 533866 + flags = 0 + data = length 781, hash 36BE495B + sample 18: + time = 700700 + flags = 0 + data = length 4725, hash AC0C8CD3 + sample 19: + time = 633966 + flags = 0 + data = length 1022, hash 5D8BFF34 + sample 20: + time = 600600 + flags = 0 + data = length 790, hash 99413A99 + sample 21: + time = 667333 + flags = 0 + data = length 610, hash 5E129290 + sample 22: + time = 834166 + flags = 0 + data = length 2751, hash 769974CB + sample 23: + time = 767433 + flags = 0 + data = length 745, hash B78A477A + sample 24: + time = 734066 + flags = 0 + data = length 621, hash CF741E7A + sample 25: + time = 800800 + flags = 0 + data = length 505, hash 1DB4894E + sample 26: + time = 967633 + flags = 0 + data = length 1268, hash C15348DC + sample 27: + time = 900900 + flags = 0 + data = length 880, hash C2DE85D0 + sample 28: + time = 867533 + flags = 0 + data = length 530, hash C98BC6A8 + sample 29: + time = 934266 + flags = 536870912 + data = length 568, hash 4FE5C8EA +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.1.dump new file mode 100644 index 0000000000..30748b0761 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.1.dump @@ -0,0 +1,148 @@ +seekMap: + isSeekable = true + duration = 1001000 + getPosition(0) = [[timeUs=0, position=1160]] + getPosition(1) = [[timeUs=0, position=1160]] + getPosition(500500) = [[timeUs=0, position=1160]] + getPosition(1001000) = [[timeUs=0, position=1160]] +numberOfTracks = 1 +track 0: + total output bytes = 89876 + sample count = 30 + format 0: + id = 1 + sampleMimeType = video/avc + codecs = avc1.64001F + maxInputSize = 36722 + width = 1080 + height = 720 + frameRate = 29.970028 + colorInfo: + colorSpace = 1 + colorRange = 2 + colorTransfer = 3 + hdrStaticInfo = length 0, hash 0 + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + sample 0: + time = 0 + flags = 1 + data = length 36692, hash D216076E + sample 1: + time = 66733 + flags = 0 + data = length 5312, hash D45D3CA0 + sample 2: + time = 33366 + flags = 0 + data = length 599, hash 1BE7812D + sample 3: + time = 200200 + flags = 0 + data = length 7735, hash 4490F110 + sample 4: + time = 133466 + flags = 0 + data = length 987, hash 560B5036 + sample 5: + time = 100100 + flags = 0 + data = length 673, hash ED7CD8C7 + sample 6: + time = 166833 + flags = 0 + data = length 523, hash 3020DF50 + sample 7: + time = 333666 + flags = 0 + data = length 6061, hash 736C72B2 + sample 8: + time = 266933 + flags = 0 + data = length 992, hash FE132F23 + sample 9: + time = 233566 + flags = 0 + data = length 623, hash 5B2C1816 + sample 10: + time = 300300 + flags = 0 + data = length 421, hash 742E69C1 + sample 11: + time = 433766 + flags = 0 + data = length 4899, hash F72F86A1 + sample 12: + time = 400400 + flags = 0 + data = length 568, hash 519A8E50 + sample 13: + time = 367033 + flags = 0 + data = length 620, hash 3990AA39 + sample 14: + time = 567233 + flags = 0 + data = length 5450, hash F06EC4AA + sample 15: + time = 500500 + flags = 0 + data = length 1051, hash 92DFA63A + sample 16: + time = 467133 + flags = 0 + data = length 874, hash 69587FB4 + sample 17: + time = 533866 + flags = 0 + data = length 781, hash 36BE495B + sample 18: + time = 700700 + flags = 0 + data = length 4725, hash AC0C8CD3 + sample 19: + time = 633966 + flags = 0 + data = length 1022, hash 5D8BFF34 + sample 20: + time = 600600 + flags = 0 + data = length 790, hash 99413A99 + sample 21: + time = 667333 + flags = 0 + data = length 610, hash 5E129290 + sample 22: + time = 834166 + flags = 0 + data = length 2751, hash 769974CB + sample 23: + time = 767433 + flags = 0 + data = length 745, hash B78A477A + sample 24: + time = 734066 + flags = 0 + data = length 621, hash CF741E7A + sample 25: + time = 800800 + flags = 0 + data = length 505, hash 1DB4894E + sample 26: + time = 967633 + flags = 0 + data = length 1268, hash C15348DC + sample 27: + time = 900900 + flags = 0 + data = length 880, hash C2DE85D0 + sample 28: + time = 867533 + flags = 0 + data = length 530, hash C98BC6A8 + sample 29: + time = 934266 + flags = 536870912 + data = length 568, hash 4FE5C8EA +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.2.dump new file mode 100644 index 0000000000..30748b0761 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.2.dump @@ -0,0 +1,148 @@ +seekMap: + isSeekable = true + duration = 1001000 + getPosition(0) = [[timeUs=0, position=1160]] + getPosition(1) = [[timeUs=0, position=1160]] + getPosition(500500) = [[timeUs=0, position=1160]] + getPosition(1001000) = [[timeUs=0, position=1160]] +numberOfTracks = 1 +track 0: + total output bytes = 89876 + sample count = 30 + format 0: + id = 1 + sampleMimeType = video/avc + codecs = avc1.64001F + maxInputSize = 36722 + width = 1080 + height = 720 + frameRate = 29.970028 + colorInfo: + colorSpace = 1 + colorRange = 2 + colorTransfer = 3 + hdrStaticInfo = length 0, hash 0 + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + sample 0: + time = 0 + flags = 1 + data = length 36692, hash D216076E + sample 1: + time = 66733 + flags = 0 + data = length 5312, hash D45D3CA0 + sample 2: + time = 33366 + flags = 0 + data = length 599, hash 1BE7812D + sample 3: + time = 200200 + flags = 0 + data = length 7735, hash 4490F110 + sample 4: + time = 133466 + flags = 0 + data = length 987, hash 560B5036 + sample 5: + time = 100100 + flags = 0 + data = length 673, hash ED7CD8C7 + sample 6: + time = 166833 + flags = 0 + data = length 523, hash 3020DF50 + sample 7: + time = 333666 + flags = 0 + data = length 6061, hash 736C72B2 + sample 8: + time = 266933 + flags = 0 + data = length 992, hash FE132F23 + sample 9: + time = 233566 + flags = 0 + data = length 623, hash 5B2C1816 + sample 10: + time = 300300 + flags = 0 + data = length 421, hash 742E69C1 + sample 11: + time = 433766 + flags = 0 + data = length 4899, hash F72F86A1 + sample 12: + time = 400400 + flags = 0 + data = length 568, hash 519A8E50 + sample 13: + time = 367033 + flags = 0 + data = length 620, hash 3990AA39 + sample 14: + time = 567233 + flags = 0 + data = length 5450, hash F06EC4AA + sample 15: + time = 500500 + flags = 0 + data = length 1051, hash 92DFA63A + sample 16: + time = 467133 + flags = 0 + data = length 874, hash 69587FB4 + sample 17: + time = 533866 + flags = 0 + data = length 781, hash 36BE495B + sample 18: + time = 700700 + flags = 0 + data = length 4725, hash AC0C8CD3 + sample 19: + time = 633966 + flags = 0 + data = length 1022, hash 5D8BFF34 + sample 20: + time = 600600 + flags = 0 + data = length 790, hash 99413A99 + sample 21: + time = 667333 + flags = 0 + data = length 610, hash 5E129290 + sample 22: + time = 834166 + flags = 0 + data = length 2751, hash 769974CB + sample 23: + time = 767433 + flags = 0 + data = length 745, hash B78A477A + sample 24: + time = 734066 + flags = 0 + data = length 621, hash CF741E7A + sample 25: + time = 800800 + flags = 0 + data = length 505, hash 1DB4894E + sample 26: + time = 967633 + flags = 0 + data = length 1268, hash C15348DC + sample 27: + time = 900900 + flags = 0 + data = length 880, hash C2DE85D0 + sample 28: + time = 867533 + flags = 0 + data = length 530, hash C98BC6A8 + sample 29: + time = 934266 + flags = 536870912 + data = length 568, hash 4FE5C8EA +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.3.dump new file mode 100644 index 0000000000..30748b0761 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.3.dump @@ -0,0 +1,148 @@ +seekMap: + isSeekable = true + duration = 1001000 + getPosition(0) = [[timeUs=0, position=1160]] + getPosition(1) = [[timeUs=0, position=1160]] + getPosition(500500) = [[timeUs=0, position=1160]] + getPosition(1001000) = [[timeUs=0, position=1160]] +numberOfTracks = 1 +track 0: + total output bytes = 89876 + sample count = 30 + format 0: + id = 1 + sampleMimeType = video/avc + codecs = avc1.64001F + maxInputSize = 36722 + width = 1080 + height = 720 + frameRate = 29.970028 + colorInfo: + colorSpace = 1 + colorRange = 2 + colorTransfer = 3 + hdrStaticInfo = length 0, hash 0 + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + sample 0: + time = 0 + flags = 1 + data = length 36692, hash D216076E + sample 1: + time = 66733 + flags = 0 + data = length 5312, hash D45D3CA0 + sample 2: + time = 33366 + flags = 0 + data = length 599, hash 1BE7812D + sample 3: + time = 200200 + flags = 0 + data = length 7735, hash 4490F110 + sample 4: + time = 133466 + flags = 0 + data = length 987, hash 560B5036 + sample 5: + time = 100100 + flags = 0 + data = length 673, hash ED7CD8C7 + sample 6: + time = 166833 + flags = 0 + data = length 523, hash 3020DF50 + sample 7: + time = 333666 + flags = 0 + data = length 6061, hash 736C72B2 + sample 8: + time = 266933 + flags = 0 + data = length 992, hash FE132F23 + sample 9: + time = 233566 + flags = 0 + data = length 623, hash 5B2C1816 + sample 10: + time = 300300 + flags = 0 + data = length 421, hash 742E69C1 + sample 11: + time = 433766 + flags = 0 + data = length 4899, hash F72F86A1 + sample 12: + time = 400400 + flags = 0 + data = length 568, hash 519A8E50 + sample 13: + time = 367033 + flags = 0 + data = length 620, hash 3990AA39 + sample 14: + time = 567233 + flags = 0 + data = length 5450, hash F06EC4AA + sample 15: + time = 500500 + flags = 0 + data = length 1051, hash 92DFA63A + sample 16: + time = 467133 + flags = 0 + data = length 874, hash 69587FB4 + sample 17: + time = 533866 + flags = 0 + data = length 781, hash 36BE495B + sample 18: + time = 700700 + flags = 0 + data = length 4725, hash AC0C8CD3 + sample 19: + time = 633966 + flags = 0 + data = length 1022, hash 5D8BFF34 + sample 20: + time = 600600 + flags = 0 + data = length 790, hash 99413A99 + sample 21: + time = 667333 + flags = 0 + data = length 610, hash 5E129290 + sample 22: + time = 834166 + flags = 0 + data = length 2751, hash 769974CB + sample 23: + time = 767433 + flags = 0 + data = length 745, hash B78A477A + sample 24: + time = 734066 + flags = 0 + data = length 621, hash CF741E7A + sample 25: + time = 800800 + flags = 0 + data = length 505, hash 1DB4894E + sample 26: + time = 967633 + flags = 0 + data = length 1268, hash C15348DC + sample 27: + time = 900900 + flags = 0 + data = length 880, hash C2DE85D0 + sample 28: + time = 867533 + flags = 0 + data = length 530, hash C98BC6A8 + sample 29: + time = 934266 + flags = 536870912 + data = length 568, hash 4FE5C8EA +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.unknown_length.dump new file mode 100644 index 0000000000..30748b0761 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_18byte_nclx_colr.mp4.unknown_length.dump @@ -0,0 +1,148 @@ +seekMap: + isSeekable = true + duration = 1001000 + getPosition(0) = [[timeUs=0, position=1160]] + getPosition(1) = [[timeUs=0, position=1160]] + getPosition(500500) = [[timeUs=0, position=1160]] + getPosition(1001000) = [[timeUs=0, position=1160]] +numberOfTracks = 1 +track 0: + total output bytes = 89876 + sample count = 30 + format 0: + id = 1 + sampleMimeType = video/avc + codecs = avc1.64001F + maxInputSize = 36722 + width = 1080 + height = 720 + frameRate = 29.970028 + colorInfo: + colorSpace = 1 + colorRange = 2 + colorTransfer = 3 + hdrStaticInfo = length 0, hash 0 + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + sample 0: + time = 0 + flags = 1 + data = length 36692, hash D216076E + sample 1: + time = 66733 + flags = 0 + data = length 5312, hash D45D3CA0 + sample 2: + time = 33366 + flags = 0 + data = length 599, hash 1BE7812D + sample 3: + time = 200200 + flags = 0 + data = length 7735, hash 4490F110 + sample 4: + time = 133466 + flags = 0 + data = length 987, hash 560B5036 + sample 5: + time = 100100 + flags = 0 + data = length 673, hash ED7CD8C7 + sample 6: + time = 166833 + flags = 0 + data = length 523, hash 3020DF50 + sample 7: + time = 333666 + flags = 0 + data = length 6061, hash 736C72B2 + sample 8: + time = 266933 + flags = 0 + data = length 992, hash FE132F23 + sample 9: + time = 233566 + flags = 0 + data = length 623, hash 5B2C1816 + sample 10: + time = 300300 + flags = 0 + data = length 421, hash 742E69C1 + sample 11: + time = 433766 + flags = 0 + data = length 4899, hash F72F86A1 + sample 12: + time = 400400 + flags = 0 + data = length 568, hash 519A8E50 + sample 13: + time = 367033 + flags = 0 + data = length 620, hash 3990AA39 + sample 14: + time = 567233 + flags = 0 + data = length 5450, hash F06EC4AA + sample 15: + time = 500500 + flags = 0 + data = length 1051, hash 92DFA63A + sample 16: + time = 467133 + flags = 0 + data = length 874, hash 69587FB4 + sample 17: + time = 533866 + flags = 0 + data = length 781, hash 36BE495B + sample 18: + time = 700700 + flags = 0 + data = length 4725, hash AC0C8CD3 + sample 19: + time = 633966 + flags = 0 + data = length 1022, hash 5D8BFF34 + sample 20: + time = 600600 + flags = 0 + data = length 790, hash 99413A99 + sample 21: + time = 667333 + flags = 0 + data = length 610, hash 5E129290 + sample 22: + time = 834166 + flags = 0 + data = length 2751, hash 769974CB + sample 23: + time = 767433 + flags = 0 + data = length 745, hash B78A477A + sample 24: + time = 734066 + flags = 0 + data = length 621, hash CF741E7A + sample 25: + time = 800800 + flags = 0 + data = length 505, hash 1DB4894E + sample 26: + time = 967633 + flags = 0 + data = length 1268, hash C15348DC + sample 27: + time = 900900 + flags = 0 + data = length 880, hash C2DE85D0 + sample 28: + time = 867533 + flags = 0 + data = length 530, hash C98BC6A8 + sample 29: + time = 934266 + flags = 536870912 + data = length 568, hash 4FE5C8EA +tracksEnded = true diff --git a/testdata/src/test/assets/media/mp4/sample_18byte_nclx_colr.mp4 b/testdata/src/test/assets/media/mp4/sample_18byte_nclx_colr.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1a2afa2c406af0e398fa1c7ea3d22ac8768753a2 GIT binary patch literal 91100 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 RF)lDJF)L(db7wIvH2^n2*^dAK literal 0 HcmV?d00001 From 9c9671a0af86a21b58c42e2e679aead2dcb90cd0 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 27 Oct 2021 13:08:18 +0100 Subject: [PATCH 031/113] Remove all references to @NonNull Our package-info.java files are annotated with @NonNullApi which results in everything being non-null by default, so this annotation is never needed. #minor-release PiperOrigin-RevId: 405864737 --- .../android/exoplayer2/castdemo/MainActivity.java | 11 +++-------- .../android/exoplayer2/castdemo/PlayerManager.java | 3 +-- .../gldemo/VideoProcessingGLSurfaceView.java | 3 +-- .../android/exoplayer2/demo/DemoDownloadService.java | 5 +---- .../android/exoplayer2/demo/DownloadTracker.java | 12 ++++-------- .../android/exoplayer2/demo/PlayerActivity.java | 10 ++++------ .../exoplayer2/demo/SampleChooserActivity.java | 3 +-- .../exoplayer2/demo/TrackSelectionDialog.java | 8 ++------ .../exoplayer2/ext/media2/SessionCallback.java | 10 +++------- .../google/android/exoplayer2/util/ListenerSet.java | 10 +++++----- .../android/exoplayer2/audio/DefaultAudioSink.java | 3 +-- .../mediacodec/AsynchronousMediaCodecCallback.java | 10 ++++------ .../hls/MediaParserHlsMediaChunkExtractor.java | 3 +-- .../android/exoplayer2/testutil/FakeClockTest.java | 3 +-- 14 files changed, 32 insertions(+), 62 deletions(-) diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java index 478f11d74f..ef91f29e48 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java @@ -27,7 +27,6 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -199,7 +198,6 @@ public class MainActivity extends AppCompatActivity private class MediaQueueListAdapter extends RecyclerView.Adapter { @Override - @NonNull public QueueItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { TextView v = (TextView) @@ -240,9 +238,7 @@ public class MainActivity extends AppCompatActivity @Override public boolean onMove( - @NonNull RecyclerView list, - RecyclerView.ViewHolder origin, - RecyclerView.ViewHolder target) { + RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) { int fromPosition = origin.getAdapterPosition(); int toPosition = target.getAdapterPosition(); if (draggingFromPosition == C.INDEX_UNSET) { @@ -266,7 +262,7 @@ public class MainActivity extends AppCompatActivity } @Override - public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) { + public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); if (draggingFromPosition != C.INDEX_UNSET) { QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder; @@ -306,8 +302,7 @@ public class MainActivity extends AppCompatActivity } @Override - @NonNull - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + public View getView(int position, @Nullable View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); ((TextView) view).setText(Util.castNonNull(getItem(position)).mediaMetadata.title); return view; diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index 0c9406227d..9e66c823a0 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.castdemo; import android.content.Context; import android.view.KeyEvent; import android.view.View; -import androidx.annotation.NonNull; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; @@ -226,7 +225,7 @@ import java.util.ArrayList; } @Override - public void onTimelineChanged(@NonNull Timeline timeline, @TimelineChangeReason int reason) { + public void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) { updateCurrentItemIndex(); } diff --git a/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/VideoProcessingGLSurfaceView.java b/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/VideoProcessingGLSurfaceView.java index 4c04ca4c70..4cc0813dab 100644 --- a/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/VideoProcessingGLSurfaceView.java +++ b/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/VideoProcessingGLSurfaceView.java @@ -23,7 +23,6 @@ import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.os.Handler; import android.view.Surface; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; @@ -290,7 +289,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { public void onVideoFrameAboutToBeRendered( long presentationTimeUs, long releaseTimeNs, - @NonNull Format format, + Format format, @Nullable MediaFormat mediaFormat) { sampleTimestampQueue.add(releaseTimeNs, presentationTimeUs); } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index e32f72f39f..a83992d9b7 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.demo.DemoUtil.DOWNLOAD_NOTIFICATION_ import android.app.Notification; import android.content.Context; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.exoplayer2.offline.Download; import com.google.android.exoplayer2.offline.DownloadManager; @@ -48,7 +47,6 @@ public class DemoDownloadService extends DownloadService { } @Override - @NonNull protected DownloadManager getDownloadManager() { // This will only happen once, because getDownloadManager is guaranteed to be called only once // in the life cycle of the process. @@ -67,9 +65,8 @@ public class DemoDownloadService extends DownloadService { } @Override - @NonNull protected Notification getForegroundNotification( - @NonNull List downloads, @Requirements.RequirementFlags int notMetRequirements) { + List downloads, @Requirements.RequirementFlags int notMetRequirements) { return DemoUtil.getDownloadNotificationHelper(/* context= */ this) .buildProgressNotification( /* context= */ this, diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 2bbf99b03c..9eb141e659 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -23,7 +23,6 @@ import android.content.DialogInterface; import android.net.Uri; import android.os.AsyncTask; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.fragment.app.FragmentManager; @@ -142,9 +141,7 @@ public class DownloadTracker { @Override public void onDownloadChanged( - @NonNull DownloadManager downloadManager, - @NonNull Download download, - @Nullable Exception finalException) { + DownloadManager downloadManager, Download download, @Nullable Exception finalException) { downloads.put(download.request.uri, download); for (Listener listener : listeners) { listener.onDownloadsChanged(); @@ -152,8 +149,7 @@ public class DownloadTracker { } @Override - public void onDownloadRemoved( - @NonNull DownloadManager downloadManager, @NonNull Download download) { + public void onDownloadRemoved(DownloadManager downloadManager, Download download) { downloads.remove(download.request.uri); for (Listener listener : listeners) { listener.onDownloadsChanged(); @@ -196,7 +192,7 @@ public class DownloadTracker { // DownloadHelper.Callback implementation. @Override - public void onPrepared(@NonNull DownloadHelper helper) { + public void onPrepared(DownloadHelper helper) { @Nullable Format format = getFirstFormatWithDrmInitData(helper); if (format == null) { onDownloadPrepared(helper); @@ -231,7 +227,7 @@ public class DownloadTracker { } @Override - public void onPrepareError(@NonNull DownloadHelper helper, @NonNull IOException e) { + public void onPrepareError(DownloadHelper helper, IOException e) { boolean isLiveContent = e instanceof LiveContentUnsupportedException; int toastStringId = isLiveContent ? R.string.download_live_unsupported : R.string.download_start_error; diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index b5a1eefdfd..e080c23f99 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -28,7 +28,6 @@ import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.C; @@ -185,7 +184,7 @@ public class PlayerActivity extends AppCompatActivity @Override public void onRequestPermissionsResult( - int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length == 0) { // Empty results are triggered if a permission is requested while another request was already @@ -201,7 +200,7 @@ public class PlayerActivity extends AppCompatActivity } @Override - public void onSaveInstanceState(@NonNull Bundle outState) { + public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); updateTrackSelectorParameters(); updateStartPosition(); @@ -424,7 +423,7 @@ public class PlayerActivity extends AppCompatActivity } @Override - public void onPlayerError(@NonNull PlaybackException error) { + public void onPlayerError(PlaybackException error) { if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { player.seekToDefaultPosition(); player.prepare(); @@ -454,8 +453,7 @@ public class PlayerActivity extends AppCompatActivity private class PlayerErrorMessageProvider implements ErrorMessageProvider { @Override - @NonNull - public Pair getErrorMessage(@NonNull PlaybackException e) { + public Pair getErrorMessage(PlaybackException e) { String errorString = getString(R.string.error_generic); Throwable cause = e.getCause(); if (cause instanceof DecoderInitializationException) { diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 70a215ee8f..6cfe215a78 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -40,7 +40,6 @@ import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.MediaItem; @@ -163,7 +162,7 @@ public class SampleChooserActivity extends AppCompatActivity @Override public void onRequestPermissionsResult( - int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length == 0) { // Empty results are triggered if a permission is requested while another request was already diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index 5b50298df0..00d90bbcf5 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -24,7 +24,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatDialog; import androidx.fragment.app.DialogFragment; @@ -213,7 +212,6 @@ public final class TrackSelectionDialog extends DialogFragment { } @Override - @NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { // We need to own the view to let tab layout work correctly on all API levels. We can't use // AlertDialog because it owns the view itself, so we use AppCompatDialog instead, themed using @@ -225,7 +223,7 @@ public final class TrackSelectionDialog extends DialogFragment { } @Override - public void onDismiss(@NonNull DialogInterface dialog) { + public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); onDismissListener.onDismiss(dialog); } @@ -290,7 +288,6 @@ public final class TrackSelectionDialog extends DialogFragment { } @Override - @NonNull public Fragment getItem(int position) { return tabFragments.valueAt(position); } @@ -364,8 +361,7 @@ public final class TrackSelectionDialog extends DialogFragment { } @Override - public void onTrackSelectionChanged( - boolean isDisabled, @NonNull List overrides) { + public void onTrackSelectionChanged(boolean isDisabled, List overrides) { this.isDisabled = isDisabled; this.overrides = overrides; } diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java index c718efa85d..a682d255a8 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ext.media2; import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.os.Bundle; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media2.common.MediaItem; import androidx.media2.common.MediaMetadata; @@ -106,8 +105,7 @@ import java.util.concurrent.TimeoutException; } @Override - public void onPostConnect( - @NonNull MediaSession session, @NonNull MediaSession.ControllerInfo controller) { + public void onPostConnect(MediaSession session, MediaSession.ControllerInfo controller) { if (postConnectCallback != null) { postConnectCallback.onPostConnect(session, controller); } @@ -175,8 +173,7 @@ import java.util.concurrent.TimeoutException; } @Override - public int onSkipBackward( - @NonNull MediaSession session, @NonNull MediaSession.ControllerInfo controller) { + public int onSkipBackward(MediaSession session, MediaSession.ControllerInfo controller) { if (skipCallback != null) { return skipCallback.onSkipBackward(session, controller); } @@ -184,8 +181,7 @@ import java.util.concurrent.TimeoutException; } @Override - public int onSkipForward( - @NonNull MediaSession session, @NonNull MediaSession.ControllerInfo controller) { + public int onSkipForward(MediaSession session, MediaSession.ControllerInfo controller) { if (skipCallback != null) { return skipCallback.onSkipForward(session, controller); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java index 7f2ce9759a..8aa4025bca 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java @@ -22,7 +22,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import java.util.ArrayDeque; import java.util.concurrent.CopyOnWriteArraySet; -import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.qual.NonNull; /** * A set of listeners. @@ -35,7 +35,7 @@ import javax.annotation.Nonnull; * * @param The listener type. */ -public final class ListenerSet { +public final class ListenerSet { /** * An event sent to a listener. @@ -232,15 +232,15 @@ public final class ListenerSet { return true; } - private static final class ListenerHolder { + private static final class ListenerHolder { - @Nonnull public final T listener; + public final T listener; private FlagSet.Builder flagsBuilder; private boolean needsIterationFinishedEvent; private boolean released; - public ListenerHolder(@Nonnull T listener) { + public ListenerHolder(T listener) { this.listener = listener; this.flagsBuilder = new FlagSet.Builder(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 3bfca92b38..5687282fae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -27,7 +27,6 @@ import android.os.Handler; import android.os.SystemClock; import android.util.Pair; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; @@ -1829,7 +1828,7 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public void onTearDown(@NonNull AudioTrack track) { + public void onTearDown(AudioTrack track) { Assertions.checkState(track == audioTrack); if (listener != null && playing) { // The audio track was destroyed while in use. Thus a new AudioTrack needs to be diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java index e4e76ad0b4..b6fe773f4f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java @@ -24,7 +24,6 @@ import android.media.MediaFormat; import android.os.Handler; import android.os.HandlerThread; import androidx.annotation.GuardedBy; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.util.Util; @@ -207,15 +206,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // Called from the callback thread. @Override - public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) { + public void onInputBufferAvailable(MediaCodec codec, int index) { synchronized (lock) { availableInputBuffers.add(index); } } @Override - public void onOutputBufferAvailable( - @NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) { + public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { synchronized (lock) { if (pendingOutputFormat != null) { addOutputFormat(pendingOutputFormat); @@ -227,14 +225,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } @Override - public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) { + public void onError(MediaCodec codec, MediaCodec.CodecException e) { synchronized (lock) { mediaCodecException = e; } } @Override - public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) { + public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { synchronized (lock) { addOutputFormat(format); pendingOutputFormat = null; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java index 893bfc0a32..907d785f26 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java @@ -31,7 +31,6 @@ import android.media.MediaParser; import android.media.MediaParser.OutputConsumer; import android.media.MediaParser.SeekPoint; import android.text.TextUtils; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.Format; @@ -262,7 +261,7 @@ public final class MediaParserHlsMediaChunkExtractor implements HlsMediaChunkExt } @Override - public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException { + public int read(byte[] buffer, int offset, int readLength) throws IOException { int peekedBytes = extractorInput.peek(buffer, offset, readLength); totalPeekedBytes += peekedBytes; return peekedBytes; diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java index bcd6fa902e..7fba60ec24 100644 --- a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java @@ -22,7 +22,6 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.util.HandlerWrapper; @@ -398,7 +397,7 @@ public final class FakeClockTest { } @Override - public boolean handleMessage(@NonNull Message msg) { + public boolean handleMessage(Message msg) { messages.add(new MessageData(msg.what, msg.arg1, msg.arg2, msg.obj)); return true; } From 09c6ccfb66ea4e2d33f3ffaddb15a598889a9782 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 27 Oct 2021 15:00:45 +0100 Subject: [PATCH 032/113] Add missing javadoc to new ExoPlayer.Builder constructors Should have been part of https://github.com/google/ExoPlayer/commit/98200c2692ba007ba0b177d7b285b957dc08ff93 #minor-release PiperOrigin-RevId: 405880982 --- .../google/android/exoplayer2/ExoPlayer.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 635a4051c9..21851f3d5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -457,10 +457,30 @@ public interface ExoPlayer extends Player { new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory())); } + /** + * Creates a builder with a custom {@link MediaSourceFactory}. + * + *

      See {@link #Builder(Context)} for a list of default values. + * + * @param context A {@link Context}. + * @param mediaSourceFactory A factory for creating a {@link MediaSource} from a {@link + * MediaItem}. + */ public Builder(Context context, MediaSourceFactory mediaSourceFactory) { this(context, new DefaultRenderersFactory(context), mediaSourceFactory); } + /** + * Creates a builder with a custom {@link RenderersFactory} and {@link MediaSourceFactory}. + * + *

      See {@link #Builder(Context)} for a list of default values. + * + * @param context A {@link Context}. + * @param renderersFactory A factory for creating {@link Renderer Renderers} to be used by the + * player. + * @param mediaSourceFactory A factory for creating a {@link MediaSource} from a {@link + * MediaItem}. + */ public Builder( Context context, RenderersFactory renderersFactory, MediaSourceFactory mediaSourceFactory) { this( From 3ef7b70c29936acdd1d7da5a779f33f421f591d6 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 27 Oct 2021 15:10:30 +0100 Subject: [PATCH 033/113] Remove IntRange from Player.getMediaItemAt No other index-related methods in Player are annotated, it's considered obvious that these should be >=0. PiperOrigin-RevId: 405882756 --- .../src/main/java/com/google/android/exoplayer2/Player.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7c00e1dfb9..3a14e03d1b 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 @@ -2155,7 +2155,7 @@ public interface Player { int getMediaItemCount(); /** Returns the {@link MediaItem} at the given index. */ - MediaItem getMediaItemAt(@IntRange(from = 0) int index); + MediaItem getMediaItemAt(int index); /** * Returns the duration of the current content or ad in milliseconds, or {@link C#TIME_UNSET} if From 9e8bcc9587c248b2df2598bc7283fa1c13340258 Mon Sep 17 00:00:00 2001 From: hschlueter Date: Wed, 27 Oct 2021 16:23:22 +0100 Subject: [PATCH 034/113] Refactor nullness checks in renderers. `checkNotNull` should be avoided where possible. This change adds `@EnsuresNonNull` or `@EnsuresNonNullIf` to configuration methods for fields they initialize. `checkNotNull` is now avoided for the `@MonotonicNonNull` formats by adding `@RequiresNonNull` annotations. `checkNotNull` is now avoided for the encoder and decoder in `feedMuxerFromEncoder()`, `feedEncoderFromDecoder()`, `feedDecoderFromInput()`, etc. by creating local variables for `encoder` and `decoder` in `render` after the configuration method calls and passing these as non-null parameters. PiperOrigin-RevId: 405893824 --- .../transformer/TransformerAudioRenderer.java | 77 +++++++++-------- .../TransformerTranscodingVideoRenderer.java | 86 +++++++++++-------- 2 files changed, 94 insertions(+), 69 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 9a8da0ecf6..02248296d0 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -36,6 +36,9 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import java.io.IOException; import java.nio.ByteBuffer; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; @RequiresApi(18) /* package */ final class TransformerAudioRenderer extends TransformerBaseRenderer { @@ -51,8 +54,8 @@ import java.nio.ByteBuffer; @Nullable private MediaCodecAdapterWrapper decoder; @Nullable private MediaCodecAdapterWrapper encoder; @Nullable private SpeedProvider speedProvider; - @Nullable private Format decoderInputFormat; - @Nullable private AudioFormat encoderInputAudioFormat; + private @MonotonicNonNull Format decoderInputFormat; + private @MonotonicNonNull AudioFormat encoderInputAudioFormat; private ByteBuffer sonicOutputBuffer; private long nextEncoderInputBufferTimeUs; @@ -100,8 +103,6 @@ import java.nio.ByteBuffer; encoder = null; } speedProvider = null; - decoderInputFormat = null; - encoderInputAudioFormat = null; sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER; nextEncoderInputBufferTimeUs = 0; currentSpeed = SPEED_UNSET; @@ -117,16 +118,18 @@ import java.nio.ByteBuffer; } if (ensureDecoderConfigured()) { + MediaCodecAdapterWrapper decoder = this.decoder; if (ensureEncoderAndAudioProcessingConfigured()) { - while (feedMuxerFromEncoder()) {} + MediaCodecAdapterWrapper encoder = this.encoder; + while (feedMuxerFromEncoder(encoder)) {} if (sonicAudioProcessor.isActive()) { - while (feedEncoderFromSonic()) {} - while (feedSonicFromDecoder()) {} + while (feedEncoderFromSonic(decoder, encoder)) {} + while (feedSonicFromDecoder(decoder)) {} } else { - while (feedEncoderFromDecoder()) {} + while (feedEncoderFromDecoder(decoder, encoder)) {} } } - while (feedDecoderFromInput()) {} + while (feedDecoderFromInput(decoder)) {} } } @@ -134,8 +137,7 @@ import java.nio.ByteBuffer; * Attempts to write encoder output data to the muxer, and returns whether it may be possible to * write more data immediately by calling this method again. */ - private boolean feedMuxerFromEncoder() { - MediaCodecAdapterWrapper encoder = checkNotNull(this.encoder); + private boolean feedMuxerFromEncoder(MediaCodecAdapterWrapper encoder) { if (!hasEncoderOutputFormat) { @Nullable Format encoderOutputFormat = encoder.getOutputFormat(); if (encoderOutputFormat == null) { @@ -170,15 +172,15 @@ import java.nio.ByteBuffer; * Attempts to pass decoder output data to the encoder, and returns whether it may be possible to * pass more data immediately by calling this method again. */ - private boolean feedEncoderFromDecoder() { - MediaCodecAdapterWrapper decoder = checkNotNull(this.decoder); - MediaCodecAdapterWrapper encoder = checkNotNull(this.encoder); + @RequiresNonNull({"encoderInputAudioFormat"}) + private boolean feedEncoderFromDecoder( + MediaCodecAdapterWrapper decoder, MediaCodecAdapterWrapper encoder) { if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) { return false; } if (decoder.isEnded()) { - queueEndOfStreamToEncoder(); + queueEndOfStreamToEncoder(encoder); return false; } @@ -190,7 +192,7 @@ import java.nio.ByteBuffer; flushSonicAndSetSpeed(currentSpeed); return false; } - feedEncoder(decoderOutputBuffer); + feedEncoder(encoder, decoderOutputBuffer); if (!decoderOutputBuffer.hasRemaining()) { decoder.releaseOutputBuffer(); } @@ -201,8 +203,9 @@ import java.nio.ByteBuffer; * Attempts to pass audio processor output data to the encoder, and returns whether it may be * possible to pass more data immediately by calling this method again. */ - private boolean feedEncoderFromSonic() { - MediaCodecAdapterWrapper encoder = checkNotNull(this.encoder); + @RequiresNonNull({"encoderInputAudioFormat"}) + private boolean feedEncoderFromSonic( + MediaCodecAdapterWrapper decoder, MediaCodecAdapterWrapper encoder) { if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) { return false; } @@ -210,14 +213,14 @@ import java.nio.ByteBuffer; if (!sonicOutputBuffer.hasRemaining()) { sonicOutputBuffer = sonicAudioProcessor.getOutput(); if (!sonicOutputBuffer.hasRemaining()) { - if (checkNotNull(decoder).isEnded() && sonicAudioProcessor.isEnded()) { - queueEndOfStreamToEncoder(); + if (decoder.isEnded() && sonicAudioProcessor.isEnded()) { + queueEndOfStreamToEncoder(encoder); } return false; } } - feedEncoder(sonicOutputBuffer); + feedEncoder(encoder, sonicOutputBuffer); return true; } @@ -225,9 +228,7 @@ import java.nio.ByteBuffer; * Attempts to process decoder output data, and returns whether it may be possible to process more * data immediately by calling this method again. */ - private boolean feedSonicFromDecoder() { - MediaCodecAdapterWrapper decoder = checkNotNull(this.decoder); - + private boolean feedSonicFromDecoder(MediaCodecAdapterWrapper decoder) { if (drainingSonicForSpeedChange) { if (sonicAudioProcessor.isEnded() && !sonicOutputBuffer.hasRemaining()) { flushSonicAndSetSpeed(currentSpeed); @@ -268,8 +269,7 @@ import java.nio.ByteBuffer; * Attempts to pass input data to the decoder, and returns whether it may be possible to pass more * data immediately by calling this method again. */ - private boolean feedDecoderFromInput() { - MediaCodecAdapterWrapper decoder = checkNotNull(this.decoder); + private boolean feedDecoderFromInput(MediaCodecAdapterWrapper decoder) { if (!decoder.maybeDequeueInputBuffer(decoderInputBuffer)) { return false; } @@ -295,9 +295,8 @@ import java.nio.ByteBuffer; * Feeds as much data as possible between the current position and limit of the specified {@link * ByteBuffer} to the encoder, and advances its position by the number of bytes fed. */ - private void feedEncoder(ByteBuffer inputBuffer) { - AudioFormat encoderInputAudioFormat = checkNotNull(this.encoderInputAudioFormat); - MediaCodecAdapterWrapper encoder = checkNotNull(this.encoder); + @RequiresNonNull({"encoderInputAudioFormat"}) + private void feedEncoder(MediaCodecAdapterWrapper encoder, ByteBuffer inputBuffer) { ByteBuffer encoderInputBufferData = checkNotNull(encoderInputBuffer.data); int bufferLimit = inputBuffer.limit(); inputBuffer.limit(min(bufferLimit, inputBuffer.position() + encoderInputBufferData.capacity())); @@ -314,8 +313,7 @@ import java.nio.ByteBuffer; encoder.queueInputBuffer(encoderInputBuffer); } - private void queueEndOfStreamToEncoder() { - MediaCodecAdapterWrapper encoder = checkNotNull(this.encoder); + private void queueEndOfStreamToEncoder(MediaCodecAdapterWrapper encoder) { checkState(checkNotNull(encoderInputBuffer.data).position() == 0); encoderInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); encoderInputBuffer.flip(); @@ -327,11 +325,15 @@ import java.nio.ByteBuffer; * Attempts to configure the {@link #encoder} and Sonic (if applicable), if they have not been * configured yet, and returns whether they have been configured. */ + @RequiresNonNull({"decoder", "decoderInputFormat"}) + @EnsuresNonNullIf( + expression = {"encoder", "encoderInputAudioFormat"}, + result = true) private boolean ensureEncoderAndAudioProcessingConfigured() throws ExoPlaybackException { - if (encoder != null) { + if (encoder != null && encoderInputAudioFormat != null) { return true; } - MediaCodecAdapterWrapper decoder = checkNotNull(this.decoder); + MediaCodecAdapterWrapper decoder = this.decoder; @Nullable Format decoderOutputFormat = decoder.getOutputFormat(); if (decoderOutputFormat == null) { return false; @@ -352,7 +354,7 @@ import java.nio.ByteBuffer; } String audioMimeType = transformation.audioMimeType == null - ? checkNotNull(decoderInputFormat).sampleMimeType + ? decoderInputFormat.sampleMimeType : transformation.audioMimeType; try { encoder = @@ -375,8 +377,11 @@ import java.nio.ByteBuffer; * Attempts to configure the {@link #decoder} if it has not been configured yet, and returns * whether the decoder has been configured. */ + @EnsuresNonNullIf( + expression = {"decoderInputFormat", "decoder"}, + result = true) private boolean ensureDecoderConfigured() throws ExoPlaybackException { - if (decoder != null) { + if (decoder != null && decoderInputFormat != null) { return true; } @@ -386,6 +391,7 @@ import java.nio.ByteBuffer; return false; } decoderInputFormat = checkNotNull(formatHolder.format); + MediaCodecAdapterWrapper decoder; try { decoder = MediaCodecAdapterWrapper.createForAudioDecoding(decoderInputFormat); } catch (IOException e) { @@ -394,6 +400,7 @@ import java.nio.ByteBuffer; } speedProvider = new SegmentSpeedProvider(decoderInputFormat); currentSpeed = speedProvider.getSpeed(0); + this.decoder = decoder; return true; } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index 8c1e11df61..90ce3a8071 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -42,7 +42,10 @@ import com.google.android.exoplayer2.util.GlUtil; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.nio.ByteBuffer; +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; @RequiresApi(18) /* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer { @@ -101,14 +104,26 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return; } ensureEncoderConfigured(); + MediaCodecAdapterWrapper encoder = this.encoder; ensureOpenGlConfigured(); + EGLDisplay eglDisplay = this.eglDisplay; + EGLSurface eglSurface = this.eglSurface; + GlUtil.Uniform decoderTextureTransformUniform = this.decoderTextureTransformUniform; if (!ensureDecoderConfigured()) { return; } + MediaCodecAdapterWrapper decoder = this.decoder; + SurfaceTexture decoderSurfaceTexture = this.decoderSurfaceTexture; - while (feedMuxerFromEncoder()) {} - while (feedEncoderFromDecoder()) {} - while (feedDecoderFromInput()) {} + while (feedMuxerFromEncoder(encoder)) {} + while (feedEncoderFromDecoder( + decoder, + encoder, + decoderSurfaceTexture, + eglDisplay, + eglSurface, + decoderTextureTransformUniform)) {} + while (feedDecoderFromInput(decoder)) {} } @Override @@ -150,6 +165,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; muxerWrapperTrackEnded = false; } + @EnsuresNonNullIf(expression = "decoderInputFormat", result = true) private boolean ensureInputFormatRead() { if (decoderInputFormat != null) { return true; @@ -166,6 +182,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return true; } + @RequiresNonNull({"decoderInputFormat"}) + @EnsuresNonNull({"encoder"}) private void ensureEncoderConfigured() throws ExoPlaybackException { if (encoder != null) { return; @@ -175,7 +193,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; encoder = MediaCodecAdapterWrapper.createForVideoEncoding( new Format.Builder() - .setWidth(checkNotNull(decoderInputFormat).width) + .setWidth(decoderInputFormat.width) .setHeight(decoderInputFormat.height) .setSampleMimeType( transformation.videoMimeType != null @@ -186,18 +204,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } catch (IOException e) { throw createRendererException( // TODO(claincly): should be "ENCODER_INIT_FAILED" - e, - checkNotNull(this.decoder).getOutputFormat(), - PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); + e, decoderInputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); } } + @RequiresNonNull({"encoder", "decoderInputFormat"}) + @EnsuresNonNull({"eglDisplay", "eglSurface", "decoderTextureTransformUniform"}) private void ensureOpenGlConfigured() { - if (eglDisplay != null) { + if (eglDisplay != null && eglSurface != null && decoderTextureTransformUniform != null) { return; } - eglDisplay = GlUtil.createEglDisplay(); + MediaCodecAdapterWrapper encoder = this.encoder; + EGLDisplay eglDisplay = GlUtil.createEglDisplay(); EGLContext eglContext; try { eglContext = GlUtil.createEglContext(eglDisplay); @@ -205,14 +224,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } catch (GlUtil.UnsupportedEglVersionException e) { throw new IllegalStateException("EGL version is unsupported", e); } - eglSurface = - GlUtil.getEglSurface(eglDisplay, checkNotNull(checkNotNull(encoder).getInputSurface())); + EGLSurface eglSurface = + GlUtil.getEglSurface(eglDisplay, checkNotNull(encoder.getInputSurface())); GlUtil.focusSurface( - eglDisplay, - eglContext, - eglSurface, - checkNotNull(decoderInputFormat).width, - decoderInputFormat.height); + eglDisplay, eglContext, eglSurface, decoderInputFormat.width, decoderInputFormat.height); decoderTextureId = GlUtil.createExternalTexture(); String vertexShaderCode; String fragmentShaderCode; @@ -262,31 +277,36 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; throw new IllegalStateException("Unexpected uniform name."); } } + checkNotNull(decoderTextureTransformUniform); + this.eglDisplay = eglDisplay; + this.eglSurface = eglSurface; } + @RequiresNonNull({"decoderInputFormat"}) + @EnsuresNonNullIf( + expression = {"decoder", "decoderSurfaceTexture"}, + result = true) private boolean ensureDecoderConfigured() throws ExoPlaybackException { - if (decoder != null) { + if (decoder != null && decoderSurfaceTexture != null) { return true; } checkState(decoderTextureId != GlUtil.TEXTURE_ID_UNSET); - decoderSurfaceTexture = new SurfaceTexture(decoderTextureId); + SurfaceTexture decoderSurfaceTexture = new SurfaceTexture(decoderTextureId); decoderSurfaceTexture.setOnFrameAvailableListener( surfaceTexture -> isDecoderSurfacePopulated = true); decoderSurface = new Surface(decoderSurfaceTexture); try { - decoder = - MediaCodecAdapterWrapper.createForVideoDecoding( - checkNotNull(decoderInputFormat), decoderSurface); + decoder = MediaCodecAdapterWrapper.createForVideoDecoding(decoderInputFormat, decoderSurface); } catch (IOException e) { throw createRendererException( e, decoderInputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); } + this.decoderSurfaceTexture = decoderSurfaceTexture; return true; } - private boolean feedDecoderFromInput() { - MediaCodecAdapterWrapper decoder = checkNotNull(this.decoder); + private boolean feedDecoderFromInput(MediaCodecAdapterWrapper decoder) { if (!decoder.maybeDequeueInputBuffer(decoderInputBuffer)) { return false; } @@ -294,7 +314,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; decoderInputBuffer.clear(); @SampleStream.ReadDataResult int result = readSource(getFormatHolder(), decoderInputBuffer, /* readFlags= */ 0); - switch (result) { case C.RESULT_FORMAT_READ: throw new IllegalStateException("Format changes are not supported."); @@ -310,8 +329,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } } - private boolean feedEncoderFromDecoder() { - MediaCodecAdapterWrapper decoder = checkNotNull(this.decoder); + private boolean feedEncoderFromDecoder( + MediaCodecAdapterWrapper decoder, + MediaCodecAdapterWrapper encoder, + SurfaceTexture decoderSurfaceTexture, + EGLDisplay eglDisplay, + EGLSurface eglSurface, + GlUtil.Uniform decoderTextureTransformUniform) { if (decoder.isEnded()) { return false; } @@ -323,23 +347,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; waitingForPopulatedDecoderSurface = true; } if (decoder.isEnded()) { - checkNotNull(encoder).signalEndOfInputStream(); + encoder.signalEndOfInputStream(); } } return false; } waitingForPopulatedDecoderSurface = false; - SurfaceTexture decoderSurfaceTexture = checkNotNull(this.decoderSurfaceTexture); decoderSurfaceTexture.updateTexImage(); decoderSurfaceTexture.getTransformMatrix(decoderTextureTransformMatrix); - GlUtil.Uniform decoderTextureTransformUniform = - checkNotNull(this.decoderTextureTransformUniform); decoderTextureTransformUniform.setFloats(decoderTextureTransformMatrix); decoderTextureTransformUniform.bind(); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - EGLDisplay eglDisplay = checkNotNull(this.eglDisplay); - EGLSurface eglSurface = checkNotNull(this.eglSurface); long decoderSurfaceTextureTimestampNs = decoderSurfaceTexture.getTimestamp(); EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, decoderSurfaceTextureTimestampNs); EGL14.eglSwapBuffers(eglDisplay, eglSurface); @@ -347,8 +366,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return true; } - private boolean feedMuxerFromEncoder() { - MediaCodecAdapterWrapper encoder = checkNotNull(this.encoder); + private boolean feedMuxerFromEncoder(MediaCodecAdapterWrapper encoder) { if (!hasEncoderActualOutputFormat) { @Nullable Format encoderOutputFormat = encoder.getOutputFormat(); if (encoderOutputFormat == null) { From 6285564904cef410612b75383fc4586573474808 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 27 Oct 2021 18:16:48 +0100 Subject: [PATCH 035/113] Clarify that ExoPlayer.Builder constructor overloads only exist for R8 Also add a setRenderersFactory() method, so that all constructor-provided components can also be passed via setters. This comment already appears on the constructor that takes all components, but it applies to these ones as well. PiperOrigin-RevId: 405917343 --- .../google/android/exoplayer2/ExoPlayer.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 21851f3d5b..11d84fc1d4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -363,10 +363,10 @@ public interface ExoPlayer extends Player { final class Builder { /* package */ final Context context; - /* package */ final RenderersFactory renderersFactory; /* package */ Clock clock; /* package */ long foregroundModeTimeoutMs; + /* package */ RenderersFactory renderersFactory; /* package */ MediaSourceFactory mediaSourceFactory; /* package */ TrackSelector trackSelector; /* package */ LoadControl loadControl; @@ -446,6 +446,9 @@ public interface ExoPlayer extends Player { * *

      See {@link #Builder(Context)} for a list of default values. * + *

      Note that this constructor is only useful to try and ensure that ExoPlayer's {@link + * DefaultRenderersFactory} can be removed by ProGuard or R8. + * * @param context A {@link Context}. * @param renderersFactory A factory for creating {@link Renderer Renderers} to be used by the * player. @@ -462,6 +465,10 @@ public interface ExoPlayer extends Player { * *

      See {@link #Builder(Context)} for a list of default values. * + *

      Note that this constructor is only useful to try and ensure that ExoPlayer's {@link + * DefaultMediaSourceFactory} (and therefore {@link DefaultExtractorsFactory}) can be removed by + * ProGuard or R8. + * * @param context A {@link Context}. * @param mediaSourceFactory A factory for creating a {@link MediaSource} from a {@link * MediaItem}. @@ -475,6 +482,10 @@ public interface ExoPlayer extends Player { * *

      See {@link #Builder(Context)} for a list of default values. * + *

      Note that this constructor is only useful to try and ensure that ExoPlayer's {@link + * DefaultRenderersFactory}, {@link DefaultMediaSourceFactory} (and therefore {@link + * DefaultExtractorsFactory}) can be removed by ProGuard or R8. + * * @param context A {@link Context}. * @param renderersFactory A factory for creating {@link Renderer Renderers} to be used by the * player. @@ -553,6 +564,19 @@ public interface ExoPlayer extends Player { return this; } + /** + * Sets the {@link RenderersFactory} that will be used by the player. + * + * @param renderersFactory A {@link RenderersFactory}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setRenderersFactory(RenderersFactory renderersFactory) { + checkState(!buildCalled); + this.renderersFactory = renderersFactory; + return this; + } + /** * Sets the {@link MediaSourceFactory} that will be used by the player. * From ad39f38995d7bc9bb8588e203e90fbcb5de895fe Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 27 Oct 2021 18:51:03 +0100 Subject: [PATCH 036/113] Update most Player parameter & doc references from Window to MediaItem Only deprecated references remain. Usages of the deprecated methods will be migrated in a follow-up change. #minor-release PiperOrigin-RevId: 405927141 --- .../exoplayer2/ext/cast/CastPlayer.java | 15 ++-- .../google/android/exoplayer2/BasePlayer.java | 4 +- .../android/exoplayer2/ForwardingPlayer.java | 13 ++- .../com/google/android/exoplayer2/Player.java | 85 ++++++++++--------- .../android/exoplayer2/ExoPlayerImpl.java | 16 ++-- .../android/exoplayer2/SimpleExoPlayer.java | 9 +- .../exoplayer2/testutil/StubExoPlayer.java | 5 +- 7 files changed, 74 insertions(+), 73 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 25e792f16b..1bf2cd410b 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -324,10 +324,9 @@ public final class CastPlayer extends BasePlayer { } @Override - public void setMediaItems( - List mediaItems, int startWindowIndex, long startPositionMs) { + public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { setMediaItemsInternal( - toMediaQueueItems(mediaItems), startWindowIndex, startPositionMs, repeatMode.value); + toMediaQueueItems(mediaItems), startIndex, startPositionMs, repeatMode.value); } @Override @@ -438,23 +437,23 @@ public final class CastPlayer extends BasePlayer { // don't implement onPositionDiscontinuity(). @SuppressWarnings("deprecation") @Override - public void seekTo(int windowIndex, long positionMs) { + public void seekTo(int mediaItemIndex, long positionMs) { MediaStatus mediaStatus = getMediaStatus(); // We assume the default position is 0. There is no support for seeking to the default position // in RemoteMediaClient. positionMs = positionMs != C.TIME_UNSET ? positionMs : 0; if (mediaStatus != null) { - if (getCurrentWindowIndex() != windowIndex) { + if (getCurrentWindowIndex() != mediaItemIndex) { remoteMediaClient .queueJumpToItem( - (int) currentTimeline.getPeriod(windowIndex, period).uid, positionMs, null) + (int) currentTimeline.getPeriod(mediaItemIndex, period).uid, positionMs, null) .setResultCallback(seekResultCallback); } else { remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback); } PositionInfo oldPosition = getCurrentPositionInfo(); pendingSeekCount++; - pendingSeekWindowIndex = windowIndex; + pendingSeekWindowIndex = mediaItemIndex; pendingSeekPositionMs = positionMs; PositionInfo newPosition = getCurrentPositionInfo(); listeners.queueEvent( @@ -466,7 +465,7 @@ public final class CastPlayer extends BasePlayer { if (oldPosition.mediaItemIndex != newPosition.mediaItemIndex) { // TODO(internal b/182261884): queue `onMediaItemTransition` event when the media item is // repeated. - MediaItem mediaItem = getCurrentTimeline().getWindow(windowIndex, window).mediaItem; + MediaItem mediaItem = getCurrentTimeline().getWindow(mediaItemIndex, window).mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> diff --git a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java index 06d675d59f..b60e4a3a7e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -122,8 +122,8 @@ public abstract class BasePlayer implements Player { } @Override - public final void seekToDefaultPosition(int windowIndex) { - seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET); + public final void seekToDefaultPosition(int mediaItemIndex) { + seekTo(mediaItemIndex, /* positionMs= */ C.TIME_UNSET); } @Override diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java index 62e113481d..6b2b7426d8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java @@ -69,9 +69,8 @@ public class ForwardingPlayer implements Player { } @Override - public void setMediaItems( - List mediaItems, int startWindowIndex, long startPositionMs) { - player.setMediaItems(mediaItems, startWindowIndex, startPositionMs); + public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { + player.setMediaItems(mediaItems, startIndex, startPositionMs); } @Override @@ -226,8 +225,8 @@ public class ForwardingPlayer implements Player { } @Override - public void seekToDefaultPosition(int windowIndex) { - player.seekToDefaultPosition(windowIndex); + public void seekToDefaultPosition(int mediaItemIndex) { + player.seekToDefaultPosition(mediaItemIndex); } @Override @@ -236,8 +235,8 @@ public class ForwardingPlayer implements Player { } @Override - public void seekTo(int windowIndex, long positionMs) { - player.seekTo(windowIndex, positionMs); + public void seekTo(int mediaItemIndex, long positionMs) { + player.seekTo(mediaItemIndex, positionMs); } @Override 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 3a14e03d1b..5c60097eb0 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 @@ -1507,16 +1507,16 @@ public interface Player { * Clears the playlist and adds the specified {@link MediaItem MediaItems}. * * @param mediaItems The new {@link MediaItem MediaItems}. - * @param startWindowIndex The window index to start playback from. If {@link C#INDEX_UNSET} is - * passed, the current position is not reset. + * @param startIndex The {@link MediaItem} index to start playback from. If {@link C#INDEX_UNSET} + * is passed, the current position is not reset. * @param startPositionMs The position in milliseconds to start playback from. If {@link - * C#TIME_UNSET} is passed, the default position of the given window is used. In any case, if - * {@code startWindowIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored and the - * position is not reset at all. - * @throws IllegalSeekPositionException If the provided {@code startWindowIndex} is not within the + * C#TIME_UNSET} is passed, the default position of the given {@link MediaItem} is used. In + * any case, if {@code startIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored + * and the position is not reset at all. + * @throws IllegalSeekPositionException If the provided {@code startIndex} is not within the * bounds of the list of media items. */ - void setMediaItems(List mediaItems, int startWindowIndex, long startPositionMs); + void setMediaItems(List mediaItems, int startIndex, long startPositionMs); /** * Clears the playlist, adds the specified {@link MediaItem} and resets the position to the @@ -1643,9 +1643,9 @@ public interface Player { * Listener#onAvailableCommandsChanged(Commands)} to get an update when the available commands * change. * - *

      Executing a command that is not available (for example, calling {@link #seekToNextWindow()} - * if {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} is unavailable) will neither throw an exception nor - * generate a {@link #getPlayerError()} player error}. + *

      Executing a command that is not available (for example, calling {@link + * #seekToNextMediaItem()} if {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} is unavailable) will + * neither throw an exception nor generate a {@link #getPlayerError()} player error}. * *

      {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} and {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} * are unavailable if there is no such {@link MediaItem}. @@ -1750,14 +1750,14 @@ public interface Player { int getRepeatMode(); /** - * Sets whether shuffling of windows is enabled. + * Sets whether shuffling of media items is enabled. * * @param shuffleModeEnabled Whether shuffling is enabled. */ void setShuffleModeEnabled(boolean shuffleModeEnabled); /** - * Returns whether shuffling of windows is enabled. + * Returns whether shuffling of media items is enabled. * * @see Listener#onShuffleModeEnabledChanged(boolean) */ @@ -1772,42 +1772,42 @@ public interface Player { boolean isLoading(); /** - * Seeks to the default position associated with the current window. The position can depend on - * the type of media being played. For live streams it will typically be the live edge of the - * window. For other streams it will typically be the start of the window. + * Seeks to the default position associated with the current {@link MediaItem}. The position can + * depend on the type of media being played. For live streams it will typically be the live edge. + * For other streams it will typically be the start. */ void seekToDefaultPosition(); /** - * Seeks to the default position associated with the specified window. The position can depend on - * the type of media being played. For live streams it will typically be the live edge of the - * window. For other streams it will typically be the start of the window. + * Seeks to the default position associated with the specified {@link MediaItem}. The position can + * depend on the type of media being played. For live streams it will typically be the live edge. + * For other streams it will typically be the start. * - * @param windowIndex The index of the window whose associated default position should be seeked - * to. + * @param mediaItemIndex The index of the {@link MediaItem} whose associated default position + * should be seeked to. * @throws IllegalSeekPositionException If the player has a non-empty timeline and the provided - * {@code windowIndex} is not within the bounds of the current timeline. + * {@code mediaItemIndex} is not within the bounds of the current timeline. */ - void seekToDefaultPosition(int windowIndex); + void seekToDefaultPosition(int mediaItemIndex); /** - * Seeks to a position specified in milliseconds in the current window. + * Seeks to a position specified in milliseconds in the current {@link MediaItem}. * - * @param positionMs The seek position in the current window, or {@link C#TIME_UNSET} to seek to - * the window's default position. + * @param positionMs The seek position in the current {@link MediaItem}, or {@link C#TIME_UNSET} + * to seek to the media item's default position. */ void seekTo(long positionMs); /** - * Seeks to a position specified in milliseconds in the specified window. + * Seeks to a position specified in milliseconds in the specified {@link MediaItem}. * - * @param windowIndex The index of the window. - * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek to - * the window's default position. + * @param mediaItemIndex The index of the {@link MediaItem}. + * @param positionMs The seek position in the specified {@link MediaItem}, or {@link C#TIME_UNSET} + * to seek to the media item's default position. * @throws IllegalSeekPositionException If the player has a non-empty timeline and the provided - * {@code windowIndex} is not within the bounds of the current timeline. + * {@code mediaItemIndex} is not within the bounds of the current timeline. */ - void seekTo(int windowIndex, long positionMs); + void seekTo(int mediaItemIndex, long positionMs); /** * Returns the {@link #seekBack()} increment. @@ -1817,7 +1817,9 @@ public interface Player { */ long getSeekBackIncrement(); - /** Seeks back in the current window by {@link #getSeekBackIncrement()} milliseconds. */ + /** + * Seeks back in the current {@link MediaItem} by {@link #getSeekBackIncrement()} milliseconds. + */ void seekBack(); /** @@ -1828,7 +1830,10 @@ public interface Player { */ long getSeekForwardIncrement(); - /** Seeks forward in the current window by {@link #getSeekForwardIncrement()} milliseconds. */ + /** + * Seeks forward in the current {@link MediaItem} by {@link #getSeekForwardIncrement()} + * milliseconds. + */ void seekForward(); /** @deprecated Use {@link #hasPreviousMediaItem()} instead. */ @@ -1908,8 +1913,8 @@ public interface Player { boolean hasNextWindow(); /** - * Returns whether a next window exists, which may depend on the current repeat mode and whether - * shuffle mode is enabled. + * Returns whether a next {@link MediaItem} exists, which may depend on the current repeat mode + * and whether shuffle mode is enabled. * *

      Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more @@ -1926,9 +1931,9 @@ public interface Player { void seekToNextWindow(); /** - * Seeks to the default position of the next window, which may depend on the current repeat mode - * and whether shuffle mode is enabled. Does nothing if {@link #hasNextMediaItem()} is {@code - * false}. + * Seeks to the default position of the next {@link MediaItem}, which may depend on the current + * repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasNextMediaItem()} is + * {@code false}. * *

      Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more @@ -1944,8 +1949,8 @@ public interface Player { *

    • If the timeline is empty or seeking is not possible, does nothing. *
    • Otherwise, if {@link #hasNextMediaItem() a next media item exists}, seeks to the default * position of the next {@link MediaItem}. - *
    • Otherwise, if the current window is {@link #isCurrentMediaItemLive() live} and has not - * ended, seeks to the live edge of the current {@link MediaItem}. + *
    • Otherwise, if the current {@link MediaItem} is {@link #isCurrentMediaItemLive() live} and + * has not ended, seeks to the live edge of the current {@link MediaItem}. *
    • Otherwise, does nothing. *
    */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 78f232e9a5..31fb99dbc2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -408,9 +408,8 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public void setMediaItems( - List mediaItems, int startWindowIndex, long startPositionMs) { - setMediaSources(createMediaSources(mediaItems), startWindowIndex, startPositionMs); + public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { + setMediaSources(createMediaSources(mediaItems), startIndex, startPositionMs); } public void setMediaSource(MediaSource mediaSource) { @@ -642,10 +641,11 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public void seekTo(int windowIndex, long positionMs) { + public void seekTo(int mediaItemIndex, long positionMs) { Timeline timeline = playbackInfo.timeline; - if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) { - throw new IllegalSeekPositionException(timeline, windowIndex, positionMs); + if (mediaItemIndex < 0 + || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { + throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs); } pendingOperationAcks++; if (isPlayingAd()) { @@ -668,8 +668,8 @@ import java.util.concurrent.CopyOnWriteArraySet; maskTimelineAndPosition( newPlaybackInfo, timeline, - getPeriodPositionOrMaskWindowPosition(timeline, windowIndex, positionMs)); - internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); + getPeriodPositionOrMaskWindowPosition(timeline, mediaItemIndex, positionMs)); + internalPlayer.seekTo(timeline, mediaItemIndex, C.msToUs(positionMs)); updatePlaybackInfo( newPlaybackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, 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 c6fddec392..6a061bef5f 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 @@ -1086,10 +1086,9 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public void setMediaItems( - List mediaItems, int startWindowIndex, long startPositionMs) { + public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { verifyApplicationThread(); - player.setMediaItems(mediaItems, startWindowIndex, startPositionMs); + player.setMediaItems(mediaItems, startIndex, startPositionMs); } @Override @@ -1235,10 +1234,10 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public void seekTo(int windowIndex, long positionMs) { + public void seekTo(int mediaItemIndex, long positionMs) { verifyApplicationThread(); analyticsCollector.notifySeekStarted(); - player.seekTo(windowIndex, positionMs); + player.seekTo(mediaItemIndex, positionMs); } @Override diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index eeb4c5856a..9a5a50d1d3 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -206,8 +206,7 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { } @Override - public void setMediaItems( - List mediaItems, int startWindowIndex, long startPositionMs) { + public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { throw new UnsupportedOperationException(); } @@ -398,7 +397,7 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { } @Override - public void seekTo(int windowIndex, long positionMs) { + public void seekTo(int mediaItemIndex, long positionMs) { throw new UnsupportedOperationException(); } From dcdcb919f48f2b28b2b46bb6021a01bf5218e9c5 Mon Sep 17 00:00:00 2001 From: huangdarwin Date: Thu, 28 Oct 2021 11:43:55 +0100 Subject: [PATCH 037/113] Transformer GL: Simplify GL program handling. Relanding http://https://github.com/google/ExoPlayer/commit/9788750ddb23b2064dddf99d6e1ea491b2e45cea, with some changes applied to improve primarily readability, naming, and nullness checks. PiperOrigin-RevId: 406101742 --- .../gldemo/BitmapOverlayVideoProcessor.java | 46 ++- .../android/exoplayer2/util/GlUtil.java | 289 ++++++++++-------- .../video/VideoDecoderGLSurfaceView.java | 28 +- .../video/spherical/ProjectionRenderer.java | 24 +- .../TransformerTranscodingVideoRenderer.java | 18 +- 5 files changed, 233 insertions(+), 172 deletions(-) diff --git a/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/BitmapOverlayVideoProcessor.java b/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/BitmapOverlayVideoProcessor.java index 13e2a60684..ecae8033e4 100644 --- a/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/BitmapOverlayVideoProcessor.java +++ b/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/BitmapOverlayVideoProcessor.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.gldemo; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -26,11 +28,11 @@ import android.opengl.GLES20; import android.opengl.GLUtils; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.GlUtil; import java.io.IOException; import java.util.Locale; import javax.microedition.khronos.opengles.GL10; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Video processor that demonstrates how to overlay a bitmap on video output using a GL shader. The @@ -49,7 +51,7 @@ import javax.microedition.khronos.opengles.GL10; private final Bitmap logoBitmap; private final Canvas overlayCanvas; - private int program; + private GlUtil.@MonotonicNonNull Program program; @Nullable private GlUtil.Attribute[] attributes; @Nullable private GlUtil.Uniform[] uniforms; @@ -77,27 +79,39 @@ import javax.microedition.khronos.opengles.GL10; @Override public void initialize() { - String vertexShaderCode; - String fragmentShaderCode; try { - vertexShaderCode = GlUtil.loadAsset(context, "bitmap_overlay_video_processor_vertex.glsl"); - fragmentShaderCode = - GlUtil.loadAsset(context, "bitmap_overlay_video_processor_fragment.glsl"); + program = + new GlUtil.Program( + context, + /* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl", + /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); } catch (IOException e) { throw new IllegalStateException(e); } - program = GlUtil.compileProgram(vertexShaderCode, fragmentShaderCode); - GlUtil.Attribute[] attributes = GlUtil.getAttributes(program); - GlUtil.Uniform[] uniforms = GlUtil.getUniforms(program); + GlUtil.Attribute[] attributes = program.getAttributes(); for (GlUtil.Attribute attribute : attributes) { if (attribute.name.equals("a_position")) { - attribute.setBuffer(new float[] {-1, -1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1}, 4); + attribute.setBuffer( + new float[] { + -1, -1, 0, 1, + 1, -1, 0, 1, + -1, 1, 0, 1, + 1, 1, 0, 1 + }, + 4); } else if (attribute.name.equals("a_texcoord")) { - attribute.setBuffer(new float[] {0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1}, 4); + attribute.setBuffer( + new float[] { + 0, 0, 0, 1, + 1, 0, 0, 1, + 0, 1, 0, 1, + 1, 1, 0, 1 + }, + 4); } } this.attributes = attributes; - this.uniforms = uniforms; + this.uniforms = program.getUniforms(); GLES20.glGenTextures(1, textures, 0); GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); @@ -126,9 +140,9 @@ import javax.microedition.khronos.opengles.GL10; GlUtil.checkGlError(); // Run the shader program. - GlUtil.Uniform[] uniforms = Assertions.checkNotNull(this.uniforms); - GlUtil.Attribute[] attributes = Assertions.checkNotNull(this.attributes); - GLES20.glUseProgram(program); + GlUtil.Uniform[] uniforms = checkNotNull(this.uniforms); + GlUtil.Attribute[] attributes = checkNotNull(this.attributes); + checkNotNull(program).use(); for (GlUtil.Uniform uniform : uniforms) { switch (uniform.name) { case "tex_sampler_0": diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java index a0643ad393..a8e2d5aa48 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java @@ -54,6 +54,160 @@ public final class GlUtil { /** Thrown when the required EGL version is not supported by the device. */ public static final class UnsupportedEglVersionException extends Exception {} + /** GL program. */ + public static final class Program { + /** The identifier of a compiled and linked GLSL shader program. */ + private final int programId; + + /** + * Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code. + * + * @param vertexShaderGlsl The vertex shader program. + * @param fragmentShaderGlsl The fragment shader program. + */ + public Program(String vertexShaderGlsl, String fragmentShaderGlsl) { + programId = GLES20.glCreateProgram(); + checkGlError(); + + // Add the vertex and fragment shaders. + addShader(GLES20.GL_VERTEX_SHADER, vertexShaderGlsl); + addShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderGlsl); + } + + /** + * Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code. + * + * @param context The {@link Context}. + * @param vertexShaderFilePath The path to a vertex shader program. + * @param fragmentShaderFilePath The path to a fragment shader program. + * @throws IOException When failing to read shader files. + */ + public Program(Context context, String vertexShaderFilePath, String fragmentShaderFilePath) + throws IOException { + this(loadAsset(context, vertexShaderFilePath), loadAsset(context, fragmentShaderFilePath)); + } + + /** + * Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code. + * + * @param vertexShaderGlsl The vertex shader program as arrays of strings. Strings are joined by + * adding a new line character in between each of them. + * @param fragmentShaderGlsl The fragment shader program as arrays of strings. Strings are + * joined by adding a new line character in between each of them. + */ + public Program(String[] vertexShaderGlsl, String[] fragmentShaderGlsl) { + this(TextUtils.join("\n", vertexShaderGlsl), TextUtils.join("\n", fragmentShaderGlsl)); + } + + /** Uses the program. */ + public void use() { + // Link and check for errors. + GLES20.glLinkProgram(programId); + int[] linkStatus = new int[] {GLES20.GL_FALSE}; + GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + throwGlException( + "Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId)); + } + checkGlError(); + + GLES20.glUseProgram(programId); + } + + /** Deletes the program. Deleted programs cannot be used again. */ + public void delete() { + GLES20.glDeleteProgram(programId); + } + + /** Returns the location of an {@link Attribute}. */ + public int getAttribLocation(String attributeName) { + return GLES20.glGetAttribLocation(programId, attributeName); + } + + /** Returns the location of a {@link Uniform}. */ + public int getUniformLocation(String uniformName) { + return GLES20.glGetUniformLocation(programId, uniformName); + } + + /** Returns the program's {@link Attribute}s. */ + public Attribute[] getAttributes() { + int[] attributeCount = new int[1]; + GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, 0); + if (attributeCount[0] != 2) { + throw new IllegalStateException("Expected two attributes."); + } + + Attribute[] attributes = new Attribute[attributeCount[0]]; + for (int i = 0; i < attributeCount[0]; i++) { + attributes[i] = createAttribute(i); + } + return attributes; + } + + /** Returns the program's {@link Uniform}s. */ + public Uniform[] getUniforms() { + int[] uniformCount = new int[1]; + GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0); + + Uniform[] uniforms = new Uniform[uniformCount[0]]; + for (int i = 0; i < uniformCount[0]; i++) { + uniforms[i] = createUniform(i); + } + + return uniforms; + } + + private Attribute createAttribute(int index) { + int[] length = new int[1]; + GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, length, 0); + + int[] type = new int[1]; + int[] size = new int[1]; + byte[] nameBytes = new byte[length[0]]; + int[] ignore = new int[1]; + + GLES20.glGetActiveAttrib( + programId, index, length[0], ignore, 0, size, 0, type, 0, nameBytes, 0); + String name = new String(nameBytes, 0, strlen(nameBytes)); + int location = getAttribLocation(name); + + return new Attribute(name, index, location); + } + + private Uniform createUniform(int index) { + int[] length = new int[1]; + GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, length, 0); + + int[] type = new int[1]; + int[] size = new int[1]; + byte[] nameBytes = new byte[length[0]]; + int[] ignore = new int[1]; + + GLES20.glGetActiveUniform( + programId, index, length[0], ignore, 0, size, 0, type, 0, nameBytes, 0); + String name = new String(nameBytes, 0, strlen(nameBytes)); + int location = getUniformLocation(name); + + return new Uniform(name, location, type[0]); + } + + private void addShader(int type, String glsl) { + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, glsl); + GLES20.glCompileShader(shader); + + int[] result = new int[] {GLES20.GL_FALSE}; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); + if (result[0] != GLES20.GL_TRUE) { + throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl); + } + + GLES20.glAttachShader(programId, shader); + GLES20.glDeleteShader(shader); + checkGlError(); + } + } + /** * GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}. */ @@ -68,26 +222,11 @@ public final class GlUtil { @Nullable private Buffer buffer; private int size; - /** - * Creates a new GL attribute. - * - * @param program The identifier of a compiled and linked GLSL shader program. - * @param index The index of the attribute. After this instance has been constructed, the name - * of the attribute is available via the {@link #name} field. - */ - public Attribute(int program, int index) { - int[] len = new int[1]; - GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, len, 0); - - int[] type = new int[1]; - int[] size = new int[1]; - byte[] nameBytes = new byte[len[0]]; - int[] ignore = new int[1]; - - GLES20.glGetActiveAttrib(program, index, len[0], ignore, 0, size, 0, type, 0, nameBytes, 0); - name = new String(nameBytes, 0, strlen(nameBytes)); - location = GLES20.glGetAttribLocation(program, name); + /* Creates a new Attribute. */ + public Attribute(String name, int index, int location) { + this.name = name; this.index = index; + this.location = location; } /** @@ -137,28 +276,12 @@ public final class GlUtil { private int texId; private int unit; - /** - * Creates a new GL uniform. - * - * @param program The identifier of a compiled and linked GLSL shader program. - * @param index The index of the uniform. After this instance has been constructed, the name of - * the uniform is available via the {@link #name} field. - */ - public Uniform(int program, int index) { - int[] len = new int[1]; - GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, len, 0); - - int[] type = new int[1]; - int[] size = new int[1]; - byte[] name = new byte[len[0]]; - int[] ignore = new int[1]; - - GLES20.glGetActiveUniform(program, index, len[0], ignore, 0, size, 0, type, 0, name, 0); - this.name = new String(name, 0, strlen(name)); - location = GLES20.glGetUniformLocation(program, this.name); - this.type = type[0]; - - value = new float[16]; + /** Creates a new uniform. */ + public Uniform(String name, int location, int type) { + this.name = name; + this.location = location; + this.type = type; + this.value = new float[16]; } /** @@ -332,74 +455,6 @@ public final class GlUtil { Api17.focusSurface(eglDisplay, eglContext, surface, width, height); } - /** - * Builds a GL shader program from vertex and fragment shader code. - * - * @param vertexCode GLES20 vertex shader program as arrays of strings. Strings are joined by - * adding a new line character in between each of them. - * @param fragmentCode GLES20 fragment shader program as arrays of strings. Strings are joined by - * adding a new line character in between each of them. - * @return GLES20 program id. - */ - public static int compileProgram(String[] vertexCode, String[] fragmentCode) { - return compileProgram(TextUtils.join("\n", vertexCode), TextUtils.join("\n", fragmentCode)); - } - - /** - * Builds a GL shader program from vertex and fragment shader code. - * - * @param vertexCode GLES20 vertex shader program. - * @param fragmentCode GLES20 fragment shader program. - * @return GLES20 program id. - */ - public static int compileProgram(String vertexCode, String fragmentCode) { - int program = GLES20.glCreateProgram(); - checkGlError(); - - // Add the vertex and fragment shaders. - addShader(GLES20.GL_VERTEX_SHADER, vertexCode, program); - addShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode, program); - - // Link and check for errors. - GLES20.glLinkProgram(program); - int[] linkStatus = new int[] {GLES20.GL_FALSE}; - GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); - if (linkStatus[0] != GLES20.GL_TRUE) { - throwGlException("Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(program)); - } - checkGlError(); - - return program; - } - - /** Returns the {@link Attribute}s in the specified {@code program}. */ - public static Attribute[] getAttributes(int program) { - int[] attributeCount = new int[1]; - GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, 0); - if (attributeCount[0] != 2) { - throw new IllegalStateException("Expected two attributes."); - } - - Attribute[] attributes = new Attribute[attributeCount[0]]; - for (int i = 0; i < attributeCount[0]; i++) { - attributes[i] = new Attribute(program, i); - } - return attributes; - } - - /** Returns the {@link Uniform}s in the specified {@code program}. */ - public static Uniform[] getUniforms(int program) { - int[] uniformCount = new int[1]; - GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0); - - Uniform[] uniforms = new Uniform[uniformCount[0]]; - for (int i = 0; i < uniformCount[0]; i++) { - uniforms[i] = new Uniform(program, i); - } - - return uniforms; - } - /** * Deletes a GL texture. * @@ -478,22 +533,6 @@ public final class GlUtil { return texId[0]; } - private static void addShader(int type, String source, int program) { - int shader = GLES20.glCreateShader(type); - GLES20.glShaderSource(shader, source); - GLES20.glCompileShader(shader); - - int[] result = new int[] {GLES20.GL_FALSE}; - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); - if (result[0] != GLES20.GL_TRUE) { - throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + source); - } - - GLES20.glAttachShader(program, shader); - GLES20.glDeleteShader(shader); - checkGlError(); - } - private static void throwGlException(String errorMsg) { Log.e(TAG, errorMsg); if (glAssertionsEnabled) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderGLSurfaceView.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderGLSurfaceView.java index d7d90c5e02..1d9dcadbfb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderGLSurfaceView.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderGLSurfaceView.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.video; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + import android.content.Context; import android.opengl.GLES20; import android.opengl.GLSurfaceView; @@ -30,6 +32,7 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * GLSurfaceView implementing {@link VideoDecoderOutputBufferRenderer} for rendering {@link @@ -141,7 +144,7 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView // glDrawArrays uses it. private final FloatBuffer[] textureCoords; - private int program; + private GlUtil.@MonotonicNonNull Program program; private int colorMatrixLocation; // Accessed only from the GL thread. @@ -162,9 +165,9 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { - program = GlUtil.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER); - GLES20.glUseProgram(program); - int posLocation = GLES20.glGetAttribLocation(program, "in_pos"); + program = new GlUtil.Program(VERTEX_SHADER, FRAGMENT_SHADER); + program.use(); + int posLocation = program.getAttribLocation("in_pos"); GLES20.glEnableVertexAttribArray(posLocation); GLES20.glVertexAttribPointer( posLocation, @@ -173,14 +176,14 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView /* normalized= */ false, /* stride= */ 0, TEXTURE_VERTICES); - texLocations[0] = GLES20.glGetAttribLocation(program, "in_tc_y"); + texLocations[0] = program.getAttribLocation("in_tc_y"); GLES20.glEnableVertexAttribArray(texLocations[0]); - texLocations[1] = GLES20.glGetAttribLocation(program, "in_tc_u"); + texLocations[1] = program.getAttribLocation("in_tc_u"); GLES20.glEnableVertexAttribArray(texLocations[1]); - texLocations[2] = GLES20.glGetAttribLocation(program, "in_tc_v"); + texLocations[2] = program.getAttribLocation("in_tc_v"); GLES20.glEnableVertexAttribArray(texLocations[2]); GlUtil.checkGlError(); - colorMatrixLocation = GLES20.glGetUniformLocation(program, "mColorConversion"); + colorMatrixLocation = program.getUniformLocation("mColorConversion"); GlUtil.checkGlError(); setupTextures(); GlUtil.checkGlError(); @@ -207,7 +210,7 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView renderedOutputBuffer = pendingOutputBuffer; } - VideoDecoderOutputBuffer outputBuffer = Assertions.checkNotNull(renderedOutputBuffer); + VideoDecoderOutputBuffer outputBuffer = checkNotNull(renderedOutputBuffer); // Set color matrix. Assume BT709 if the color space is unknown. float[] colorConversion = kColorConversion709; @@ -230,8 +233,8 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView colorConversion, /* offset= */ 0); - int[] yuvStrides = Assertions.checkNotNull(outputBuffer.yuvStrides); - ByteBuffer[] yuvPlanes = Assertions.checkNotNull(outputBuffer.yuvPlanes); + int[] yuvStrides = checkNotNull(outputBuffer.yuvStrides); + ByteBuffer[] yuvPlanes = checkNotNull(outputBuffer.yuvPlanes); for (int i = 0; i < 3; i++) { int h = (i == 0) ? outputBuffer.height : (outputBuffer.height + 1) / 2; @@ -294,10 +297,11 @@ public final class VideoDecoderGLSurfaceView extends GLSurfaceView surfaceView.requestRender(); } + @RequiresNonNull("program") private void setupTextures() { GLES20.glGenTextures(3, yuvTextures, /* offset= */ 0); for (int i = 0; i < 3; i++) { - GLES20.glUniform1i(GLES20.glGetUniformLocation(program, TEXTURE_UNIFORMS[i]), i); + GLES20.glUniform1i(program.getUniformLocation(TEXTURE_UNIFORMS[i]), i); GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); GLES20.glTexParameterf( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java index 4ba5dcca08..f313225fc6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.video.spherical; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.GlUtil.checkGlError; import android.opengl.GLES11Ext; @@ -92,9 +93,9 @@ import java.nio.FloatBuffer; private int stereoMode; @Nullable private MeshData leftMeshData; @Nullable private MeshData rightMeshData; + @Nullable private GlUtil.Program program; - // Program related GL items. These are only valid if program != 0. - private int program; + // Program related GL items. These are only valid if program is non-null. private int mvpMatrixHandle; private int uTexMatrixHandle; private int positionHandle; @@ -119,12 +120,12 @@ import java.nio.FloatBuffer; /** Initializes of the GL components. */ /* package */ void init() { - program = GlUtil.compileProgram(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE); - mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMvpMatrix"); - uTexMatrixHandle = GLES20.glGetUniformLocation(program, "uTexMatrix"); - positionHandle = GLES20.glGetAttribLocation(program, "aPosition"); - texCoordsHandle = GLES20.glGetAttribLocation(program, "aTexCoords"); - textureHandle = GLES20.glGetUniformLocation(program, "uTexture"); + program = new GlUtil.Program(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE); + mvpMatrixHandle = program.getUniformLocation("uMvpMatrix"); + uTexMatrixHandle = program.getUniformLocation("uTexMatrix"); + positionHandle = program.getAttribLocation("aPosition"); + texCoordsHandle = program.getAttribLocation("aTexCoords"); + textureHandle = program.getUniformLocation("uTexture"); } /** @@ -143,7 +144,7 @@ import java.nio.FloatBuffer; } // Configure shader. - GLES20.glUseProgram(program); + checkNotNull(program).use(); checkGlError(); GLES20.glEnableVertexAttribArray(positionHandle); @@ -196,8 +197,9 @@ import java.nio.FloatBuffer; /** Cleans up the GL resources. */ /* package */ void shutdown() { - if (program != 0) { - GLES20.glDeleteProgram(program); + if (program != null) { + program.delete(); + program = null; } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index 90ce3a8071..b04490152b 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -229,17 +229,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; GlUtil.focusSurface( eglDisplay, eglContext, eglSurface, decoderInputFormat.width, decoderInputFormat.height); decoderTextureId = GlUtil.createExternalTexture(); - String vertexShaderCode; - String fragmentShaderCode; + GlUtil.Program copyProgram; try { - vertexShaderCode = GlUtil.loadAsset(context, "shaders/blit_vertex_shader.glsl"); - fragmentShaderCode = GlUtil.loadAsset(context, "shaders/copy_external_fragment_shader.glsl"); + copyProgram = + new GlUtil.Program( + context, + /* vertexShaderFilePath= */ "shaders/blit_vertex_shader.glsl", + /* fragmentShaderFilePath= */ "shaders/copy_external_fragment_shader.glsl"); } catch (IOException e) { throw new IllegalStateException(e); } - int copyProgram = GlUtil.compileProgram(vertexShaderCode, fragmentShaderCode); - GLES20.glUseProgram(copyProgram); - GlUtil.Attribute[] copyAttributes = GlUtil.getAttributes(copyProgram); + + copyProgram.use(); + GlUtil.Attribute[] copyAttributes = copyProgram.getAttributes(); checkState(copyAttributes.length == 2, "Expected program to have two vertex attributes."); for (GlUtil.Attribute copyAttribute : copyAttributes) { if (copyAttribute.name.equals("a_position")) { @@ -265,7 +267,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } copyAttribute.bind(); } - GlUtil.Uniform[] copyUniforms = GlUtil.getUniforms(copyProgram); + GlUtil.Uniform[] copyUniforms = copyProgram.getUniforms(); checkState(copyUniforms.length == 2, "Expected program to have two uniforms."); for (GlUtil.Uniform copyUniform : copyUniforms) { if (copyUniform.name.equals("tex_sampler")) { From 689a92c9aeedea89e5ab35f663d819d41441cd5d Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 28 Oct 2021 18:01:01 +0100 Subject: [PATCH 038/113] Update the UI Components dev guide page to use MediaItem API #minor-release PiperOrigin-RevId: 406163529 --- docs/ui-components.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/ui-components.md b/docs/ui-components.md index 79078b1731..c56107eae7 100644 --- a/docs/ui-components.md +++ b/docs/ui-components.md @@ -73,8 +73,8 @@ When a player has been initialized, it can be attached to the view by calling player = new ExoPlayer.Builder(context).build(); // Attach player to the view. playerView.setPlayer(player); -// Set the media source to be played. -player.setMediaSource(createMediaSource()); +// Set the media item to be played. +player.setMediaItem(mediaItem); // Prepare the player. player.prepare(); ~~~ @@ -160,8 +160,10 @@ private void initializePlayer() { player = new ExoPlayer.Builder(context).build(); // Attach player to the view. playerControlView.setPlayer(player); - // Prepare the player with the dash media source. - player.prepare(createMediaSource()); + // Set the media item to be played. + player.setMediaItem(mediaItem); + // Prepare the player. + player.prepare(); } ~~~ {: .language-java} From 1c824561c6dc815762d3fde23e9d0e780a950a03 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 28 Oct 2021 18:15:25 +0100 Subject: [PATCH 039/113] Migrate callers of deprecated C.java methods to Util.java #minor-release PiperOrigin-RevId: 406166670 --- .../exoplayer2/ext/cast/CastUtils.java | 3 +- .../ext/cast/CastTimelineTrackerTest.java | 19 +++-- .../exoplayer2/ext/ima/AdTagLoader.java | 26 +++--- .../exoplayer2/ext/ima/ImaAdsLoader.java | 2 +- .../exoplayer2/ext/ima/FakePlayer.java | 3 +- .../exoplayer2/ext/ima/ImaAdsLoaderTest.java | 40 +++++---- .../google/android/exoplayer2/Timeline.java | 10 +-- .../DefaultLivePlaybackSpeedControl.java | 18 ++-- .../exoplayer2/DefaultLoadControl.java | 10 +-- .../exoplayer2/ExoPlaybackException.java | 2 +- .../android/exoplayer2/ExoPlayerImpl.java | 34 ++++---- .../exoplayer2/ExoPlayerImplInternal.java | 6 +- .../android/exoplayer2/SimpleExoPlayer.java | 4 +- .../analytics/AnalyticsCollector.java | 2 +- .../DefaultPlaybackSessionManager.java | 3 +- .../analytics/PlaybackStatsListener.java | 2 +- .../audio/AudioTrackPositionTracker.java | 8 +- .../android/exoplayer2/drm/DrmUtil.java | 3 +- .../mediacodec/MediaCodecRenderer.java | 4 +- .../source/ClippingMediaSource.java | 3 +- .../source/MediaSourceEventListener.java | 3 +- .../source/ProgressiveMediaPeriod.java | 4 +- .../source/SingleSampleMediaPeriod.java | 3 +- .../ads/ServerSideInsertedAdsMediaSource.java | 6 +- .../source/ads/ServerSideInsertedAdsUtil.java | 5 +- .../source/chunk/ChunkSampleStream.java | 4 +- .../android/exoplayer2/util/EventLogger.java | 4 +- .../exoplayer2/util/StandaloneMediaClock.java | 3 +- .../video/DecoderVideoRenderer.java | 3 +- .../exoplayer2/DefaultLoadControlTest.java | 17 ++-- .../exoplayer2/DefaultMediaClockTest.java | 5 +- .../android/exoplayer2/ExoPlayerTest.java | 82 ++++++++++--------- .../source/dash/DashMediaSource.java | 34 ++++---- .../source/dash/DefaultDashChunkSource.java | 9 +- .../source/dash/manifest/DashManifest.java | 3 +- .../dash/manifest/DashManifestParser.java | 8 +- .../source/dash/offline/DashDownloader.java | 4 +- .../dash/DefaultDashChunkSourceTest.java | 3 +- .../exoplayer2/extractor/mp3/MlltSeeker.java | 8 +- .../extractor/mp3/Mp3Extractor.java | 2 +- .../exoplayer2/source/hls/HlsMediaSource.java | 12 +-- .../source/hls/HlsSampleStreamWrapper.java | 4 +- .../UnexpectedSampleTimestampException.java | 3 +- .../playlist/DefaultHlsPlaylistTracker.java | 6 +- .../hls/playlist/HlsPlaylistParser.java | 2 +- .../source/hls/HlsMediaSourceTest.java | 3 +- .../exoplayer2/source/rtsp/RtspClient.java | 4 +- .../source/rtsp/RtspMediaSource.java | 3 +- .../source/smoothstreaming/SsMediaSource.java | 2 +- .../exoplayer2/transformer/MuxerWrapper.java | 3 +- .../SefSlowMotionVideoSampleTransformer.java | 5 +- .../transformer/SegmentSpeedProvider.java | 7 +- .../exoplayer2/ui/PlayerControlView.java | 6 +- .../ui/StyledPlayerControlView.java | 6 +- .../testutil/FakeMediaSourceFactory.java | 3 +- .../exoplayer2/testutil/FakeTimeline.java | 2 +- 56 files changed, 257 insertions(+), 226 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java index 3077077616..5b96bc5846 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.cast; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.util.Util; import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaTrack; @@ -42,7 +43,7 @@ import com.google.android.gms.cast.MediaTrack; } long durationMs = mediaInfo.getStreamDuration(); return durationMs != MediaInfo.UNKNOWN_DURATION && durationMs != LIVE_STREAM_DURATION - ? C.msToUs(durationMs) + ? Util.msToUs(durationMs) : C.TIME_UNSET; } diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java index cae117ea00..5b6c6c8511 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.TimelineAsserts; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaStatus; import com.google.android.gms.cast.framework.media.MediaQueue; @@ -52,7 +53,7 @@ public class CastTimelineTrackerTest { TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(remoteMediaClient), C.TIME_UNSET, - C.msToUs(DURATION_2_MS), + Util.msToUs(DURATION_2_MS), C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET); @@ -65,8 +66,8 @@ public class CastTimelineTrackerTest { TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(remoteMediaClient), C.TIME_UNSET, - C.msToUs(DURATION_2_MS), - C.msToUs(DURATION_3_MS)); + Util.msToUs(DURATION_2_MS), + Util.msToUs(DURATION_3_MS)); remoteMediaClient = mockRemoteMediaClient( @@ -74,7 +75,7 @@ public class CastTimelineTrackerTest { /* currentItemId= */ 3, /* currentDurationMs= */ DURATION_3_MS); TimelineAsserts.assertPeriodDurations( - tracker.getCastTimeline(remoteMediaClient), C.TIME_UNSET, C.msToUs(DURATION_3_MS)); + tracker.getCastTimeline(remoteMediaClient), C.TIME_UNSET, Util.msToUs(DURATION_3_MS)); remoteMediaClient = mockRemoteMediaClient( @@ -85,8 +86,8 @@ public class CastTimelineTrackerTest { tracker.getCastTimeline(remoteMediaClient), C.TIME_UNSET, C.TIME_UNSET, - C.msToUs(DURATION_3_MS), - C.msToUs(DURATION_4_MS), + Util.msToUs(DURATION_3_MS), + Util.msToUs(DURATION_4_MS), C.TIME_UNSET); remoteMediaClient = @@ -98,9 +99,9 @@ public class CastTimelineTrackerTest { tracker.getCastTimeline(remoteMediaClient), C.TIME_UNSET, C.TIME_UNSET, - C.msToUs(DURATION_3_MS), - C.msToUs(DURATION_4_MS), - C.msToUs(DURATION_5_MS)); + Util.msToUs(DURATION_3_MS), + Util.msToUs(DURATION_4_MS), + Util.msToUs(DURATION_5_MS)); } private static RemoteMediaClient mockRemoteMediaClient( diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java index f0c4a199e1..2d446527a1 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java @@ -348,7 +348,7 @@ import java.util.Map; long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); int adGroupForPositionIndex = adPlaybackState.getAdGroupIndexForPositionUs( - C.msToUs(contentPositionMs), C.msToUs(contentDurationMs)); + Util.msToUs(contentPositionMs), Util.msToUs(contentDurationMs)); if (adGroupForPositionIndex != C.INDEX_UNSET && imaAdInfo != null && imaAdInfo.adGroupIndex != adGroupForPositionIndex) { @@ -372,7 +372,7 @@ import java.util.Map; } adPlaybackState = adPlaybackState.withAdResumePositionUs( - playingAd ? C.msToUs(player.getCurrentPosition()) : 0); + playingAd ? Util.msToUs(player.getCurrentPosition()) : 0); } lastVolumePercent = getPlayerVolumePercent(); lastAdProgress = getAdVideoProgressUpdate(); @@ -456,7 +456,7 @@ import java.util.Map; this.timeline = timeline; Player player = checkNotNull(this.player); long contentDurationUs = timeline.getPeriod(player.getCurrentPeriodIndex(), period).durationUs; - contentDurationMs = C.usToMs(contentDurationUs); + contentDurationMs = Util.usToMs(contentDurationUs); if (contentDurationUs != adPlaybackState.contentDurationUs) { adPlaybackState = adPlaybackState.withContentDurationUs(contentDurationUs); updateAdPlaybackState(); @@ -602,10 +602,11 @@ import java.util.Map; // Skip ads based on the start position as required. int adGroupForPositionIndex = adPlaybackState.getAdGroupIndexForPositionUs( - C.msToUs(contentPositionMs), C.msToUs(contentDurationMs)); + Util.msToUs(contentPositionMs), Util.msToUs(contentDurationMs)); if (adGroupForPositionIndex != C.INDEX_UNSET) { boolean playAdWhenStartingPlayback = - adPlaybackState.getAdGroup(adGroupForPositionIndex).timeUs == C.msToUs(contentPositionMs) + adPlaybackState.getAdGroup(adGroupForPositionIndex).timeUs + == Util.msToUs(contentPositionMs) || configuration.playAdBeforeStartPosition; if (!playAdWhenStartingPlayback) { adGroupForPositionIndex++; @@ -790,7 +791,7 @@ import java.util.Map; // An ad is available already. return false; } - long adGroupTimeMs = C.usToMs(adGroup.timeUs); + long adGroupTimeMs = Util.usToMs(adGroup.timeUs); long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); long timeUntilAdMs = adGroupTimeMs - contentPositionMs; return timeUntilAdMs < configuration.adPreloadTimeoutMs; @@ -840,7 +841,7 @@ import java.util.Map; if (!sentContentComplete && !timeline.isEmpty()) { long positionMs = getContentPeriodPositionMs(player, timeline, period); timeline.getPeriod(player.getCurrentPeriodIndex(), period); - int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs)); + int newAdGroupIndex = period.getAdGroupIndexForPositionUs(Util.msToUs(positionMs)); if (newAdGroupIndex != C.INDEX_UNSET) { sentPendingContentPositionMs = false; pendingContentPositionMs = positionMs; @@ -880,7 +881,7 @@ import java.util.Map; } else { // IMA hasn't called playAd yet, so fake the content position. fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); - fakeContentProgressOffsetMs = C.usToMs(adGroup.timeUs); + fakeContentProgressOffsetMs = Util.usToMs(adGroup.timeUs); if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { fakeContentProgressOffsetMs = contentDurationMs; } @@ -1091,7 +1092,7 @@ import java.util.Map; // Send IMA a content position at the ad group so that it will try to play it, at which point // we can notify that it failed to load. fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); - fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.getAdGroup(adGroupIndex).timeUs); + fakeContentProgressOffsetMs = Util.usToMs(adPlaybackState.getAdGroup(adGroupIndex).timeUs); if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { fakeContentProgressOffsetMs = contentDurationMs; } @@ -1192,13 +1193,14 @@ import java.util.Map; if (player == null) { return C.INDEX_UNSET; } - long playerPositionUs = C.msToUs(getContentPeriodPositionMs(player, timeline, period)); + long playerPositionUs = Util.msToUs(getContentPeriodPositionMs(player, timeline, period)); int adGroupIndex = - adPlaybackState.getAdGroupIndexForPositionUs(playerPositionUs, C.msToUs(contentDurationMs)); + adPlaybackState.getAdGroupIndexForPositionUs( + playerPositionUs, Util.msToUs(contentDurationMs)); if (adGroupIndex == C.INDEX_UNSET) { adGroupIndex = adPlaybackState.getAdGroupIndexAfterPositionUs( - playerPositionUs, C.msToUs(contentDurationMs)); + playerPositionUs, Util.msToUs(contentDurationMs)); } return adGroupIndex; } diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 7f52005b68..85a39c77e8 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -703,7 +703,7 @@ public final class ImaAdsLoader implements Player.Listener, AdsLoader { timeline.getPeriodPosition( window, period, period.windowIndex, /* windowPositionUs= */ C.TIME_UNSET) .second; - nextAdTagLoader.maybePreloadAds(C.usToMs(periodPositionUs), C.usToMs(period.durationUs)); + nextAdTagLoader.maybePreloadAds(Util.usToMs(periodPositionUs), Util.usToMs(period.durationUs)); } /** diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index d0a9d4206b..e8f80feed0 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; +import com.google.android.exoplayer2.util.Util; /** A fake player for testing content/ad playback. */ /* package */ final class FakePlayer extends StubExoPlayer { @@ -277,7 +278,7 @@ import com.google.android.exoplayer2.util.ListenerSet; if (isPlayingAd()) { long adDurationUs = timeline.getPeriod(0, period).getAdDurationUs(adGroupIndex, adIndexInAdGroup); - return C.usToMs(adDurationUs); + return Util.usToMs(adDurationUs); } else { return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); } diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java index 06711d6055..cb0deb494b 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java @@ -384,13 +384,13 @@ public final class ImaAdsLoaderTest { playerPositionUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US; long periodDurationUs = CONTENT_TIMELINE.getPeriod(/* periodIndex= */ 0, new Period()).durationUs; - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(playerPositionUs)); + fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, Util.usToMs(playerPositionUs)); // Verify the content progress is updated to reflect the new player position. assertThat(contentProgressProvider.getContentProgress()) .isEqualTo( new VideoProgressUpdate( - C.usToMs(playerPositionInPeriodUs), C.usToMs(periodDurationUs))); + Util.usToMs(playerPositionInPeriodUs), Util.usToMs(periodDurationUs))); } @Test @@ -428,7 +428,8 @@ public final class ImaAdsLoaderTest { // Advance playback to just before the midroll and simulate buffering. imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs)); + fakePlayer.setPlayingContentPosition( + /* periodIndex= */ 0, Util.usToMs(adGroupPositionInWindowUs)); fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true); // Advance before the timeout and simulating polling content progress. ShadowSystemClock.advanceBy(Duration.ofSeconds(1)); @@ -453,7 +454,8 @@ public final class ImaAdsLoaderTest { // Advance playback to just before the midroll and simulate buffering. imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs)); + fakePlayer.setPlayingContentPosition( + /* periodIndex= */ 0, Util.usToMs(adGroupPositionInWindowUs)); fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true); // Advance past the timeout and simulate polling content progress. ShadowSystemClock.advanceBy(Duration.ofSeconds(5)); @@ -478,7 +480,8 @@ public final class ImaAdsLoaderTest { ImmutableList cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND); when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs)); + fakePlayer.setPlayingContentPosition( + /* periodIndex= */ 0, Util.usToMs(adGroupPositionInWindowUs)); // Start ad loading while still buffering and simulate the calls from the IMA SDK to resume then // immediately pause content playback. @@ -506,7 +509,8 @@ public final class ImaAdsLoaderTest { ImmutableList cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND); when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs)); + fakePlayer.setPlayingContentPosition( + /* periodIndex= */ 0, Util.usToMs(adGroupPositionInWindowUs)); // Start ad loading while still buffering and poll progress without the ad loading. imaAdsLoader.start( @@ -597,7 +601,7 @@ public final class ImaAdsLoaderTest { verify(mockVideoAdPlayerCallback) .onAdProgress( TEST_AD_MEDIA_INFO, - new VideoProgressUpdate(newPlayerPositionMs, C.usToMs(TEST_AD_DURATION_US))); + new VideoProgressUpdate(newPlayerPositionMs, Util.usToMs(TEST_AD_DURATION_US))); } @Test @@ -610,7 +614,7 @@ public final class ImaAdsLoaderTest { when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setPlayingContentPosition( - /* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) - 1_000); + /* periodIndex= */ 0, Util.usToMs(midrollWindowTimeUs) - 1_000); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -630,7 +634,7 @@ public final class ImaAdsLoaderTest { ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND); when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs)); + fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, Util.usToMs(midrollWindowTimeUs)); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -657,7 +661,7 @@ public final class ImaAdsLoaderTest { when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setPlayingContentPosition( - /* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) + 1_000); + /* periodIndex= */ 0, Util.usToMs(midrollWindowTimeUs) + 1_000); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -691,7 +695,7 @@ public final class ImaAdsLoaderTest { when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setPlayingContentPosition( - /* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs) - 1_000); + /* periodIndex= */ 0, Util.usToMs(secondMidrollWindowTimeUs) - 1_000); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -718,7 +722,8 @@ public final class ImaAdsLoaderTest { (float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND); when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs)); + fakePlayer.setPlayingContentPosition( + /* periodIndex= */ 0, Util.usToMs(secondMidrollWindowTimeUs)); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -760,7 +765,7 @@ public final class ImaAdsLoaderTest { when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setPlayingContentPosition( - /* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) - 1_000); + /* periodIndex= */ 0, Util.usToMs(midrollWindowTimeUs) - 1_000); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -801,7 +806,7 @@ public final class ImaAdsLoaderTest { ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND); when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs)); + fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, Util.usToMs(midrollWindowTimeUs)); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -843,7 +848,7 @@ public final class ImaAdsLoaderTest { when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setPlayingContentPosition( - /* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) + 1_000); + /* periodIndex= */ 0, Util.usToMs(midrollWindowTimeUs) + 1_000); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -889,7 +894,7 @@ public final class ImaAdsLoaderTest { when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); fakePlayer.setPlayingContentPosition( - /* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs) - 1_000); + /* periodIndex= */ 0, Util.usToMs(secondMidrollWindowTimeUs) - 1_000); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -937,7 +942,8 @@ public final class ImaAdsLoaderTest { (float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND); when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); - fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs)); + fakePlayer.setPlayingContentPosition( + /* periodIndex= */ 0, Util.usToMs(secondMidrollWindowTimeUs)); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 945f2c5107..8b6e734e68 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -300,7 +300,7 @@ public abstract class Timeline implements Bundleable { * whilst remaining within the bounds of the window. */ public long getDefaultPositionMs() { - return C.usToMs(defaultPositionUs); + return Util.usToMs(defaultPositionUs); } /** @@ -315,7 +315,7 @@ public abstract class Timeline implements Bundleable { /** Returns the duration of the window in milliseconds, or {@link C#TIME_UNSET} if unknown. */ public long getDurationMs() { - return C.usToMs(durationUs); + return Util.usToMs(durationUs); } /** Returns the duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown. */ @@ -328,7 +328,7 @@ public abstract class Timeline implements Bundleable { * belonging to it, in milliseconds. */ public long getPositionInFirstPeriodMs() { - return C.usToMs(positionInFirstPeriodUs); + return Util.usToMs(positionInFirstPeriodUs); } /** @@ -671,7 +671,7 @@ public abstract class Timeline implements Bundleable { /** Returns the duration of the period in milliseconds, or {@link C#TIME_UNSET} if unknown. */ public long getDurationMs() { - return C.usToMs(durationUs); + return Util.usToMs(durationUs); } /** Returns the duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown. */ @@ -685,7 +685,7 @@ public abstract class Timeline implements Bundleable { * window. */ public long getPositionInWindowMs() { - return C.usToMs(positionInWindowUs); + return Util.usToMs(positionInWindowUs); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java index e4fa65c436..59753448db 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java @@ -106,9 +106,10 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC fallbackMaxPlaybackSpeed = DEFAULT_FALLBACK_MAX_PLAYBACK_SPEED; minUpdateIntervalMs = DEFAULT_MIN_UPDATE_INTERVAL_MS; proportionalControlFactorUs = DEFAULT_PROPORTIONAL_CONTROL_FACTOR / C.MICROS_PER_SECOND; - maxLiveOffsetErrorUsForUnitSpeed = C.msToUs(DEFAULT_MAX_LIVE_OFFSET_ERROR_MS_FOR_UNIT_SPEED); + maxLiveOffsetErrorUsForUnitSpeed = + Util.msToUs(DEFAULT_MAX_LIVE_OFFSET_ERROR_MS_FOR_UNIT_SPEED); targetLiveOffsetIncrementOnRebufferUs = - C.msToUs(DEFAULT_TARGET_LIVE_OFFSET_INCREMENT_ON_REBUFFER_MS); + Util.msToUs(DEFAULT_TARGET_LIVE_OFFSET_INCREMENT_ON_REBUFFER_MS); minPossibleLiveOffsetSmoothingFactor = DEFAULT_MIN_POSSIBLE_LIVE_OFFSET_SMOOTHING_FACTOR; } @@ -187,7 +188,7 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC */ public Builder setMaxLiveOffsetErrorMsForUnitSpeed(long maxLiveOffsetErrorMsForUnitSpeed) { Assertions.checkArgument(maxLiveOffsetErrorMsForUnitSpeed > 0); - this.maxLiveOffsetErrorUsForUnitSpeed = C.msToUs(maxLiveOffsetErrorMsForUnitSpeed); + this.maxLiveOffsetErrorUsForUnitSpeed = Util.msToUs(maxLiveOffsetErrorMsForUnitSpeed); return this; } @@ -202,7 +203,8 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC public Builder setTargetLiveOffsetIncrementOnRebufferMs( long targetLiveOffsetIncrementOnRebufferMs) { Assertions.checkArgument(targetLiveOffsetIncrementOnRebufferMs >= 0); - this.targetLiveOffsetIncrementOnRebufferUs = C.msToUs(targetLiveOffsetIncrementOnRebufferMs); + this.targetLiveOffsetIncrementOnRebufferUs = + Util.msToUs(targetLiveOffsetIncrementOnRebufferMs); return this; } @@ -295,9 +297,9 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC @Override public void setLiveConfiguration(LiveConfiguration liveConfiguration) { - mediaConfigurationTargetLiveOffsetUs = C.msToUs(liveConfiguration.targetOffsetMs); - minTargetLiveOffsetUs = C.msToUs(liveConfiguration.minOffsetMs); - maxTargetLiveOffsetUs = C.msToUs(liveConfiguration.maxOffsetMs); + mediaConfigurationTargetLiveOffsetUs = Util.msToUs(liveConfiguration.targetOffsetMs); + minTargetLiveOffsetUs = Util.msToUs(liveConfiguration.minOffsetMs); + maxTargetLiveOffsetUs = Util.msToUs(liveConfiguration.maxOffsetMs); minPlaybackSpeed = liveConfiguration.minPlaybackSpeed != C.RATE_UNSET ? liveConfiguration.minPlaybackSpeed @@ -416,7 +418,7 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC // There is room for decreasing the target offset towards the ideal or safe offset (whichever // is larger). We want to limit the decrease so that the playback speed delta we achieve is // the same as the maximum delta when slowing down towards the target. - long minUpdateIntervalUs = C.msToUs(minUpdateIntervalMs); + long minUpdateIntervalUs = Util.msToUs(minUpdateIntervalMs); long decrementToOffsetCurrentSpeedUs = (long) ((adjustedPlaybackSpeed - 1f) * minUpdateIntervalUs); long decrementToIncreaseSpeedUs = (long) ((maxPlaybackSpeed - 1f) * minUpdateIntervalUs); 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 b947755f6d..d2ea709e76 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 @@ -299,17 +299,17 @@ public class DefaultLoadControl implements LoadControl { assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0"); this.allocator = allocator; - this.minBufferUs = C.msToUs(minBufferMs); - this.maxBufferUs = C.msToUs(maxBufferMs); - this.bufferForPlaybackUs = C.msToUs(bufferForPlaybackMs); - this.bufferForPlaybackAfterRebufferUs = C.msToUs(bufferForPlaybackAfterRebufferMs); + this.minBufferUs = Util.msToUs(minBufferMs); + this.maxBufferUs = Util.msToUs(maxBufferMs); + this.bufferForPlaybackUs = Util.msToUs(bufferForPlaybackMs); + this.bufferForPlaybackAfterRebufferUs = Util.msToUs(bufferForPlaybackAfterRebufferMs); this.targetBufferBytesOverwrite = targetBufferBytes; this.targetBufferBytes = targetBufferBytesOverwrite != C.LENGTH_UNSET ? targetBufferBytesOverwrite : DEFAULT_MIN_BUFFER_SIZE; this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds; - this.backBufferDurationUs = C.msToUs(backBufferDurationMs); + this.backBufferDurationUs = Util.msToUs(backBufferDurationMs); this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java index f78441bda1..bc342e7995 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java @@ -359,7 +359,7 @@ public final class ExoPlaybackException extends PlaybackException { + ", format=" + rendererFormat + ", format_supported=" - + C.getFormatSupportString(rendererFormatSupport); + + Util.getFormatSupportString(rendererFormatSupport); break; case TYPE_REMOTE: message = "Remote error"; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 31fb99dbc2..653135e59c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -669,7 +669,7 @@ import java.util.concurrent.CopyOnWriteArraySet; newPlaybackInfo, timeline, getPeriodPositionOrMaskWindowPosition(timeline, mediaItemIndex, positionMs)); - internalPlayer.seekTo(timeline, mediaItemIndex, C.msToUs(positionMs)); + internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs)); updatePlaybackInfo( newPlaybackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, @@ -865,21 +865,21 @@ import java.util.concurrent.CopyOnWriteArraySet; MediaPeriodId periodId = playbackInfo.periodId; playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup); - return C.usToMs(adDurationUs); + return Util.usToMs(adDurationUs); } return getContentDuration(); } @Override public long getCurrentPosition() { - return C.usToMs(getCurrentPositionUsInternal(playbackInfo)); + return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); } @Override public long getBufferedPosition() { if (isPlayingAd()) { return playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId) - ? C.usToMs(playbackInfo.bufferedPositionUs) + ? Util.usToMs(playbackInfo.bufferedPositionUs) : getDuration(); } return getContentBufferedPosition(); @@ -887,7 +887,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public long getTotalBufferedDuration() { - return C.usToMs(playbackInfo.totalBufferedDurationUs); + return Util.usToMs(playbackInfo.totalBufferedDurationUs); } @Override @@ -911,7 +911,7 @@ import java.util.concurrent.CopyOnWriteArraySet; playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET ? playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDefaultPositionMs() - : period.getPositionInWindowMs() + C.usToMs(playbackInfo.requestedContentPositionUs); + : period.getPositionInWindowMs() + Util.usToMs(playbackInfo.requestedContentPositionUs); } else { return getCurrentPosition(); } @@ -936,7 +936,7 @@ import java.util.concurrent.CopyOnWriteArraySet; contentBufferedPositionUs = loadingPeriod.durationUs; } } - return C.usToMs( + return Util.usToMs( periodPositionUsToWindowPositionUs( playbackInfo.timeline, playbackInfo.loadingMediaPeriodId, contentBufferedPositionUs)); } @@ -1136,7 +1136,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private long getCurrentPositionUsInternal(PlaybackInfo playbackInfo) { if (playbackInfo.timeline.isEmpty()) { - return C.msToUs(maskingWindowPositionMs); + return Util.msToUs(maskingWindowPositionMs); } else if (playbackInfo.periodId.isAd()) { return playbackInfo.positionUs; } else { @@ -1425,8 +1425,8 @@ import java.util.concurrent.CopyOnWriteArraySet; oldMediaItem, oldPeriodUid, oldPeriodIndex, - C.usToMs(oldPositionUs), - C.usToMs(oldContentPositionUs), + Util.usToMs(oldPositionUs), + Util.usToMs(oldContentPositionUs), oldPlaybackInfo.periodId.adGroupIndex, oldPlaybackInfo.periodId.adIndexInAdGroup); } @@ -1444,7 +1444,7 @@ import java.util.concurrent.CopyOnWriteArraySet; newWindowUid = playbackInfo.timeline.getWindow(newWindowIndex, window).uid; newMediaItem = window.mediaItem; } - long positionMs = C.usToMs(discontinuityWindowStartPositionUs); + long positionMs = Util.usToMs(discontinuityWindowStartPositionUs); return new PositionInfo( newWindowUid, newWindowIndex, @@ -1453,7 +1453,7 @@ import java.util.concurrent.CopyOnWriteArraySet; newPeriodIndex, positionMs, /* contentPositionMs= */ playbackInfo.periodId.isAd() - ? C.usToMs(getRequestedContentPositionUs(playbackInfo)) + ? Util.usToMs(getRequestedContentPositionUs(playbackInfo)) : positionMs, playbackInfo.periodId.adGroupIndex, playbackInfo.periodId.adIndexInAdGroup); @@ -1567,7 +1567,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState); internalPlayer.setMediaSources( - holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder); + holders, startWindowIndex, Util.msToUs(startPositionMs), shuffleOrder); boolean positionDiscontinuity = !playbackInfo.periodId.periodUid.equals(newPlaybackInfo.periodId.periodUid) && !playbackInfo.timeline.isEmpty(); @@ -1647,7 +1647,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline.isEmpty()) { // Reset periodId and loadingPeriodId. MediaPeriodId dummyMediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline(); - long positionUs = C.msToUs(maskingWindowPositionMs); + long positionUs = Util.msToUs(maskingWindowPositionMs); playbackInfo = playbackInfo.copyWithNewPosition( dummyMediaPeriodId, @@ -1668,7 +1668,7 @@ import java.util.concurrent.CopyOnWriteArraySet; MediaPeriodId newPeriodId = playingPeriodChanged ? new MediaPeriodId(periodPosition.first) : playbackInfo.periodId; long newContentPositionUs = periodPosition.second; - long oldContentPositionUs = C.msToUs(getContentPosition()); + long oldContentPositionUs = Util.msToUs(getContentPosition()); if (!oldTimeline.isEmpty()) { oldContentPositionUs -= oldTimeline.getPeriodByUid(oldPeriodUid, period).getPositionInWindowUs(); @@ -1757,7 +1757,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Nullable Pair oldPeriodPosition = oldTimeline.getPeriodPosition( - window, period, currentWindowIndex, C.msToUs(currentPositionMs)); + window, period, currentWindowIndex, Util.msToUs(currentPositionMs)); Object periodUid = castNonNull(oldPeriodPosition).first; if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) { // The old period position is still available in the new timeline. @@ -1798,7 +1798,7 @@ import java.util.concurrent.CopyOnWriteArraySet; windowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); windowPositionMs = timeline.getWindow(windowIndex, window).getDefaultPositionMs(); } - return timeline.getPeriodPosition(window, period, windowIndex, C.msToUs(windowPositionMs)); + return timeline.getPeriodPosition(window, period, windowIndex, Util.msToUs(windowPositionMs)); } private long periodPositionUsToWindowPositionUs( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 02cf785eed..30085aeb9a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -1082,7 +1082,7 @@ import java.util.concurrent.atomic.AtomicBoolean; if (window.windowStartTimeMs == C.TIME_UNSET || !window.isLive() || !window.isDynamic) { return C.TIME_UNSET; } - return C.msToUs(window.getCurrentUnixTimeMs() - window.windowStartTimeMs) + return Util.msToUs(window.getCurrentUnixTimeMs() - window.windowStartTimeMs) - (periodPositionUs + period.getPositionInWindowUs()); } @@ -1184,7 +1184,7 @@ import java.util.concurrent.atomic.AtomicBoolean; playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs( newPeriodPositionUs, seekParameters); } - if (C.usToMs(newPeriodPositionUs) == C.usToMs(playbackInfo.positionUs) + if (Util.usToMs(newPeriodPositionUs) == Util.usToMs(playbackInfo.positionUs) && (playbackInfo.playbackState == Player.STATE_BUFFERING || playbackInfo.playbackState == Player.STATE_READY)) { // Seek will be performed to the current position. Do nothing. @@ -2710,7 +2710,7 @@ import java.util.concurrent.atomic.AtomicBoolean; long requestPositionUs = pendingMessageInfo.message.getPositionMs() == C.TIME_END_OF_SOURCE ? C.TIME_UNSET - : C.msToUs(pendingMessageInfo.message.getPositionMs()); + : Util.msToUs(pendingMessageInfo.message.getPositionMs()); @Nullable Pair periodPosition = resolveSeekPosition( 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 6a061bef5f..f0637df30a 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 @@ -455,7 +455,7 @@ public class SimpleExoPlayer extends BasePlayer if (Util.SDK_INT < 21) { audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); } else { - audioSessionId = C.generateAudioSessionIdV21(applicationContext); + audioSessionId = Util.generateAudioSessionIdV21(applicationContext); } currentCues = Collections.emptyList(); throwsWhenUsingWrongThread = true; @@ -770,7 +770,7 @@ public class SimpleExoPlayer extends BasePlayer if (Util.SDK_INT < 21) { audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); } else { - audioSessionId = C.generateAudioSessionIdV21(applicationContext); + audioSessionId = Util.generateAudioSessionIdV21(applicationContext); } } else if (Util.SDK_INT < 21) { // We need to re-initialize keepSessionIdAudioTrack to make sure the session is kept alive for diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 4d1bcd457f..ae22609e29 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -1157,7 +1157,7 @@ public class AnalyticsCollector : playerTimeline .getPeriod(playerPeriodIndex, period) .getAdGroupIndexAfterPositionUs( - C.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs()); + Util.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs()); for (int i = 0; i < mediaPeriodQueue.size(); i++) { MediaPeriodId mediaPeriodId = mediaPeriodQueue.get(i); if (isMatchingMediaPeriod( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java index 7d4d6087dc..2403f9bf77 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.analytics; -import static com.google.android.exoplayer2.C.usToMs; import static java.lang.Math.max; import android.util.Base64; @@ -141,7 +140,7 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag contentSession.isCreated = true; eventTime.timeline.getPeriodByUid(eventTime.mediaPeriodId.periodUid, period); long adGroupPositionMs = - usToMs(period.getAdGroupTimeUs(eventTime.mediaPeriodId.adGroupIndex)) + Util.usToMs(period.getAdGroupTimeUs(eventTime.mediaPeriodId.adGroupIndex)) + period.getPositionInWindowMs(); // getAdGroupTimeUs may return 0 for prerolls despite period offset. adGroupPositionMs = max(0, adGroupPositionMs); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java index 343fa4ce76..868bfb617c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java @@ -333,7 +333,7 @@ public final class PlaybackStatsListener eventTime.mediaPeriodId.periodUid, eventTime.mediaPeriodId.windowSequenceNumber, eventTime.mediaPeriodId.adGroupIndex), - /* eventPlaybackPositionMs= */ C.usToMs(contentWindowPositionUs), + /* eventPlaybackPositionMs= */ Util.usToMs(contentWindowPositionUs), eventTime.timeline, eventTime.currentWindowIndex, eventTime.currentMediaPeriodId, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java index 6c26d87fcf..9c8186ffa6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java @@ -302,12 +302,12 @@ import java.lang.reflect.Method; if (!notifiedPositionIncreasing && positionUs > lastPositionUs) { notifiedPositionIncreasing = true; - long mediaDurationSinceLastPositionUs = C.usToMs(positionUs - lastPositionUs); + long mediaDurationSinceLastPositionUs = Util.usToMs(positionUs - lastPositionUs); long playoutDurationSinceLastPositionUs = Util.getPlayoutDurationForMediaDuration( mediaDurationSinceLastPositionUs, audioTrackPlaybackSpeed); long playoutStartSystemTimeMs = - System.currentTimeMillis() - C.usToMs(playoutDurationSinceLastPositionUs); + System.currentTimeMillis() - Util.usToMs(playoutDurationSinceLastPositionUs); listener.onPositionAdvancing(playoutStartSystemTimeMs); } @@ -357,7 +357,7 @@ import java.lang.reflect.Method; boolean hadData = hasData; hasData = hasPendingData(writtenFrames); if (hadData && !hasData && playState != PLAYSTATE_STOPPED) { - listener.onUnderrun(bufferSize, C.usToMs(bufferSizeUs)); + listener.onUnderrun(bufferSize, Util.usToMs(bufferSizeUs)); } return true; @@ -379,7 +379,7 @@ import java.lang.reflect.Method; /** Returns the duration of audio that is buffered but unplayed. */ public long getPendingBufferDurationMs(long writtenFrames) { - return C.usToMs(framesToDurationUs(writtenFrames - getPlaybackHeadPosition())); + return Util.usToMs(framesToDurationUs(writtenFrames - getPlaybackHeadPosition())); } /** Returns whether the track is in an invalid state and must be recreated. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmUtil.java index bd89d2d373..1b2237bf1b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmUtil.java @@ -23,7 +23,6 @@ import androidx.annotation.DoNotInline; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; @@ -122,7 +121,7 @@ public final class DrmUtil { @Nullable String diagnosticsInfo = ((MediaDrm.MediaDrmStateException) throwable).getDiagnosticInfo(); int drmErrorCode = Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticsInfo); - return C.getErrorCodeForMediaDrmErrorCode(drmErrorCode); + return Util.getErrorCodeForMediaDrmErrorCode(drmErrorCode); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 389e675c59..3a476229e1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -1240,7 +1240,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } catch (CryptoException e) { throw createRendererException( - e, inputFormat, C.getErrorCodeForMediaDrmErrorCode(e.getErrorCode())); + e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode())); } return false; } @@ -1312,7 +1312,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } catch (CryptoException e) { throw createRendererException( - e, inputFormat, C.getErrorCodeForMediaDrmErrorCode(e.getErrorCode())); + e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode())); } resetInputBuffer(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index b81d222221..f86b81760d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -338,7 +339,7 @@ public final class ClippingMediaSource extends CompositeMediaSource { endUs == C.TIME_UNSET ? window.defaultPositionUs : min(window.defaultPositionUs, endUs); window.defaultPositionUs -= startUs; } - long startMs = C.usToMs(startUs); + long startMs = Util.usToMs(startUs); if (window.presentationStartTimeMs != C.TIME_UNSET) { window.presentationStartTimeMs += startMs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java index 13f7562576..d1bcf9380c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; @@ -472,7 +473,7 @@ public interface MediaSourceEventListener { } private long adjustMediaTime(long mediaTimeUs) { - long mediaTimeMs = C.usToMs(mediaTimeUs); + long mediaTimeMs = Util.usToMs(mediaTimeUs); return mediaTimeMs == C.TIME_UNSET ? C.TIME_UNSET : mediaTimeOffsetMs + mediaTimeMs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java index c30476405d..4c0b795f80 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java @@ -641,8 +641,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /* trackFormat= */ null, C.SELECTION_REASON_UNKNOWN, /* trackSelectionData= */ null, - /* mediaStartTimeMs= */ C.usToMs(loadable.seekTimeUs), - C.usToMs(durationUs)); + /* mediaStartTimeMs= */ Util.usToMs(loadable.seekTimeUs), + Util.usToMs(durationUs)); LoadErrorAction loadErrorAction; long retryDelayMs = loadErrorHandlingPolicy.getRetryDelayMsFor( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java index a018f94de2..e589e8cca4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -282,7 +283,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; C.SELECTION_REASON_UNKNOWN, /* trackSelectionData= */ null, /* mediaStartTimeMs= */ 0, - C.usToMs(durationUs)); + Util.usToMs(durationUs)); long retryDelay = loadErrorHandlingPolicy.getRetryDelayMsFor( new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java index 69588f504c..1a5d94ad1f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java @@ -512,7 +512,7 @@ public final class ServerSideInsertedAdsMediaSource extends BaseMediaSource if (mediaPositionMs == C.TIME_UNSET) { return C.TIME_UNSET; } - long mediaPositionUs = C.msToUs(mediaPositionMs); + long mediaPositionUs = Util.msToUs(mediaPositionMs); MediaPeriodId id = mediaPeriod.mediaPeriodId; long correctedPositionUs = id.isAd() @@ -522,7 +522,7 @@ public final class ServerSideInsertedAdsMediaSource extends BaseMediaSource // content pieces (beyond nextAdGroupIndex). : getMediaPeriodPositionUsForContent( mediaPositionUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState); - return C.usToMs(correctedPositionUs); + return Util.usToMs(correctedPositionUs); } private static final class SharedMediaPeriod implements MediaPeriod.Callback { @@ -591,7 +591,7 @@ public final class ServerSideInsertedAdsMediaSource extends BaseMediaSource MediaPeriodImpl mediaPeriod = mediaPeriods.get(i); long startTimeInPeriodUs = getMediaPeriodPositionUs( - C.msToUs(mediaLoadData.mediaStartTimeMs), + Util.msToUs(mediaLoadData.mediaStartTimeMs), mediaPeriod.mediaPeriodId, adPlaybackState); long mediaPeriodEndPositionUs = getMediaPeriodEndPositionUs(mediaPeriod, adPlaybackState); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java index 65567d005b..48ba8da7aa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java @@ -124,11 +124,12 @@ public final class ServerSideInsertedAdsUtil { if (player.isPlayingAd()) { int adGroupIndex = player.getCurrentAdGroupIndex(); int adIndexInAdGroup = player.getCurrentAdIndexInAdGroup(); - long adPositionUs = C.msToUs(player.getCurrentPosition()); + long adPositionUs = Util.msToUs(player.getCurrentPosition()); return getStreamPositionUsForAd( adPositionUs, adGroupIndex, adIndexInAdGroup, adPlaybackState); } - long periodPositionUs = C.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs(); + long periodPositionUs = + Util.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs(); return getStreamPositionUsForContent( periodPositionUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index a44a0615c4..9df2e489f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -510,8 +510,8 @@ public class ChunkSampleStream loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, - C.usToMs(loadable.startTimeUs), - C.usToMs(loadable.endTimeUs)); + Util.usToMs(loadable.startTimeUs), + Util.usToMs(loadable.endTimeUs)); LoadErrorInfo loadErrorInfo = new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index 16801c6bab..62a78ca42e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -277,7 +277,7 @@ public class EventLogger implements AnalyticsListener { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { String status = getTrackStatusString(trackSelection, trackGroup, trackIndex); String formatSupport = - C.getFormatSupportString( + Util.getFormatSupportString( mappedTrackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex)); logd( " " @@ -315,7 +315,7 @@ public class EventLogger implements AnalyticsListener { TrackGroup trackGroup = unassociatedTrackGroups.get(groupIndex); for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { String status = getTrackStatusString(false); - String formatSupport = C.getFormatSupportString(C.FORMAT_UNSUPPORTED_TYPE); + String formatSupport = Util.getFormatSupportString(C.FORMAT_UNSUPPORTED_TYPE); logd( " " + status diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java index 4c4d4d114b..46806cb65a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.util; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.PlaybackParameters; /** @@ -75,7 +74,7 @@ public final class StandaloneMediaClock implements MediaClock { if (started) { long elapsedSinceBaseMs = clock.elapsedRealtime() - baseElapsedMs; if (playbackParameters.speed == 1f) { - positionUs += C.msToUs(elapsedSinceBaseMs); + positionUs += Util.msToUs(elapsedSinceBaseMs); } else { // Add the media time in microseconds that will elapse in elapsedSinceBaseMs milliseconds of // wallclock time diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java index e0e49d478b..b4af6cfb46 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java @@ -50,6 +50,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.util.TraceUtil; +import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -558,7 +559,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { frameMetadataListener.onVideoFrameAboutToBeRendered( presentationTimeUs, System.nanoTime(), outputFormat, /* mediaFormat= */ null); } - lastRenderTimeUs = C.msToUs(SystemClock.elapsedRealtime() * 1000); + lastRenderTimeUs = Util.msToUs(SystemClock.elapsedRealtime() * 1000); int bufferMode = outputBuffer.mode; boolean renderSurface = bufferMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && outputSurface != null; boolean renderYuv = bufferMode == C.VIDEO_OUTPUT_MODE_YUV && outputBufferRenderer != null; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java b/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java index 1cebbbd011..f89e7b3f48 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.DefaultLoadControl.Builder; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.upstream.DefaultAllocator; +import com.google.android.exoplayer2.util.Util; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,7 +32,7 @@ import org.junit.runner.RunWith; public class DefaultLoadControlTest { private static final float SPEED = 1f; - private static final long MAX_BUFFER_US = C.msToUs(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS); + private static final long MAX_BUFFER_US = Util.msToUs(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS); private static final long MIN_BUFFER_US = MAX_BUFFER_US / 2; private static final int TARGET_BUFFER_BYTES = C.DEFAULT_BUFFER_SEGMENT_SIZE * 2; @@ -64,8 +65,8 @@ public class DefaultLoadControlTest { @Test public void shouldNotContinueLoadingOnceBufferingStopped_untilBelowMinBuffer() { builder.setBufferDurationsMs( - /* minBufferMs= */ (int) C.usToMs(MIN_BUFFER_US), - /* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US), + /* minBufferMs= */ (int) Util.usToMs(MIN_BUFFER_US), + /* maxBufferMs= */ (int) Util.usToMs(MAX_BUFFER_US), /* bufferForPlaybackMs= */ 0, /* bufferForPlaybackAfterRebufferMs= */ 0); build(); @@ -88,7 +89,7 @@ public class DefaultLoadControlTest { public void continueLoadingOnceBufferingStopped_andBufferAlmostEmpty_evenIfMinBufferNotReached() { builder.setBufferDurationsMs( /* minBufferMs= */ 0, - /* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US), + /* maxBufferMs= */ (int) Util.usToMs(MAX_BUFFER_US), /* bufferForPlaybackMs= */ 0, /* bufferForPlaybackAfterRebufferMs= */ 0); build(); @@ -107,8 +108,8 @@ public class DefaultLoadControlTest { public void shouldContinueLoadingWithTargetBufferBytesReached_untilMinBufferReached() { builder.setPrioritizeTimeOverSizeThresholds(true); builder.setBufferDurationsMs( - /* minBufferMs= */ (int) C.usToMs(MIN_BUFFER_US), - /* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US), + /* minBufferMs= */ (int) Util.usToMs(MIN_BUFFER_US), + /* maxBufferMs= */ (int) Util.usToMs(MAX_BUFFER_US), /* bufferForPlaybackMs= */ 0, /* bufferForPlaybackAfterRebufferMs= */ 0); build(); @@ -158,8 +159,8 @@ public class DefaultLoadControlTest { @Test public void shouldContinueLoadingWithMinBufferReached_inFastPlayback() { builder.setBufferDurationsMs( - /* minBufferMs= */ (int) C.usToMs(MIN_BUFFER_US), - /* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US), + /* minBufferMs= */ (int) Util.usToMs(MIN_BUFFER_US), + /* maxBufferMs= */ (int) Util.usToMs(MAX_BUFFER_US), /* bufferForPlaybackMs= */ 0, /* bufferForPlaybackAfterRebufferMs= */ 0); build(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java index 9be4c76e62..38c41b5d68 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParametersListener; import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer; +import com.google.android.exoplayer2.util.Util; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -263,7 +264,7 @@ public class DefaultMediaClockTest { mediaClock.onRendererDisabled(mediaClockRenderer); fakeClock.advanceTime(SLEEP_TIME_MS); assertThat(mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false)) - .isEqualTo(TEST_POSITION_US + C.msToUs(SLEEP_TIME_MS)); + .isEqualTo(TEST_POSITION_US + Util.msToUs(SLEEP_TIME_MS)); assertClockIsRunning(/* isReadingAhead= */ false); } @@ -329,7 +330,7 @@ public class DefaultMediaClockTest { mediaClockRenderer.positionUs = TEST_POSITION_US; mediaClock.onRendererDisabled(mediaClockRenderer); assertThat(mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false)) - .isEqualTo(C.msToUs(fakeClock.elapsedRealtime())); + .isEqualTo(Util.msToUs(fakeClock.elapsedRealtime())); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 07f61f4e5e..64044b2a3f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -145,6 +145,7 @@ import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Range; @@ -2765,7 +2766,7 @@ public final class ExoPlayerTest { // Ensure next period is pre-buffered by playing until end of first period. .playUntilPosition( /* windowIndex= */ 0, - /* positionMs= */ C.usToMs(TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US)) + /* positionMs= */ Util.usToMs(TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US)) .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2)) .waitForTimelineChanged( timeline2, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) @@ -2857,7 +2858,7 @@ public final class ExoPlayerTest { /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */ TimelineWindowDefinition .DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US - + C.msToUs(adGroupWindowTimeMs)); + + Util.msToUs(adGroupWindowTimeMs)); Timeline timeline = new FakeTimeline( new TimelineWindowDefinition( @@ -2865,7 +2866,7 @@ public final class ExoPlayerTest { /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(contentDurationMs), + /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); AtomicBoolean hasCreatedAdMediaPeriod = new AtomicBoolean(); FakeMediaSource mediaSource = @@ -3268,7 +3269,7 @@ public final class ExoPlayerTest { assertThat(positionAtDiscontinuityMs.get()).isAtLeast(0L); assertThat(clockAtDiscontinuityMs.get() - clockAtStartMs.get()) - .isAtLeast(C.usToMs(expectedDurationUs)); + .isAtLeast(Util.usToMs(expectedDurationUs)); } @Test @@ -3487,7 +3488,8 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(bufferedPositionAtFirstDiscontinuityMs.get()).isEqualTo(C.usToMs(windowDurationUs)); + assertThat(bufferedPositionAtFirstDiscontinuityMs.get()) + .isEqualTo(Util.usToMs(windowDurationUs)); } @Test @@ -4603,7 +4605,7 @@ public final class ExoPlayerTest { ImmutableList.of( oneByteSample(windowOffsetInFirstPeriodUs, C.BUFFER_FLAG_KEY_FRAME), oneByteSample( - windowOffsetInFirstPeriodUs + C.msToUs(maxBufferedPositionMs), + windowOffsetInFirstPeriodUs + Util.msToUs(maxBufferedPositionMs), C.BUFFER_FLAG_KEY_FRAME)), mediaSourceEventDispatcher, drmSessionManager, @@ -4623,7 +4625,7 @@ public final class ExoPlayerTest { adPlaybackState = adPlaybackState.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY); long[][] durationsUs = new long[1][]; - durationsUs[0] = new long[] {C.msToUs(adDurationMs)}; + durationsUs[0] = new long[] {Util.msToUs(adDurationMs)}; adPlaybackState = adPlaybackState.withAdDurationsUs(durationsUs); Timeline adTimeline = new FakeTimeline( @@ -4632,7 +4634,7 @@ public final class ExoPlayerTest { /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(contentDurationMs), + /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline); int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4724,7 +4726,7 @@ public final class ExoPlayerTest { adPlaybackState = adPlaybackState.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY); long[][] durationsUs = new long[1][]; - durationsUs[0] = new long[] {C.msToUs(adDurationMs)}; + durationsUs[0] = new long[] {Util.msToUs(adDurationMs)}; adPlaybackState = adPlaybackState.withAdDurationsUs(durationsUs); Timeline adTimeline = new FakeTimeline( @@ -4733,7 +4735,7 @@ public final class ExoPlayerTest { /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(contentDurationMs), + /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline); int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4805,7 +4807,7 @@ public final class ExoPlayerTest { .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY); long[][] durationsUs = new long[1][]; - durationsUs[0] = new long[] {C.msToUs(adDurationMs)}; + durationsUs[0] = new long[] {Util.msToUs(adDurationMs)}; adPlaybackState = adPlaybackState.withAdDurationsUs(durationsUs); Timeline adTimeline = new FakeTimeline( @@ -4814,7 +4816,7 @@ public final class ExoPlayerTest { /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(contentDurationMs), + /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline); @@ -5037,7 +5039,7 @@ public final class ExoPlayerTest { /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000), + /* durationUs= */ Util.msToUs(10000), adPlaybackState)); // Simulate the second ad not being prepared. FakeMediaSource mediaSource = @@ -5078,14 +5080,14 @@ public final class ExoPlayerTest { /* id= */ 1, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); TimelineWindowDefinition secondWindowDefinition = new TimelineWindowDefinition( /* periodCount= */ 1, /* id= */ 2, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); Timeline timeline1 = new FakeTimeline(firstWindowDefinition); Timeline timeline2 = new FakeTimeline(secondWindowDefinition); MediaSource mediaSource1 = new FakeMediaSource(timeline1); @@ -5128,21 +5130,21 @@ public final class ExoPlayerTest { /* id= */ 1, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); TimelineWindowDefinition secondWindowDefinition = new TimelineWindowDefinition( /* periodCount= */ 1, /* id= */ 2, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); TimelineWindowDefinition thirdWindowDefinition = new TimelineWindowDefinition( /* periodCount= */ 1, /* id= */ 3, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); Timeline timeline1 = new FakeTimeline(firstWindowDefinition); Timeline timeline2 = new FakeTimeline(secondWindowDefinition); Timeline timeline3 = new FakeTimeline(thirdWindowDefinition); @@ -5188,21 +5190,21 @@ public final class ExoPlayerTest { /* id= */ 1, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); TimelineWindowDefinition secondWindowDefinition = new TimelineWindowDefinition( /* periodCount= */ 1, /* id= */ 2, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); TimelineWindowDefinition thirdWindowDefinition = new TimelineWindowDefinition( /* periodCount= */ 1, /* id= */ 3, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10000)); + /* durationUs= */ Util.msToUs(10000)); Timeline timeline1 = new FakeTimeline(firstWindowDefinition); Timeline timeline2 = new FakeTimeline(secondWindowDefinition); Timeline timeline3 = new FakeTimeline(thirdWindowDefinition); @@ -8354,7 +8356,7 @@ public final class ExoPlayerTest { new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY) - .withAdDurationsUs(/* adDurationUs= */ new long[][] {{C.msToUs(4_000)}}); + .withAdDurationsUs(/* adDurationUs= */ new long[][] {{Util.msToUs(4_000)}}); Timeline adTimeline = new FakeTimeline( new TimelineWindowDefinition( @@ -8362,7 +8364,7 @@ public final class ExoPlayerTest { /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10_000), + /* durationUs= */ Util.msToUs(10_000), adPlaybackState)); ExoPlayer player = new TestExoPlayerBuilder(context).build(); @@ -8391,7 +8393,7 @@ public final class ExoPlayerTest { new TimelineWindowDefinition( /* isSeekable= */ false, /* isDynamic= */ false, - /* durationUs= */ C.msToUs(10_000))); + /* durationUs= */ Util.msToUs(10_000))); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addMediaSource(new FakeMediaSource(timelineWithUnseekableWindow)); @@ -9107,7 +9109,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9156,7 +9158,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9201,7 +9203,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9248,7 +9250,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9266,7 +9268,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs + 50_000), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs + 50_000), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9330,7 +9332,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 20 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9383,7 +9385,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9427,7 +9429,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9445,7 +9447,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9493,7 +9495,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9511,7 +9513,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder() .setUri(Uri.EMPTY) @@ -9599,7 +9601,7 @@ public final class ExoPlayerTest { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder().setUri(Uri.EMPTY).build())); player.pause(); @@ -10808,7 +10810,7 @@ public final class ExoPlayerTest { new TimelineWindowDefinition( /* isSeekable= */ true, /* isDynamic= */ true, - /* durationUs= */ C.msToUs(3 * C.DEFAULT_SEEK_BACK_INCREMENT_MS))); + /* durationUs= */ Util.msToUs(3 * C.DEFAULT_SEEK_BACK_INCREMENT_MS))); player.setMediaSource(new FakeMediaSource(fakeTimeline)); player.prepare(); @@ -10855,7 +10857,7 @@ public final class ExoPlayerTest { new TimelineWindowDefinition( /* isSeekable= */ true, /* isDynamic= */ true, - /* durationUs= */ C.msToUs(C.DEFAULT_SEEK_BACK_INCREMENT_MS))); + /* durationUs= */ Util.msToUs(C.DEFAULT_SEEK_BACK_INCREMENT_MS))); player.setMediaSource(new FakeMediaSource(fakeTimeline)); player.prepare(); @@ -10878,7 +10880,7 @@ public final class ExoPlayerTest { new TimelineWindowDefinition( /* isSeekable= */ true, /* isDynamic= */ true, - /* durationUs= */ C.msToUs(2 * C.DEFAULT_SEEK_FORWARD_INCREMENT_MS))); + /* durationUs= */ Util.msToUs(2 * C.DEFAULT_SEEK_FORWARD_INCREMENT_MS))); player.setMediaSource(new FakeMediaSource(fakeTimeline)); player.prepare(); @@ -10915,7 +10917,7 @@ public final class ExoPlayerTest { new TimelineWindowDefinition( /* isSeekable= */ true, /* isDynamic= */ true, - /* durationUs= */ C.msToUs(C.DEFAULT_SEEK_FORWARD_INCREMENT_MS / 2))); + /* durationUs= */ Util.msToUs(C.DEFAULT_SEEK_FORWARD_INCREMENT_MS / 2))); player.setMediaSource(new FakeMediaSource(fakeTimeline)); player.prepare(); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index af0ee0ef22..327cbb308f 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -907,7 +907,7 @@ public final class DashMediaSource extends BaseMediaSource { int lastPeriodIndex = manifest.getPeriodCount() - 1; Period lastPeriod = manifest.getPeriod(lastPeriodIndex); long lastPeriodDurationUs = manifest.getPeriodDurationUs(lastPeriodIndex); - long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); + long nowUnixTimeUs = Util.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); long windowStartTimeInManifestUs = getAvailableStartTimeInManifestUs( firstPeriod, manifest.getPeriodDurationUs(0), nowUnixTimeUs); @@ -917,7 +917,7 @@ public final class DashMediaSource extends BaseMediaSource { if (windowChangingImplicitly && manifest.timeShiftBufferDepthMs != C.TIME_UNSET) { // Update the available start time to reflect the manifest's time shift buffer depth. long timeShiftBufferStartTimeInManifestUs = - windowEndTimeInManifestUs - C.msToUs(manifest.timeShiftBufferDepthMs); + windowEndTimeInManifestUs - Util.msToUs(manifest.timeShiftBufferDepthMs); windowStartTimeInManifestUs = max(windowStartTimeInManifestUs, timeShiftBufferStartTimeInManifestUs); } @@ -927,11 +927,13 @@ public final class DashMediaSource extends BaseMediaSource { if (manifest.dynamic) { checkState(manifest.availabilityStartTimeMs != C.TIME_UNSET); long nowInWindowUs = - nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs) - windowStartTimeInManifestUs; + nowUnixTimeUs + - Util.msToUs(manifest.availabilityStartTimeMs) + - windowStartTimeInManifestUs; updateMediaItemLiveConfiguration(nowInWindowUs, windowDurationUs); windowStartUnixTimeMs = - manifest.availabilityStartTimeMs + C.usToMs(windowStartTimeInManifestUs); - windowDefaultPositionUs = nowInWindowUs - C.msToUs(liveConfiguration.targetOffsetMs); + manifest.availabilityStartTimeMs + Util.usToMs(windowStartTimeInManifestUs); + windowDefaultPositionUs = nowInWindowUs - Util.msToUs(liveConfiguration.targetOffsetMs); long minimumWindowDefaultPositionUs = min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); if (windowDefaultPositionUs < minimumWindowDefaultPositionUs) { @@ -941,7 +943,7 @@ public final class DashMediaSource extends BaseMediaSource { windowDefaultPositionUs = minimumWindowDefaultPositionUs; } } - long offsetInFirstPeriodUs = windowStartTimeInManifestUs - C.msToUs(firstPeriod.startMs); + long offsetInFirstPeriodUs = windowStartTimeInManifestUs - Util.msToUs(firstPeriod.startMs); DashTimeline timeline = new DashTimeline( manifest.availabilityStartTimeMs, @@ -995,7 +997,7 @@ public final class DashMediaSource extends BaseMediaSource { && manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) { maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs; } else { - maxLiveOffsetMs = C.usToMs(nowInWindowUs); + maxLiveOffsetMs = Util.usToMs(nowInWindowUs); } long minLiveOffsetMs; if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) { @@ -1004,7 +1006,7 @@ public final class DashMediaSource extends BaseMediaSource { && manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) { minLiveOffsetMs = manifest.serviceDescription.minOffsetMs; } else { - minLiveOffsetMs = C.usToMs(nowInWindowUs - windowDurationUs); + minLiveOffsetMs = Util.usToMs(nowInWindowUs - windowDurationUs); if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) { // The current time is in the window, so assume all clocks are synchronized and set the // minimum to a live offset of zero. @@ -1033,7 +1035,7 @@ public final class DashMediaSource extends BaseMediaSource { long safeDistanceFromWindowStartUs = min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); long maxTargetOffsetForSafeDistanceToWindowStartMs = - C.usToMs(nowInWindowUs - safeDistanceFromWindowStartUs); + Util.usToMs(nowInWindowUs - safeDistanceFromWindowStartUs); targetOffsetMs = Util.constrainValue( maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs); @@ -1097,11 +1099,11 @@ public final class DashMediaSource extends BaseMediaSource { DashManifest manifest, long nowUnixTimeMs) { int periodIndex = manifest.getPeriodCount() - 1; Period period = manifest.getPeriod(periodIndex); - long periodStartUs = C.msToUs(period.startMs); + long periodStartUs = Util.msToUs(period.startMs); long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); - long nowUnixTimeUs = C.msToUs(nowUnixTimeMs); - long availabilityStartTimeUs = C.msToUs(manifest.availabilityStartTimeMs); - long intervalUs = C.msToUs(DEFAULT_NOTIFY_MANIFEST_INTERVAL_MS); + long nowUnixTimeUs = Util.msToUs(nowUnixTimeMs); + long availabilityStartTimeUs = Util.msToUs(manifest.availabilityStartTimeMs); + long intervalUs = Util.msToUs(DEFAULT_NOTIFY_MANIFEST_INTERVAL_MS); for (int i = 0; i < period.adaptationSets.size(); i++) { List representations = period.adaptationSets.get(i).representations; if (representations.isEmpty()) { @@ -1127,7 +1129,7 @@ public final class DashMediaSource extends BaseMediaSource { private static long getAvailableStartTimeInManifestUs( Period period, long periodDurationUs, long nowUnixTimeUs) { - long periodStartTimeInManifestUs = C.msToUs(period.startMs); + long periodStartTimeInManifestUs = Util.msToUs(period.startMs); long availableStartTimeInManifestUs = periodStartTimeInManifestUs; boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); for (int i = 0; i < period.adaptationSets.size(); i++) { @@ -1159,7 +1161,7 @@ public final class DashMediaSource extends BaseMediaSource { private static long getAvailableEndTimeInManifestUs( Period period, long periodDurationUs, long nowUnixTimeUs) { - long periodStartTimeInManifestUs = C.msToUs(period.startMs); + long periodStartTimeInManifestUs = Util.msToUs(period.startMs); long availableEndTimeInManifestUs = Long.MAX_VALUE; boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); for (int i = 0; i < period.adaptationSets.size(); i++) { @@ -1266,7 +1268,7 @@ public final class DashMediaSource extends BaseMediaSource { uid, 0, manifest.getPeriodDurationUs(periodIndex), - C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs) + Util.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs) - offsetInFirstPeriodUs); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 234c0f4dfd..3d850161df 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -305,8 +305,8 @@ public class DefaultDashChunkSource implements DashChunkSource { long bufferedDurationUs = loadPositionUs - playbackPositionUs; long presentationPositionUs = - C.msToUs(manifest.availabilityStartTimeMs) - + C.msToUs(manifest.getPeriod(periodIndex).startMs) + Util.msToUs(manifest.availabilityStartTimeMs) + + Util.msToUs(manifest.getPeriod(periodIndex).startMs) + loadPositionUs; if (playerTrackEmsgHandler != null @@ -315,7 +315,7 @@ public class DefaultDashChunkSource implements DashChunkSource { return; } - long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); + long nowUnixTimeUs = Util.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); long nowPeriodTimeUs = getNowPeriodTimeUs(nowUnixTimeUs); MediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1); MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()]; @@ -600,7 +600,8 @@ public class DefaultDashChunkSource implements DashChunkSource { return manifest.availabilityStartTimeMs == C.TIME_UNSET ? C.TIME_UNSET : nowUnixTimeUs - - C.msToUs(manifest.availabilityStartTimeMs + manifest.getPeriod(periodIndex).startMs); + - Util.msToUs( + manifest.availabilityStartTimeMs + manifest.getPeriod(periodIndex).startMs); } protected Chunk newInitializationChunk( diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java index c0d8a36e9d..6bdbb0d6b0 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java @@ -20,6 +20,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.FilterableManifest; import com.google.android.exoplayer2.offline.StreamKey; +import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -132,7 +133,7 @@ public class DashManifest implements FilterableManifest { } public final long getPeriodDurationUs(int index) { - return C.msToUs(getPeriodDurationMs(index)); + return Util.msToUs(getPeriodDurationMs(index)); } @Override 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 91bf3b2eca..d997187a9d 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 @@ -967,8 +967,8 @@ public class DashManifestParser extends DefaultHandler timeline, availabilityTimeOffsetUs, segments, - C.msToUs(timeShiftBufferDepthMs), - C.msToUs(periodStartUnixTimeMs)); + Util.msToUs(timeShiftBufferDepthMs), + Util.msToUs(periodStartUnixTimeMs)); } protected SegmentTemplate parseSegmentTemplate( @@ -1057,8 +1057,8 @@ public class DashManifestParser extends DefaultHandler availabilityTimeOffsetUs, initializationTemplate, mediaTemplate, - C.msToUs(timeShiftBufferDepthMs), - C.msToUs(periodStartUnixTimeMs)); + Util.msToUs(timeShiftBufferDepthMs), + Util.msToUs(periodStartUnixTimeMs)); } /** diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java index 5bc557761d..f397b5c2be 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source.dash.offline; import static com.google.android.exoplayer2.util.Util.castNonNull; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.offline.DownloadException; @@ -38,6 +37,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.ParsingLoadable.Parser; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.util.RunnableFutureTask; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -128,7 +128,7 @@ public final class DashDownloader extends SegmentDownloader { ArrayList segments = new ArrayList<>(); for (int i = 0; i < manifest.getPeriodCount(); i++) { Period period = manifest.getPeriod(i); - long periodStartUs = C.msToUs(period.startMs); + long periodStartUs = Util.msToUs(period.startMs); long periodDurationUs = manifest.getPeriodDurationUs(i); List adaptationSets = period.adaptationSets; for (int j = 0; j < adaptationSets.size(); j++) { diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java index 88e8b3f84c..7343c07bff 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -96,7 +97,7 @@ public class DefaultDashChunkSourceTest { /* closedCaptionFormats */ ImmutableList.of(), /* playerTrackEmsgHandler= */ null); - long nowInPeriodUs = C.msToUs(nowMs - manifest.availabilityStartTimeMs); + long nowInPeriodUs = Util.msToUs(nowMs - manifest.availabilityStartTimeMs); ChunkHolder output = new ChunkHolder(); chunkSource.getNextChunk( diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java index f30b830249..2204cfccd7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java @@ -62,7 +62,7 @@ import com.google.android.exoplayer2.util.Util; this.durationUs = durationUs != C.TIME_UNSET ? durationUs - : C.msToUs(referenceTimesMs[referenceTimesMs.length - 1]); + : Util.msToUs(referenceTimesMs[referenceTimesMs.length - 1]); } @Override @@ -74,8 +74,8 @@ import com.google.android.exoplayer2.util.Util; public SeekPoints getSeekPoints(long timeUs) { timeUs = Util.constrainValue(timeUs, 0, durationUs); Pair timeMsAndPosition = - linearlyInterpolate(C.usToMs(timeUs), referenceTimesMs, referencePositions); - timeUs = C.msToUs(timeMsAndPosition.first); + linearlyInterpolate(Util.usToMs(timeUs), referenceTimesMs, referencePositions); + timeUs = Util.msToUs(timeMsAndPosition.first); long position = timeMsAndPosition.second; return new SeekPoints(new SeekPoint(timeUs, position)); } @@ -84,7 +84,7 @@ import com.google.android.exoplayer2.util.Util; public long getTimeUs(long position) { Pair positionAndTimeMs = linearlyInterpolate(position, referencePositions, referenceTimesMs); - return C.msToUs(positionAndTimeMs.second); + return Util.msToUs(positionAndTimeMs.second); } @Override diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 4e22637144..d70b6c2f90 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -586,7 +586,7 @@ public final class Mp3Extractor implements Extractor { Metadata.Entry entry = metadata.get(i); if (entry instanceof TextInformationFrame && ((TextInformationFrame) entry).id.equals("TLEN")) { - return C.msToUs(Long.parseLong(((TextInformationFrame) entry).value)); + return Util.msToUs(Long.parseLong(((TextInformationFrame) entry).value)); } } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 45c16bb8c9..56ebfb5762 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -508,7 +508,7 @@ public final class HlsMediaSource extends BaseMediaSource @Override public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist) { long windowStartTimeMs = - mediaPlaylist.hasProgramDateTime ? C.usToMs(mediaPlaylist.startTimeUs) : C.TIME_UNSET; + mediaPlaylist.hasProgramDateTime ? Util.usToMs(mediaPlaylist.startTimeUs) : C.TIME_UNSET; // For playlist types EVENT and VOD we know segments are never removed, so the presentation // started at the same time as the window. Otherwise, we don't know the presentation start time. long presentationStartTimeMs = @@ -541,7 +541,7 @@ public final class HlsMediaSource extends BaseMediaSource long targetLiveOffsetUs; if (liveConfiguration.targetOffsetMs != C.TIME_UNSET) { // Media item has a defined target offset. - targetLiveOffsetUs = C.msToUs(liveConfiguration.targetOffsetMs); + targetLiveOffsetUs = Util.msToUs(liveConfiguration.targetOffsetMs); } else { // Decide target offset from playlist. targetLiveOffsetUs = getTargetLiveOffsetUs(playlist, liveEdgeOffsetUs); @@ -607,7 +607,7 @@ public final class HlsMediaSource extends BaseMediaSource private long getLiveEdgeOffsetUs(HlsMediaPlaylist playlist) { return playlist.hasProgramDateTime - ? C.msToUs(Util.getNowUnixTimeMs(elapsedRealTimeOffsetMs)) - playlist.getEndTimeUs() + ? Util.msToUs(Util.getNowUnixTimeMs(elapsedRealTimeOffsetMs)) - playlist.getEndTimeUs() : 0; } @@ -616,7 +616,9 @@ public final class HlsMediaSource extends BaseMediaSource long startPositionUs = playlist.startOffsetUs != C.TIME_UNSET ? playlist.startOffsetUs - : playlist.durationUs + liveEdgeOffsetUs - C.msToUs(liveConfiguration.targetOffsetMs); + : playlist.durationUs + + liveEdgeOffsetUs + - Util.msToUs(liveConfiguration.targetOffsetMs); if (playlist.preciseStart) { return startPositionUs; } @@ -639,7 +641,7 @@ public final class HlsMediaSource extends BaseMediaSource } private void maybeUpdateLiveConfiguration(long targetLiveOffsetUs) { - long targetLiveOffsetMs = C.usToMs(targetLiveOffsetUs); + long targetLiveOffsetMs = Util.usToMs(targetLiveOffsetUs); if (targetLiveOffsetMs != liveConfiguration.targetOffsetMs) { liveConfiguration = liveConfiguration.buildUpon().setTargetOffsetMs(targetLiveOffsetMs).build(); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index ca72c5516d..da4fbad97d 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -916,8 +916,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, - C.usToMs(loadable.startTimeUs), - C.usToMs(loadable.endTimeUs)); + Util.usToMs(loadable.startTimeUs), + Util.usToMs(loadable.endTimeUs)); LoadErrorInfo loadErrorInfo = new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount); LoadErrorAction loadErrorAction; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/UnexpectedSampleTimestampException.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/UnexpectedSampleTimestampException.java index 50a11170a3..331af0fa7d 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/UnexpectedSampleTimestampException.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/UnexpectedSampleTimestampException.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.hls; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.chunk.MediaChunk; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** @@ -52,7 +53,7 @@ import java.io.IOException; MediaChunk mediaChunk, long lastAcceptedSampleTimeUs, long rejectedSampleTimeUs) { super( "Unexpected sample timestamp: " - + C.usToMs(rejectedSampleTimeUs) + + Util.usToMs(rejectedSampleTimeUs) + " in chunk [" + mediaChunk.startTimeUs + ", " diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index 0558105100..68d300966e 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -536,7 +536,7 @@ public final class DefaultHlsPlaylistTracker return false; } long currentTimeMs = SystemClock.elapsedRealtime(); - long snapshotValidityDurationMs = max(30000, C.usToMs(playlistSnapshot.durationUs)); + long snapshotValidityDurationMs = max(30000, Util.usToMs(playlistSnapshot.durationUs)); return playlistSnapshot.hasEndTag || playlistSnapshot.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_EVENT || playlistSnapshot.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_VOD @@ -726,7 +726,7 @@ public final class DefaultHlsPlaylistTracker forceRetry = true; playlistError = new PlaylistResetException(playlistUrl); } else if (currentTimeMs - lastSnapshotChangeMs - > C.usToMs(playlistSnapshot.targetDurationUs) + > Util.usToMs(playlistSnapshot.targetDurationUs) * playlistStuckTargetDurationCoefficient) { // TODO: Allow customization of stuck playlists handling. playlistError = new PlaylistStuckException(playlistUrl); @@ -752,7 +752,7 @@ public final class DefaultHlsPlaylistTracker ? playlistSnapshot.targetDurationUs : (playlistSnapshot.targetDurationUs / 2); } - earliestNextLoadTimeMs = currentTimeMs + C.usToMs(durationUntilNextLoadUs); + earliestNextLoadTimeMs = currentTimeMs + Util.usToMs(durationUntilNextLoadUs); // Schedule a load if this is the primary playlist or a playlist of a low-latency stream and // it doesn't have an end tag. Else the next load will be scheduled when refreshPlaylist is // called, or when this playlist becomes the primary. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index ac8bf1e5a8..250ee72754 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -844,7 +844,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser { - timelineDurationUs = C.msToUs(timing.getDurationMs()); + timelineDurationUs = Util.msToUs(timing.getDurationMs()); timelineIsSeekable = !timing.isLive(); timelineIsLive = timing.isLive(); timelineIsPlaceholder = false; diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index aabe71ce73..fd2f3a49fe 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -604,7 +604,7 @@ public final class SsMediaSource extends BaseMediaSource startTimeUs = max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); } long durationUs = endTimeUs - startTimeUs; - long defaultStartPositionUs = durationUs - C.msToUs(livePresentationDelayMs); + long defaultStartPositionUs = durationUs - Util.msToUs(livePresentationDelayMs); if (defaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) { // The default start position is too close to the start of the live window. Set it to the // minimum default start position provided the window is at least twice as big. Else set diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java index e8a217f645..76e74efa95 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MuxerWrapper.java @@ -26,6 +26,7 @@ import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; /** @@ -42,7 +43,7 @@ import java.nio.ByteBuffer; *

    The value of this constant has been chosen based on the interleaving observed in a few media * files, where continuous chunks of the same track were about 0.5 seconds long. */ - private static final long MAX_TRACK_WRITE_AHEAD_US = C.msToUs(500); + private static final long MAX_TRACK_WRITE_AHEAD_US = Util.msToUs(500); private final Muxer muxer; private final Muxer.Factory muxerFactory; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SefSlowMotionVideoSampleTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SefSlowMotionVideoSampleTransformer.java index a232d82a52..db90f38094 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SefSlowMotionVideoSampleTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SefSlowMotionVideoSampleTransformer.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData; import com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import java.util.Arrays; @@ -366,8 +367,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public final int maxLayer; public SegmentInfo(SlowMotionData.Segment segment, int inputMaxLayer, int normalSpeedLayer) { - this.startTimeUs = C.msToUs(segment.startTimeMs); - this.endTimeUs = C.msToUs(segment.endTimeMs); + this.startTimeUs = Util.msToUs(segment.startTimeMs); + this.endTimeUs = Util.msToUs(segment.endTimeMs); this.speedDivisor = segment.speedDivisor; this.maxLayer = getSlowMotionMaxLayer(speedDivisor, inputMaxLayer, normalSpeedLayer); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SegmentSpeedProvider.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SegmentSpeedProvider.java index 2320367076..91a6d49da6 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SegmentSpeedProvider.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SegmentSpeedProvider.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData.Segment; import com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedMap; import java.util.ArrayList; @@ -72,7 +73,7 @@ import java.util.TreeMap; for (int i = 0; i < segments.size(); i++) { Segment currentSegment = segments.get(i); speedsByStartTimeUs.put( - C.msToUs(currentSegment.startTimeMs), baseSpeed / currentSegment.speedDivisor); + Util.msToUs(currentSegment.startTimeMs), baseSpeed / currentSegment.speedDivisor); } // If the map has an entry at endTime, this is the next segments start time. If no such entry @@ -80,8 +81,8 @@ import java.util.TreeMap; // segment. for (int i = 0; i < segments.size(); i++) { Segment currentSegment = segments.get(i); - if (!speedsByStartTimeUs.containsKey(C.msToUs(currentSegment.endTimeMs))) { - speedsByStartTimeUs.put(C.msToUs(currentSegment.endTimeMs), baseSpeed); + if (!speedsByStartTimeUs.containsKey(Util.msToUs(currentSegment.endTimeMs))) { + speedsByStartTimeUs.put(Util.msToUs(currentSegment.endTimeMs), baseSpeed); } } 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 824b44e1f3..6adfe54b4f 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 @@ -955,7 +955,7 @@ public class PlayerControlView extends FrameLayout { int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex; for (int i = firstWindowIndex; i <= lastWindowIndex; i++) { if (i == currentWindowIndex) { - currentWindowOffset = C.usToMs(durationUs); + currentWindowOffset = Util.usToMs(durationUs); } timeline.getWindow(i, window); if (window.durationUs == C.TIME_UNSET) { @@ -982,7 +982,7 @@ public class PlayerControlView extends FrameLayout { adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, newLength); playedAdGroups = Arrays.copyOf(playedAdGroups, newLength); } - adGroupTimesMs[adGroupCount] = C.usToMs(durationUs + adGroupTimeInWindowUs); + adGroupTimesMs[adGroupCount] = Util.usToMs(durationUs + adGroupTimeInWindowUs); playedAdGroups[adGroupCount] = period.hasPlayedAdGroup(adGroupIndex); adGroupCount++; } @@ -991,7 +991,7 @@ public class PlayerControlView extends FrameLayout { durationUs += window.durationUs; } } - long durationMs = C.usToMs(durationUs); + long durationMs = Util.usToMs(durationUs); if (durationView != null) { durationView.setText(Util.getStringForTime(formatBuilder, formatter, durationMs)); } 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 30ca26f4f9..279e907476 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 @@ -1274,7 +1274,7 @@ public class StyledPlayerControlView extends FrameLayout { int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex; for (int i = firstWindowIndex; i <= lastWindowIndex; i++) { if (i == currentWindowIndex) { - currentWindowOffset = C.usToMs(durationUs); + currentWindowOffset = Util.usToMs(durationUs); } timeline.getWindow(i, window); if (window.durationUs == C.TIME_UNSET) { @@ -1301,7 +1301,7 @@ public class StyledPlayerControlView extends FrameLayout { adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, newLength); playedAdGroups = Arrays.copyOf(playedAdGroups, newLength); } - adGroupTimesMs[adGroupCount] = C.usToMs(durationUs + adGroupTimeInWindowUs); + adGroupTimesMs[adGroupCount] = Util.usToMs(durationUs + adGroupTimeInWindowUs); playedAdGroups[adGroupCount] = period.hasPlayedAdGroup(adGroupIndex); adGroupCount++; } @@ -1310,7 +1310,7 @@ public class StyledPlayerControlView extends FrameLayout { durationUs += window.durationUs; } } - long durationMs = C.usToMs(durationUs); + long durationMs = Util.usToMs(durationUs); if (durationView != null) { durationView.setText(Util.getStringForTime(formatBuilder, formatter, durationMs)); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSourceFactory.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSourceFactory.java index dc77ccccb6..0be568b2ef 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSourceFactory.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSourceFactory.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; +import com.google.android.exoplayer2.util.Util; /** Fake {@link MediaSourceFactory} that creates a {@link FakeMediaSource}. */ public class FakeMediaSourceFactory implements MediaSourceFactory { @@ -81,7 +82,7 @@ public class FakeMediaSourceFactory implements MediaSourceFactory { /* isPlaceholder= */ false, /* durationUs= */ 1000 * C.MICROS_PER_SECOND, /* defaultPositionUs= */ 2 * C.MICROS_PER_SECOND, - /* windowOffsetInFirstPeriodUs= */ C.msToUs(123456789), + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(123456789), AdPlaybackState.NONE, mediaItem); return new FakeMediaSource(new FakeTimeline(timelineWindowDefinition)); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java index 6bd498e199..720e7d550b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java @@ -367,7 +367,7 @@ public final class FakeTimeline extends Timeline { manifests[windowIndex], /* presentationStartTimeMs= */ C.TIME_UNSET, /* windowStartTimeMs= */ windowDefinition.isLive - ? C.usToMs(windowDefinition.windowOffsetInFirstPeriodUs) + ? Util.usToMs(windowDefinition.windowOffsetInFirstPeriodUs) : C.TIME_UNSET, /* elapsedRealtimeEpochOffsetMs= */ windowDefinition.isLive ? 0 : C.TIME_UNSET, windowDefinition.isSeekable, From 797518285669fb12657a441e7fd1811a3fbd048d Mon Sep 17 00:00:00 2001 From: bachinger Date: Fri, 29 Oct 2021 01:30:01 +0100 Subject: [PATCH 040/113] Make package in test manifest consistent PiperOrigin-RevId: 406255369 --- extensions/cronet/src/androidTest/AndroidManifest.xml | 4 ++-- extensions/cronet/src/test/AndroidManifest.xml | 2 +- extensions/ffmpeg/src/test/AndroidManifest.xml | 2 +- extensions/flac/src/test/AndroidManifest.xml | 2 +- extensions/opus/src/test/AndroidManifest.xml | 2 +- extensions/rtmp/src/test/AndroidManifest.xml | 2 +- extensions/vp9/src/test/AndroidManifest.xml | 2 +- library/common/src/test/AndroidManifest.xml | 2 +- library/datasource/src/test/AndroidManifest.xml | 2 +- library/decoder/src/test/AndroidManifest.xml | 2 +- playbacktests/src/androidTest/AndroidManifest.xml | 4 ++-- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/extensions/cronet/src/androidTest/AndroidManifest.xml b/extensions/cronet/src/androidTest/AndroidManifest.xml index 96e8e54f57..5ce815adae 100644 --- a/extensions/cronet/src/androidTest/AndroidManifest.xml +++ b/extensions/cronet/src/androidTest/AndroidManifest.xml @@ -16,7 +16,7 @@ + package="com.google.android.exoplayer2.ext.cronet.test"> @@ -28,7 +28,7 @@ tools:ignore="MissingApplicationIcon,HardcodedDebugMode"/> diff --git a/extensions/cronet/src/test/AndroidManifest.xml b/extensions/cronet/src/test/AndroidManifest.xml index d6e09107a7..ea3f510328 100644 --- a/extensions/cronet/src/test/AndroidManifest.xml +++ b/extensions/cronet/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/extensions/ffmpeg/src/test/AndroidManifest.xml b/extensions/ffmpeg/src/test/AndroidManifest.xml index 6ec1cea289..cb214bc41a 100644 --- a/extensions/ffmpeg/src/test/AndroidManifest.xml +++ b/extensions/ffmpeg/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/extensions/flac/src/test/AndroidManifest.xml b/extensions/flac/src/test/AndroidManifest.xml index 509151aa21..362e3986e3 100644 --- a/extensions/flac/src/test/AndroidManifest.xml +++ b/extensions/flac/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/extensions/opus/src/test/AndroidManifest.xml b/extensions/opus/src/test/AndroidManifest.xml index d17f889d17..b015221785 100644 --- a/extensions/opus/src/test/AndroidManifest.xml +++ b/extensions/opus/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/extensions/rtmp/src/test/AndroidManifest.xml b/extensions/rtmp/src/test/AndroidManifest.xml index b2e19827d9..ff41eda581 100644 --- a/extensions/rtmp/src/test/AndroidManifest.xml +++ b/extensions/rtmp/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/extensions/vp9/src/test/AndroidManifest.xml b/extensions/vp9/src/test/AndroidManifest.xml index 851213e653..08c7e6b6ee 100644 --- a/extensions/vp9/src/test/AndroidManifest.xml +++ b/extensions/vp9/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/library/common/src/test/AndroidManifest.xml b/library/common/src/test/AndroidManifest.xml index 46c19f53c9..a90b358a73 100644 --- a/library/common/src/test/AndroidManifest.xml +++ b/library/common/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/library/datasource/src/test/AndroidManifest.xml b/library/datasource/src/test/AndroidManifest.xml index 173af4332e..869326cce0 100644 --- a/library/datasource/src/test/AndroidManifest.xml +++ b/library/datasource/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/library/decoder/src/test/AndroidManifest.xml b/library/decoder/src/test/AndroidManifest.xml index 66cfd433a3..bcb61fbc63 100644 --- a/library/decoder/src/test/AndroidManifest.xml +++ b/library/decoder/src/test/AndroidManifest.xml @@ -14,6 +14,6 @@ limitations under the License. --> - + diff --git a/playbacktests/src/androidTest/AndroidManifest.xml b/playbacktests/src/androidTest/AndroidManifest.xml index 2c2099496a..5f9b5429f9 100644 --- a/playbacktests/src/androidTest/AndroidManifest.xml +++ b/playbacktests/src/androidTest/AndroidManifest.xml @@ -16,7 +16,7 @@ + package="com.google.android.exoplayer2.playbacktests.test"> @@ -32,7 +32,7 @@ From dacdf5c42d6c8031277be1e52731a0b1e548e2b5 Mon Sep 17 00:00:00 2001 From: bachinger Date: Fri, 29 Oct 2021 12:13:32 +0100 Subject: [PATCH 041/113] Defer setting defaults for rendition reports until playlist is parsed This makes sure that #EXT-X-RENDITION-REPORT tags can be placed before the list of segments/parts as well. We were previously assuming that these come at the end, which naturally would make sense and is done like this in all examples, but it is not explicitly defined by the spec. Issue: google/ExoPlayer#9592 PiperOrigin-RevId: 406329684 --- .../hls/playlist/HlsPlaylistParser.java | 34 +++-- .../playlist/HlsMediaPlaylistParserTest.java | 116 +++++++++++++++++- 2 files changed, 135 insertions(+), 15 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 250ee72754..863ea198bb 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -645,7 +645,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = new ArrayList<>(); List trailingParts = new ArrayList<>(); @Nullable Part preloadPart = null; - Map renditionReports = new HashMap<>(); + List renditionReports = new ArrayList<>(); List tags = new ArrayList<>(); long segmentDurationUs = 0; @@ -854,17 +854,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser lastParts = - trailingParts.isEmpty() ? Iterables.getLast(segments).parts : trailingParts; - int defaultPartIndex = - partTargetDurationUs != C.TIME_UNSET ? lastParts.size() - 1 : C.INDEX_UNSET; - int lastPartIndex = parseOptionalIntAttr(line, REGEX_LAST_PART, defaultPartIndex); + long lastMediaSequence = parseOptionalLongAttr(line, REGEX_LAST_MSN, C.INDEX_UNSET); + int lastPartIndex = parseOptionalIntAttr(line, REGEX_LAST_PART, C.INDEX_UNSET); String uri = parseStringAttr(line, REGEX_URI, variableDefinitions); Uri playlistUri = Uri.parse(UriUtil.resolve(baseUri, uri)); - renditionReports.put( - playlistUri, new RenditionReport(playlistUri, lastMediaSequence, lastPartIndex)); + renditionReports.add(new RenditionReport(playlistUri, lastMediaSequence, lastPartIndex)); } else if (line.startsWith(TAG_PRELOAD_HINT)) { if (preloadPart != null) { continue; @@ -1022,6 +1016,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser renditionReportMap = new HashMap<>(); + for (int i = 0; i < renditionReports.size(); i++) { + RenditionReport renditionReport = renditionReports.get(i); + long lastMediaSequence = renditionReport.lastMediaSequence; + if (lastMediaSequence == C.INDEX_UNSET) { + lastMediaSequence = mediaSequence + segments.size() - (trailingParts.isEmpty() ? 1 : 0); + } + int lastPartIndex = renditionReport.lastPartIndex; + if (lastPartIndex == C.INDEX_UNSET && partTargetDurationUs != C.TIME_UNSET) { + List lastParts = + trailingParts.isEmpty() ? Iterables.getLast(segments).parts : trailingParts; + lastPartIndex = lastParts.size() - 1; + } + renditionReportMap.put( + renditionReport.playlistUri, + new RenditionReport(renditionReport.playlistUri, lastMediaSequence, lastPartIndex)); + } + if (preloadPart != null) { trailingParts.add(preloadPart); } @@ -1046,7 +1058,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Fri, 29 Oct 2021 12:34:43 +0100 Subject: [PATCH 042/113] DefaultExtractorsFactory: lazily load flac extension PiperOrigin-RevId: 406332026 --- .../extractor/DefaultExtractorsFactory.java | 95 ++++++++++++------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index abaefe2547..a71796cbb8 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -19,6 +19,7 @@ import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromResp import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromUri; import android.net.Uri; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; @@ -46,6 +47,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; /** * An {@link ExtractorsFactory} that provides an array of extractors for the following formats: @@ -99,32 +101,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { FileTypes.JPEG, }; - @Nullable - private static final Constructor FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR; - - static { - @Nullable Constructor flacExtensionExtractorConstructor = null; - try { - @SuppressWarnings("nullness:argument") - boolean isFlacNativeLibraryAvailable = - Boolean.TRUE.equals( - Class.forName("com.google.android.exoplayer2.ext.flac.FlacLibrary") - .getMethod("isAvailable") - .invoke(/* obj= */ null)); - if (isFlacNativeLibraryAvailable) { - flacExtensionExtractorConstructor = - Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor") - .asSubclass(Extractor.class) - .getConstructor(int.class); - } - } catch (ClassNotFoundException e) { - // Expected if the app was built without the FLAC extension. - } catch (Exception e) { - // The FLAC extension is present, but instantiation failed. - throw new RuntimeException("Error instantiating FLAC extension", e); - } - FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR = flacExtensionExtractorConstructor; - } + private static final FlacExtensionLoader FLAC_EXTENSION_LOADER = new FlacExtensionLoader(); private boolean constantBitrateSeekingEnabled; private boolean constantBitrateSeekingAlwaysEnabled; @@ -377,13 +354,9 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { : 0))); break; case FileTypes.FLAC: - if (FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR != null) { - try { - extractors.add(FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR.newInstance(flacFlags)); - } catch (Exception e) { - // Should never happen. - throw new IllegalStateException("Unexpected error creating FLAC extractor", e); - } + @Nullable Extractor flacExtractor = FLAC_EXTENSION_LOADER.getExtractor(flacFlags); + if (flacExtractor != null) { + extractors.add(flacExtractor); } else { extractors.add(new FlacExtractor(flacFlags)); } @@ -430,4 +403,60 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { break; } } + + private static final class FlacExtensionLoader { + private final AtomicBoolean extensionLoaded; + + @GuardedBy("extensionLoaded") + @Nullable + private Constructor extractorConstructor; + + public FlacExtensionLoader() { + extensionLoaded = new AtomicBoolean(false); + } + + @Nullable + public Extractor getExtractor(int flags) { + @Nullable + Constructor extractorConstructor = maybeLoadExtractorConstructor(); + if (extractorConstructor == null) { + return null; + } + try { + return extractorConstructor.newInstance(flags); + } catch (Exception e) { + throw new IllegalStateException("Unexpected error creating FLAC extractor", e); + } + } + + @Nullable + private Constructor maybeLoadExtractorConstructor() { + synchronized (extensionLoaded) { + if (extensionLoaded.get()) { + return extractorConstructor; + } + try { + @SuppressWarnings("nullness:argument") + boolean isFlacNativeLibraryAvailable = + Boolean.TRUE.equals( + Class.forName("com.google.android.exoplayer2.ext.flac.FlacLibrary") + .getMethod("isAvailable") + .invoke(/* obj= */ null)); + if (isFlacNativeLibraryAvailable) { + extractorConstructor = + Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor") + .asSubclass(Extractor.class) + .getConstructor(int.class); + } + } catch (ClassNotFoundException e) { + // Expected if the app was built without the FLAC extension. + } catch (Exception e) { + // The FLAC extension is present, but instantiation failed. + throw new RuntimeException("Error instantiating FLAC extension", e); + } + extensionLoaded.set(true); + return extractorConstructor; + } + } + } } From a0f8ac7503b55ad5c4a75a06b694c9f5910972f7 Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 29 Oct 2021 13:31:12 +0000 Subject: [PATCH 043/113] ExoPlayer.Builder: lazily initialize default components Initialize default components lazily in ExoPlayer.Builder to avoid redundant component instantiations, useful in cases where apps overwrite default components with ExoPlayer.Builder setters. The fields in ExoPlayer.Builder are wrapped in a Supplier (rather than just making then nullable and initializing them in ExoPlayer.Builder.build()) so that we maintain the proguarding properties of this class. The exception is ExoPlayer.Builder.AnalyticsCollector which became nullable and is initialized in ExoPlayer.Builder.build() in order to use any Clock that has been set separately with ExoPlayer.Builder.setClock(). #minor-release PiperOrigin-RevId: 406345976 --- .../google/android/exoplayer2/ExoPlayer.java | 92 ++++++++++++------- .../android/exoplayer2/SimpleExoPlayer.java | 53 ++++++----- 2 files changed, 90 insertions(+), 55 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 11d84fc1d4..e594883c72 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import android.content.Context; @@ -59,6 +60,7 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import com.google.common.base.Supplier; import java.util.List; /** @@ -366,12 +368,12 @@ public interface ExoPlayer extends Player { /* package */ Clock clock; /* package */ long foregroundModeTimeoutMs; - /* package */ RenderersFactory renderersFactory; - /* package */ MediaSourceFactory mediaSourceFactory; - /* package */ TrackSelector trackSelector; - /* package */ LoadControl loadControl; - /* package */ BandwidthMeter bandwidthMeter; - /* package */ AnalyticsCollector analyticsCollector; + /* package */ Supplier renderersFactorySupplier; + /* package */ Supplier mediaSourceFactorySupplier; + /* package */ Supplier trackSelectorSupplier; + /* package */ Supplier loadControlSupplier; + /* package */ Supplier bandwidthMeterSupplier; + /* package */ Supplier analyticsCollectorSupplier; /* package */ Looper looper; @Nullable /* package */ PriorityTaskManager priorityTaskManager; /* package */ AudioAttributes audioAttributes; @@ -437,8 +439,8 @@ public interface ExoPlayer extends Player { public Builder(Context context) { this( context, - new DefaultRenderersFactory(context), - new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory())); + () -> new DefaultRenderersFactory(context), + () -> new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory())); } /** @@ -456,8 +458,8 @@ public interface ExoPlayer extends Player { public Builder(Context context, RenderersFactory renderersFactory) { this( context, - renderersFactory, - new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory())); + () -> renderersFactory, + () -> new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory())); } /** @@ -474,7 +476,7 @@ public interface ExoPlayer extends Player { * MediaItem}. */ public Builder(Context context, MediaSourceFactory mediaSourceFactory) { - this(context, new DefaultRenderersFactory(context), mediaSourceFactory); + this(context, () -> new DefaultRenderersFactory(context), () -> mediaSourceFactory); } /** @@ -494,14 +496,7 @@ public interface ExoPlayer extends Player { */ public Builder( Context context, RenderersFactory renderersFactory, MediaSourceFactory mediaSourceFactory) { - this( - context, - renderersFactory, - mediaSourceFactory, - new DefaultTrackSelector(context), - new DefaultLoadControl(), - DefaultBandwidthMeter.getSingletonInstance(context), - new AnalyticsCollector(Clock.DEFAULT)); + this(context, () -> renderersFactory, () -> mediaSourceFactory); } /** @@ -527,13 +522,48 @@ public interface ExoPlayer extends Player { LoadControl loadControl, BandwidthMeter bandwidthMeter, AnalyticsCollector analyticsCollector) { + this( + context, + () -> renderersFactory, + () -> mediaSourceFactory, + () -> trackSelector, + () -> loadControl, + () -> bandwidthMeter, + () -> analyticsCollector); + } + + private Builder( + Context context, + Supplier renderersFactorySupplier, + Supplier mediaSourceFactorySupplier) { + this( + context, + renderersFactorySupplier, + mediaSourceFactorySupplier, + () -> new DefaultTrackSelector(context), + DefaultLoadControl::new, + () -> DefaultBandwidthMeter.getSingletonInstance(context), + /* analyticsCollectorSupplier= */ null); + } + + private Builder( + Context context, + Supplier renderersFactorySupplier, + Supplier mediaSourceFactorySupplier, + Supplier trackSelectorSupplier, + Supplier loadControlSupplier, + Supplier bandwidthMeterSupplier, + @Nullable Supplier analyticsCollectorSupplier) { this.context = context; - this.renderersFactory = renderersFactory; - this.mediaSourceFactory = mediaSourceFactory; - this.trackSelector = trackSelector; - this.loadControl = loadControl; - this.bandwidthMeter = bandwidthMeter; - this.analyticsCollector = analyticsCollector; + this.renderersFactorySupplier = renderersFactorySupplier; + this.mediaSourceFactorySupplier = mediaSourceFactorySupplier; + this.trackSelectorSupplier = trackSelectorSupplier; + this.loadControlSupplier = loadControlSupplier; + this.bandwidthMeterSupplier = bandwidthMeterSupplier; + this.analyticsCollectorSupplier = + analyticsCollectorSupplier != null + ? analyticsCollectorSupplier + : () -> new AnalyticsCollector(checkNotNull(clock)); looper = Util.getCurrentOrMainLooper(); audioAttributes = AudioAttributes.DEFAULT; wakeMode = C.WAKE_MODE_NONE; @@ -573,7 +603,7 @@ public interface ExoPlayer extends Player { */ public Builder setRenderersFactory(RenderersFactory renderersFactory) { checkState(!buildCalled); - this.renderersFactory = renderersFactory; + this.renderersFactorySupplier = () -> renderersFactory; return this; } @@ -586,7 +616,7 @@ public interface ExoPlayer extends Player { */ public Builder setMediaSourceFactory(MediaSourceFactory mediaSourceFactory) { checkState(!buildCalled); - this.mediaSourceFactory = mediaSourceFactory; + this.mediaSourceFactorySupplier = () -> mediaSourceFactory; return this; } @@ -599,7 +629,7 @@ public interface ExoPlayer extends Player { */ public Builder setTrackSelector(TrackSelector trackSelector) { checkState(!buildCalled); - this.trackSelector = trackSelector; + this.trackSelectorSupplier = () -> trackSelector; return this; } @@ -612,7 +642,7 @@ public interface ExoPlayer extends Player { */ public Builder setLoadControl(LoadControl loadControl) { checkState(!buildCalled); - this.loadControl = loadControl; + this.loadControlSupplier = () -> loadControl; return this; } @@ -625,7 +655,7 @@ public interface ExoPlayer extends Player { */ public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) { checkState(!buildCalled); - this.bandwidthMeter = bandwidthMeter; + this.bandwidthMeterSupplier = () -> bandwidthMeter; return this; } @@ -652,7 +682,7 @@ public interface ExoPlayer extends Player { */ public Builder setAnalyticsCollector(AnalyticsCollector analyticsCollector) { checkState(!buildCalled); - this.analyticsCollector = analyticsCollector; + this.analyticsCollectorSupplier = () -> analyticsCollector; return this; } 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 f0637df30a..3c6607611e 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 @@ -28,6 +28,7 @@ import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLE import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT; import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import android.content.Context; import android.graphics.Rect; @@ -66,7 +67,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.upstream.BandwidthMeter; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.Log; @@ -409,12 +409,14 @@ public class SimpleExoPlayer extends BasePlayer Clock clock, Looper applicationLooper) { this( - new ExoPlayer.Builder(context, renderersFactory) - .setTrackSelector(trackSelector) - .setMediaSourceFactory(mediaSourceFactory) - .setLoadControl(loadControl) - .setBandwidthMeter(bandwidthMeter) - .setAnalyticsCollector(analyticsCollector) + new ExoPlayer.Builder( + context, + renderersFactory, + mediaSourceFactory, + trackSelector, + loadControl, + bandwidthMeter, + analyticsCollector) .setUseLazyPreparation(useLazyPreparation) .setClock(clock) .setLooper(applicationLooper)); @@ -431,7 +433,7 @@ public class SimpleExoPlayer extends BasePlayer constructorFinished = new ConditionVariable(); try { applicationContext = builder.context.getApplicationContext(); - analyticsCollector = builder.analyticsCollector; + analyticsCollector = builder.analyticsCollectorSupplier.get(); priorityTaskManager = builder.priorityTaskManager; audioAttributes = builder.audioAttributes; videoScalingMode = builder.videoScalingMode; @@ -443,12 +445,15 @@ public class SimpleExoPlayer extends BasePlayer listeners = new CopyOnWriteArraySet<>(); Handler eventHandler = new Handler(builder.looper); renderers = - builder.renderersFactory.createRenderers( - eventHandler, - componentListener, - componentListener, - componentListener, - componentListener); + builder + .renderersFactorySupplier + .get() + .createRenderers( + eventHandler, + componentListener, + componentListener, + componentListener, + componentListener); // Set initial values. volume = 1; @@ -476,10 +481,10 @@ public class SimpleExoPlayer extends BasePlayer player = new ExoPlayerImpl( renderers, - builder.trackSelector, - builder.mediaSourceFactory, - builder.loadControl, - builder.bandwidthMeter, + builder.trackSelectorSupplier.get(), + builder.mediaSourceFactorySupplier.get(), + builder.loadControlSupplier.get(), + builder.bandwidthMeterSupplier.get(), analyticsCollector, builder.useLazyPreparation, builder.seekParameters, @@ -848,7 +853,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public void addAnalyticsListener(AnalyticsListener listener) { // Don't verify application thread. We allow calls to this method from any thread. - Assertions.checkNotNull(listener); + checkNotNull(listener); analyticsCollector.addListener(listener); } @@ -874,7 +879,7 @@ public class SimpleExoPlayer extends BasePlayer return; } if (isPriorityTaskManagerRegistered) { - Assertions.checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK); + checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK); } if (priorityTaskManager != null && isLoading()) { priorityTaskManager.add(C.PRIORITY_PLAYBACK); @@ -982,7 +987,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public void addListener(Listener listener) { - Assertions.checkNotNull(listener); + checkNotNull(listener); listeners.add(listener); EventListener eventListener = listener; addListener(eventListener); @@ -992,13 +997,13 @@ public class SimpleExoPlayer extends BasePlayer @Override public void addListener(Player.EventListener listener) { // Don't verify application thread. We allow calls to this method from any thread. - Assertions.checkNotNull(listener); + checkNotNull(listener); player.addEventListener(listener); } @Override public void removeListener(Listener listener) { - Assertions.checkNotNull(listener); + checkNotNull(listener); listeners.remove(listener); EventListener eventListener = listener; removeListener(eventListener); @@ -1322,7 +1327,7 @@ public class SimpleExoPlayer extends BasePlayer ownedSurface = null; } if (isPriorityTaskManagerRegistered) { - Assertions.checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); + checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = false; } currentCues = Collections.emptyList(); From 405b811454c60babf4e1f73d3a5b27e66ab48a0d Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 29 Oct 2021 13:40:08 +0000 Subject: [PATCH 044/113] Update developer guide to use non-deprecated symbols #minor-release PiperOrigin-RevId: 406347412 --- docs/ad-insertion.md | 10 ++++++++-- docs/drm.md | 19 +++++++++++-------- docs/live-streaming.md | 2 +- docs/media-items.md | 36 ++++++++++++++++++++---------------- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/docs/ad-insertion.md b/docs/ad-insertion.md index 0886ca6a92..4e42430f5b 100644 --- a/docs/ad-insertion.md +++ b/docs/ad-insertion.md @@ -178,7 +178,10 @@ MediaItem preRollAd = MediaItem.fromUri(preRollAdUri); MediaItem contentStart = new MediaItem.Builder() .setUri(contentUri) - .setClipEndPositionMs(120_000) + .setClippingConfiguration( + new ClippingConfiguration.Builder() + .setEndPositionMs(120_000) + .build()) .build(); // A mid-roll ad. MediaItem midRollAd = MediaItem.fromUri(midRollAdUri); @@ -186,7 +189,10 @@ MediaItem midRollAd = MediaItem.fromUri(midRollAdUri); MediaItem contentEnd = new MediaItem.Builder() .setUri(contentUri) - .setClipStartPositionMs(120_000) + .setClippingConfiguration( + new ClippingConfiguration.Builder() + .setStartPositionMs(120_000) + .build()) .build(); // Build the playlist. diff --git a/docs/drm.md b/docs/drm.md index a943f49ba7..4b64640989 100644 --- a/docs/drm.md +++ b/docs/drm.md @@ -24,7 +24,8 @@ outlined in the sections below. ### Key rotation ### To play streams with rotating keys, pass `true` to -`MediaItem.Builder.setDrmMultiSession` when building the media item. +`MediaItem.DrmConfiguration.Builder.setMultiSession` when building the media +item. ### Multi-key content ### @@ -49,8 +50,9 @@ to access the different streams. In this case, the license server is configured to respond with only the key specified in the request. Multi-key content can be played with this license -server configuration by passing `true` to `MediaItem.Builder.setDrmMultiSession` -when building the media item. +server configuration by passing `true` to +`MediaItem.DrmConfiguration.Builder.setMultiSession` when building the media +item. We do not recommend configuring your license server to behave in this way. It requires extra license requests to play multi-key content, which is less @@ -59,9 +61,9 @@ efficient and robust than the alternative described above. ### Offline keys ### An offline key set can be loaded by passing the key set ID to -`MediaItem.Builder.setDrmKeySetId` when building the media item. This -allows playback using the keys stored in the offline key set with the specified -ID. +`MediaItem.DrmConfiguration.Builder.setKeySetId` when building the media item. +This allows playback using the keys stored in the offline key set with the +specified ID. {% include known-issue-box.html issue-id="3872" description="Only one offline key set can be specified per playback. As a result, offline playback of @@ -75,8 +77,9 @@ clear content as are used when playing encrypted content. When media contains both clear and encrypted sections, you may want to use placeholder `DrmSessions` to avoid re-creation of decoders when transitions between clear and encrypted sections occur. Use of placeholder `DrmSessions` for audio and video tracks can -be enabled by passing `true` to `MediaItem.Builder.setDrmSessionForClearPeriods` -when building the media item. +be enabled by passing `true` to +`MediaItem.DrmConfiguration.Builder.forceSessionsForAudioAndVideoTracks` when +building the media item. ### Using a custom DrmSessionManager ### diff --git a/docs/live-streaming.md b/docs/live-streaming.md index d24d0f4dd0..f9091ba261 100644 --- a/docs/live-streaming.md +++ b/docs/live-streaming.md @@ -89,7 +89,7 @@ components to support additional modes when playing live streams. By default, ExoPlayer uses live playback parameters defined by the media. If you want to configure the live playback parameters yourself, you can set them on a -per `MediaItem` basis by calling `MediaItem.Builder.setLiveXXX` methods. If +per `MediaItem` basis by calling `MediaItem.Builder.setLiveConfiguration`. If you'd like to set these values globally for all items, you can set them on the `DefaultMediaSourceFactory` provided to the player. In both cases, the provided values will override parameters defined by the media. diff --git a/docs/media-items.md b/docs/media-items.md index 710ded16d6..f1c342c2a5 100644 --- a/docs/media-items.md +++ b/docs/media-items.md @@ -86,17 +86,17 @@ To sideload subtitle tracks, `MediaItem.Subtitle` instances can be added when when building a media item: ~~~ -MediaItem.Subtitle subtitle = - new MediaItem.Subtitle( - subtitleUri, - MimeTypes.APPLICATION_SUBRIP, // The correct MIME type. - language, // The subtitle language. May be null. - selectionFlags); // Selection flags for the track. - -MediaItem mediaItem = new MediaItem.Builder() - .setUri(videoUri) - .setSubtitles(Lists.newArrayList(subtitle)) - .build(); +MediaItem.SubtitleConfiguration subtitle = + new MediaItem.SubtitleConfiguration.Builder(subtitleUri) + .setMimeType(MimeTypes.APPLICATION_SUBRIP) // The correct MIME type (required). + .setLanguage(language) // The subtitle language (optional). + .setSelectionFlags(selectionFlags) // Selection flags for the track (optional). + .build(); +MediaItem mediaItem = + new MediaItem.Builder() + .setUri(videoUri) + .setSubtitleConfigurations(ImmutableList.of(subtitle)) + .build(); ~~~ {: .language-java} @@ -110,11 +110,15 @@ It's possible to clip the content referred to by a media item by setting custom start and end positions: ~~~ -MediaItem mediaItem = new MediaItem.Builder() - .setUri(videoUri) - .setClipStartPositionMs(startPositionMs) - .setClipEndPositionMs(endPositionMs) - .build(); +MediaItem mediaItem = + new MediaItem.Builder() + .setUri(videoUri) + .setClippingConfiguration( + new ClippingConfiguration.Builder() + .setStartPositionMs(startPositionMs) + .setEndPositionMs(endPositionMs) + .build()) + .build(); ~~~ {: .language-java} From 8e2083a27b5879dc50d6ac6c30ccc7d6a87ab145 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 29 Oct 2021 14:27:32 +0000 Subject: [PATCH 045/113] Remove dependency from common tests to exoplayer PiperOrigin-RevId: 406354526 --- .../{FakePlayer.java => FakeExoPlayer.java} | 9 +- .../exoplayer2/ext/ima/ImaAdsLoaderTest.java | 4 +- library/common/build.gradle | 1 - .../google/android/exoplayer2/FormatTest.java | 7 +- .../exoplayer2/ForwardingPlayerTest.java | 16 +- .../exoplayer2/metadata/MetadataTest.java | 5 +- .../TrackSelectionParametersTest.java | 15 - .../exoplayer2/util/AtomicFileTest.java | 2 +- .../exoplayer2/util/MediaFormatUtilTest.java | 6 +- .../DefaultTrackSelectorTest.java | 13 + .../testutil/FakeMetadataEntry.java | 78 ++++ .../exoplayer2/testutil/StubExoPlayer.java | 383 +---------------- .../exoplayer2/testutil/StubPlayer.java | 399 ++++++++++++++++++ 13 files changed, 510 insertions(+), 428 deletions(-) rename extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/{FakePlayer.java => FakeExoPlayer.java} (97%) create mode 100644 testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMetadataEntry.java create mode 100644 testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java similarity index 97% rename from extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java rename to extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java index e8f80feed0..fb2975920d 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java @@ -15,9 +15,9 @@ */ package com.google.android.exoplayer2.ext.ima; -import android.content.Context; import android.os.Looper; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; @@ -29,8 +29,8 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Util; -/** A fake player for testing content/ad playback. */ -/* package */ final class FakePlayer extends StubExoPlayer { +/** A fake {@link ExoPlayer} for testing content/ad playback. */ +/* package */ final class FakeExoPlayer extends StubExoPlayer { private final ListenerSet listeners; private final Timeline.Period period; @@ -48,8 +48,7 @@ import com.google.android.exoplayer2.util.Util; private int adGroupIndex; private int adIndexInAdGroup; - public FakePlayer(Context context) { - super(context); + public FakeExoPlayer() { listeners = new ListenerSet<>( Looper.getMainLooper(), diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java index cb0deb494b..0d9e7f042f 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java @@ -139,13 +139,13 @@ public final class ImaAdsLoaderTest { private ContentProgressProvider contentProgressProvider; private VideoAdPlayer videoAdPlayer; private TestAdsLoaderListener adsLoaderListener; - private FakePlayer fakePlayer; + private FakeExoPlayer fakePlayer; private ImaAdsLoader imaAdsLoader; @Before public void setUp() { setupMocks(); - fakePlayer = new FakePlayer(getApplicationContext()); + fakePlayer = new FakeExoPlayer(); adViewGroup = new FrameLayout(getApplicationContext()); View adOverlayView = new View(getApplicationContext()); adViewProvider = diff --git a/library/common/build.gradle b/library/common/build.gradle index 40d6c7c610..b59552d366 100644 --- a/library/common/build.gradle +++ b/library/common/build.gradle @@ -49,7 +49,6 @@ dependencies { testImplementation 'junit:junit:' + junitVersion testImplementation 'com.google.truth:truth:' + truthVersion testImplementation 'org.robolectric:robolectric:' + robolectricVersion - testImplementation project(modulePrefix + 'library-core') testImplementation project(modulePrefix + 'testutils') } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java index fbc40afd12..36c5e448b1 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java @@ -23,7 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; +import com.google.android.exoplayer2.testutil.FakeMetadataEntry; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.video.ColorInfo; import java.util.ArrayList; @@ -65,10 +65,7 @@ public final class FormatTest { byte[] projectionData = new byte[] {1, 2, 3}; - Metadata metadata = - new Metadata( - new TextInformationFrame("id1", "description1", "value1"), - new TextInformationFrame("id2", "description2", "value2")); + Metadata metadata = new Metadata(new FakeMetadataEntry("id1"), new FakeMetadataEntry("id2")); ColorInfo colorInfo = new ColorInfo( diff --git a/library/common/src/test/java/com/google/android/exoplayer2/ForwardingPlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/ForwardingPlayerTest.java index 41f590ed03..9c6e481e50 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/ForwardingPlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/ForwardingPlayerTest.java @@ -24,10 +24,8 @@ import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.content.Context; -import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.testutil.StubExoPlayer; +import com.google.android.exoplayer2.testutil.StubPlayer; import com.google.android.exoplayer2.util.FlagSet; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -48,7 +46,7 @@ public class ForwardingPlayerTest { @Test public void addListener_addsForwardingListener() { - FakePlayer player = new FakePlayer(ApplicationProvider.getApplicationContext()); + FakePlayer player = new FakePlayer(); Player.Listener listener1 = mock(Player.Listener.class); Player.Listener listener2 = mock(Player.Listener.class); @@ -63,7 +61,7 @@ public class ForwardingPlayerTest { @Test public void removeListener_removesForwardingListener() { - FakePlayer player = new FakePlayer(ApplicationProvider.getApplicationContext()); + FakePlayer player = new FakePlayer(); Player.Listener listener1 = mock(Player.Listener.class); Player.Listener listener2 = mock(Player.Listener.class); ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player); @@ -81,7 +79,7 @@ public class ForwardingPlayerTest { @Test public void onEvents_passesForwardingPlayerAsArgument() { - FakePlayer player = new FakePlayer(ApplicationProvider.getApplicationContext()); + FakePlayer player = new FakePlayer(); Player.Listener listener = mock(Player.Listener.class); ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player); forwardingPlayer.addListener(listener); @@ -180,14 +178,10 @@ public class ForwardingPlayerTest { throw new IllegalStateException(); } - private static class FakePlayer extends StubExoPlayer { + private static class FakePlayer extends StubPlayer { private final Set listeners = new HashSet<>(); - public FakePlayer(Context context) { - super(context); - } - @Override public void addListener(Listener listener) { listeners.add(listener); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java index ac3bfdcef9..f2457e0346 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java @@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.metadata.id3.BinaryFrame; +import com.google.android.exoplayer2.testutil.FakeMetadataEntry; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,8 +30,7 @@ public class MetadataTest { @Test public void parcelable() { Metadata metadataToParcel = - new Metadata( - new BinaryFrame("id1", new byte[] {1}), new BinaryFrame("id2", new byte[] {2})); + new Metadata(new FakeMetadataEntry("id1"), new FakeMetadataEntry("id2")); Parcel parcel = Parcel.obtain(); metadataToParcel.writeToParcel(parcel, 0); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java index 075898d4d0..7fab202421 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java @@ -19,10 +19,8 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; @@ -183,17 +181,4 @@ public final class TrackSelectionParametersTest { assertThat(parameters.viewportHeight).isEqualTo(Integer.MAX_VALUE); assertThat(parameters.viewportOrientationMayChange).isTrue(); } - - /** Tests {@link SelectionOverride}'s {@link Bundleable} implementation. */ - @Test - public void roundTripViaBundle_ofSelectionOverride_yieldsEqualInstance() { - SelectionOverride selectionOverrideToBundle = - new SelectionOverride(/* groupIndex= */ 1, /* tracks...= */ 2, 3); - - SelectionOverride selectionOverrideFromBundle = - DefaultTrackSelector.SelectionOverride.CREATOR.fromBundle( - selectionOverrideToBundle.toBundle()); - - assertThat(selectionOverrideFromBundle).isEqualTo(selectionOverrideToBundle); - } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java index dd5ccac8cd..c5486b5f4a 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java @@ -39,7 +39,7 @@ public final class AtomicFileTest { @Before public void setUp() throws Exception { tempFolder = - Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest"); + Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "AtomicFileTest"); file = new File(tempFolder, "atomicFile"); atomicFile = new AtomicFile(file); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java index 0888796417..959b1279a0 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java @@ -31,7 +31,7 @@ import org.junit.runner.RunWith; public class MediaFormatUtilTest { @Test - public void createMediaFormatFromEmptyExoPlayerFormat_generatesExpectedEntries() { + public void createMediaFormatFromFormat_withEmptyFormat_generatesExpectedEntries() { MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(new Format.Builder().build()); // Assert that no invalid keys are accidentally being populated. @@ -59,7 +59,7 @@ public class MediaFormatUtilTest { } @Test - public void createMediaFormatFromPopulatedExoPlayerFormat_generatesExpectedMediaFormatEntries() { + public void createMediaFormatFromFormat_withPopulatedFormat_generatesExpectedEntries() { Format format = new Format.Builder() .setAverageBitrate(1) @@ -145,7 +145,7 @@ public class MediaFormatUtilTest { } @Test - public void createMediaFormatWithExoPlayerPcmEncoding_containsExoPlayerSpecificEncoding() { + public void createMediaFormatFromFormat_withPcmEncoding_setsCustomPcmEncodingEntry() { Format format = new Format.Builder().setPcmEncoding(C.ENCODING_PCM_32BIT).build(); MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format); assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING)) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 0a12b61a12..c2eaf5ddaf 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1750,6 +1750,19 @@ public final class DefaultTrackSelectorTest { assertThat(trackGroupInfos.get(0).getTrackSupport(0)).isEqualTo(FORMAT_HANDLED); } + /** Tests {@link SelectionOverride}'s {@link Bundleable} implementation. */ + @Test + public void roundTripViaBundle_ofSelectionOverride_yieldsEqualInstance() { + SelectionOverride selectionOverrideToBundle = + new SelectionOverride(/* groupIndex= */ 1, /* tracks...= */ 2, 3); + + SelectionOverride selectionOverrideFromBundle = + DefaultTrackSelector.SelectionOverride.CREATOR.fromBundle( + selectionOverrideToBundle.toBundle()); + + assertThat(selectionOverrideFromBundle).isEqualTo(selectionOverrideToBundle); + } + private static void assertSelections(TrackSelectorResult result, TrackSelection[] expected) { assertThat(result.length).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMetadataEntry.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMetadataEntry.java new file mode 100644 index 0000000000..e84d60dc4f --- /dev/null +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMetadataEntry.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 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.testutil; + +import static com.google.android.exoplayer2.util.Util.castNonNull; + +import android.os.Parcel; +import android.os.Parcelable; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.metadata.Metadata; + +/** A fake {@link Metadata.Entry}. */ +public final class FakeMetadataEntry implements Metadata.Entry { + + public final String data; + + public FakeMetadataEntry(String data) { + this.data = data; + } + + /* package */ FakeMetadataEntry(Parcel in) { + data = castNonNull(in.readString()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + FakeMetadataEntry other = (FakeMetadataEntry) obj; + return data.equals(other.data); + } + + @Override + public int hashCode() { + return data.hashCode(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(data); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public FakeMetadataEntry createFromParcel(Parcel in) { + return new FakeMetadataEntry(in); + } + + @Override + public FakeMetadataEntry[] newArray(int size) { + return new FakeMetadataEntry[size]; + } + }; +} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 9a5a50d1d3..c0acae1da6 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -15,26 +15,14 @@ */ package com.google.android.exoplayer2.testutil; -import android.content.Context; import android.os.Looper; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.TextureView; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.BasePlayer; -import com.google.android.exoplayer2.DeviceInfo; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.MediaMetadata; -import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.PlayerMessage; import com.google.android.exoplayer2.SeekParameters; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioAttributes; @@ -42,15 +30,10 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ShuffleOrder; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; -import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import java.util.List; @@ -58,11 +41,7 @@ import java.util.List; * An abstract {@link ExoPlayer} implementation that throws {@link UnsupportedOperationException} * from every method. */ -public class StubExoPlayer extends BasePlayer implements ExoPlayer { - - public StubExoPlayer(Context context) { - super(); - } +public class StubExoPlayer extends StubPlayer implements ExoPlayer { @Override @Deprecated @@ -93,31 +72,16 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public Looper getApplicationLooper() { - throw new UnsupportedOperationException(); - } - @Override public Clock getClock() { throw new UnsupportedOperationException(); } - @Override - public void addListener(Listener listener) { - throw new UnsupportedOperationException(); - } - @Override public void addListener(Player.EventListener listener) { throw new UnsupportedOperationException(); } - @Override - public void removeListener(Listener listener) { - throw new UnsupportedOperationException(); - } - @Override public void removeListener(Player.EventListener listener) { throw new UnsupportedOperationException(); @@ -148,68 +112,29 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - @State - public int getPlaybackState() { - throw new UnsupportedOperationException(); - } - - @Override - @PlaybackSuppressionReason - public int getPlaybackSuppressionReason() { - throw new UnsupportedOperationException(); - } - @Override public ExoPlaybackException getPlayerError() { throw new UnsupportedOperationException(); } - /** @deprecated Use {@link #prepare()} instead. */ @Deprecated @Override public void retry() { throw new UnsupportedOperationException(); } - /** - * @deprecated Use {@link #setMediaSource(MediaSource)} and {@link ExoPlayer#prepare()} instead. - */ - @Deprecated - @Override - public void prepare() { - throw new UnsupportedOperationException(); - } - - /** - * @deprecated Use {@link #setMediaSource(MediaSource)} and {@link ExoPlayer#prepare()} instead. - */ @Deprecated @Override public void prepare(MediaSource mediaSource) { throw new UnsupportedOperationException(); } - /** - * @deprecated Use {@link #setMediaSource(MediaSource, boolean)} and {@link ExoPlayer#prepare()} - * instead. - */ @Deprecated @Override public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { throw new UnsupportedOperationException(); } - @Override - public void setMediaItems(List mediaItems, boolean resetPosition) { - throw new UnsupportedOperationException(); - } - - @Override - public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { - throw new UnsupportedOperationException(); - } - @Override public void setMediaSource(MediaSource mediaSource) { throw new UnsupportedOperationException(); @@ -241,11 +166,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public void addMediaItems(int index, List mediaItems) { - throw new UnsupportedOperationException(); - } - @Override public void addMediaSource(MediaSource mediaSource) { throw new UnsupportedOperationException(); @@ -266,41 +186,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeMediaItems(int fromIndex, int toIndex) { - throw new UnsupportedOperationException(); - } - - @Override - public Commands getAvailableCommands() { - throw new UnsupportedOperationException(); - } - - @Override - public void setPlayWhenReady(boolean playWhenReady) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getPlayWhenReady() { - throw new UnsupportedOperationException(); - } - - @Override - public void setRepeatMode(@RepeatMode int repeatMode) { - throw new UnsupportedOperationException(); - } - - @Override - public int getRepeatMode() { - throw new UnsupportedOperationException(); - } - @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { throw new UnsupportedOperationException(); @@ -381,51 +266,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public void setShuffleModeEnabled(boolean shuffleModeEnabled) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getShuffleModeEnabled() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isLoading() { - throw new UnsupportedOperationException(); - } - - @Override - public void seekTo(int mediaItemIndex, long positionMs) { - throw new UnsupportedOperationException(); - } - - @Override - public long getSeekBackIncrement() { - throw new UnsupportedOperationException(); - } - - @Override - public long getSeekForwardIncrement() { - throw new UnsupportedOperationException(); - } - - @Override - public long getMaxSeekToPreviousPosition() { - throw new UnsupportedOperationException(); - } - - @Override - public void setPlaybackParameters(PlaybackParameters playbackParameters) { - throw new UnsupportedOperationException(); - } - - @Override - public PlaybackParameters getPlaybackParameters() { - throw new UnsupportedOperationException(); - } - @Override public void setSeekParameters(@Nullable SeekParameters seekParameters) { throw new UnsupportedOperationException(); @@ -436,22 +276,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public void stop() { - throw new UnsupportedOperationException(); - } - - @Deprecated - @Override - public void stop(boolean reset) { - throw new UnsupportedOperationException(); - } - - @Override - public void release() { - throw new UnsupportedOperationException(); - } - @Override public PlayerMessage createMessage(PlayerMessage.Target target) { throw new UnsupportedOperationException(); @@ -473,211 +297,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public TrackGroupArray getCurrentTrackGroups() { - throw new UnsupportedOperationException(); - } - - @Override - public TrackSelectionArray getCurrentTrackSelections() { - throw new UnsupportedOperationException(); - } - - @Override - public TracksInfo getCurrentTracksInfo() { - throw new UnsupportedOperationException(); - } - - @Override - public TrackSelectionParameters getTrackSelectionParameters() { - throw new UnsupportedOperationException(); - } - - @Override - public void setTrackSelectionParameters(TrackSelectionParameters parameters) { - throw new UnsupportedOperationException(); - } - - @Override - public MediaMetadata getMediaMetadata() { - throw new UnsupportedOperationException(); - } - - @Override - public MediaMetadata getPlaylistMetadata() { - throw new UnsupportedOperationException(); - } - - @Override - public void setPlaylistMetadata(MediaMetadata mediaMetadata) { - throw new UnsupportedOperationException(); - } - - @Override - public Timeline getCurrentTimeline() { - throw new UnsupportedOperationException(); - } - - @Override - public int getCurrentPeriodIndex() { - throw new UnsupportedOperationException(); - } - - @Override - public int getCurrentMediaItemIndex() { - throw new UnsupportedOperationException(); - } - - @Override - public long getDuration() { - throw new UnsupportedOperationException(); - } - - @Override - public long getCurrentPosition() { - throw new UnsupportedOperationException(); - } - - @Override - public long getBufferedPosition() { - throw new UnsupportedOperationException(); - } - - @Override - public long getTotalBufferedDuration() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isPlayingAd() { - throw new UnsupportedOperationException(); - } - - @Override - public int getCurrentAdGroupIndex() { - throw new UnsupportedOperationException(); - } - - @Override - public int getCurrentAdIndexInAdGroup() { - throw new UnsupportedOperationException(); - } - - @Override - public long getContentPosition() { - throw new UnsupportedOperationException(); - } - - @Override - public long getContentBufferedPosition() { - throw new UnsupportedOperationException(); - } - - @Override - public AudioAttributes getAudioAttributes() { - throw new UnsupportedOperationException(); - } - - @Override - public void setVolume(float volume) { - throw new UnsupportedOperationException(); - } - - @Override - public float getVolume() { - throw new UnsupportedOperationException(); - } - - @Override - public void clearVideoSurface() { - throw new UnsupportedOperationException(); - } - - @Override - public void clearVideoSurface(@Nullable Surface surface) { - throw new UnsupportedOperationException(); - } - - @Override - public void setVideoSurface(@Nullable Surface surface) { - throw new UnsupportedOperationException(); - } - - @Override - public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { - throw new UnsupportedOperationException(); - } - - @Override - public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { - throw new UnsupportedOperationException(); - } - - @Override - public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { - throw new UnsupportedOperationException(); - } - - @Override - public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { - throw new UnsupportedOperationException(); - } - - @Override - public void setVideoTextureView(@Nullable TextureView textureView) { - throw new UnsupportedOperationException(); - } - - @Override - public void clearVideoTextureView(@Nullable TextureView textureView) { - throw new UnsupportedOperationException(); - } - - @Override - public VideoSize getVideoSize() { - throw new UnsupportedOperationException(); - } - - @Override - public List getCurrentCues() { - throw new UnsupportedOperationException(); - } - - @Override - public DeviceInfo getDeviceInfo() { - throw new UnsupportedOperationException(); - } - - @Override - public int getDeviceVolume() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isDeviceMuted() { - throw new UnsupportedOperationException(); - } - - @Override - public void setDeviceVolume(int volume) { - throw new UnsupportedOperationException(); - } - - @Override - public void increaseDeviceVolume() { - throw new UnsupportedOperationException(); - } - - @Override - public void decreaseDeviceVolume() { - throw new UnsupportedOperationException(); - } - - @Override - public void setDeviceMuted(boolean muted) { - throw new UnsupportedOperationException(); - } - @Override public void setForegroundMode(boolean foregroundMode) { throw new UnsupportedOperationException(); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java new file mode 100644 index 0000000000..79f1214810 --- /dev/null +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2017 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.testutil; + +import android.os.Looper; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.BasePlayer; +import com.google.android.exoplayer2.DeviceInfo; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.MediaMetadata; +import com.google.android.exoplayer2.PlaybackException; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; +import com.google.android.exoplayer2.video.VideoSize; +import java.util.List; + +/** + * An abstract {@link Player} implementation that throws {@link UnsupportedOperationException} from + * every method. + */ +public class StubPlayer extends BasePlayer { + + @Override + public Looper getApplicationLooper() { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(Listener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeListener(Listener listener) { + throw new UnsupportedOperationException(); + } + + @Override + @State + public int getPlaybackState() { + throw new UnsupportedOperationException(); + } + + @Override + @PlaybackSuppressionReason + public int getPlaybackSuppressionReason() { + throw new UnsupportedOperationException(); + } + + @Override + public PlaybackException getPlayerError() { + throw new UnsupportedOperationException(); + } + + @Override + public void prepare() { + throw new UnsupportedOperationException(); + } + + @Override + public void setMediaItems(List mediaItems, boolean resetPosition) { + throw new UnsupportedOperationException(); + } + + @Override + public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { + throw new UnsupportedOperationException(); + } + + @Override + public void addMediaItems(int index, List mediaItems) { + throw new UnsupportedOperationException(); + } + + @Override + public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeMediaItems(int fromIndex, int toIndex) { + throw new UnsupportedOperationException(); + } + + @Override + public Commands getAvailableCommands() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPlayWhenReady(boolean playWhenReady) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean getPlayWhenReady() { + throw new UnsupportedOperationException(); + } + + @Override + public void setRepeatMode(@RepeatMode int repeatMode) { + throw new UnsupportedOperationException(); + } + + @Override + public int getRepeatMode() { + throw new UnsupportedOperationException(); + } + + @Override + public void setShuffleModeEnabled(boolean shuffleModeEnabled) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean getShuffleModeEnabled() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isLoading() { + throw new UnsupportedOperationException(); + } + + @Override + public void seekTo(int mediaItemIndex, long positionMs) { + throw new UnsupportedOperationException(); + } + + @Override + public long getSeekBackIncrement() { + throw new UnsupportedOperationException(); + } + + @Override + public long getSeekForwardIncrement() { + throw new UnsupportedOperationException(); + } + + @Override + public long getMaxSeekToPreviousPosition() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPlaybackParameters(PlaybackParameters playbackParameters) { + throw new UnsupportedOperationException(); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + throw new UnsupportedOperationException(); + } + + @Override + public void stop() { + throw new UnsupportedOperationException(); + } + + @Deprecated + @Override + public void stop(boolean reset) { + throw new UnsupportedOperationException(); + } + + @Override + public void release() { + throw new UnsupportedOperationException(); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + throw new UnsupportedOperationException(); + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + throw new UnsupportedOperationException(); + } + + @Override + public TracksInfo getCurrentTracksInfo() { + throw new UnsupportedOperationException(); + } + + @Override + public TrackSelectionParameters getTrackSelectionParameters() { + throw new UnsupportedOperationException(); + } + + @Override + public void setTrackSelectionParameters(TrackSelectionParameters parameters) { + throw new UnsupportedOperationException(); + } + + @Override + public MediaMetadata getMediaMetadata() { + throw new UnsupportedOperationException(); + } + + @Override + public MediaMetadata getPlaylistMetadata() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPlaylistMetadata(MediaMetadata mediaMetadata) { + throw new UnsupportedOperationException(); + } + + @Override + public Timeline getCurrentTimeline() { + throw new UnsupportedOperationException(); + } + + @Override + public int getCurrentPeriodIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public int getCurrentMediaItemIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public long getDuration() { + throw new UnsupportedOperationException(); + } + + @Override + public long getCurrentPosition() { + throw new UnsupportedOperationException(); + } + + @Override + public long getBufferedPosition() { + throw new UnsupportedOperationException(); + } + + @Override + public long getTotalBufferedDuration() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isPlayingAd() { + throw new UnsupportedOperationException(); + } + + @Override + public int getCurrentAdGroupIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public int getCurrentAdIndexInAdGroup() { + throw new UnsupportedOperationException(); + } + + @Override + public long getContentPosition() { + throw new UnsupportedOperationException(); + } + + @Override + public long getContentBufferedPosition() { + throw new UnsupportedOperationException(); + } + + @Override + public AudioAttributes getAudioAttributes() { + throw new UnsupportedOperationException(); + } + + @Override + public void setVolume(float volume) { + throw new UnsupportedOperationException(); + } + + @Override + public float getVolume() { + throw new UnsupportedOperationException(); + } + + @Override + public void clearVideoSurface() { + throw new UnsupportedOperationException(); + } + + @Override + public void clearVideoSurface(@Nullable Surface surface) { + throw new UnsupportedOperationException(); + } + + @Override + public void setVideoSurface(@Nullable Surface surface) { + throw new UnsupportedOperationException(); + } + + @Override + public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { + throw new UnsupportedOperationException(); + } + + @Override + public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { + throw new UnsupportedOperationException(); + } + + @Override + public void setVideoTextureView(@Nullable TextureView textureView) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearVideoTextureView(@Nullable TextureView textureView) { + throw new UnsupportedOperationException(); + } + + @Override + public VideoSize getVideoSize() { + throw new UnsupportedOperationException(); + } + + @Override + public List getCurrentCues() { + throw new UnsupportedOperationException(); + } + + @Override + public DeviceInfo getDeviceInfo() { + throw new UnsupportedOperationException(); + } + + @Override + public int getDeviceVolume() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDeviceMuted() { + throw new UnsupportedOperationException(); + } + + @Override + public void setDeviceVolume(int volume) { + throw new UnsupportedOperationException(); + } + + @Override + public void increaseDeviceVolume() { + throw new UnsupportedOperationException(); + } + + @Override + public void decreaseDeviceVolume() { + throw new UnsupportedOperationException(); + } + + @Override + public void setDeviceMuted(boolean muted) { + throw new UnsupportedOperationException(); + } +} From 288899ee9d57926f02b3acff720c1359eb0860c6 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Fri, 29 Oct 2021 14:35:53 +0000 Subject: [PATCH 046/113] Change Transformer to use Player.Listener. AnalyticsListener should not be used for non-analytical actions. PiperOrigin-RevId: 406355758 --- .../transformer/TranscodingTransformer.java | 18 ++++++++---------- .../exoplayer2/transformer/Transformer.java | 17 ++++++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index d766987aa8..6f58812be4 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -45,7 +45,6 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.TracksInfo; -import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; @@ -573,8 +572,7 @@ public final class TranscodingTransformer { .setClock(clock) .build(); player.setMediaItem(mediaItem); - player.addAnalyticsListener( - new TranscodingTransformerAnalyticsListener(mediaItem, muxerWrapper)); + player.addListener(new TranscodingTransformerPlayerListener(mediaItem, muxerWrapper)); player.prepare(); progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; @@ -688,30 +686,30 @@ public final class TranscodingTransformer { } } - private final class TranscodingTransformerAnalyticsListener implements AnalyticsListener { + private final class TranscodingTransformerPlayerListener implements Player.Listener { private final MediaItem mediaItem; private final MuxerWrapper muxerWrapper; - public TranscodingTransformerAnalyticsListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) { + public TranscodingTransformerPlayerListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) { this.mediaItem = mediaItem; this.muxerWrapper = muxerWrapper; } @Override - public void onPlaybackStateChanged(EventTime eventTime, int state) { + public void onPlaybackStateChanged(int state) { if (state == Player.STATE_ENDED) { handleTransformationEnded(/* exception= */ null); } } @Override - public void onTimelineChanged(EventTime eventTime, int reason) { + public void onTimelineChanged(Timeline timeline, int reason) { if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) { return; } Timeline.Window window = new Timeline.Window(); - eventTime.timeline.getWindow(/* windowIndex= */ 0, window); + timeline.getWindow(/* windowIndex= */ 0, window); if (!window.isPlaceholder) { long durationUs = window.durationUs; // Make progress permanently unavailable if the duration is unknown, so that it doesn't jump @@ -726,7 +724,7 @@ public final class TranscodingTransformer { } @Override - public void onTracksInfoChanged(EventTime eventTime, TracksInfo tracksInfo) { + public void onTracksInfoChanged(TracksInfo tracksInfo) { if (muxerWrapper.getTrackCount() == 0) { handleTransformationEnded( new IllegalStateException( @@ -736,7 +734,7 @@ public final class TranscodingTransformer { } @Override - public void onPlayerError(EventTime eventTime, PlaybackException error) { + public void onPlayerError(PlaybackException error) { handleTransformationEnded(error); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 1a79061f66..0fd763ad26 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -46,7 +46,6 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.TracksInfo; -import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; @@ -496,7 +495,7 @@ public final class Transformer { .setClock(clock) .build(); player.setMediaItem(mediaItem); - player.addAnalyticsListener(new TransformerAnalyticsListener(mediaItem, muxerWrapper)); + player.addListener(new TransformerPlayerListener(mediaItem, muxerWrapper)); player.prepare(); progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; @@ -606,30 +605,30 @@ public final class Transformer { } } - private final class TransformerAnalyticsListener implements AnalyticsListener { + private final class TransformerPlayerListener implements Player.Listener { private final MediaItem mediaItem; private final MuxerWrapper muxerWrapper; - public TransformerAnalyticsListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) { + public TransformerPlayerListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) { this.mediaItem = mediaItem; this.muxerWrapper = muxerWrapper; } @Override - public void onPlaybackStateChanged(EventTime eventTime, int state) { + public void onPlaybackStateChanged(int state) { if (state == Player.STATE_ENDED) { handleTransformationEnded(/* exception= */ null); } } @Override - public void onTimelineChanged(EventTime eventTime, int reason) { + public void onTimelineChanged(Timeline timeline, int reason) { if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) { return; } Timeline.Window window = new Timeline.Window(); - eventTime.timeline.getWindow(/* windowIndex= */ 0, window); + timeline.getWindow(/* windowIndex= */ 0, window); if (!window.isPlaceholder) { long durationUs = window.durationUs; // Make progress permanently unavailable if the duration is unknown, so that it doesn't jump @@ -644,7 +643,7 @@ public final class Transformer { } @Override - public void onTracksInfoChanged(EventTime eventTime, TracksInfo tracksInfo) { + public void onTracksInfoChanged(TracksInfo tracksInfo) { if (muxerWrapper.getTrackCount() == 0) { handleTransformationEnded( new IllegalStateException( @@ -654,7 +653,7 @@ public final class Transformer { } @Override - public void onPlayerError(EventTime eventTime, PlaybackException error) { + public void onPlayerError(PlaybackException error) { handleTransformationEnded(error); } From fa98935c0615efc9432de96c6cd397fa22204ec1 Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 29 Oct 2021 16:39:07 +0000 Subject: [PATCH 047/113] WavExtractor: split read stages into states This refactoring is the basis to support RF64 (see Issue: google/ExoPlayer#9543). #minor-release PiperOrigin-RevId: 406377924 --- .../extractor/wav/WavExtractor.java | 148 ++++++++++++------ .../extractor/wav/WavHeaderReader.java | 10 +- 2 files changed, 100 insertions(+), 58 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java index c1dd2a6ddc..97d98b5fcc 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java @@ -19,6 +19,7 @@ import static java.lang.Math.max; import static java.lang.Math.min; import android.util.Pair; +import androidx.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; @@ -34,8 +35,14 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** Extracts data from WAV byte streams. */ public final class WavExtractor implements Extractor { @@ -50,13 +57,26 @@ public final class WavExtractor implements Extractor { /** Factory for {@link WavExtractor} instances. */ public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new WavExtractor()}; + /** Parser state. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_USE}) + @IntDef({STATE_READING_HEADER, STATE_SKIPPING_TO_SAMPLE_DATA, STATE_READING_SAMPLE_DATA}) + private @interface State {} + + private static final int STATE_READING_HEADER = 0; + private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 1; + private static final int STATE_READING_SAMPLE_DATA = 2; + private @MonotonicNonNull ExtractorOutput extractorOutput; private @MonotonicNonNull TrackOutput trackOutput; + private @State int state; private @MonotonicNonNull OutputWriter outputWriter; private int dataStartPosition; private long dataEndPosition; public WavExtractor() { + state = STATE_READING_HEADER; dataStartPosition = C.POSITION_UNSET; dataEndPosition = C.POSITION_UNSET; } @@ -75,6 +95,7 @@ public final class WavExtractor implements Extractor { @Override public void seek(long position, long timeUs) { + state = position == 0 ? STATE_READING_HEADER : STATE_READING_SAMPLE_DATA; if (outputWriter != null) { outputWriter.reset(timeUs); } @@ -86,59 +107,21 @@ public final class WavExtractor implements Extractor { } @Override + @ReadResult public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { assertInitialized(); - if (outputWriter == null) { - WavHeader header = WavHeaderReader.peek(input); - if (header == null) { - // Should only happen if the media wasn't sniffed. - throw ParserException.createForMalformedContainer( - "Unsupported or unrecognized wav header.", /* cause= */ null); - } - - if (header.formatType == WavUtil.TYPE_IMA_ADPCM) { - outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header); - } else if (header.formatType == WavUtil.TYPE_ALAW) { - outputWriter = - new PassthroughOutputWriter( - extractorOutput, - trackOutput, - header, - MimeTypes.AUDIO_ALAW, - /* pcmEncoding= */ Format.NO_VALUE); - } else if (header.formatType == WavUtil.TYPE_MLAW) { - outputWriter = - new PassthroughOutputWriter( - extractorOutput, - trackOutput, - header, - MimeTypes.AUDIO_MLAW, - /* pcmEncoding= */ Format.NO_VALUE); - } else { - @C.PcmEncoding - int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample); - if (pcmEncoding == C.ENCODING_INVALID) { - throw ParserException.createForUnsupportedContainerFeature( - "Unsupported WAV format type: " + header.formatType); - } - outputWriter = - new PassthroughOutputWriter( - extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding); - } + switch (state) { + case STATE_READING_HEADER: + readHeader(input); + return Extractor.RESULT_CONTINUE; + case STATE_SKIPPING_TO_SAMPLE_DATA: + skipToSampleData(input); + return Extractor.RESULT_CONTINUE; + case STATE_READING_SAMPLE_DATA: + return readSampleData(input); + default: + throw new IllegalStateException(); } - - if (dataStartPosition == C.POSITION_UNSET) { - Pair dataBounds = WavHeaderReader.skipToData(input); - dataStartPosition = dataBounds.first.intValue(); - dataEndPosition = dataBounds.second; - outputWriter.init(dataStartPosition, dataEndPosition); - } else if (input.getPosition() == 0) { - input.skipFully(dataStartPosition); - } - - Assertions.checkState(dataEndPosition != C.POSITION_UNSET); - long bytesLeft = dataEndPosition - input.getPosition(); - return outputWriter.sampleData(input, bytesLeft) ? RESULT_END_OF_INPUT : RESULT_CONTINUE; } @EnsuresNonNull({"extractorOutput", "trackOutput"}) @@ -147,6 +130,71 @@ public final class WavExtractor implements Extractor { Util.castNonNull(extractorOutput); } + @RequiresNonNull({"extractorOutput", "trackOutput"}) + private void readHeader(ExtractorInput input) throws IOException { + Assertions.checkState(input.getPosition() == 0); + if (dataStartPosition != C.POSITION_UNSET) { + input.skipFully(dataStartPosition); + state = STATE_READING_SAMPLE_DATA; + return; + } + WavHeader header = WavHeaderReader.peek(input); + if (header == null) { + // Should only happen if the media wasn't sniffed. + throw ParserException.createForMalformedContainer( + "Unsupported or unrecognized wav header.", /* cause= */ null); + } + input.skipFully((int) (input.getPeekPosition() - input.getPosition())); + + if (header.formatType == WavUtil.TYPE_IMA_ADPCM) { + outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header); + } else if (header.formatType == WavUtil.TYPE_ALAW) { + outputWriter = + new PassthroughOutputWriter( + extractorOutput, + trackOutput, + header, + MimeTypes.AUDIO_ALAW, + /* pcmEncoding= */ Format.NO_VALUE); + } else if (header.formatType == WavUtil.TYPE_MLAW) { + outputWriter = + new PassthroughOutputWriter( + extractorOutput, + trackOutput, + header, + MimeTypes.AUDIO_MLAW, + /* pcmEncoding= */ Format.NO_VALUE); + } else { + @C.PcmEncoding + int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample); + if (pcmEncoding == C.ENCODING_INVALID) { + throw ParserException.createForUnsupportedContainerFeature( + "Unsupported WAV format type: " + header.formatType); + } + outputWriter = + new PassthroughOutputWriter( + extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding); + } + state = STATE_SKIPPING_TO_SAMPLE_DATA; + } + + private void skipToSampleData(ExtractorInput input) throws IOException { + Pair dataBounds = WavHeaderReader.skipToSampleData(input); + dataStartPosition = dataBounds.first.intValue(); + dataEndPosition = dataBounds.second; + Assertions.checkNotNull(outputWriter).init(dataStartPosition, dataEndPosition); + state = STATE_READING_SAMPLE_DATA; + } + + @ReadResult + private int readSampleData(ExtractorInput input) throws IOException { + Assertions.checkState(dataEndPosition != C.POSITION_UNSET); + long bytesLeft = dataEndPosition - input.getPosition(); + return Assertions.checkNotNull(outputWriter).sampleData(input, bytesLeft) + ? RESULT_END_OF_INPUT + : RESULT_CONTINUE; + } + /** Writes to the extractor's output. */ private interface OutputWriter { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java index f794933d16..147fba9c53 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java @@ -108,7 +108,7 @@ import java.io.IOException; * @throws ParserException If an error occurs parsing chunks. * @throws IOException If reading from the input fails. */ - public static Pair skipToData(ExtractorInput input) throws IOException { + public static Pair skipToSampleData(ExtractorInput input) throws IOException { Assertions.checkNotNull(input); // Make sure the peek position is set to the read position before we peek the first header. @@ -118,14 +118,8 @@ import java.io.IOException; // Skip all chunks until we find the data header. ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); while (chunkHeader.id != WavUtil.DATA_FOURCC) { - if (chunkHeader.id != WavUtil.RIFF_FOURCC && chunkHeader.id != WavUtil.FMT_FOURCC) { - Log.w(TAG, "Ignoring unknown WAV chunk: " + chunkHeader.id); - } + Log.w(TAG, "Ignoring unknown WAV chunk: " + chunkHeader.id); long bytesToSkip = ChunkHeader.SIZE_IN_BYTES + chunkHeader.size; - // Override size of RIFF chunk, since it describes its size as the entire file. - if (chunkHeader.id == WavUtil.RIFF_FOURCC) { - bytesToSkip = ChunkHeader.SIZE_IN_BYTES + 4; - } if (bytesToSkip > Integer.MAX_VALUE) { throw ParserException.createForUnsupportedContainerFeature( "Chunk is too large (~2GB+) to skip; id: " + chunkHeader.id); From c53924326dd100874d080c55812659a3cb9e843a Mon Sep 17 00:00:00 2001 From: huangdarwin Date: Fri, 29 Oct 2021 17:16:01 +0000 Subject: [PATCH 048/113] GL: Make ProjectionRenderer's GL Program @MonotonicNonNull. PiperOrigin-RevId: 406385758 --- .../video/spherical/ProjectionRenderer.java | 8 ++--- .../video/spherical/SceneRenderer.java | 2 +- .../transformer/TranscodingTransformer.java | 29 +++++++++++++++++++ .../transformer/Transformation.java | 3 ++ .../exoplayer2/transformer/Transformer.java | 2 ++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java index f313225fc6..692b7e687e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java @@ -24,6 +24,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.GlUtil; import java.nio.FloatBuffer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Utility class to render spherical meshes for video or images. Call {@link #init()} on the GL @@ -93,9 +94,9 @@ import java.nio.FloatBuffer; private int stereoMode; @Nullable private MeshData leftMeshData; @Nullable private MeshData rightMeshData; - @Nullable private GlUtil.Program program; + private GlUtil.@MonotonicNonNull Program program; - // Program related GL items. These are only valid if program is non-null. + // Program related GL items. These are only valid if Program is valid. private int mvpMatrixHandle; private int uTexMatrixHandle; private int positionHandle; @@ -195,11 +196,10 @@ import java.nio.FloatBuffer; GLES20.glDisableVertexAttribArray(texCoordsHandle); } - /** Cleans up the GL resources. */ + /** Cleans up GL resources. */ /* package */ void shutdown() { if (program != null) { program.delete(); - program = null; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SceneRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SceneRenderer.java index dcc15d1fed..0fcff23c5e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SceneRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SceneRenderer.java @@ -129,7 +129,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; projectionRenderer.draw(textureId, tempMatrix, rightEye); } - /** Cleans up the GL resources. */ + /** Cleans up GL resources. */ public void shutdown() { projectionRenderer.shutdown(); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 6f58812be4..348f3dac04 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -55,6 +55,7 @@ import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -91,12 +92,16 @@ public final class TranscodingTransformer { /** A builder for {@link TranscodingTransformer} instances. */ public static final class Builder { + // Mandatory field. private @MonotonicNonNull Context context; + + // Optional fields. private @MonotonicNonNull MediaSourceFactory mediaSourceFactory; private Muxer.Factory muxerFactory; private boolean removeAudio; private boolean removeVideo; private boolean flattenForSlowMotion; + private int outputHeight; private String outputMimeType; @Nullable private String audioMimeType; @Nullable private String videoMimeType; @@ -121,6 +126,7 @@ public final class TranscodingTransformer { this.removeAudio = transcodingTransformer.transformation.removeAudio; this.removeVideo = transcodingTransformer.transformation.removeVideo; this.flattenForSlowMotion = transcodingTransformer.transformation.flattenForSlowMotion; + this.outputHeight = transcodingTransformer.transformation.outputHeight; this.outputMimeType = transcodingTransformer.transformation.outputMimeType; this.audioMimeType = transcodingTransformer.transformation.audioMimeType; this.videoMimeType = transcodingTransformer.transformation.videoMimeType; @@ -213,6 +219,21 @@ public final class TranscodingTransformer { return this; } + /** + * Sets the output resolution for the video, using the output height. The default value is to + * use the same height as the input. Output width will scale to preserve the input video's + * aspect ratio. + * + *

    For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480). + * + * @param outputHeight The output height for the video, in pixels. + * @return This builder. + */ + public Builder setResolution(int outputHeight) { + this.outputHeight = outputHeight; + return this; + } + /** * Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported * values are: @@ -356,6 +377,12 @@ public final class TranscodingTransformer { checkState( muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); + // TODO(ME): Test with values of 10, 100, 1000). + Log.e("TranscodingTransformer", "outputHeight = " + outputHeight); + if (outputHeight == 0) { + // TODO(ME): get output height from input video. + outputHeight = 480; + } if (audioMimeType != null) { checkSampleMimeType(audioMimeType); } @@ -367,6 +394,7 @@ public final class TranscodingTransformer { removeAudio, removeVideo, flattenForSlowMotion, + outputHeight, outputMimeType, audioMimeType, videoMimeType); @@ -453,6 +481,7 @@ public final class TranscodingTransformer { checkState( !transformation.removeAudio || !transformation.removeVideo, "Audio and video cannot both be removed."); + checkState(!(transformation.removeVideo)); this.context = context; this.mediaSourceFactory = mediaSourceFactory; this.muxerFactory = muxerFactory; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java index e273c1fde5..ed8bf64bf3 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java @@ -24,6 +24,7 @@ import androidx.annotation.Nullable; public final boolean removeAudio; public final boolean removeVideo; public final boolean flattenForSlowMotion; + public final int outputHeight; public final String outputMimeType; @Nullable public final String audioMimeType; @Nullable public final String videoMimeType; @@ -32,12 +33,14 @@ import androidx.annotation.Nullable; boolean removeAudio, boolean removeVideo, boolean flattenForSlowMotion, + int outputHeight, String outputMimeType, @Nullable String audioMimeType, @Nullable String videoMimeType) { this.removeAudio = removeAudio; this.removeVideo = removeVideo; this.flattenForSlowMotion = flattenForSlowMotion; + this.outputHeight = outputHeight; this.outputMimeType = outputMimeType; this.audioMimeType = audioMimeType; this.videoMimeType = videoMimeType; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 0fd763ad26..093f63caeb 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -297,11 +297,13 @@ public final class Transformer { checkState( muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); + int outputHeight = 0; // TODO(ME): How do we get the input height here? Transformation transformation = new Transformation( removeAudio, removeVideo, flattenForSlowMotion, + outputHeight, outputMimeType, /* audioMimeType= */ null, /* videoMimeType= */ null); From 42f9ddb54ed6ce8d6f42302c8542028fff6345a6 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 1 Nov 2021 10:06:02 +0000 Subject: [PATCH 049/113] Remove unecessary warning suppression in PlaybackException PiperOrigin-RevId: 406783965 --- .../java/com/google/android/exoplayer2/PlaybackException.java | 1 - 1 file changed, 1 deletion(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index 335dcf2612..b643530678 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -423,7 +423,6 @@ public class PlaybackException extends Exception implements Bundleable { protected static final int FIELD_CUSTOM_ID_BASE = 1000; /** Object that can create a {@link PlaybackException} from a {@link Bundle}. */ - @SuppressWarnings("unchecked") public static final Creator CREATOR = PlaybackException::new; @CallSuper From 9f352434c72da527d1fa7963447c3cf680db884f Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 10:23:58 +0000 Subject: [PATCH 050/113] Add large renderer position offset. This helps to prevent issues where decoders can't handle negative timestamps. In particular it avoids issues when the media accidentally or intentionally starts with small negative timestamps. But it also helps to prevent other renderer resets at a later point, for example if a live stream with a large start offset is enqueued in the playlist. #minor-release PiperOrigin-RevId: 406786977 --- .../exoplayer2/ExoPlayerImplInternal.java | 9 +-- .../android/exoplayer2/MediaPeriodQueue.java | 24 +++++++- .../exoplayer2/MediaPeriodQueueTest.java | 47 ++++++++++----- .../transformer/TransformerAudioRenderer.java | 1 + .../transformer/TransformerBaseRenderer.java | 7 +++ .../TransformerMuxingVideoRenderer.java | 1 + .../TransformerTranscodingVideoRenderer.java | 1 + .../mka/bear-flac-16bit.mka.audiosink.dump | 58 +++++++++---------- .../mka/bear-flac-24bit.mka.audiosink.dump | 58 +++++++++---------- 9 files changed, 127 insertions(+), 79 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 30085aeb9a..9fadb56a79 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -1266,7 +1266,8 @@ import java.util.concurrent.atomic.AtomicBoolean; queue.advancePlayingPeriod(); } queue.removeAfter(newPlayingPeriodHolder); - newPlayingPeriodHolder.setRendererOffset(/* rendererPositionOffsetUs= */ 0); + newPlayingPeriodHolder.setRendererOffset( + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US); enableRenderers(); } } @@ -1299,7 +1300,7 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodHolder playingMediaPeriod = queue.getPlayingPeriod(); rendererPositionUs = playingMediaPeriod == null - ? periodPositionUs + ? MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + periodPositionUs : playingMediaPeriod.toRendererTime(periodPositionUs); mediaClock.resetPosition(rendererPositionUs); for (Renderer renderer : renderers) { @@ -1375,7 +1376,7 @@ import java.util.concurrent.atomic.AtomicBoolean; pendingRecoverableRendererError = null; isRebuffering = false; mediaClock.stop(); - rendererPositionUs = 0; + rendererPositionUs = MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US; for (Renderer renderer : renderers) { try { disableRenderer(renderer); @@ -1963,7 +1964,7 @@ import java.util.concurrent.atomic.AtomicBoolean; emptyTrackSelectorResult); mediaPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs); if (queue.getPlayingPeriod() == mediaPeriodHolder) { - resetRendererPosition(mediaPeriodHolder.getStartPositionRendererTime()); + resetRendererPosition(info.startPositionUs); } handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 18b258d779..f00ca859f1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -37,6 +37,26 @@ import com.google.common.collect.ImmutableList; */ /* package */ final class MediaPeriodQueue { + /** + * Initial renderer position offset used for the first item in the queue, in microseconds. + * + *

    Choosing a positive value, larger than any reasonable single media duration, ensures three + * things: + * + *

      + *
    • Media that accidentally or intentionally starts with small negative timestamps doesn't + * send samples with negative timestamps to decoders. This makes rendering more robust as + * many decoders are known to have problems with negative timestamps. + *
    • Enqueueing media after the initial item with a non-zero start offset (e.g. content after + * ad breaks or live streams) is virtually guaranteed to stay in the positive timestamp + * range even when seeking back. This prevents renderer resets that are required if the + * allowed timestamp range may become negative. + *
    • Choosing a large value with zeros at all relevant digits simplifies debugging as the + * original timestamp of the media is still visible. + *
    + */ + public static final long INITIAL_RENDERER_POSITION_OFFSET_US = 1_000_000_000_000L; + /** * Limits the maximum number of periods to buffer ahead of the current playing period. The * buffering policy normally prevents buffering too far ahead, but the policy could allow too many @@ -163,9 +183,7 @@ import com.google.common.collect.ImmutableList; TrackSelectorResult emptyTrackSelectorResult) { long rendererPositionOffsetUs = loading == null - ? (info.id.isAd() && info.requestedContentPositionUs != C.TIME_UNSET - ? info.requestedContentPositionUs - : 0) + ? INITIAL_RENDERER_POSITION_OFFSET_US : (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs); MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index 635167e658..53bc87e5e6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -493,10 +493,13 @@ public final class MediaPeriodQueueTest { // Change position of first ad (= change duration of playing content before first ad). updateAdPlaybackStateAndTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000); setAdGroupLoaded(/* adGroupIndex= */ 0); - long maxRendererReadPositionUs = FIRST_AD_START_TIME_US - 3000; + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 3000; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, maxRendererReadPositionUs); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isTrue(); assertThat(getQueueLength()).isEqualTo(1); @@ -518,10 +521,13 @@ public final class MediaPeriodQueueTest { // Change position of first ad (= change duration of playing content before first ad). updateAdPlaybackStateAndTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000); setAdGroupLoaded(/* adGroupIndex= */ 0); - long maxRendererReadPositionUs = FIRST_AD_START_TIME_US - 1000; + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, maxRendererReadPositionUs); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isFalse(); assertThat(getQueueLength()).isEqualTo(1); @@ -552,10 +558,13 @@ public final class MediaPeriodQueueTest { .withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true); updateTimeline(); setAdGroupLoaded(/* adGroupIndex= */ 0); - long maxRendererReadPositionUs = FIRST_AD_START_TIME_US - 1000; + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, maxRendererReadPositionUs); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isTrue(); assertThat(getQueueLength()).isEqualTo(1); @@ -583,7 +592,9 @@ public final class MediaPeriodQueueTest { setAdGroupLoaded(/* adGroupIndex= */ 1); boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, /* maxRendererReadPositionUs= */ 0); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + /* maxRendererReadPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US); assertThat(changeHandled).isTrue(); assertThat(getQueueLength()).isEqualTo(3); @@ -608,11 +619,13 @@ public final class MediaPeriodQueueTest { /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, - /* maxRendererReadPositionUs= */ FIRST_AD_START_TIME_US); + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isFalse(); assertThat(getQueueLength()).isEqualTo(3); @@ -636,11 +649,14 @@ public final class MediaPeriodQueueTest { /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); - long readingPositionAtStartOfContentBetweenAds = FIRST_AD_START_TIME_US + AD_DURATION_US; + long readingPositionAtStartOfContentBetweenAds = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + + FIRST_AD_START_TIME_US + + AD_DURATION_US; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds); assertThat(changeHandled).isTrue(); @@ -665,11 +681,14 @@ public final class MediaPeriodQueueTest { /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); - long readingPositionAtEndOfContentBetweenAds = SECOND_AD_START_TIME_US + AD_DURATION_US; + long readingPositionAtEndOfContentBetweenAds = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + + SECOND_AD_START_TIME_US + + AD_DURATION_US; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* maxRendererReadPositionUs= */ readingPositionAtEndOfContentBetweenAds); assertThat(changeHandled).isFalse(); @@ -697,7 +716,7 @@ public final class MediaPeriodQueueTest { boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* maxRendererReadPositionUs= */ C.TIME_END_OF_SOURCE); assertThat(changeHandled).isFalse(); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 02248296d0..8da9dcd2e1 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -279,6 +279,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; int result = readSource(getFormatHolder(), decoderInputBuffer, /* readFlags= */ 0); switch (result) { case C.RESULT_BUFFER_READ: + decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); decoderInputBuffer.flip(); decoder.queueInputBuffer(decoderInputBuffer); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java index e0ceb945fc..6e19f0b9f9 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.util.MimeTypes; protected final Transformation transformation; protected boolean isRendererStarted; + protected long streamOffsetUs; public TransformerBaseRenderer( int trackType, @@ -46,6 +47,12 @@ import com.google.android.exoplayer2.util.MimeTypes; this.transformation = transformation; } + @Override + protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) + throws ExoPlaybackException { + this.streamOffsetUs = offsetUs; + } + @Override @C.FormatSupport public final int supportsFormat(Format format) { diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java index 0be02ecdee..d14378754e 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java @@ -117,6 +117,7 @@ import java.nio.ByteBuffer; muxerWrapper.endTrack(getTrackType()); return false; } + buffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), buffer.timeUs); ByteBuffer data = checkNotNull(buffer.data); data.flip(); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index b04490152b..931e985a5d 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -320,6 +320,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; case C.RESULT_FORMAT_READ: throw new IllegalStateException("Format changes are not supported."); case C.RESULT_BUFFER_READ: + decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); ByteBuffer data = checkNotNull(decoderInputBuffer.data); data.flip(); diff --git a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump index 044cde306c..b7319b872b 100644 --- a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump +++ b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump @@ -3,89 +3,89 @@ config: channelCount = 2 sampleRate = 48000 buffer: - time = 1000 + time = 1000000001000 data = 1217833679 buffer: - time = 97000 + time = 1000000097000 data = 558614672 buffer: - time = 193000 + time = 1000000193000 data = -709714787 buffer: - time = 289000 + time = 1000000289000 data = 1367870571 buffer: - time = 385000 + time = 1000000385000 data = -141229457 buffer: - time = 481000 + time = 1000000481000 data = 1287758361 buffer: - time = 577000 + time = 1000000577000 data = 1125289147 buffer: - time = 673000 + time = 1000000673000 data = -1677383475 buffer: - time = 769000 + time = 1000000769000 data = 2130742861 buffer: - time = 865000 + time = 1000000865000 data = -1292320253 buffer: - time = 961000 + time = 1000000961000 data = -456587163 buffer: - time = 1057000 + time = 1000001057000 data = 748981534 buffer: - time = 1153000 + time = 1000001153000 data = 1550456016 buffer: - time = 1249000 + time = 1000001249000 data = 1657906039 buffer: - time = 1345000 + time = 1000001345000 data = -762677083 buffer: - time = 1441000 + time = 1000001441000 data = -1343810763 buffer: - time = 1537000 + time = 1000001537000 data = 1137318783 buffer: - time = 1633000 + time = 1000001633000 data = -1891318229 buffer: - time = 1729000 + time = 1000001729000 data = -472068495 buffer: - time = 1825000 + time = 1000001825000 data = 832315001 buffer: - time = 1921000 + time = 1000001921000 data = 2054935175 buffer: - time = 2017000 + time = 1000002017000 data = 57921641 buffer: - time = 2113000 + time = 1000002113000 data = 2132759067 buffer: - time = 2209000 + time = 1000002209000 data = -1742540521 buffer: - time = 2305000 + time = 1000002305000 data = 1657024301 buffer: - time = 2401000 + time = 1000002401000 data = -585080145 buffer: - time = 2497000 + time = 1000002497000 data = 427271397 buffer: - time = 2593000 + time = 1000002593000 data = -364201340 buffer: - time = 2689000 + time = 1000002689000 data = -627965287 diff --git a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump index 319ee311f0..f425e7e2f2 100644 --- a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump +++ b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump @@ -3,89 +3,89 @@ config: channelCount = 2 sampleRate = 48000 buffer: - time = 0 + time = 1000000000000 data = 225023649 buffer: - time = 96000 + time = 1000000096000 data = 455106306 buffer: - time = 192000 + time = 1000000192000 data = 2025727297 buffer: - time = 288000 + time = 1000000288000 data = 758514657 buffer: - time = 384000 + time = 1000000384000 data = 1044986473 buffer: - time = 480000 + time = 1000000480000 data = -2030029695 buffer: - time = 576000 + time = 1000000576000 data = 1907053281 buffer: - time = 672000 + time = 1000000672000 data = -1974954431 buffer: - time = 768000 + time = 1000000768000 data = -206248383 buffer: - time = 864000 + time = 1000000864000 data = 1484984417 buffer: - time = 960000 + time = 1000000960000 data = -1306117439 buffer: - time = 1056000 + time = 1000001056000 data = 692829792 buffer: - time = 1152000 + time = 1000001152000 data = 1070563058 buffer: - time = 1248000 + time = 1000001248000 data = -1444096479 buffer: - time = 1344000 + time = 1000001344000 data = 1753016419 buffer: - time = 1440000 + time = 1000001440000 data = 1947797953 buffer: - time = 1536000 + time = 1000001536000 data = 266121411 buffer: - time = 1632000 + time = 1000001632000 data = 1275494369 buffer: - time = 1728000 + time = 1000001728000 data = 372077825 buffer: - time = 1824000 + time = 1000001824000 data = -993079679 buffer: - time = 1920000 + time = 1000001920000 data = 177307937 buffer: - time = 2016000 + time = 1000002016000 data = 2037083009 buffer: - time = 2112000 + time = 1000002112000 data = -435776287 buffer: - time = 2208000 + time = 1000002208000 data = 1867447329 buffer: - time = 2304000 + time = 1000002304000 data = 1884495937 buffer: - time = 2400000 + time = 1000002400000 data = -804673375 buffer: - time = 2496000 + time = 1000002496000 data = -588531007 buffer: - time = 2592000 + time = 1000002592000 data = -1064642970 buffer: - time = 2688000 + time = 1000002688000 data = -1771406207 From e1494398a039acd209c731ec1d0c1356ccf997cc Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 1 Nov 2021 10:42:22 +0000 Subject: [PATCH 051/113] Upgrade gradle plugin version PiperOrigin-RevId: 406789671 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 055e3cdaff..d362ff785b 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2' } } From 8829c45d3265cbd298e167a0457720f54af2a985 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 1 Nov 2021 11:13:15 +0000 Subject: [PATCH 052/113] Add TYPE_USE to IntDefs used in the media3 stable API This allows the use of the intdef in parameterized types, e.g. List<@MyIntDef Integer> For IntDefs that are already released in ExoPlayer 2.15.1 we add TYPE_USE in addition to all other reasonable targets, to maintain backwards compatibility with Kotlin code (where an incorrectly positioned annotation is a compilation failure). 'reasonable targets' includes FIELD, METHOD, PARAMETER and LOCAL_VARIABLE but not TYPE, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE or MODULE. TYPE_PARAMETER is implied by TYPE_USE. For not-yet-released IntDefs we just add TYPE_USE. #minor-release PiperOrigin-RevId: 406793413 --- .../java/com/google/android/exoplayer2/C.java | 17 +++++++++++++++-- .../android/exoplayer2/MediaMetadata.java | 9 +++++++++ .../android/exoplayer2/PlaybackException.java | 8 ++++++++ .../com/google/android/exoplayer2/Player.java | 16 ++++++++++++++++ .../com/google/android/exoplayer2/text/Cue.java | 11 +++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index a779729bd9..7c26b57600 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.content.Context; import android.media.AudioAttributes; import android.media.AudioFormat; @@ -29,7 +35,6 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.errorprone.annotations.InlineMe; import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -126,6 +131,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) @IntDef( open = true, value = { @@ -306,6 +312,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ CONTENT_TYPE_MOVIE, CONTENT_TYPE_MUSIC, @@ -334,6 +341,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( flag = true, value = {FLAG_AUDIBILITY_ENFORCED}) @@ -354,6 +362,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ USAGE_ALARM, USAGE_ASSISTANCE_ACCESSIBILITY, @@ -422,6 +431,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ALLOW_CAPTURE_BY_ALL, ALLOW_CAPTURE_BY_NONE, ALLOW_CAPTURE_BY_SYSTEM}) public @interface AudioAllowedCapturePolicy {} /** See {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_ALL}. */ @@ -565,6 +575,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, SELECTION_FLAG_AUTOSELECT}) @@ -680,7 +691,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) - @Target({ElementType.TYPE_USE}) + @Target(TYPE_USE) @IntDef( open = true, value = { @@ -976,6 +987,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({WAKE_MODE_NONE, WAKE_MODE_LOCAL, WAKE_MODE_NETWORK}) public @interface WakeMode {} /** @@ -1012,6 +1024,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( flag = true, value = { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 4dad2da4c3..3df88a5aa9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.net.Uri; import android.os.Bundle; import androidx.annotation.IntDef; @@ -26,6 +32,7 @@ import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.Arrays; import java.util.List; @@ -500,6 +507,7 @@ public final class MediaMetadata implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ FOLDER_TYPE_NONE, FOLDER_TYPE_MIXED, @@ -537,6 +545,7 @@ public final class MediaMetadata implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ PICTURE_TYPE_OTHER, PICTURE_TYPE_FILE_ICON, diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index b643530678..0adea0535a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.net.ConnectivityManager; import android.os.Bundle; import android.os.RemoteException; @@ -28,6 +34,7 @@ import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** Thrown when a non locally recoverable playback failure occurs. */ public class PlaybackException extends Exception implements Bundleable { @@ -40,6 +47,7 @@ public class PlaybackException extends Exception implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( open = true, value = { 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 5c60097eb0..08bfbe9016 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 @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.os.Bundle; import android.os.Looper; import android.view.Surface; @@ -40,6 +46,7 @@ import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; @@ -1079,6 +1086,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @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. */ @@ -1107,6 +1115,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, @@ -1133,6 +1142,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ PLAYBACK_SUPPRESSION_REASON_NONE, PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS @@ -1149,6 +1159,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) @interface RepeatMode {} /** @@ -1180,6 +1191,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ DISCONTINUITY_REASON_AUTO_TRANSITION, DISCONTINUITY_REASON_SEEK, @@ -1218,6 +1230,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, TIMELINE_CHANGE_REASON_SOURCE_UPDATE}) @interface TimelineChangeReason {} /** Timeline changed as a result of a change of the playlist items or the order of the items. */ @@ -1238,6 +1251,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ MEDIA_ITEM_TRANSITION_REASON_REPEAT, MEDIA_ITEM_TRANSITION_REASON_AUTO, @@ -1270,6 +1284,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ EVENT_TIMELINE_CHANGED, EVENT_MEDIA_ITEM_TRANSITION, @@ -1355,6 +1370,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ COMMAND_INVALID, COMMAND_PLAY_PAUSE, 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 66ab2f3682..0ef89f9b0b 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 @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2.text; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.graphics.Bitmap; import android.graphics.Color; import android.os.Bundle; @@ -32,6 +38,7 @@ import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.dataflow.qual.Pure; /** Contains information about a specific cue, including textual content and formatting data. */ @@ -53,6 +60,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) public @interface AnchorType {} @@ -80,6 +88,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) public @interface LineType {} @@ -96,6 +105,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ TYPE_UNSET, TEXT_SIZE_TYPE_FRACTIONAL, @@ -119,6 +129,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ TYPE_UNSET, VERTICAL_TYPE_RL, From 23de0be4c992ed8b213a18f6e8b878669e08fae8 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 1 Nov 2021 12:29:21 +0000 Subject: [PATCH 053/113] Re-position IntDefs in media3 stable API These IntDefs are now annotated with TYPE_USE [1], so they can be moved to directly before the type (int). [1] Since PiperOrigin-RevId: 406803555 --- .../main/java/com/google/android/exoplayer2/Format.java | 8 ++++---- .../java/com/google/android/exoplayer2/MediaItem.java | 8 ++++---- .../java/com/google/android/exoplayer2/MediaMetadata.java | 4 ++-- .../com/google/android/exoplayer2/PlaybackException.java | 2 +- .../main/java/com/google/android/exoplayer2/Player.java | 6 ++---- .../main/java/com/google/android/exoplayer2/text/Cue.java | 8 ++++---- .../trackselection/TrackSelectionParameters.java | 8 ++++---- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index 909c759c85..c21e68c886 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -128,8 +128,8 @@ public final class Format implements Bundleable { @Nullable private String id; @Nullable private String label; @Nullable private String language; - @C.SelectionFlags private int selectionFlags; - @C.RoleFlags private int roleFlags; + private @C.SelectionFlags int selectionFlags; + private @C.RoleFlags int roleFlags; private int averageBitrate; private int peakBitrate; @Nullable private String codecs; @@ -620,9 +620,9 @@ public final class Format implements Bundleable { /** The language as an IETF BCP 47 conformant tag, or null if unknown or not applicable. */ @Nullable public final String language; /** Track selection flags. */ - @C.SelectionFlags public final int selectionFlags; + public final @C.SelectionFlags int selectionFlags; /** Track role flags. */ - @C.RoleFlags public final int roleFlags; + public final @C.RoleFlags int roleFlags; /** * The average bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. The * way in which this field is populated depends on the type of media to which the format diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java index 7b4ed43d38..b18c52018e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java @@ -1235,8 +1235,8 @@ public final class MediaItem implements Bundleable { private Uri uri; @Nullable private String mimeType; @Nullable private String language; - @C.SelectionFlags private int selectionFlags; - @C.RoleFlags private int roleFlags; + private @C.SelectionFlags int selectionFlags; + private @C.RoleFlags int roleFlags; @Nullable private String label; /** @@ -1310,9 +1310,9 @@ public final class MediaItem implements Bundleable { /** The language. */ @Nullable public final String language; /** The selection flags. */ - @C.SelectionFlags public final int selectionFlags; + public final @C.SelectionFlags int selectionFlags; /** The role flags. */ - @C.RoleFlags public final int roleFlags; + public final @C.RoleFlags int roleFlags; /** The label. */ @Nullable public final String label; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 3df88a5aa9..f0c1243e09 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -623,7 +623,7 @@ public final class MediaMetadata implements Bundleable { /** Optional artwork data as a compressed byte array. */ @Nullable public final byte[] artworkData; /** Optional {@link PictureType} of the artwork data. */ - @Nullable @PictureType public final Integer artworkDataType; + @Nullable public final @PictureType Integer artworkDataType; /** Optional artwork {@link Uri}. */ @Nullable public final Uri artworkUri; /** Optional track number. */ @@ -631,7 +631,7 @@ public final class MediaMetadata implements Bundleable { /** Optional total number of tracks. */ @Nullable public final Integer totalTrackCount; /** Optional {@link FolderType}. */ - @Nullable @FolderType public final Integer folderType; + @Nullable public final @FolderType Integer folderType; /** Optional boolean for media playability. */ @Nullable public final Boolean isPlayable; /** @deprecated Use {@link #recordingYear} instead. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index 0adea0535a..febb938fdb 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -320,7 +320,7 @@ public class PlaybackException extends Exception implements Bundleable { } /** An error code which identifies the cause of the playback failure. */ - @ErrorCode public final int errorCode; + public final @ErrorCode int errorCode; /** The value of {@link SystemClock#elapsedRealtime()} when this exception was created. */ public final long timestampMs; 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 08bfbe9016..b6222596a7 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 @@ -454,8 +454,7 @@ public interface Player { * @return The {@link Event} at the given index. * @throws IndexOutOfBoundsException If index is outside the allowed range. */ - @Event - public int get(int index) { + public @Event int get(int index) { return flags.get(index); } @@ -870,8 +869,7 @@ public interface Player { * @return The {@link Command} at the given index. * @throws IndexOutOfBoundsException If index is outside the allowed range. */ - @Command - public int get(int index) { + public @Command int get(int index) { return flags.get(index); } 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 0ef89f9b0b..a386580f3d 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 @@ -556,16 +556,16 @@ public final class Cue implements Bundleable { @Nullable private Alignment multiRowAlignment; private float line; @LineType private int lineType; - @AnchorType private int lineAnchor; + private @AnchorType int lineAnchor; private float position; - @AnchorType private int positionAnchor; - @TextSizeType private int textSizeType; + private @AnchorType int positionAnchor; + private @TextSizeType int textSizeType; private float textSize; private float size; private float bitmapHeight; private boolean windowColorSet; @ColorInt private int windowColor; - @VerticalType private int verticalType; + private @VerticalType int verticalType; private float shearDegrees; public Builder() { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java index 738cf74e2b..8080451246 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java @@ -82,13 +82,13 @@ public class TrackSelectionParameters implements Bundleable { private ImmutableList preferredVideoMimeTypes; // Audio private ImmutableList preferredAudioLanguages; - @C.RoleFlags private int preferredAudioRoleFlags; + private @C.RoleFlags int preferredAudioRoleFlags; private int maxAudioChannelCount; private int maxAudioBitrate; private ImmutableList preferredAudioMimeTypes; // Text private ImmutableList preferredTextLanguages; - @C.RoleFlags private int preferredTextRoleFlags; + private @C.RoleFlags int preferredTextRoleFlags; private boolean selectUndeterminedTextLanguage; // General private boolean forceLowestBitrate; @@ -781,7 +781,7 @@ public class TrackSelectionParameters implements Bundleable { * The preferred {@link C.RoleFlags} for audio tracks. {@code 0} selects the default track if * there is one, or the first track if there's no default. The default value is {@code 0}. */ - @C.RoleFlags public final int preferredAudioRoleFlags; + public final @C.RoleFlags int preferredAudioRoleFlags; /** * Maximum allowed audio channel count. The default value is {@link Integer#MAX_VALUE} (i.e. no * constraint). @@ -811,7 +811,7 @@ public class TrackSelectionParameters implements Bundleable { * | {@link C#ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND} if the accessibility {@link CaptioningManager} * is enabled. */ - @C.RoleFlags public final int preferredTextRoleFlags; + public final @C.RoleFlags int preferredTextRoleFlags; /** * Whether a text track with undetermined language should be selected if no track with {@link * #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The From 69d6f84159072bcaf465bae4ca9b52e59fa690ef Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 13:18:48 +0000 Subject: [PATCH 054/113] Throw pending clipping errors created during period preparation. Currently, clipping errors are never thrown if we already have a MediaPeriod. This may happen for example for ProgressiveMediaSource where we need to create a MediaPeriod before knowing whether clipping is supported. Playback will still fail, but with unrelated assertion errors that are hard to understand for users. Fix this by setting the pending error on the ClippingMediaPeriod. #minor-release Issue: Issue: google/ExoPlayer#9580 PiperOrigin-RevId: 406809737 --- .../exoplayer2/source/ClippingMediaPeriod.java | 18 ++++++++++++++++++ .../exoplayer2/source/ClippingMediaSource.java | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java index 464bf497fe..10cc75de32 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; @@ -42,6 +43,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb private long pendingInitialDiscontinuityPositionUs; /* package */ long startUs; /* package */ long endUs; + @Nullable private IllegalClippingException clippingError; /** * Creates a new clipping media period that provides a clipped view of the specified {@link @@ -78,6 +80,16 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb this.endUs = endUs; } + /** + * Sets a clipping error detected by the media source so that it can be thrown as a period error + * at the next opportunity. + * + * @param clippingError The clipping error. + */ + public void setClippingError(IllegalClippingException clippingError) { + this.clippingError = clippingError; + } + @Override public void prepare(MediaPeriod.Callback callback, long positionUs) { this.callback = callback; @@ -86,6 +98,9 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public void maybeThrowPrepareError() throws IOException { + if (clippingError != null) { + throw clippingError; + } mediaPeriod.maybeThrowPrepareError(); } @@ -218,6 +233,9 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public void onPrepared(MediaPeriod mediaPeriod) { + if (clippingError != null) { + return; + } Assertions.checkNotNull(callback).onPrepared(this); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index f86b81760d..ab62cca0c5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -276,6 +276,11 @@ public final class ClippingMediaSource extends CompositeMediaSource { clippingTimeline = new ClippingTimeline(timeline, windowStartUs, windowEndUs); } catch (IllegalClippingException e) { clippingError = e; + // The clipping error won't be propagated while we have existing MediaPeriods. Setting the + // error at the MediaPeriods ensures it will be thrown as soon as possible. + for (int i = 0; i < mediaPeriods.size(); i++) { + mediaPeriods.get(i).setClippingError(clippingError); + } return; } refreshSourceInfo(clippingTimeline); From c05a5a162f28700b36d9e882880740445f4d8d92 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 13:19:35 +0000 Subject: [PATCH 055/113] Fix rounding error in fMP4 presentation time calculation The presentation time in fMP4 is calculated by adding and subtracting 3 values. All 3 values are currently converted to microseconds first before the calculation, leading to rounding errors. The rounding errors can be avoided by doing the conversion to microseconds as the last step. For example: In timescale 96000: 8008+8008-16016 = 0 Rounding to us first: 83416+83416-166833=-1 #minor-release PiperOrigin-RevId: 406809844 --- .../extractor/mp4/FragmentedMp4Extractor.java | 23 ++++++++----------- .../extractor/mp4/TrackFragment.java | 14 ++++------- .../mp4/sample_fragmented.mp4.0.dump | 20 ++++++++-------- .../sample_fragmented.mp4.unknown_length.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.0.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.1.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.2.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.3.dump | 20 ++++++++-------- ...ragmented_seekable.mp4.unknown_length.dump | 20 ++++++++-------- .../mp4/sample_fragmented_sei.mp4.0.dump | 20 ++++++++-------- ...ple_fragmented_sei.mp4.unknown_length.dump | 20 ++++++++-------- ...ple_fragmented_sideloaded_track.mp4.0.dump | 20 ++++++++-------- ...d_sideloaded_track.mp4.unknown_length.dump | 20 ++++++++-------- .../sample_partially_fragmented.mp4.0.dump | 18 +++++++-------- ...rtially_fragmented.mp4.unknown_length.dump | 18 +++++++-------- 15 files changed, 142 insertions(+), 151 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index b72159be1d..df05019f9b 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -999,21 +999,18 @@ public class FragmentedMp4Extractor implements Extractor { // Offset to the entire video timeline. In the presence of B-frames this is usually used to // ensure that the first frame's presentation timestamp is zero. - long edtsOffsetUs = 0; + long edtsOffset = 0; // Currently we only support a single edit that moves the entire media timeline (indicated by // duration == 0). Other uses of edit lists are uncommon and unsupported. if (track.editListDurations != null && track.editListDurations.length == 1 && track.editListDurations[0] == 0) { - edtsOffsetUs = - Util.scaleLargeTimestamp( - castNonNull(track.editListMediaTimes)[0], C.MICROS_PER_SECOND, track.timescale); + edtsOffset = castNonNull(track.editListMediaTimes)[0]; } int[] sampleSizeTable = fragment.sampleSizeTable; - int[] sampleCompositionTimeOffsetUsTable = fragment.sampleCompositionTimeOffsetUsTable; - long[] sampleDecodingTimeUsTable = fragment.sampleDecodingTimeUsTable; + long[] samplePresentationTimesUs = fragment.samplePresentationTimesUs; boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable; boolean workaroundEveryVideoFrameIsSyncFrame = @@ -1033,22 +1030,20 @@ public class FragmentedMp4Extractor implements Extractor { sampleFlagsPresent ? trun.readInt() : (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags : defaultSampleValues.flags; + int sampleCompositionTimeOffset = 0; if (sampleCompositionTimeOffsetsPresent) { // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in // version 0 trun boxes, however a significant number of streams violate the spec and use // signed integers instead. It's safe to always decode sample offsets as signed integers // here, because unsigned integers will still be parsed correctly (unless their top bit is // set, which is never true in practice because sample offsets are always small). - int sampleOffset = trun.readInt(); - sampleCompositionTimeOffsetUsTable[i] = - (int) ((sampleOffset * C.MICROS_PER_SECOND) / timescale); - } else { - sampleCompositionTimeOffsetUsTable[i] = 0; + sampleCompositionTimeOffset = trun.readInt(); } - sampleDecodingTimeUsTable[i] = - Util.scaleLargeTimestamp(cumulativeTime, C.MICROS_PER_SECOND, timescale) - edtsOffsetUs; + long samplePresentationTime = cumulativeTime + sampleCompositionTimeOffset - edtsOffset; + samplePresentationTimesUs[i] = + Util.scaleLargeTimestamp(samplePresentationTime, C.MICROS_PER_SECOND, timescale); if (!fragment.nextFragmentDecodeTimeIncludesMoov) { - sampleDecodingTimeUsTable[i] += trackBundle.moovSampleTable.durationUs; + samplePresentationTimesUs[i] += trackBundle.moovSampleTable.durationUs; } sampleSizeTable[i] = sampleSize; sampleIsSyncFrameTable[i] = diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java index 5a1fc6e0d8..d87f7ba443 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java @@ -42,10 +42,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public int[] trunLength; /** The size of each sample in the fragment. */ public int[] sampleSizeTable; - /** The composition time offset of each sample in the fragment, in microseconds. */ - public int[] sampleCompositionTimeOffsetUsTable; - /** The decoding time of each sample in the fragment, in microseconds. */ - public long[] sampleDecodingTimeUsTable; + /** The presentation time of each sample in the fragment, in microseconds. */ + public long[] samplePresentationTimesUs; /** Indicates which samples are sync frames. */ public boolean[] sampleIsSyncFrameTable; /** Whether the fragment defines encryption data. */ @@ -80,8 +78,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; trunDataPosition = new long[0]; trunLength = new int[0]; sampleSizeTable = new int[0]; - sampleCompositionTimeOffsetUsTable = new int[0]; - sampleDecodingTimeUsTable = new long[0]; + samplePresentationTimesUs = new long[0]; sampleIsSyncFrameTable = new boolean[0]; sampleHasSubsampleEncryptionTable = new boolean[0]; sampleEncryptionData = new ParsableByteArray(); @@ -123,8 +120,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // likely. The choice of 25% is relatively arbitrary. int tableSize = (sampleCount * 125) / 100; sampleSizeTable = new int[tableSize]; - sampleCompositionTimeOffsetUsTable = new int[tableSize]; - sampleDecodingTimeUsTable = new long[tableSize]; + samplePresentationTimesUs = new long[tableSize]; sampleIsSyncFrameTable = new boolean[tableSize]; sampleHasSubsampleEncryptionTable = new boolean[tableSize]; } @@ -173,7 +169,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @return The presentation timestamps of this sample in microseconds. */ public long getSamplePresentationTimeUs(int index) { - return sampleDecodingTimeUsTable[index] + sampleCompositionTimeOffsetUsTable[index]; + return samplePresentationTimesUs[index]; } /** Returns whether the sample at the given index has a subsample encryption table. */ diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump index 3bb4239d8f..1fca581047 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump index 3bb4239d8f..1fca581047 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump index d6ff4a544c..b96cd5335f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump index 4d5f324b4a..508eb65d16 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump index 4b23814b0a..4aa40d6145 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump index d234a0f03f..5196703b22 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump index d6ff4a544c..b96cd5335f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump index dbcfe75f35..91376322a8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump index dbcfe75f35..91376322a8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump index f90492cfa1..c1b45465ee 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump @@ -13,7 +13,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -25,7 +25,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -41,7 +41,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -49,7 +49,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -61,7 +61,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -73,7 +73,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -89,7 +89,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -97,7 +97,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -109,7 +109,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -121,7 +121,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump index f90492cfa1..c1b45465ee 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump @@ -13,7 +13,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -25,7 +25,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -41,7 +41,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -49,7 +49,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -61,7 +61,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -73,7 +73,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -89,7 +89,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -97,7 +97,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -109,7 +109,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -121,7 +121,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump index 5bb4411a69..9e58eea933 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump @@ -32,7 +32,7 @@ track 0: flags = 536870912 data = length 5867, hash 56F9EE87 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 570, hash 984421BD sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 4310, hash 291E6161 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 497, hash 398CBFAA sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4449, hash 322CAA2B sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1076, hash B479B634 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 463, hash A85F9769 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5339, hash F232195D sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 689, hash 3EB753A3 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 516, hash E6DF9C1C sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 625, hash ED1C8EF1 sample 20: - time = 700699 + time = 700700 flags = 0 data = length 492, hash E6E066EA sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2973, hash A3C54C3B sample 22: - time = 800799 + time = 800800 flags = 0 data = length 833, hash 41CA807D sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 384, hash A0E8FA50 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1450, hash 92741C3B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 413, hash 886904C sample 28: - time = 967632 + time = 967633 flags = 0 data = length 427, hash FC2FA8CC sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump index 5bb4411a69..9e58eea933 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump @@ -32,7 +32,7 @@ track 0: flags = 536870912 data = length 5867, hash 56F9EE87 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 570, hash 984421BD sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 4310, hash 291E6161 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 497, hash 398CBFAA sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4449, hash 322CAA2B sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1076, hash B479B634 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 463, hash A85F9769 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5339, hash F232195D sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 689, hash 3EB753A3 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 516, hash E6DF9C1C sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 625, hash ED1C8EF1 sample 20: - time = 700699 + time = 700700 flags = 0 data = length 492, hash E6E066EA sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2973, hash A3C54C3B sample 22: - time = 800799 + time = 800800 flags = 0 data = length 833, hash 41CA807D sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 384, hash A0E8FA50 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1450, hash 92741C3B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 413, hash 886904C sample 28: - time = 967632 + time = 967633 flags = 0 data = length 427, hash FC2FA8CC sample 29: From 8e03a0653a3d4b2d7c43945c7b75d694c6f50ad2 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 13:57:42 +0000 Subject: [PATCH 056/113] Suppress lint warning about wrong IntDef usage in media2 Utils AudioAttributesCompat.AttributeUsage and C.AudioUsage use equivalent values and can be directly assigned. PiperOrigin-RevId: 406815447 --- .../java/com/google/android/exoplayer2/ext/media2/Utils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java index 87b52f3598..70f984016e 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.media2; +import android.annotation.SuppressLint; import androidx.media.AudioAttributesCompat; import androidx.media2.common.SessionPlayer; import com.google.android.exoplayer2.Player; @@ -24,6 +25,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes; /* package */ final class Utils { /** Returns ExoPlayer audio attributes for the given audio attributes. */ + @SuppressLint("WrongConstant") // AudioAttributesCompat.AttributeUsage is equal to C.AudioUsage public static AudioAttributes getAudioAttributes(AudioAttributesCompat audioAttributesCompat) { return new AudioAttributes.Builder() .setContentType(audioAttributesCompat.getContentType()) From e039e335cd22583ff1b8a2e6cb696ec20817bb46 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 1 Nov 2021 14:00:45 +0000 Subject: [PATCH 057/113] Rename MediaFormatUtil constants PiperOrigin-RevId: 406816023 --- .../exoplayer2/util/MediaFormatUtil.java | 18 ++++++++++-------- .../exoplayer2/util/MediaFormatUtilTest.java | 10 +++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java index 653a1e3a99..45b2293c24 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java @@ -32,17 +32,19 @@ public final class MediaFormatUtil { * Custom {@link MediaFormat} key associated with a float representing the ratio between a pixel's * width and height. */ - public static final String KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT = + // The constant value must not be changed, because it's also set by the framework MediaParser API. + public static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT = "exo-pixel-width-height-ratio-float"; /** * Custom {@link MediaFormat} key associated with an integer representing the PCM encoding. * - *

    Equivalent to {@link MediaFormat#KEY_PCM_ENCODING}, except it allows additional - * ExoPlayer-specific values including {@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link + *

    Equivalent to {@link MediaFormat#KEY_PCM_ENCODING}, except it allows additional values + * defined by {@link C.PcmEncoding}, including {@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link * C#ENCODING_PCM_24BIT}, and {@link C#ENCODING_PCM_32BIT}. */ - public static final String KEY_EXO_PCM_ENCODING = "exo-pcm-encoding-int"; + // The constant value must not be changed, because it's also set by the framework MediaParser API. + public static final String KEY_PCM_ENCODING_EXTENDED = "exo-pcm-encoding-int"; private static final int MAX_POWER_OF_TWO_INT = 1 << 30; @@ -52,8 +54,8 @@ public final class MediaFormatUtil { *

    May include the following custom keys: * *

      - *
    • {@link #KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT}. - *
    • {@link #KEY_EXO_PCM_ENCODING}. + *
    • {@link #KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT}. + *
    • {@link #KEY_PCM_ENCODING_EXTENDED}. *
    */ @SuppressLint("InlinedApi") // Inlined MediaFormat keys. @@ -184,7 +186,7 @@ public final class MediaFormatUtil { @SuppressLint("InlinedApi") private static void maybeSetPixelAspectRatio( MediaFormat mediaFormat, float pixelWidthHeightRatio) { - mediaFormat.setFloat(KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, pixelWidthHeightRatio); + mediaFormat.setFloat(KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, pixelWidthHeightRatio); int pixelAspectRatioWidth = 1; int pixelAspectRatioHeight = 1; // ExoPlayer extractors output the pixel aspect ratio as a float. Do our best to recreate the @@ -207,7 +209,7 @@ public final class MediaFormatUtil { return; } int mediaFormatPcmEncoding; - maybeSetInteger(mediaFormat, KEY_EXO_PCM_ENCODING, exoPcmEncoding); + maybeSetInteger(mediaFormat, KEY_PCM_ENCODING_EXTENDED, exoPcmEncoding); switch (exoPcmEncoding) { case C.ENCODING_PCM_8BIT: mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java index 959b1279a0..d35cbe5853 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java @@ -37,7 +37,7 @@ public class MediaFormatUtilTest { // Assert that no invalid keys are accidentally being populated. assertThat(mediaFormat.getKeys()) .containsExactly( - MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, + MediaFormatUtil.KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, MediaFormat.KEY_ENCODER_DELAY, MediaFormat.KEY_ENCODER_PADDING, MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, @@ -46,7 +46,7 @@ public class MediaFormatUtilTest { MediaFormat.KEY_IS_FORCED_SUBTITLE, MediaFormat.KEY_IS_AUTOSELECT, MediaFormat.KEY_ROTATION); - assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) + assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) .isEqualTo(1.f); assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_DELAY)).isEqualTo(0); assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_PADDING)).isEqualTo(0); @@ -116,7 +116,7 @@ public class MediaFormatUtilTest { .isEqualTo(format.initializationData.get(1)); assertThat(mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING)).isEqualTo(format.pcmEncoding); - assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING)) + assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_PCM_ENCODING_EXTENDED)) .isEqualTo(format.pcmEncoding); assertThat(mediaFormat.getString(MediaFormat.KEY_LANGUAGE)).isEqualTo(format.language); @@ -140,7 +140,7 @@ public class MediaFormatUtilTest { (float) mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH) / mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT); assertThat(calculatedPixelAspectRatio).isWithin(.0001f).of(format.pixelWidthHeightRatio); - assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) + assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) .isEqualTo(format.pixelWidthHeightRatio); } @@ -148,7 +148,7 @@ public class MediaFormatUtilTest { public void createMediaFormatFromFormat_withPcmEncoding_setsCustomPcmEncodingEntry() { Format format = new Format.Builder().setPcmEncoding(C.ENCODING_PCM_32BIT).build(); MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format); - assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING)) + assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_PCM_ENCODING_EXTENDED)) .isEqualTo(C.ENCODING_PCM_32BIT); assertThat(mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING)).isFalse(); } From bf29d5248bb99522fce34aad5b2eceaefee900d2 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 14:08:07 +0000 Subject: [PATCH 058/113] Suppress lint warning about wrong IntDef assignment. The return values of AudioManager.getPlaybackOffloadSupport are the same as the values defined in C.AudioManagerOffloadMode. PiperOrigin-RevId: 406817413 --- .../com/google/android/exoplayer2/audio/DefaultAudioSink.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 5687282fae..5d2c7947d0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.audio; import static java.lang.Math.max; import static java.lang.Math.min; +import android.annotation.SuppressLint; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; @@ -1634,6 +1635,8 @@ public final class DefaultAudioSink implements AudioSink { } @RequiresApi(29) + // Return values of AudioManager.getPlaybackOffloadSupport are equal to C.AudioManagerOffloadMode. + @SuppressLint("WrongConstant") @C.AudioManagerOffloadMode private int getOffloadedPlaybackSupport( AudioFormat audioFormat, android.media.AudioAttributes audioAttributes) { From 5823ffeec0a9690bfd42b648ab60a059ca1057ba Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 14:28:22 +0000 Subject: [PATCH 059/113] Remove wrong IntDef usage. The variable is storing OpenGL's draw mode, which is different from Projection.DrawMode. PiperOrigin-RevId: 406820812 --- .../android/exoplayer2/video/spherical/ProjectionRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java index 692b7e687e..c52e5ddd57 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java @@ -207,7 +207,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final int vertexCount; private final FloatBuffer vertexBuffer; private final FloatBuffer textureBuffer; - @Projection.DrawMode private final int drawMode; + private final int drawMode; public MeshData(Projection.SubMesh subMesh) { vertexCount = subMesh.getVertexCount(); From 2702501783ad7a5c1c68c2866590b462f3b30455 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 15:09:25 +0000 Subject: [PATCH 060/113] Add missing import for gradle build PiperOrigin-RevId: 406827799 --- .../exoplayer2/trackselection/TrackSelectionOverridesTest.java | 1 + .../exoplayer2/trackselection/TrackSelectionParametersTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java index ba04f73429..d96e497b1f 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java index 7fab202421..f1271ecf7a 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; From 861474f6f9d53323bcb4085bcef9652832a147ac Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 15:46:37 +0000 Subject: [PATCH 061/113] Add missing IntDef constant. The video scaling mode and stream type defines a default constant that needs to be added to the IntDef definition to be assignable. PiperOrigin-RevId: 406835696 --- .../java/com/google/android/exoplayer2/C.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index 7c26b57600..2c2548468b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE_USE; +import android.annotation.SuppressLint; import android.content.Context; import android.media.AudioAttributes; import android.media.AudioFormat; @@ -274,8 +275,10 @@ public final class C { /** * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link * #STREAM_TYPE_DTMF}, {@link #STREAM_TYPE_MUSIC}, {@link #STREAM_TYPE_NOTIFICATION}, {@link - * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM} or {@link #STREAM_TYPE_VOICE_CALL}. + * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link + * #STREAM_TYPE_DEFAULT}. */ + @SuppressLint("UniqueConstants") // Intentional duplication to set STREAM_TYPE_DEFAULT. @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -285,7 +288,8 @@ public final class C { STREAM_TYPE_NOTIFICATION, STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, - STREAM_TYPE_VOICE_CALL + STREAM_TYPE_VOICE_CALL, + STREAM_TYPE_DEFAULT }) public @interface StreamType {} /** @see AudioManager#STREAM_ALARM */ @@ -537,11 +541,17 @@ public final class C { /** * Video scaling modes for {@link MediaCodec}-based renderers. One of {@link - * #VIDEO_SCALING_MODE_SCALE_TO_FIT} or {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}. + * #VIDEO_SCALING_MODE_SCALE_TO_FIT}, {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} or + * {@link #VIDEO_SCALING_MODE_DEFAULT}. */ + @SuppressLint("UniqueConstants") // Intentional duplication to set VIDEO_SCALING_MODE_DEFAULT. @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) + @IntDef({ + VIDEO_SCALING_MODE_SCALE_TO_FIT, + VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING, + VIDEO_SCALING_MODE_DEFAULT + }) public @interface VideoScalingMode {} /** See {@link MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT}. */ public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = From 0d01cae27082aae73f8e6b500bf124dc3cd9d6b1 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 16:03:06 +0000 Subject: [PATCH 062/113] Suppress lint warning about wrong IntDef in FrameworkMuxer The values are equivalent and we can suppress the warning. PiperOrigin-RevId: 406839242 --- .../google/android/exoplayer2/transformer/FrameworkMuxer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java index fc5b65891d..182f34e114 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java @@ -19,6 +19,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.SDK_INT; import static com.google.android.exoplayer2.util.Util.castNonNull; +import android.annotation.SuppressLint; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaMuxer; @@ -122,6 +123,7 @@ import java.nio.ByteBuffer; return mediaMuxer.addTrack(mediaFormat); } + @SuppressLint("WrongConstant") // C.BUFFER_FLAG_KEY_FRAME equals MediaCodec.BUFFER_FLAG_KEY_FRAME. @Override public void writeSampleData( int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) { From 70713c84584b699332f2d8905aa2b9925ca07fd6 Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 1 Nov 2021 16:07:05 +0000 Subject: [PATCH 063/113] Fix rewriting upstream/crypto package in lib-datasource PiperOrigin-RevId: 406840246 --- .../exoplayer2/upstream/{ => crypto}/AesCipherDataSink.java | 4 +++- .../upstream/{ => crypto}/AesCipherDataSource.java | 5 ++++- .../exoplayer2/upstream/{ => crypto}/AesFlushingCipher.java | 2 +- .../android/exoplayer2/upstream/crypto/package-info.java | 0 .../upstream/{ => crypto}/AesFlushingCipherTest.java | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) rename library/datasource/src/main/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesCipherDataSink.java (95%) rename library/datasource/src/main/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesCipherDataSource.java (91%) rename library/datasource/src/main/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesFlushingCipher.java (99%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java (100%) rename library/datasource/src/test/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesFlushingCipherTest.java (99%) diff --git a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java similarity index 95% rename from library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java index 5d84485e24..4e5b9f2b8e 100644 --- a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.min; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.upstream.DataSink; +import com.google.android.exoplayer2.upstream.DataSpec; import java.io.IOException; import javax.crypto.Cipher; diff --git a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java similarity index 91% rename from library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java index 77126904bb..98ec914fa0 100644 --- a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; @@ -21,6 +21,9 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.TransferListener; import java.io.IOException; import java.util.List; import java.util.Map; diff --git a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java similarity index 99% rename from library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java index 5a60a985db..96cb13604e 100644 --- a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Assertions; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java diff --git a/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java similarity index 99% rename from library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java index b6cdb61ba0..2811b0eada 100644 --- a/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java +++ b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import static com.google.common.truth.Truth.assertThat; import static java.lang.Math.min; From 4c40e80da60547b9ccfd53cc22ce7a29586d1202 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 1 Nov 2021 10:06:02 +0000 Subject: [PATCH 064/113] Remove unecessary warning suppression in PlaybackException PiperOrigin-RevId: 406783965 --- .../java/com/google/android/exoplayer2/PlaybackException.java | 1 - 1 file changed, 1 deletion(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index 335dcf2612..b643530678 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -423,7 +423,6 @@ public class PlaybackException extends Exception implements Bundleable { protected static final int FIELD_CUSTOM_ID_BASE = 1000; /** Object that can create a {@link PlaybackException} from a {@link Bundle}. */ - @SuppressWarnings("unchecked") public static final Creator CREATOR = PlaybackException::new; @CallSuper From 4e6960ee168c73c3c9a5647d0a18fda3af676c49 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 10:23:58 +0000 Subject: [PATCH 065/113] Add large renderer position offset. This helps to prevent issues where decoders can't handle negative timestamps. In particular it avoids issues when the media accidentally or intentionally starts with small negative timestamps. But it also helps to prevent other renderer resets at a later point, for example if a live stream with a large start offset is enqueued in the playlist. #minor-release PiperOrigin-RevId: 406786977 --- .../exoplayer2/ExoPlayerImplInternal.java | 9 +-- .../android/exoplayer2/MediaPeriodQueue.java | 24 +++++++- .../exoplayer2/MediaPeriodQueueTest.java | 47 ++++++++++----- .../transformer/TransformerAudioRenderer.java | 1 + .../transformer/TransformerBaseRenderer.java | 7 +++ .../TransformerMuxingVideoRenderer.java | 1 + .../TransformerTranscodingVideoRenderer.java | 1 + .../mka/bear-flac-16bit.mka.audiosink.dump | 58 +++++++++---------- .../mka/bear-flac-24bit.mka.audiosink.dump | 58 +++++++++---------- 9 files changed, 127 insertions(+), 79 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 30085aeb9a..9fadb56a79 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -1266,7 +1266,8 @@ import java.util.concurrent.atomic.AtomicBoolean; queue.advancePlayingPeriod(); } queue.removeAfter(newPlayingPeriodHolder); - newPlayingPeriodHolder.setRendererOffset(/* rendererPositionOffsetUs= */ 0); + newPlayingPeriodHolder.setRendererOffset( + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US); enableRenderers(); } } @@ -1299,7 +1300,7 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodHolder playingMediaPeriod = queue.getPlayingPeriod(); rendererPositionUs = playingMediaPeriod == null - ? periodPositionUs + ? MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + periodPositionUs : playingMediaPeriod.toRendererTime(periodPositionUs); mediaClock.resetPosition(rendererPositionUs); for (Renderer renderer : renderers) { @@ -1375,7 +1376,7 @@ import java.util.concurrent.atomic.AtomicBoolean; pendingRecoverableRendererError = null; isRebuffering = false; mediaClock.stop(); - rendererPositionUs = 0; + rendererPositionUs = MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US; for (Renderer renderer : renderers) { try { disableRenderer(renderer); @@ -1963,7 +1964,7 @@ import java.util.concurrent.atomic.AtomicBoolean; emptyTrackSelectorResult); mediaPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs); if (queue.getPlayingPeriod() == mediaPeriodHolder) { - resetRendererPosition(mediaPeriodHolder.getStartPositionRendererTime()); + resetRendererPosition(info.startPositionUs); } handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 18b258d779..f00ca859f1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -37,6 +37,26 @@ import com.google.common.collect.ImmutableList; */ /* package */ final class MediaPeriodQueue { + /** + * Initial renderer position offset used for the first item in the queue, in microseconds. + * + *

    Choosing a positive value, larger than any reasonable single media duration, ensures three + * things: + * + *

      + *
    • Media that accidentally or intentionally starts with small negative timestamps doesn't + * send samples with negative timestamps to decoders. This makes rendering more robust as + * many decoders are known to have problems with negative timestamps. + *
    • Enqueueing media after the initial item with a non-zero start offset (e.g. content after + * ad breaks or live streams) is virtually guaranteed to stay in the positive timestamp + * range even when seeking back. This prevents renderer resets that are required if the + * allowed timestamp range may become negative. + *
    • Choosing a large value with zeros at all relevant digits simplifies debugging as the + * original timestamp of the media is still visible. + *
    + */ + public static final long INITIAL_RENDERER_POSITION_OFFSET_US = 1_000_000_000_000L; + /** * Limits the maximum number of periods to buffer ahead of the current playing period. The * buffering policy normally prevents buffering too far ahead, but the policy could allow too many @@ -163,9 +183,7 @@ import com.google.common.collect.ImmutableList; TrackSelectorResult emptyTrackSelectorResult) { long rendererPositionOffsetUs = loading == null - ? (info.id.isAd() && info.requestedContentPositionUs != C.TIME_UNSET - ? info.requestedContentPositionUs - : 0) + ? INITIAL_RENDERER_POSITION_OFFSET_US : (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs); MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index 635167e658..53bc87e5e6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -493,10 +493,13 @@ public final class MediaPeriodQueueTest { // Change position of first ad (= change duration of playing content before first ad). updateAdPlaybackStateAndTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000); setAdGroupLoaded(/* adGroupIndex= */ 0); - long maxRendererReadPositionUs = FIRST_AD_START_TIME_US - 3000; + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 3000; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, maxRendererReadPositionUs); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isTrue(); assertThat(getQueueLength()).isEqualTo(1); @@ -518,10 +521,13 @@ public final class MediaPeriodQueueTest { // Change position of first ad (= change duration of playing content before first ad). updateAdPlaybackStateAndTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000); setAdGroupLoaded(/* adGroupIndex= */ 0); - long maxRendererReadPositionUs = FIRST_AD_START_TIME_US - 1000; + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, maxRendererReadPositionUs); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isFalse(); assertThat(getQueueLength()).isEqualTo(1); @@ -552,10 +558,13 @@ public final class MediaPeriodQueueTest { .withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true); updateTimeline(); setAdGroupLoaded(/* adGroupIndex= */ 0); - long maxRendererReadPositionUs = FIRST_AD_START_TIME_US - 1000; + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, maxRendererReadPositionUs); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isTrue(); assertThat(getQueueLength()).isEqualTo(1); @@ -583,7 +592,9 @@ public final class MediaPeriodQueueTest { setAdGroupLoaded(/* adGroupIndex= */ 1); boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( - playbackInfo.timeline, /* rendererPositionUs= */ 0, /* maxRendererReadPositionUs= */ 0); + playbackInfo.timeline, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + /* maxRendererReadPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US); assertThat(changeHandled).isTrue(); assertThat(getQueueLength()).isEqualTo(3); @@ -608,11 +619,13 @@ public final class MediaPeriodQueueTest { /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); + long maxRendererReadPositionUs = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, - /* maxRendererReadPositionUs= */ FIRST_AD_START_TIME_US); + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, + maxRendererReadPositionUs); assertThat(changeHandled).isFalse(); assertThat(getQueueLength()).isEqualTo(3); @@ -636,11 +649,14 @@ public final class MediaPeriodQueueTest { /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); - long readingPositionAtStartOfContentBetweenAds = FIRST_AD_START_TIME_US + AD_DURATION_US; + long readingPositionAtStartOfContentBetweenAds = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + + FIRST_AD_START_TIME_US + + AD_DURATION_US; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds); assertThat(changeHandled).isTrue(); @@ -665,11 +681,14 @@ public final class MediaPeriodQueueTest { /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); - long readingPositionAtEndOfContentBetweenAds = SECOND_AD_START_TIME_US + AD_DURATION_US; + long readingPositionAtEndOfContentBetweenAds = + MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + + SECOND_AD_START_TIME_US + + AD_DURATION_US; boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* maxRendererReadPositionUs= */ readingPositionAtEndOfContentBetweenAds); assertThat(changeHandled).isFalse(); @@ -697,7 +716,7 @@ public final class MediaPeriodQueueTest { boolean changeHandled = mediaPeriodQueue.updateQueuedPeriods( playbackInfo.timeline, - /* rendererPositionUs= */ 0, + /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* maxRendererReadPositionUs= */ C.TIME_END_OF_SOURCE); assertThat(changeHandled).isFalse(); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 02248296d0..8da9dcd2e1 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -279,6 +279,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; int result = readSource(getFormatHolder(), decoderInputBuffer, /* readFlags= */ 0); switch (result) { case C.RESULT_BUFFER_READ: + decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); decoderInputBuffer.flip(); decoder.queueInputBuffer(decoderInputBuffer); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java index e0ceb945fc..6e19f0b9f9 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.util.MimeTypes; protected final Transformation transformation; protected boolean isRendererStarted; + protected long streamOffsetUs; public TransformerBaseRenderer( int trackType, @@ -46,6 +47,12 @@ import com.google.android.exoplayer2.util.MimeTypes; this.transformation = transformation; } + @Override + protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) + throws ExoPlaybackException { + this.streamOffsetUs = offsetUs; + } + @Override @C.FormatSupport public final int supportsFormat(Format format) { diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java index 0be02ecdee..d14378754e 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java @@ -117,6 +117,7 @@ import java.nio.ByteBuffer; muxerWrapper.endTrack(getTrackType()); return false; } + buffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), buffer.timeUs); ByteBuffer data = checkNotNull(buffer.data); data.flip(); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index b04490152b..931e985a5d 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -320,6 +320,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; case C.RESULT_FORMAT_READ: throw new IllegalStateException("Format changes are not supported."); case C.RESULT_BUFFER_READ: + decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); ByteBuffer data = checkNotNull(decoderInputBuffer.data); data.flip(); diff --git a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump index 044cde306c..b7319b872b 100644 --- a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump +++ b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-16bit.mka.audiosink.dump @@ -3,89 +3,89 @@ config: channelCount = 2 sampleRate = 48000 buffer: - time = 1000 + time = 1000000001000 data = 1217833679 buffer: - time = 97000 + time = 1000000097000 data = 558614672 buffer: - time = 193000 + time = 1000000193000 data = -709714787 buffer: - time = 289000 + time = 1000000289000 data = 1367870571 buffer: - time = 385000 + time = 1000000385000 data = -141229457 buffer: - time = 481000 + time = 1000000481000 data = 1287758361 buffer: - time = 577000 + time = 1000000577000 data = 1125289147 buffer: - time = 673000 + time = 1000000673000 data = -1677383475 buffer: - time = 769000 + time = 1000000769000 data = 2130742861 buffer: - time = 865000 + time = 1000000865000 data = -1292320253 buffer: - time = 961000 + time = 1000000961000 data = -456587163 buffer: - time = 1057000 + time = 1000001057000 data = 748981534 buffer: - time = 1153000 + time = 1000001153000 data = 1550456016 buffer: - time = 1249000 + time = 1000001249000 data = 1657906039 buffer: - time = 1345000 + time = 1000001345000 data = -762677083 buffer: - time = 1441000 + time = 1000001441000 data = -1343810763 buffer: - time = 1537000 + time = 1000001537000 data = 1137318783 buffer: - time = 1633000 + time = 1000001633000 data = -1891318229 buffer: - time = 1729000 + time = 1000001729000 data = -472068495 buffer: - time = 1825000 + time = 1000001825000 data = 832315001 buffer: - time = 1921000 + time = 1000001921000 data = 2054935175 buffer: - time = 2017000 + time = 1000002017000 data = 57921641 buffer: - time = 2113000 + time = 1000002113000 data = 2132759067 buffer: - time = 2209000 + time = 1000002209000 data = -1742540521 buffer: - time = 2305000 + time = 1000002305000 data = 1657024301 buffer: - time = 2401000 + time = 1000002401000 data = -585080145 buffer: - time = 2497000 + time = 1000002497000 data = 427271397 buffer: - time = 2593000 + time = 1000002593000 data = -364201340 buffer: - time = 2689000 + time = 1000002689000 data = -627965287 diff --git a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump index 319ee311f0..f425e7e2f2 100644 --- a/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump +++ b/testdata/src/test/assets/audiosinkdumps/mka/bear-flac-24bit.mka.audiosink.dump @@ -3,89 +3,89 @@ config: channelCount = 2 sampleRate = 48000 buffer: - time = 0 + time = 1000000000000 data = 225023649 buffer: - time = 96000 + time = 1000000096000 data = 455106306 buffer: - time = 192000 + time = 1000000192000 data = 2025727297 buffer: - time = 288000 + time = 1000000288000 data = 758514657 buffer: - time = 384000 + time = 1000000384000 data = 1044986473 buffer: - time = 480000 + time = 1000000480000 data = -2030029695 buffer: - time = 576000 + time = 1000000576000 data = 1907053281 buffer: - time = 672000 + time = 1000000672000 data = -1974954431 buffer: - time = 768000 + time = 1000000768000 data = -206248383 buffer: - time = 864000 + time = 1000000864000 data = 1484984417 buffer: - time = 960000 + time = 1000000960000 data = -1306117439 buffer: - time = 1056000 + time = 1000001056000 data = 692829792 buffer: - time = 1152000 + time = 1000001152000 data = 1070563058 buffer: - time = 1248000 + time = 1000001248000 data = -1444096479 buffer: - time = 1344000 + time = 1000001344000 data = 1753016419 buffer: - time = 1440000 + time = 1000001440000 data = 1947797953 buffer: - time = 1536000 + time = 1000001536000 data = 266121411 buffer: - time = 1632000 + time = 1000001632000 data = 1275494369 buffer: - time = 1728000 + time = 1000001728000 data = 372077825 buffer: - time = 1824000 + time = 1000001824000 data = -993079679 buffer: - time = 1920000 + time = 1000001920000 data = 177307937 buffer: - time = 2016000 + time = 1000002016000 data = 2037083009 buffer: - time = 2112000 + time = 1000002112000 data = -435776287 buffer: - time = 2208000 + time = 1000002208000 data = 1867447329 buffer: - time = 2304000 + time = 1000002304000 data = 1884495937 buffer: - time = 2400000 + time = 1000002400000 data = -804673375 buffer: - time = 2496000 + time = 1000002496000 data = -588531007 buffer: - time = 2592000 + time = 1000002592000 data = -1064642970 buffer: - time = 2688000 + time = 1000002688000 data = -1771406207 From f0be0931404b14092db118557569b9465c3e15ba Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 1 Nov 2021 10:42:22 +0000 Subject: [PATCH 066/113] Upgrade gradle plugin version PiperOrigin-RevId: 406789671 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 055e3cdaff..d362ff785b 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2' } } From 3f4cde1873cd1d06b163c894bb9ae842e0647ac5 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 1 Nov 2021 11:13:15 +0000 Subject: [PATCH 067/113] Add TYPE_USE to IntDefs used in the media3 stable API This allows the use of the intdef in parameterized types, e.g. List<@MyIntDef Integer> For IntDefs that are already released in ExoPlayer 2.15.1 we add TYPE_USE in addition to all other reasonable targets, to maintain backwards compatibility with Kotlin code (where an incorrectly positioned annotation is a compilation failure). 'reasonable targets' includes FIELD, METHOD, PARAMETER and LOCAL_VARIABLE but not TYPE, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE or MODULE. TYPE_PARAMETER is implied by TYPE_USE. For not-yet-released IntDefs we just add TYPE_USE. #minor-release PiperOrigin-RevId: 406793413 --- .../java/com/google/android/exoplayer2/C.java | 17 +++++++++++++++-- .../android/exoplayer2/MediaMetadata.java | 9 +++++++++ .../android/exoplayer2/PlaybackException.java | 8 ++++++++ .../com/google/android/exoplayer2/Player.java | 16 ++++++++++++++++ .../com/google/android/exoplayer2/text/Cue.java | 11 +++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index a779729bd9..7c26b57600 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.content.Context; import android.media.AudioAttributes; import android.media.AudioFormat; @@ -29,7 +35,6 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.errorprone.annotations.InlineMe; import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -126,6 +131,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) @IntDef( open = true, value = { @@ -306,6 +312,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ CONTENT_TYPE_MOVIE, CONTENT_TYPE_MUSIC, @@ -334,6 +341,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( flag = true, value = {FLAG_AUDIBILITY_ENFORCED}) @@ -354,6 +362,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ USAGE_ALARM, USAGE_ASSISTANCE_ACCESSIBILITY, @@ -422,6 +431,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ALLOW_CAPTURE_BY_ALL, ALLOW_CAPTURE_BY_NONE, ALLOW_CAPTURE_BY_SYSTEM}) public @interface AudioAllowedCapturePolicy {} /** See {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_ALL}. */ @@ -565,6 +575,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, SELECTION_FLAG_AUTOSELECT}) @@ -680,7 +691,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) - @Target({ElementType.TYPE_USE}) + @Target(TYPE_USE) @IntDef( open = true, value = { @@ -976,6 +987,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({WAKE_MODE_NONE, WAKE_MODE_LOCAL, WAKE_MODE_NETWORK}) public @interface WakeMode {} /** @@ -1012,6 +1024,7 @@ public final class C { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( flag = true, value = { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 4dad2da4c3..3df88a5aa9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.net.Uri; import android.os.Bundle; import androidx.annotation.IntDef; @@ -26,6 +32,7 @@ import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.Arrays; import java.util.List; @@ -500,6 +507,7 @@ public final class MediaMetadata implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ FOLDER_TYPE_NONE, FOLDER_TYPE_MIXED, @@ -537,6 +545,7 @@ public final class MediaMetadata implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ PICTURE_TYPE_OTHER, PICTURE_TYPE_FILE_ICON, diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index b643530678..0adea0535a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.net.ConnectivityManager; import android.os.Bundle; import android.os.RemoteException; @@ -28,6 +34,7 @@ import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** Thrown when a non locally recoverable playback failure occurs. */ public class PlaybackException extends Exception implements Bundleable { @@ -40,6 +47,7 @@ public class PlaybackException extends Exception implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef( open = true, value = { 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 5c60097eb0..08bfbe9016 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 @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.os.Bundle; import android.os.Looper; import android.view.Surface; @@ -40,6 +46,7 @@ import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; @@ -1079,6 +1086,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @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. */ @@ -1107,6 +1115,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, @@ -1133,6 +1142,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ PLAYBACK_SUPPRESSION_REASON_NONE, PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS @@ -1149,6 +1159,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) @interface RepeatMode {} /** @@ -1180,6 +1191,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ DISCONTINUITY_REASON_AUTO_TRANSITION, DISCONTINUITY_REASON_SEEK, @@ -1218,6 +1230,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, TIMELINE_CHANGE_REASON_SOURCE_UPDATE}) @interface TimelineChangeReason {} /** Timeline changed as a result of a change of the playlist items or the order of the items. */ @@ -1238,6 +1251,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ MEDIA_ITEM_TRANSITION_REASON_REPEAT, MEDIA_ITEM_TRANSITION_REASON_AUTO, @@ -1270,6 +1284,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ EVENT_TIMELINE_CHANGED, EVENT_MEDIA_ITEM_TRANSITION, @@ -1355,6 +1370,7 @@ public interface Player { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ COMMAND_INVALID, COMMAND_PLAY_PAUSE, 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 66ab2f3682..0ef89f9b0b 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 @@ -15,6 +15,12 @@ */ package com.google.android.exoplayer2.text; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.graphics.Bitmap; import android.graphics.Color; import android.os.Bundle; @@ -32,6 +38,7 @@ import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.dataflow.qual.Pure; /** Contains information about a specific cue, including textual content and formatting data. */ @@ -53,6 +60,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) public @interface AnchorType {} @@ -80,6 +88,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) public @interface LineType {} @@ -96,6 +105,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ TYPE_UNSET, TEXT_SIZE_TYPE_FRACTIONAL, @@ -119,6 +129,7 @@ public final class Cue implements Bundleable { */ @Documented @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({ TYPE_UNSET, VERTICAL_TYPE_RL, From 56b589c422c51dd704f72e3f1a741cb91e4c9e8c Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 1 Nov 2021 12:29:21 +0000 Subject: [PATCH 068/113] Re-position IntDefs in media3 stable API These IntDefs are now annotated with TYPE_USE [1], so they can be moved to directly before the type (int). [1] Since PiperOrigin-RevId: 406803555 --- .../main/java/com/google/android/exoplayer2/Format.java | 8 ++++---- .../java/com/google/android/exoplayer2/MediaItem.java | 8 ++++---- .../java/com/google/android/exoplayer2/MediaMetadata.java | 4 ++-- .../com/google/android/exoplayer2/PlaybackException.java | 2 +- .../main/java/com/google/android/exoplayer2/Player.java | 6 ++---- .../main/java/com/google/android/exoplayer2/text/Cue.java | 8 ++++---- .../trackselection/TrackSelectionParameters.java | 8 ++++---- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index 909c759c85..c21e68c886 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -128,8 +128,8 @@ public final class Format implements Bundleable { @Nullable private String id; @Nullable private String label; @Nullable private String language; - @C.SelectionFlags private int selectionFlags; - @C.RoleFlags private int roleFlags; + private @C.SelectionFlags int selectionFlags; + private @C.RoleFlags int roleFlags; private int averageBitrate; private int peakBitrate; @Nullable private String codecs; @@ -620,9 +620,9 @@ public final class Format implements Bundleable { /** The language as an IETF BCP 47 conformant tag, or null if unknown or not applicable. */ @Nullable public final String language; /** Track selection flags. */ - @C.SelectionFlags public final int selectionFlags; + public final @C.SelectionFlags int selectionFlags; /** Track role flags. */ - @C.RoleFlags public final int roleFlags; + public final @C.RoleFlags int roleFlags; /** * The average bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. The * way in which this field is populated depends on the type of media to which the format diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java index 7b4ed43d38..b18c52018e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java @@ -1235,8 +1235,8 @@ public final class MediaItem implements Bundleable { private Uri uri; @Nullable private String mimeType; @Nullable private String language; - @C.SelectionFlags private int selectionFlags; - @C.RoleFlags private int roleFlags; + private @C.SelectionFlags int selectionFlags; + private @C.RoleFlags int roleFlags; @Nullable private String label; /** @@ -1310,9 +1310,9 @@ public final class MediaItem implements Bundleable { /** The language. */ @Nullable public final String language; /** The selection flags. */ - @C.SelectionFlags public final int selectionFlags; + public final @C.SelectionFlags int selectionFlags; /** The role flags. */ - @C.RoleFlags public final int roleFlags; + public final @C.RoleFlags int roleFlags; /** The label. */ @Nullable public final String label; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 3df88a5aa9..f0c1243e09 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -623,7 +623,7 @@ public final class MediaMetadata implements Bundleable { /** Optional artwork data as a compressed byte array. */ @Nullable public final byte[] artworkData; /** Optional {@link PictureType} of the artwork data. */ - @Nullable @PictureType public final Integer artworkDataType; + @Nullable public final @PictureType Integer artworkDataType; /** Optional artwork {@link Uri}. */ @Nullable public final Uri artworkUri; /** Optional track number. */ @@ -631,7 +631,7 @@ public final class MediaMetadata implements Bundleable { /** Optional total number of tracks. */ @Nullable public final Integer totalTrackCount; /** Optional {@link FolderType}. */ - @Nullable @FolderType public final Integer folderType; + @Nullable public final @FolderType Integer folderType; /** Optional boolean for media playability. */ @Nullable public final Boolean isPlayable; /** @deprecated Use {@link #recordingYear} instead. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index 0adea0535a..febb938fdb 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -320,7 +320,7 @@ public class PlaybackException extends Exception implements Bundleable { } /** An error code which identifies the cause of the playback failure. */ - @ErrorCode public final int errorCode; + public final @ErrorCode int errorCode; /** The value of {@link SystemClock#elapsedRealtime()} when this exception was created. */ public final long timestampMs; 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 08bfbe9016..b6222596a7 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 @@ -454,8 +454,7 @@ public interface Player { * @return The {@link Event} at the given index. * @throws IndexOutOfBoundsException If index is outside the allowed range. */ - @Event - public int get(int index) { + public @Event int get(int index) { return flags.get(index); } @@ -870,8 +869,7 @@ public interface Player { * @return The {@link Command} at the given index. * @throws IndexOutOfBoundsException If index is outside the allowed range. */ - @Command - public int get(int index) { + public @Command int get(int index) { return flags.get(index); } 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 0ef89f9b0b..a386580f3d 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 @@ -556,16 +556,16 @@ public final class Cue implements Bundleable { @Nullable private Alignment multiRowAlignment; private float line; @LineType private int lineType; - @AnchorType private int lineAnchor; + private @AnchorType int lineAnchor; private float position; - @AnchorType private int positionAnchor; - @TextSizeType private int textSizeType; + private @AnchorType int positionAnchor; + private @TextSizeType int textSizeType; private float textSize; private float size; private float bitmapHeight; private boolean windowColorSet; @ColorInt private int windowColor; - @VerticalType private int verticalType; + private @VerticalType int verticalType; private float shearDegrees; public Builder() { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java index 738cf74e2b..8080451246 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java @@ -82,13 +82,13 @@ public class TrackSelectionParameters implements Bundleable { private ImmutableList preferredVideoMimeTypes; // Audio private ImmutableList preferredAudioLanguages; - @C.RoleFlags private int preferredAudioRoleFlags; + private @C.RoleFlags int preferredAudioRoleFlags; private int maxAudioChannelCount; private int maxAudioBitrate; private ImmutableList preferredAudioMimeTypes; // Text private ImmutableList preferredTextLanguages; - @C.RoleFlags private int preferredTextRoleFlags; + private @C.RoleFlags int preferredTextRoleFlags; private boolean selectUndeterminedTextLanguage; // General private boolean forceLowestBitrate; @@ -781,7 +781,7 @@ public class TrackSelectionParameters implements Bundleable { * The preferred {@link C.RoleFlags} for audio tracks. {@code 0} selects the default track if * there is one, or the first track if there's no default. The default value is {@code 0}. */ - @C.RoleFlags public final int preferredAudioRoleFlags; + public final @C.RoleFlags int preferredAudioRoleFlags; /** * Maximum allowed audio channel count. The default value is {@link Integer#MAX_VALUE} (i.e. no * constraint). @@ -811,7 +811,7 @@ public class TrackSelectionParameters implements Bundleable { * | {@link C#ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND} if the accessibility {@link CaptioningManager} * is enabled. */ - @C.RoleFlags public final int preferredTextRoleFlags; + public final @C.RoleFlags int preferredTextRoleFlags; /** * Whether a text track with undetermined language should be selected if no track with {@link * #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The From 08f66c4d5bb1937198a6f21861477507fde58b4b Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 13:18:48 +0000 Subject: [PATCH 069/113] Throw pending clipping errors created during period preparation. Currently, clipping errors are never thrown if we already have a MediaPeriod. This may happen for example for ProgressiveMediaSource where we need to create a MediaPeriod before knowing whether clipping is supported. Playback will still fail, but with unrelated assertion errors that are hard to understand for users. Fix this by setting the pending error on the ClippingMediaPeriod. #minor-release Issue: Issue: google/ExoPlayer#9580 PiperOrigin-RevId: 406809737 --- .../exoplayer2/source/ClippingMediaPeriod.java | 18 ++++++++++++++++++ .../exoplayer2/source/ClippingMediaSource.java | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java index 464bf497fe..10cc75de32 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; @@ -42,6 +43,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb private long pendingInitialDiscontinuityPositionUs; /* package */ long startUs; /* package */ long endUs; + @Nullable private IllegalClippingException clippingError; /** * Creates a new clipping media period that provides a clipped view of the specified {@link @@ -78,6 +80,16 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb this.endUs = endUs; } + /** + * Sets a clipping error detected by the media source so that it can be thrown as a period error + * at the next opportunity. + * + * @param clippingError The clipping error. + */ + public void setClippingError(IllegalClippingException clippingError) { + this.clippingError = clippingError; + } + @Override public void prepare(MediaPeriod.Callback callback, long positionUs) { this.callback = callback; @@ -86,6 +98,9 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public void maybeThrowPrepareError() throws IOException { + if (clippingError != null) { + throw clippingError; + } mediaPeriod.maybeThrowPrepareError(); } @@ -218,6 +233,9 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public void onPrepared(MediaPeriod mediaPeriod) { + if (clippingError != null) { + return; + } Assertions.checkNotNull(callback).onPrepared(this); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index f86b81760d..ab62cca0c5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -276,6 +276,11 @@ public final class ClippingMediaSource extends CompositeMediaSource { clippingTimeline = new ClippingTimeline(timeline, windowStartUs, windowEndUs); } catch (IllegalClippingException e) { clippingError = e; + // The clipping error won't be propagated while we have existing MediaPeriods. Setting the + // error at the MediaPeriods ensures it will be thrown as soon as possible. + for (int i = 0; i < mediaPeriods.size(); i++) { + mediaPeriods.get(i).setClippingError(clippingError); + } return; } refreshSourceInfo(clippingTimeline); From 4803ab3bd12b71fc32150e96475c0e16aaf6ddc5 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 13:19:35 +0000 Subject: [PATCH 070/113] Fix rounding error in fMP4 presentation time calculation The presentation time in fMP4 is calculated by adding and subtracting 3 values. All 3 values are currently converted to microseconds first before the calculation, leading to rounding errors. The rounding errors can be avoided by doing the conversion to microseconds as the last step. For example: In timescale 96000: 8008+8008-16016 = 0 Rounding to us first: 83416+83416-166833=-1 #minor-release PiperOrigin-RevId: 406809844 --- .../extractor/mp4/FragmentedMp4Extractor.java | 23 ++++++++----------- .../extractor/mp4/TrackFragment.java | 14 ++++------- .../mp4/sample_fragmented.mp4.0.dump | 20 ++++++++-------- .../sample_fragmented.mp4.unknown_length.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.0.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.1.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.2.dump | 20 ++++++++-------- .../mp4/sample_fragmented_seekable.mp4.3.dump | 20 ++++++++-------- ...ragmented_seekable.mp4.unknown_length.dump | 20 ++++++++-------- .../mp4/sample_fragmented_sei.mp4.0.dump | 20 ++++++++-------- ...ple_fragmented_sei.mp4.unknown_length.dump | 20 ++++++++-------- ...ple_fragmented_sideloaded_track.mp4.0.dump | 20 ++++++++-------- ...d_sideloaded_track.mp4.unknown_length.dump | 20 ++++++++-------- .../sample_partially_fragmented.mp4.0.dump | 18 +++++++-------- ...rtially_fragmented.mp4.unknown_length.dump | 18 +++++++-------- 15 files changed, 142 insertions(+), 151 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index b72159be1d..df05019f9b 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -999,21 +999,18 @@ public class FragmentedMp4Extractor implements Extractor { // Offset to the entire video timeline. In the presence of B-frames this is usually used to // ensure that the first frame's presentation timestamp is zero. - long edtsOffsetUs = 0; + long edtsOffset = 0; // Currently we only support a single edit that moves the entire media timeline (indicated by // duration == 0). Other uses of edit lists are uncommon and unsupported. if (track.editListDurations != null && track.editListDurations.length == 1 && track.editListDurations[0] == 0) { - edtsOffsetUs = - Util.scaleLargeTimestamp( - castNonNull(track.editListMediaTimes)[0], C.MICROS_PER_SECOND, track.timescale); + edtsOffset = castNonNull(track.editListMediaTimes)[0]; } int[] sampleSizeTable = fragment.sampleSizeTable; - int[] sampleCompositionTimeOffsetUsTable = fragment.sampleCompositionTimeOffsetUsTable; - long[] sampleDecodingTimeUsTable = fragment.sampleDecodingTimeUsTable; + long[] samplePresentationTimesUs = fragment.samplePresentationTimesUs; boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable; boolean workaroundEveryVideoFrameIsSyncFrame = @@ -1033,22 +1030,20 @@ public class FragmentedMp4Extractor implements Extractor { sampleFlagsPresent ? trun.readInt() : (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags : defaultSampleValues.flags; + int sampleCompositionTimeOffset = 0; if (sampleCompositionTimeOffsetsPresent) { // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in // version 0 trun boxes, however a significant number of streams violate the spec and use // signed integers instead. It's safe to always decode sample offsets as signed integers // here, because unsigned integers will still be parsed correctly (unless their top bit is // set, which is never true in practice because sample offsets are always small). - int sampleOffset = trun.readInt(); - sampleCompositionTimeOffsetUsTable[i] = - (int) ((sampleOffset * C.MICROS_PER_SECOND) / timescale); - } else { - sampleCompositionTimeOffsetUsTable[i] = 0; + sampleCompositionTimeOffset = trun.readInt(); } - sampleDecodingTimeUsTable[i] = - Util.scaleLargeTimestamp(cumulativeTime, C.MICROS_PER_SECOND, timescale) - edtsOffsetUs; + long samplePresentationTime = cumulativeTime + sampleCompositionTimeOffset - edtsOffset; + samplePresentationTimesUs[i] = + Util.scaleLargeTimestamp(samplePresentationTime, C.MICROS_PER_SECOND, timescale); if (!fragment.nextFragmentDecodeTimeIncludesMoov) { - sampleDecodingTimeUsTable[i] += trackBundle.moovSampleTable.durationUs; + samplePresentationTimesUs[i] += trackBundle.moovSampleTable.durationUs; } sampleSizeTable[i] = sampleSize; sampleIsSyncFrameTable[i] = diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java index 5a1fc6e0d8..d87f7ba443 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java @@ -42,10 +42,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public int[] trunLength; /** The size of each sample in the fragment. */ public int[] sampleSizeTable; - /** The composition time offset of each sample in the fragment, in microseconds. */ - public int[] sampleCompositionTimeOffsetUsTable; - /** The decoding time of each sample in the fragment, in microseconds. */ - public long[] sampleDecodingTimeUsTable; + /** The presentation time of each sample in the fragment, in microseconds. */ + public long[] samplePresentationTimesUs; /** Indicates which samples are sync frames. */ public boolean[] sampleIsSyncFrameTable; /** Whether the fragment defines encryption data. */ @@ -80,8 +78,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; trunDataPosition = new long[0]; trunLength = new int[0]; sampleSizeTable = new int[0]; - sampleCompositionTimeOffsetUsTable = new int[0]; - sampleDecodingTimeUsTable = new long[0]; + samplePresentationTimesUs = new long[0]; sampleIsSyncFrameTable = new boolean[0]; sampleHasSubsampleEncryptionTable = new boolean[0]; sampleEncryptionData = new ParsableByteArray(); @@ -123,8 +120,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // likely. The choice of 25% is relatively arbitrary. int tableSize = (sampleCount * 125) / 100; sampleSizeTable = new int[tableSize]; - sampleCompositionTimeOffsetUsTable = new int[tableSize]; - sampleDecodingTimeUsTable = new long[tableSize]; + samplePresentationTimesUs = new long[tableSize]; sampleIsSyncFrameTable = new boolean[tableSize]; sampleHasSubsampleEncryptionTable = new boolean[tableSize]; } @@ -173,7 +169,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @return The presentation timestamps of this sample in microseconds. */ public long getSamplePresentationTimeUs(int index) { - return sampleDecodingTimeUsTable[index] + sampleCompositionTimeOffsetUsTable[index]; + return samplePresentationTimesUs[index]; } /** Returns whether the sample at the given index has a subsample encryption table. */ diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump index 3bb4239d8f..1fca581047 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.0.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump index 3bb4239d8f..1fca581047 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump index d6ff4a544c..b96cd5335f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.0.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump index 4d5f324b4a..508eb65d16 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.1.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump index 4b23814b0a..4aa40d6145 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.2.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump index d234a0f03f..5196703b22 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.3.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump index d6ff4a544c..b96cd5335f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_seekable.mp4.unknown_length.dump @@ -23,7 +23,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -35,7 +35,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -51,7 +51,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -59,7 +59,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -71,7 +71,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -83,7 +83,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -99,7 +99,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -107,7 +107,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -119,7 +119,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -131,7 +131,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump index dbcfe75f35..91376322a8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.0.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump index dbcfe75f35..91376322a8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sei.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -32,7 +32,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump index f90492cfa1..c1b45465ee 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.0.dump @@ -13,7 +13,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -25,7 +25,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -41,7 +41,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -49,7 +49,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -61,7 +61,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -73,7 +73,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -89,7 +89,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -97,7 +97,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -109,7 +109,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -121,7 +121,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump index f90492cfa1..c1b45465ee 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_fragmented_sideloaded_track.mp4.unknown_length.dump @@ -13,7 +13,7 @@ track 0: flags = 1 data = length 38070, hash B58E1AEE sample 1: - time = 200199 + time = 200200 flags = 0 data = length 8340, hash 8AC449FF sample 2: @@ -25,7 +25,7 @@ track 0: flags = 0 data = length 469, hash D6E0A200 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 564, hash E5F56C5B sample 5: @@ -41,7 +41,7 @@ track 0: flags = 0 data = length 455, hash B9CCE047 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 467, hash 69806D94 sample 9: @@ -49,7 +49,7 @@ track 0: flags = 0 data = length 4549, hash 3944F501 sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1087, hash 491BF106 sample 11: @@ -61,7 +61,7 @@ track 0: flags = 0 data = length 455, hash 8A0610 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5190, hash B9031D8 sample 14: @@ -73,7 +73,7 @@ track 0: flags = 0 data = length 653, hash 8494F326 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 485, hash 2CCC85F4 sample 17: @@ -89,7 +89,7 @@ track 0: flags = 0 data = length 640, hash F664125B sample 20: - time = 700699 + time = 700700 flags = 0 data = length 491, hash B5930C7C sample 21: @@ -97,7 +97,7 @@ track 0: flags = 0 data = length 2989, hash 92CF4FCF sample 22: - time = 800799 + time = 800800 flags = 0 data = length 838, hash 294A3451 sample 23: @@ -109,7 +109,7 @@ track 0: flags = 0 data = length 329, hash A654FFA1 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1517, hash 5F7EBF8B sample 26: @@ -121,7 +121,7 @@ track 0: flags = 0 data = length 415, hash B31BBC3B sample 28: - time = 967632 + time = 967633 flags = 0 data = length 415, hash 850DFEA3 sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump index 5bb4411a69..9e58eea933 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump @@ -32,7 +32,7 @@ track 0: flags = 536870912 data = length 5867, hash 56F9EE87 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 570, hash 984421BD sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 4310, hash 291E6161 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 497, hash 398CBFAA sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4449, hash 322CAA2B sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1076, hash B479B634 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 463, hash A85F9769 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5339, hash F232195D sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 689, hash 3EB753A3 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 516, hash E6DF9C1C sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 625, hash ED1C8EF1 sample 20: - time = 700699 + time = 700700 flags = 0 data = length 492, hash E6E066EA sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2973, hash A3C54C3B sample 22: - time = 800799 + time = 800800 flags = 0 data = length 833, hash 41CA807D sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 384, hash A0E8FA50 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1450, hash 92741C3B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 413, hash 886904C sample 28: - time = 967632 + time = 967633 flags = 0 data = length 427, hash FC2FA8CC sample 29: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump index 5bb4411a69..9e58eea933 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump @@ -32,7 +32,7 @@ track 0: flags = 536870912 data = length 5867, hash 56F9EE87 sample 4: - time = 166832 + time = 166833 flags = 0 data = length 570, hash 984421BD sample 5: @@ -48,7 +48,7 @@ track 0: flags = 0 data = length 4310, hash 291E6161 sample 8: - time = 300299 + time = 300300 flags = 0 data = length 497, hash 398CBFAA sample 9: @@ -56,7 +56,7 @@ track 0: flags = 0 data = length 4449, hash 322CAA2B sample 10: - time = 400399 + time = 400400 flags = 0 data = length 1076, hash B479B634 sample 11: @@ -68,7 +68,7 @@ track 0: flags = 0 data = length 463, hash A85F9769 sample 13: - time = 600599 + time = 600600 flags = 0 data = length 5339, hash F232195D sample 14: @@ -80,7 +80,7 @@ track 0: flags = 0 data = length 689, hash 3EB753A3 sample 16: - time = 567232 + time = 567233 flags = 0 data = length 516, hash E6DF9C1C sample 17: @@ -96,7 +96,7 @@ track 0: flags = 0 data = length 625, hash ED1C8EF1 sample 20: - time = 700699 + time = 700700 flags = 0 data = length 492, hash E6E066EA sample 21: @@ -104,7 +104,7 @@ track 0: flags = 0 data = length 2973, hash A3C54C3B sample 22: - time = 800799 + time = 800800 flags = 0 data = length 833, hash 41CA807D sample 23: @@ -116,7 +116,7 @@ track 0: flags = 0 data = length 384, hash A0E8FA50 sample 25: - time = 1000999 + time = 1001000 flags = 0 data = length 1450, hash 92741C3B sample 26: @@ -128,7 +128,7 @@ track 0: flags = 0 data = length 413, hash 886904C sample 28: - time = 967632 + time = 967633 flags = 0 data = length 427, hash FC2FA8CC sample 29: From f45a8bc4f41ec1f6b4128c9fa2cd7f359823a35e Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 13:57:42 +0000 Subject: [PATCH 071/113] Suppress lint warning about wrong IntDef usage in media2 Utils AudioAttributesCompat.AttributeUsage and C.AudioUsage use equivalent values and can be directly assigned. PiperOrigin-RevId: 406815447 --- .../java/com/google/android/exoplayer2/ext/media2/Utils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java index 87b52f3598..70f984016e 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/Utils.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.media2; +import android.annotation.SuppressLint; import androidx.media.AudioAttributesCompat; import androidx.media2.common.SessionPlayer; import com.google.android.exoplayer2.Player; @@ -24,6 +25,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes; /* package */ final class Utils { /** Returns ExoPlayer audio attributes for the given audio attributes. */ + @SuppressLint("WrongConstant") // AudioAttributesCompat.AttributeUsage is equal to C.AudioUsage public static AudioAttributes getAudioAttributes(AudioAttributesCompat audioAttributesCompat) { return new AudioAttributes.Builder() .setContentType(audioAttributesCompat.getContentType()) From eeaa61716087e7cd446eebc076bc02d2dfb58eac Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 1 Nov 2021 14:00:45 +0000 Subject: [PATCH 072/113] Rename MediaFormatUtil constants PiperOrigin-RevId: 406816023 --- .../exoplayer2/util/MediaFormatUtil.java | 18 ++++++++++-------- .../exoplayer2/util/MediaFormatUtilTest.java | 10 +++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java index 653a1e3a99..45b2293c24 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java @@ -32,17 +32,19 @@ public final class MediaFormatUtil { * Custom {@link MediaFormat} key associated with a float representing the ratio between a pixel's * width and height. */ - public static final String KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT = + // The constant value must not be changed, because it's also set by the framework MediaParser API. + public static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT = "exo-pixel-width-height-ratio-float"; /** * Custom {@link MediaFormat} key associated with an integer representing the PCM encoding. * - *

    Equivalent to {@link MediaFormat#KEY_PCM_ENCODING}, except it allows additional - * ExoPlayer-specific values including {@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link + *

    Equivalent to {@link MediaFormat#KEY_PCM_ENCODING}, except it allows additional values + * defined by {@link C.PcmEncoding}, including {@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link * C#ENCODING_PCM_24BIT}, and {@link C#ENCODING_PCM_32BIT}. */ - public static final String KEY_EXO_PCM_ENCODING = "exo-pcm-encoding-int"; + // The constant value must not be changed, because it's also set by the framework MediaParser API. + public static final String KEY_PCM_ENCODING_EXTENDED = "exo-pcm-encoding-int"; private static final int MAX_POWER_OF_TWO_INT = 1 << 30; @@ -52,8 +54,8 @@ public final class MediaFormatUtil { *

    May include the following custom keys: * *

      - *
    • {@link #KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT}. - *
    • {@link #KEY_EXO_PCM_ENCODING}. + *
    • {@link #KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT}. + *
    • {@link #KEY_PCM_ENCODING_EXTENDED}. *
    */ @SuppressLint("InlinedApi") // Inlined MediaFormat keys. @@ -184,7 +186,7 @@ public final class MediaFormatUtil { @SuppressLint("InlinedApi") private static void maybeSetPixelAspectRatio( MediaFormat mediaFormat, float pixelWidthHeightRatio) { - mediaFormat.setFloat(KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, pixelWidthHeightRatio); + mediaFormat.setFloat(KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, pixelWidthHeightRatio); int pixelAspectRatioWidth = 1; int pixelAspectRatioHeight = 1; // ExoPlayer extractors output the pixel aspect ratio as a float. Do our best to recreate the @@ -207,7 +209,7 @@ public final class MediaFormatUtil { return; } int mediaFormatPcmEncoding; - maybeSetInteger(mediaFormat, KEY_EXO_PCM_ENCODING, exoPcmEncoding); + maybeSetInteger(mediaFormat, KEY_PCM_ENCODING_EXTENDED, exoPcmEncoding); switch (exoPcmEncoding) { case C.ENCODING_PCM_8BIT: mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java index 959b1279a0..d35cbe5853 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java @@ -37,7 +37,7 @@ public class MediaFormatUtilTest { // Assert that no invalid keys are accidentally being populated. assertThat(mediaFormat.getKeys()) .containsExactly( - MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, + MediaFormatUtil.KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, MediaFormat.KEY_ENCODER_DELAY, MediaFormat.KEY_ENCODER_PADDING, MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, @@ -46,7 +46,7 @@ public class MediaFormatUtilTest { MediaFormat.KEY_IS_FORCED_SUBTITLE, MediaFormat.KEY_IS_AUTOSELECT, MediaFormat.KEY_ROTATION); - assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) + assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) .isEqualTo(1.f); assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_DELAY)).isEqualTo(0); assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_PADDING)).isEqualTo(0); @@ -116,7 +116,7 @@ public class MediaFormatUtilTest { .isEqualTo(format.initializationData.get(1)); assertThat(mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING)).isEqualTo(format.pcmEncoding); - assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING)) + assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_PCM_ENCODING_EXTENDED)) .isEqualTo(format.pcmEncoding); assertThat(mediaFormat.getString(MediaFormat.KEY_LANGUAGE)).isEqualTo(format.language); @@ -140,7 +140,7 @@ public class MediaFormatUtilTest { (float) mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH) / mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT); assertThat(calculatedPixelAspectRatio).isWithin(.0001f).of(format.pixelWidthHeightRatio); - assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) + assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT)) .isEqualTo(format.pixelWidthHeightRatio); } @@ -148,7 +148,7 @@ public class MediaFormatUtilTest { public void createMediaFormatFromFormat_withPcmEncoding_setsCustomPcmEncodingEntry() { Format format = new Format.Builder().setPcmEncoding(C.ENCODING_PCM_32BIT).build(); MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format); - assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING)) + assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_PCM_ENCODING_EXTENDED)) .isEqualTo(C.ENCODING_PCM_32BIT); assertThat(mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING)).isFalse(); } From f382ef0ae23c71a7af45c76eb73da388b5bdc24e Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 14:08:07 +0000 Subject: [PATCH 073/113] Suppress lint warning about wrong IntDef assignment. The return values of AudioManager.getPlaybackOffloadSupport are the same as the values defined in C.AudioManagerOffloadMode. PiperOrigin-RevId: 406817413 --- .../com/google/android/exoplayer2/audio/DefaultAudioSink.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 5687282fae..5d2c7947d0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.audio; import static java.lang.Math.max; import static java.lang.Math.min; +import android.annotation.SuppressLint; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; @@ -1634,6 +1635,8 @@ public final class DefaultAudioSink implements AudioSink { } @RequiresApi(29) + // Return values of AudioManager.getPlaybackOffloadSupport are equal to C.AudioManagerOffloadMode. + @SuppressLint("WrongConstant") @C.AudioManagerOffloadMode private int getOffloadedPlaybackSupport( AudioFormat audioFormat, android.media.AudioAttributes audioAttributes) { From 3ecf882c345543184370ba70ec81f93d94fa6682 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 14:28:22 +0000 Subject: [PATCH 074/113] Remove wrong IntDef usage. The variable is storing OpenGL's draw mode, which is different from Projection.DrawMode. PiperOrigin-RevId: 406820812 --- .../android/exoplayer2/video/spherical/ProjectionRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java index 692b7e687e..c52e5ddd57 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java @@ -207,7 +207,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final int vertexCount; private final FloatBuffer vertexBuffer; private final FloatBuffer textureBuffer; - @Projection.DrawMode private final int drawMode; + private final int drawMode; public MeshData(Projection.SubMesh subMesh) { vertexCount = subMesh.getVertexCount(); From 45ef34eb2f62c2ba21e2f9b00210b711bb54d6d7 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 15:09:25 +0000 Subject: [PATCH 075/113] Add missing import for gradle build PiperOrigin-RevId: 406827799 --- .../exoplayer2/trackselection/TrackSelectionOverridesTest.java | 1 + .../exoplayer2/trackselection/TrackSelectionParametersTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java index ba04f73429..d96e497b1f 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java index 7fab202421..f1271ecf7a 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; From f67ec8973cc981cf759856d981e142a810c03501 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 15:46:37 +0000 Subject: [PATCH 076/113] Add missing IntDef constant. The video scaling mode and stream type defines a default constant that needs to be added to the IntDef definition to be assignable. PiperOrigin-RevId: 406835696 --- .../java/com/google/android/exoplayer2/C.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index 7c26b57600..2c2548468b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE_USE; +import android.annotation.SuppressLint; import android.content.Context; import android.media.AudioAttributes; import android.media.AudioFormat; @@ -274,8 +275,10 @@ public final class C { /** * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link * #STREAM_TYPE_DTMF}, {@link #STREAM_TYPE_MUSIC}, {@link #STREAM_TYPE_NOTIFICATION}, {@link - * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM} or {@link #STREAM_TYPE_VOICE_CALL}. + * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link + * #STREAM_TYPE_DEFAULT}. */ + @SuppressLint("UniqueConstants") // Intentional duplication to set STREAM_TYPE_DEFAULT. @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -285,7 +288,8 @@ public final class C { STREAM_TYPE_NOTIFICATION, STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, - STREAM_TYPE_VOICE_CALL + STREAM_TYPE_VOICE_CALL, + STREAM_TYPE_DEFAULT }) public @interface StreamType {} /** @see AudioManager#STREAM_ALARM */ @@ -537,11 +541,17 @@ public final class C { /** * Video scaling modes for {@link MediaCodec}-based renderers. One of {@link - * #VIDEO_SCALING_MODE_SCALE_TO_FIT} or {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}. + * #VIDEO_SCALING_MODE_SCALE_TO_FIT}, {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} or + * {@link #VIDEO_SCALING_MODE_DEFAULT}. */ + @SuppressLint("UniqueConstants") // Intentional duplication to set VIDEO_SCALING_MODE_DEFAULT. @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) + @IntDef({ + VIDEO_SCALING_MODE_SCALE_TO_FIT, + VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING, + VIDEO_SCALING_MODE_DEFAULT + }) public @interface VideoScalingMode {} /** See {@link MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT}. */ public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = From 8cefb845df6d3a6eeb91387fa1097aa5ac15c0ab Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 17:16:56 +0000 Subject: [PATCH 077/113] Merge pull request #9576 from TiVo:p-fix-duration-round PiperOrigin-RevId: 406839109 --- .../hls/playlist/HlsPlaylistParser.java | 10 ++++++++-- .../playlist/HlsMediaPlaylistParserTest.java | 19 +++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 863ea198bb..4a445c54e4 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -48,6 +48,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.math.BigDecimal; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -759,8 +760,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = mediaPlaylist.segments; assertThat(segments).isNotNull(); - assertThat(segments).hasSize(5); + assertThat(segments).hasSize(6); Segment segment = segments.get(0); assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence) @@ -152,6 +156,9 @@ public class HlsMediaPlaylistParserTest { assertThat(segment.byteRangeLength).isEqualTo(C.LENGTH_UNSET); assertThat(segment.byteRangeOffset).isEqualTo(0); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts"); + + segment = segments.get(5); + assertThat(segment.durationUs).isEqualTo(2002000); } @Test @@ -389,7 +396,7 @@ public class HlsMediaPlaylistParserTest { .parse(playlistUri, inputStream); assertThat(playlist.segments).hasSize(3); - assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000079); + assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000080); assertThat(previousPlaylist.segments.get(0).relativeDiscontinuitySequence).isEqualTo(0); assertThat(previousPlaylist.segments.get(1).relativeDiscontinuitySequence).isEqualTo(1); assertThat(previousPlaylist.segments.get(2).relativeDiscontinuitySequence).isEqualTo(1); @@ -448,12 +455,12 @@ public class HlsMediaPlaylistParserTest { assertThat(playlist.segments.get(0).parts.get(0).relativeDiscontinuitySequence).isEqualTo(1); assertThat(playlist.segments.get(0).parts.get(1).relativeStartTimeUs).isEqualTo(2000000); assertThat(playlist.segments.get(0).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); - assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000079); - assertThat(playlist.segments.get(1).parts.get(0).relativeStartTimeUs).isEqualTo(4000079); + assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000080); + assertThat(playlist.segments.get(1).parts.get(0).relativeStartTimeUs).isEqualTo(4000080); assertThat(playlist.segments.get(1).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); - assertThat(playlist.segments.get(1).parts.get(1).relativeStartTimeUs).isEqualTo(6000079); + assertThat(playlist.segments.get(1).parts.get(1).relativeStartTimeUs).isEqualTo(6000080); assertThat(playlist.segments.get(1).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); - assertThat(playlist.trailingParts.get(0).relativeStartTimeUs).isEqualTo(8000158); + assertThat(playlist.trailingParts.get(0).relativeStartTimeUs).isEqualTo(8000160); assertThat(playlist.trailingParts.get(0).relativeDiscontinuitySequence).isEqualTo(1); } From 455fb89cd42a72eec65656ad274b92933c0507dc Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Nov 2021 16:03:06 +0000 Subject: [PATCH 078/113] Suppress lint warning about wrong IntDef in FrameworkMuxer The values are equivalent and we can suppress the warning. PiperOrigin-RevId: 406839242 --- .../google/android/exoplayer2/transformer/FrameworkMuxer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java index fc5b65891d..182f34e114 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FrameworkMuxer.java @@ -19,6 +19,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.SDK_INT; import static com.google.android.exoplayer2.util.Util.castNonNull; +import android.annotation.SuppressLint; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaMuxer; @@ -122,6 +123,7 @@ import java.nio.ByteBuffer; return mediaMuxer.addTrack(mediaFormat); } + @SuppressLint("WrongConstant") // C.BUFFER_FLAG_KEY_FRAME equals MediaCodec.BUFFER_FLAG_KEY_FRAME. @Override public void writeSampleData( int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) { From 58f36fb854d040253a66448a291492efbba29325 Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 1 Nov 2021 16:07:05 +0000 Subject: [PATCH 079/113] Fix rewriting upstream/crypto package in lib-datasource PiperOrigin-RevId: 406840246 --- .../exoplayer2/upstream/{ => crypto}/AesCipherDataSink.java | 4 +++- .../upstream/{ => crypto}/AesCipherDataSource.java | 5 ++++- .../exoplayer2/upstream/{ => crypto}/AesFlushingCipher.java | 2 +- .../android/exoplayer2/upstream/crypto/package-info.java | 0 .../upstream/{ => crypto}/AesFlushingCipherTest.java | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) rename library/datasource/src/main/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesCipherDataSink.java (95%) rename library/datasource/src/main/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesCipherDataSource.java (91%) rename library/datasource/src/main/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesFlushingCipher.java (99%) rename library/{common => datasource}/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java (100%) rename library/datasource/src/test/java/com/google/android/exoplayer2/upstream/{ => crypto}/AesFlushingCipherTest.java (99%) diff --git a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java similarity index 95% rename from library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java index 5d84485e24..4e5b9f2b8e 100644 --- a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSink.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.min; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.upstream.DataSink; +import com.google.android.exoplayer2.upstream.DataSpec; import java.io.IOException; import javax.crypto.Cipher; diff --git a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java similarity index 91% rename from library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java index 77126904bb..98ec914fa0 100644 --- a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesCipherDataSource.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; @@ -21,6 +21,9 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.TransferListener; import java.io.IOException; import java.util.List; import java.util.Map; diff --git a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java similarity index 99% rename from library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java index 5a60a985db..96cb13604e 100644 --- a/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/AesFlushingCipher.java +++ b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Assertions; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java b/library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java rename to library/datasource/src/main/java/com/google/android/exoplayer2/upstream/crypto/package-info.java diff --git a/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java similarity index 99% rename from library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java rename to library/datasource/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java index b6cdb61ba0..2811b0eada 100644 --- a/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/AesFlushingCipherTest.java +++ b/library/datasource/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.upstream; +package com.google.android.exoplayer2.upstream.crypto; import static com.google.common.truth.Truth.assertThat; import static java.lang.Math.min; From 18e8ebe0f867aa82a07fcc0a7e050c853a7bf455 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Mon, 1 Nov 2021 16:49:44 +0000 Subject: [PATCH 080/113] Allow remove video transformer option. PiperOrigin-RevId: 406849436 --- .../android/exoplayer2/transformer/TranscodingTransformer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 348f3dac04..4b01c191a7 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -481,7 +481,6 @@ public final class TranscodingTransformer { checkState( !transformation.removeAudio || !transformation.removeVideo, "Audio and video cannot both be removed."); - checkState(!(transformation.removeVideo)); this.context = context; this.mediaSourceFactory = mediaSourceFactory; this.muxerFactory = muxerFactory; From c5f4843de29c0c6cefeb769f72619cb08716950c Mon Sep 17 00:00:00 2001 From: huangdarwin Date: Mon, 1 Nov 2021 17:26:35 +0000 Subject: [PATCH 081/113] Transformer GL: Undo accidental setResolution changes(). Accidental changes were introduced in http://https://github.com/google/ExoPlayer/commit/c53924326dd100874d080c55812659a3cb9e843a PiperOrigin-RevId: 406858888 --- .../transformer/TranscodingTransformer.java | 28 ------------------- .../transformer/Transformation.java | 3 -- .../exoplayer2/transformer/Transformer.java | 2 -- 3 files changed, 33 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 4b01c191a7..6f58812be4 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -55,7 +55,6 @@ import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.util.Clock; -import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -92,16 +91,12 @@ public final class TranscodingTransformer { /** A builder for {@link TranscodingTransformer} instances. */ public static final class Builder { - // Mandatory field. private @MonotonicNonNull Context context; - - // Optional fields. private @MonotonicNonNull MediaSourceFactory mediaSourceFactory; private Muxer.Factory muxerFactory; private boolean removeAudio; private boolean removeVideo; private boolean flattenForSlowMotion; - private int outputHeight; private String outputMimeType; @Nullable private String audioMimeType; @Nullable private String videoMimeType; @@ -126,7 +121,6 @@ public final class TranscodingTransformer { this.removeAudio = transcodingTransformer.transformation.removeAudio; this.removeVideo = transcodingTransformer.transformation.removeVideo; this.flattenForSlowMotion = transcodingTransformer.transformation.flattenForSlowMotion; - this.outputHeight = transcodingTransformer.transformation.outputHeight; this.outputMimeType = transcodingTransformer.transformation.outputMimeType; this.audioMimeType = transcodingTransformer.transformation.audioMimeType; this.videoMimeType = transcodingTransformer.transformation.videoMimeType; @@ -219,21 +213,6 @@ public final class TranscodingTransformer { return this; } - /** - * Sets the output resolution for the video, using the output height. The default value is to - * use the same height as the input. Output width will scale to preserve the input video's - * aspect ratio. - * - *

    For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480). - * - * @param outputHeight The output height for the video, in pixels. - * @return This builder. - */ - public Builder setResolution(int outputHeight) { - this.outputHeight = outputHeight; - return this; - } - /** * Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported * values are: @@ -377,12 +356,6 @@ public final class TranscodingTransformer { checkState( muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); - // TODO(ME): Test with values of 10, 100, 1000). - Log.e("TranscodingTransformer", "outputHeight = " + outputHeight); - if (outputHeight == 0) { - // TODO(ME): get output height from input video. - outputHeight = 480; - } if (audioMimeType != null) { checkSampleMimeType(audioMimeType); } @@ -394,7 +367,6 @@ public final class TranscodingTransformer { removeAudio, removeVideo, flattenForSlowMotion, - outputHeight, outputMimeType, audioMimeType, videoMimeType); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java index ed8bf64bf3..e273c1fde5 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java @@ -24,7 +24,6 @@ import androidx.annotation.Nullable; public final boolean removeAudio; public final boolean removeVideo; public final boolean flattenForSlowMotion; - public final int outputHeight; public final String outputMimeType; @Nullable public final String audioMimeType; @Nullable public final String videoMimeType; @@ -33,14 +32,12 @@ import androidx.annotation.Nullable; boolean removeAudio, boolean removeVideo, boolean flattenForSlowMotion, - int outputHeight, String outputMimeType, @Nullable String audioMimeType, @Nullable String videoMimeType) { this.removeAudio = removeAudio; this.removeVideo = removeVideo; this.flattenForSlowMotion = flattenForSlowMotion; - this.outputHeight = outputHeight; this.outputMimeType = outputMimeType; this.audioMimeType = audioMimeType; this.videoMimeType = videoMimeType; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 093f63caeb..0fd763ad26 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -297,13 +297,11 @@ public final class Transformer { checkState( muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); - int outputHeight = 0; // TODO(ME): How do we get the input height here? Transformation transformation = new Transformation( removeAudio, removeVideo, flattenForSlowMotion, - outputHeight, outputMimeType, /* audioMimeType= */ null, /* videoMimeType= */ null); From 2c2705aa6b06982e246f0660f31c8fc106b7301e Mon Sep 17 00:00:00 2001 From: samrobinson Date: Mon, 1 Nov 2021 16:49:44 +0000 Subject: [PATCH 082/113] Allow remove video transformer option. PiperOrigin-RevId: 406849436 --- .../android/exoplayer2/transformer/TranscodingTransformer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 348f3dac04..4b01c191a7 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -481,7 +481,6 @@ public final class TranscodingTransformer { checkState( !transformation.removeAudio || !transformation.removeVideo, "Audio and video cannot both be removed."); - checkState(!(transformation.removeVideo)); this.context = context; this.mediaSourceFactory = mediaSourceFactory; this.muxerFactory = muxerFactory; From 14eba83df6de32cfe030951e070e61b19b05b221 Mon Sep 17 00:00:00 2001 From: huangdarwin Date: Mon, 1 Nov 2021 17:26:35 +0000 Subject: [PATCH 083/113] Transformer GL: Undo accidental setResolution changes(). Accidental changes were introduced in http://https://github.com/google/ExoPlayer/commit/c53924326dd100874d080c55812659a3cb9e843a PiperOrigin-RevId: 406858888 --- .../transformer/TranscodingTransformer.java | 28 ------------------- .../transformer/Transformation.java | 3 -- .../exoplayer2/transformer/Transformer.java | 2 -- 3 files changed, 33 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java index 4b01c191a7..6f58812be4 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TranscodingTransformer.java @@ -55,7 +55,6 @@ import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.util.Clock; -import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -92,16 +91,12 @@ public final class TranscodingTransformer { /** A builder for {@link TranscodingTransformer} instances. */ public static final class Builder { - // Mandatory field. private @MonotonicNonNull Context context; - - // Optional fields. private @MonotonicNonNull MediaSourceFactory mediaSourceFactory; private Muxer.Factory muxerFactory; private boolean removeAudio; private boolean removeVideo; private boolean flattenForSlowMotion; - private int outputHeight; private String outputMimeType; @Nullable private String audioMimeType; @Nullable private String videoMimeType; @@ -126,7 +121,6 @@ public final class TranscodingTransformer { this.removeAudio = transcodingTransformer.transformation.removeAudio; this.removeVideo = transcodingTransformer.transformation.removeVideo; this.flattenForSlowMotion = transcodingTransformer.transformation.flattenForSlowMotion; - this.outputHeight = transcodingTransformer.transformation.outputHeight; this.outputMimeType = transcodingTransformer.transformation.outputMimeType; this.audioMimeType = transcodingTransformer.transformation.audioMimeType; this.videoMimeType = transcodingTransformer.transformation.videoMimeType; @@ -219,21 +213,6 @@ public final class TranscodingTransformer { return this; } - /** - * Sets the output resolution for the video, using the output height. The default value is to - * use the same height as the input. Output width will scale to preserve the input video's - * aspect ratio. - * - *

    For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480). - * - * @param outputHeight The output height for the video, in pixels. - * @return This builder. - */ - public Builder setResolution(int outputHeight) { - this.outputHeight = outputHeight; - return this; - } - /** * Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported * values are: @@ -377,12 +356,6 @@ public final class TranscodingTransformer { checkState( muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); - // TODO(ME): Test with values of 10, 100, 1000). - Log.e("TranscodingTransformer", "outputHeight = " + outputHeight); - if (outputHeight == 0) { - // TODO(ME): get output height from input video. - outputHeight = 480; - } if (audioMimeType != null) { checkSampleMimeType(audioMimeType); } @@ -394,7 +367,6 @@ public final class TranscodingTransformer { removeAudio, removeVideo, flattenForSlowMotion, - outputHeight, outputMimeType, audioMimeType, videoMimeType); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java index ed8bf64bf3..e273c1fde5 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformation.java @@ -24,7 +24,6 @@ import androidx.annotation.Nullable; public final boolean removeAudio; public final boolean removeVideo; public final boolean flattenForSlowMotion; - public final int outputHeight; public final String outputMimeType; @Nullable public final String audioMimeType; @Nullable public final String videoMimeType; @@ -33,14 +32,12 @@ import androidx.annotation.Nullable; boolean removeAudio, boolean removeVideo, boolean flattenForSlowMotion, - int outputHeight, String outputMimeType, @Nullable String audioMimeType, @Nullable String videoMimeType) { this.removeAudio = removeAudio; this.removeVideo = removeVideo; this.flattenForSlowMotion = flattenForSlowMotion; - this.outputHeight = outputHeight; this.outputMimeType = outputMimeType; this.audioMimeType = audioMimeType; this.videoMimeType = videoMimeType; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 093f63caeb..0fd763ad26 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -297,13 +297,11 @@ public final class Transformer { checkState( muxerFactory.supportsOutputMimeType(outputMimeType), "Unsupported output MIME type: " + outputMimeType); - int outputHeight = 0; // TODO(ME): How do we get the input height here? Transformation transformation = new Transformation( removeAudio, removeVideo, flattenForSlowMotion, - outputHeight, outputMimeType, /* audioMimeType= */ null, /* videoMimeType= */ null); From f5cdf1657a853697815161187e7ec3b522bb6e1d Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Nov 2021 11:57:30 +0000 Subject: [PATCH 084/113] Remove FfmpegVideoRenderer from 2.16.0 release --- .../exoplayer2/ext/ffmpeg/FfmpegLibrary.java | 6 +- .../ext/ffmpeg/FfmpegVideoRenderer.java | 135 ------------------ .../ffmpeg/DefaultRenderersFactoryTest.java | 9 +- library/core/proguard-rules.txt | 4 - .../exoplayer2/DefaultRenderersFactory.java | 26 ---- 5 files changed, 2 insertions(+), 178 deletions(-) delete mode 100644 extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java index 97f11225bb..abd184b59e 100644 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java @@ -42,7 +42,7 @@ public final class FfmpegLibrary { /** * Override the names of the FFmpeg native libraries. If an application wishes to call this * method, it must do so before calling any other method defined by this class, and before - * instantiating a {@link FfmpegAudioRenderer} or {@link FfmpegVideoRenderer} instance. + * instantiating a {@link FfmpegAudioRenderer} instance. * * @param libraries The names of the FFmpeg native libraries. */ @@ -140,10 +140,6 @@ public final class FfmpegLibrary { return "pcm_mulaw"; case MimeTypes.AUDIO_ALAW: return "pcm_alaw"; - case MimeTypes.VIDEO_H264: - return "h264"; - case MimeTypes.VIDEO_H265: - return "hevc"; default: return null; } diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java deleted file mode 100644 index e074b87a21..0000000000 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2020 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.ext.ffmpeg; - -import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED; -import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; -import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; - -import android.os.Handler; -import android.view.Surface; -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.RendererCapabilities; -import com.google.android.exoplayer2.decoder.CryptoConfig; -import com.google.android.exoplayer2.decoder.Decoder; -import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; -import com.google.android.exoplayer2.decoder.VideoDecoderOutputBuffer; -import com.google.android.exoplayer2.util.TraceUtil; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.DecoderVideoRenderer; -import com.google.android.exoplayer2.video.VideoRendererEventListener; - -// TODO: Remove the NOTE below. -/** - * NOTE: This class if under development and is not yet functional. - * - *

    Decodes and renders video using FFmpeg. - */ -public final class FfmpegVideoRenderer extends DecoderVideoRenderer { - - private static final String TAG = "FfmpegVideoRenderer"; - - /** - * Creates a new instance. - * - * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer - * can attempt to seamlessly join an ongoing playback. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between - * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. - */ - public FfmpegVideoRenderer( - long allowedJoiningTimeMs, - @Nullable Handler eventHandler, - @Nullable VideoRendererEventListener eventListener, - int maxDroppedFramesToNotify) { - super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify); - // TODO: Implement. - } - - @Override - public String getName() { - return TAG; - } - - @Override - @RendererCapabilities.Capabilities - public final int supportsFormat(Format format) { - // TODO: Remove this line and uncomment the implementation below. - return C.FORMAT_UNSUPPORTED_TYPE; - /* - String mimeType = Assertions.checkNotNull(format.sampleMimeType); - if (!FfmpegLibrary.isAvailable() || !MimeTypes.isVideo(mimeType)) { - return FORMAT_UNSUPPORTED_TYPE; - } else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType)) { - return RendererCapabilities.create(FORMAT_UNSUPPORTED_SUBTYPE); - } else if (format.exoMediaCryptoType != null) { - return RendererCapabilities.create(FORMAT_UNSUPPORTED_DRM); - } else { - return RendererCapabilities.create( - FORMAT_HANDLED, - ADAPTIVE_SEAMLESS, - TUNNELING_NOT_SUPPORTED); - } - */ - } - - @SuppressWarnings("nullness:return") - @Override - protected Decoder - createDecoder(Format format, @Nullable CryptoConfig cryptoConfig) - throws FfmpegDecoderException { - TraceUtil.beginSection("createFfmpegVideoDecoder"); - // TODO: Implement, remove the SuppressWarnings annotation, and update the return type to use - // the concrete type of the decoder (probably FfmepgVideoDecoder). - TraceUtil.endSection(); - return null; - } - - @Override - protected void renderOutputBufferToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface) - throws FfmpegDecoderException { - // TODO: Implement. - } - - @Override - protected void setDecoderOutputMode(@C.VideoOutputMode int outputMode) { - // TODO: Uncomment the implementation below. - /* - if (decoder != null) { - decoder.setOutputMode(outputMode); - } - */ - } - - @Override - protected DecoderReuseEvaluation canReuseDecoder( - String decoderName, Format oldFormat, Format newFormat) { - boolean sameMimeType = Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType); - // TODO: Ability to reuse the decoder may be MIME type dependent. - return new DecoderReuseEvaluation( - decoderName, - oldFormat, - newFormat, - sameMimeType ? REUSE_RESULT_YES_WITHOUT_RECONFIGURATION : REUSE_RESULT_NO, - sameMimeType ? 0 : DISCARD_REASON_MIME_TYPE_CHANGED); - } -} diff --git a/extensions/ffmpeg/src/test/java/com/google/android/exoplayer2/ext/ffmpeg/DefaultRenderersFactoryTest.java b/extensions/ffmpeg/src/test/java/com/google/android/exoplayer2/ext/ffmpeg/DefaultRenderersFactoryTest.java index cc8ca5487e..fa4c6809aa 100644 --- a/extensions/ffmpeg/src/test/java/com/google/android/exoplayer2/ext/ffmpeg/DefaultRenderersFactoryTest.java +++ b/extensions/ffmpeg/src/test/java/com/google/android/exoplayer2/ext/ffmpeg/DefaultRenderersFactoryTest.java @@ -22,8 +22,7 @@ import org.junit.Test; import org.junit.runner.RunWith; /** - * Unit test for {@link DefaultRenderersFactoryTest} with {@link FfmpegAudioRenderer} and {@link - * FfmpegVideoRenderer}. + * Unit test for {@link DefaultRenderersFactoryTest} with {@link FfmpegAudioRenderer}. */ @RunWith(AndroidJUnit4.class) public final class DefaultRenderersFactoryTest { @@ -33,10 +32,4 @@ public final class DefaultRenderersFactoryTest { DefaultRenderersFactoryAsserts.assertExtensionRendererCreated( FfmpegAudioRenderer.class, C.TRACK_TYPE_AUDIO); } - - @Test - public void createRenderers_instantiatesFfmpegVideoRenderer() { - DefaultRenderersFactoryAsserts.assertExtensionRendererCreated( - FfmpegVideoRenderer.class, C.TRACK_TYPE_VIDEO); - } } diff --git a/library/core/proguard-rules.txt b/library/core/proguard-rules.txt index fc6787e09d..ebe0c271ef 100644 --- a/library/core/proguard-rules.txt +++ b/library/core/proguard-rules.txt @@ -9,10 +9,6 @@ -keepclassmembers class com.google.android.exoplayer2.ext.av1.Libgav1VideoRenderer { (long, android.os.Handler, com.google.android.exoplayer2.video.VideoRendererEventListener, int); } --dontnote com.google.android.exoplayer2.ext.ffmpeg.FfmpegVideoRenderer --keepclassmembers class com.google.android.exoplayer2.ext.ffmpeg.FfmpegVideoRenderer { - (long, android.os.Handler, com.google.android.exoplayer2.video.VideoRendererEventListener, int); -} -dontnote com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer -keepclassmembers class com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer { (android.os.Handler, com.google.android.exoplayer2.audio.AudioRendererEventListener, com.google.android.exoplayer2.audio.AudioSink); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 03ccc297a8..b9895d60ae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -434,32 +434,6 @@ public class DefaultRenderersFactory implements RenderersFactory { // The extension is present, but instantiation failed. throw new RuntimeException("Error instantiating AV1 extension", e); } - - try { - // Full class names used for constructor args so the LINT rule triggers if any of them move. - Class clazz = - Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegVideoRenderer"); - Constructor constructor = - clazz.getConstructor( - long.class, - android.os.Handler.class, - com.google.android.exoplayer2.video.VideoRendererEventListener.class, - int.class); - Renderer renderer = - (Renderer) - constructor.newInstance( - allowedVideoJoiningTimeMs, - eventHandler, - eventListener, - MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); - out.add(extensionRendererIndex++, renderer); - Log.i(TAG, "Loaded FfmpegVideoRenderer."); - } catch (ClassNotFoundException e) { - // Expected if the app was built without the extension. - } catch (Exception e) { - // The extension is present, but instantiation failed. - throw new RuntimeException("Error instantiating FFmpeg extension", e); - } } /** From 7de079493cf039b529c526b105d9289747660c90 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 2 Nov 2021 10:27:56 +0000 Subject: [PATCH 085/113] Migrate usages of Window-based Player methods Where this introduced an inconsistency (e.g. assigning to something called `windowIndex`), I generally renamed the transitive closure of identifiers to maintain consistency (meaning this change is quite large). The exception is code that interacts with Timeline and Window directly, where sometimes I kept the 'window' nomenclature. #minor-release PiperOrigin-RevId: 407040052 --- RELEASENOTES.md | 4 + .../exoplayer2/castdemo/PlayerManager.java | 12 +- .../exoplayer2/demo/PlayerActivity.java | 16 +- .../exoplayer2/ext/cast/CastPlayer.java | 18 +- .../exoplayer2/ext/ima/FakeExoPlayer.java | 2 +- .../ext/leanback/LeanbackPlayerAdapter.java | 2 +- .../exoplayer2/ext/media2/PlayerWrapper.java | 16 +- .../mediasession/MediaSessionConnector.java | 29 +- .../mediasession/TimelineQueueNavigator.java | 38 +- .../google/android/exoplayer2/BasePlayer.java | 49 +- .../google/android/exoplayer2/ExoPlayer.java | 23 +- .../android/exoplayer2/ExoPlayerImpl.java | 43 +- .../exoplayer2/ExoPlayerImplInternal.java | 2 +- .../android/exoplayer2/PlayerMessage.java | 49 +- .../android/exoplayer2/SimpleExoPlayer.java | 6 +- .../analytics/AnalyticsCollector.java | 6 +- .../analytics/AnalyticsListener.java | 10 +- .../exoplayer2/util/DebugTextViewHelper.java | 4 +- .../android/exoplayer2/ExoPlayerTest.java | 1003 +++++++++-------- .../exoplayer2/ui/PlayerControlView.java | 6 +- .../ui/PlayerNotificationManager.java | 10 +- .../android/exoplayer2/ui/PlayerView.java | 4 +- .../ui/StyledPlayerControlView.java | 7 +- .../exoplayer2/ui/StyledPlayerView.java | 4 +- .../robolectric/TestPlayerRunHelper.java | 17 +- .../android/exoplayer2/testutil/Action.java | 61 +- .../exoplayer2/testutil/ActionSchedule.java | 67 +- .../testutil/ExoPlayerTestRunner.java | 22 +- .../exoplayer2/testutil/StubExoPlayer.java | 2 +- 29 files changed, 788 insertions(+), 744 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bf708bfa32..0d8bf3fdb5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -85,6 +85,10 @@ * RTMP extension: * Upgrade to `io.antmedia:rtmp_client`, which does not rely on `jcenter()` ([#9591](https://github.com/google/ExoPlayer/issues/9591)). +* MediaSession extension: + * Rename + `MediaSessionConnector.QueueNavigator#onCurrentWindowIndexChanged` to + `onCurrentMediaItemIndexChanged`. * Remove deprecated symbols: * Remove `Renderer.VIDEO_SCALING_MODE_*` constants. Use identically named constants in `C` instead. diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index 9e66c823a0..54174b0c53 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -261,7 +261,7 @@ import java.util.ArrayList; int playbackState = currentPlayer.getPlaybackState(); maybeSetCurrentItemAndNotify( playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED - ? currentPlayer.getCurrentWindowIndex() + ? currentPlayer.getCurrentMediaItemIndex() : C.INDEX_UNSET); } @@ -281,7 +281,7 @@ import java.util.ArrayList; // Player state management. long playbackPositionMs = C.TIME_UNSET; - int windowIndex = C.INDEX_UNSET; + int currentItemIndex = C.INDEX_UNSET; boolean playWhenReady = false; Player previousPlayer = this.currentPlayer; @@ -291,10 +291,10 @@ import java.util.ArrayList; if (playbackState != Player.STATE_ENDED) { playbackPositionMs = previousPlayer.getCurrentPosition(); playWhenReady = previousPlayer.getPlayWhenReady(); - windowIndex = previousPlayer.getCurrentWindowIndex(); - if (windowIndex != currentItemIndex) { + currentItemIndex = previousPlayer.getCurrentMediaItemIndex(); + if (currentItemIndex != this.currentItemIndex) { playbackPositionMs = C.TIME_UNSET; - windowIndex = currentItemIndex; + currentItemIndex = this.currentItemIndex; } } previousPlayer.stop(); @@ -304,7 +304,7 @@ import java.util.ArrayList; this.currentPlayer = currentPlayer; // Media queue management. - currentPlayer.setMediaItems(mediaQueue, windowIndex, playbackPositionMs); + currentPlayer.setMediaItems(mediaQueue, currentItemIndex, playbackPositionMs); currentPlayer.setPlayWhenReady(playWhenReady); currentPlayer.prepare(); } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index e080c23f99..ca9fd45d42 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -65,7 +65,7 @@ public class PlayerActivity extends AppCompatActivity // Saved instance state keys. private static final String KEY_TRACK_SELECTION_PARAMETERS = "track_selection_parameters"; - private static final String KEY_WINDOW = "window"; + private static final String KEY_ITEM_INDEX = "item_index"; private static final String KEY_POSITION = "position"; private static final String KEY_AUTO_PLAY = "auto_play"; @@ -83,7 +83,7 @@ public class PlayerActivity extends AppCompatActivity private DebugTextViewHelper debugViewHelper; private TracksInfo lastSeenTracksInfo; private boolean startAutoPlay; - private int startWindow; + private int startItemIndex; private long startPosition; // For ad playback only. @@ -114,7 +114,7 @@ public class PlayerActivity extends AppCompatActivity DefaultTrackSelector.Parameters.CREATOR.fromBundle( savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS)); startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY); - startWindow = savedInstanceState.getInt(KEY_WINDOW); + startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX); startPosition = savedInstanceState.getLong(KEY_POSITION); } else { trackSelectionParameters = @@ -206,7 +206,7 @@ public class PlayerActivity extends AppCompatActivity updateStartPosition(); outState.putBundle(KEY_TRACK_SELECTION_PARAMETERS, trackSelectionParameters.toBundle()); outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay); - outState.putInt(KEY_WINDOW, startWindow); + outState.putInt(KEY_ITEM_INDEX, startItemIndex); outState.putLong(KEY_POSITION, startPosition); } @@ -282,9 +282,9 @@ public class PlayerActivity extends AppCompatActivity debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper.start(); } - boolean haveStartPosition = startWindow != C.INDEX_UNSET; + boolean haveStartPosition = startItemIndex != C.INDEX_UNSET; if (haveStartPosition) { - player.seekTo(startWindow, startPosition); + player.seekTo(startItemIndex, startPosition); } player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition); player.prepare(); @@ -382,14 +382,14 @@ public class PlayerActivity extends AppCompatActivity private void updateStartPosition() { if (player != null) { startAutoPlay = player.getPlayWhenReady(); - startWindow = player.getCurrentWindowIndex(); + startItemIndex = player.getCurrentMediaItemIndex(); startPosition = Math.max(0, player.getContentPosition()); } } protected void clearStartPosition() { startAutoPlay = true; - startWindow = C.INDEX_UNSET; + startItemIndex = C.INDEX_UNSET; startPosition = C.TIME_UNSET; } diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 1bf2cd410b..0054378727 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -318,9 +318,9 @@ public final class CastPlayer extends BasePlayer { @Override public void setMediaItems(List mediaItems, boolean resetPosition) { - int windowIndex = resetPosition ? 0 : getCurrentWindowIndex(); + int mediaItemIndex = resetPosition ? 0 : getCurrentMediaItemIndex(); long startPositionMs = resetPosition ? C.TIME_UNSET : getContentPosition(); - setMediaItems(mediaItems, windowIndex, startPositionMs); + setMediaItems(mediaItems, mediaItemIndex, startPositionMs); } @Override @@ -443,7 +443,7 @@ public final class CastPlayer extends BasePlayer { // in RemoteMediaClient. positionMs = positionMs != C.TIME_UNSET ? positionMs : 0; if (mediaStatus != null) { - if (getCurrentWindowIndex() != mediaItemIndex) { + if (getCurrentMediaItemIndex() != mediaItemIndex) { remoteMediaClient .queueJumpToItem( (int) currentTimeline.getPeriod(mediaItemIndex, period).uid, positionMs, null) @@ -636,7 +636,7 @@ public final class CastPlayer extends BasePlayer { @Override public int getCurrentPeriodIndex() { - return getCurrentWindowIndex(); + return getCurrentMediaItemIndex(); } @Override @@ -1103,15 +1103,15 @@ public final class CastPlayer extends BasePlayer { @Nullable private PendingResult setMediaItemsInternal( MediaQueueItem[] mediaQueueItems, - int startWindowIndex, + int startIndex, long startPositionMs, @RepeatMode int repeatMode) { if (remoteMediaClient == null || mediaQueueItems.length == 0) { return null; } startPositionMs = startPositionMs == C.TIME_UNSET ? 0 : startPositionMs; - if (startWindowIndex == C.INDEX_UNSET) { - startWindowIndex = getCurrentWindowIndex(); + if (startIndex == C.INDEX_UNSET) { + startIndex = getCurrentMediaItemIndex(); startPositionMs = getCurrentPosition(); } Timeline currentTimeline = getCurrentTimeline(); @@ -1120,7 +1120,7 @@ public final class CastPlayer extends BasePlayer { } return remoteMediaClient.queueLoad( mediaQueueItems, - min(startWindowIndex, mediaQueueItems.length - 1), + min(startIndex, mediaQueueItems.length - 1), getCastRepeatMode(repeatMode), startPositionMs, /* customData= */ null); @@ -1180,7 +1180,7 @@ public final class CastPlayer extends BasePlayer { } return new PositionInfo( newWindowUid, - getCurrentWindowIndex(), + getCurrentMediaItemIndex(), newMediaItem, newPeriodUid, getCurrentPeriodIndex(), diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java index fb2975920d..90e2087389 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java @@ -279,7 +279,7 @@ import com.google.android.exoplayer2.util.Util; timeline.getPeriod(0, period).getAdDurationUs(adGroupIndex, adIndexInAdGroup); return Util.usToMs(adDurationUs); } else { - return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + return timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } } 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 a914869f8f..bd70d14394 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 @@ -141,7 +141,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab if (player.getPlaybackState() == Player.STATE_IDLE) { player.prepare(); } else if (player.getPlaybackState() == Player.STATE_ENDED) { - player.seekToDefaultPosition(player.getCurrentWindowIndex()); + player.seekToDefaultPosition(player.getCurrentMediaItemIndex()); } if (player.isCommandAvailable(Player.COMMAND_PLAY_PAUSE)) { player.play(); diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java index 7891bc47f3..f69f725edb 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java @@ -253,8 +253,8 @@ import java.util.List; // checkIndex() throws IndexOutOfBoundsException which maps the RESULT_ERROR_BAD_VALUE // but RESULT_ERROR_INVALID_STATE with IllegalStateException is expected here. Assertions.checkState(0 <= index && index < timeline.getWindowCount()); - int windowIndex = player.getCurrentWindowIndex(); - if (windowIndex == index || !player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { + int currentIndex = player.getCurrentMediaItemIndex(); + if (currentIndex == index || !player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { return false; } player.seekToDefaultPosition(index); @@ -301,7 +301,7 @@ import java.util.List; } public int getCurrentMediaItemIndex() { - return media2Playlist.isEmpty() ? C.INDEX_UNSET : player.getCurrentWindowIndex(); + return media2Playlist.isEmpty() ? C.INDEX_UNSET : player.getCurrentMediaItemIndex(); } public int getPreviousMediaItemIndex() { @@ -331,7 +331,7 @@ import java.util.List; if (!player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { return false; } - player.seekTo(player.getCurrentWindowIndex(), /* positionMs= */ 0); + player.seekTo(player.getCurrentMediaItemIndex(), /* positionMs= */ 0); } boolean playWhenReady = player.getPlayWhenReady(); int suppressReason = player.getPlaybackSuppressionReason(); @@ -358,7 +358,7 @@ import java.util.List; if (!player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { return false; } - player.seekTo(player.getCurrentWindowIndex(), position); + player.seekTo(player.getCurrentMediaItemIndex(), position); return true; } @@ -493,7 +493,7 @@ import java.util.List; public boolean isCurrentMediaItemSeekable() { return getCurrentMediaItem() != null && !player.isPlayingAd() - && player.isCurrentWindowSeekable(); + && player.isCurrentMediaItemSeekable(); } public boolean canSkipToPlaylistItem() { @@ -502,11 +502,11 @@ import java.util.List; } public boolean canSkipToPreviousPlaylistItem() { - return player.hasPreviousWindow(); + return player.hasPreviousMediaItem(); } public boolean canSkipToNextPlaylistItem() { - return player.hasNextWindow(); + return player.hasNextMediaItem(); } public boolean hasError() { diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index b823d0f90f..cfda09ff0a 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -263,12 +263,13 @@ public final class MediaSessionConnector { * @param player The player connected to the media session. */ void onTimelineChanged(Player player); + /** - * Called when the current window index changed. + * Called when the current media item index changed. * * @param player The player connected to the media session. */ - void onCurrentWindowIndexChanged(Player player); + default void onCurrentMediaItemIndexChanged(Player player) {} /** * Gets the id of the currently active queue item, or {@link * MediaSessionCompat.QueueItem#UNKNOWN_ID} if the active item is unknown. @@ -969,8 +970,8 @@ public final class MediaSessionConnector { return player != null && mediaButtonEventHandler != null; } - private void seekTo(Player player, int windowIndex, long positionMs) { - player.seekTo(windowIndex, positionMs); + private void seekTo(Player player, int mediaItemIndex, long positionMs) { + player.seekTo(mediaItemIndex, positionMs); } private static int getMediaSessionPlaybackState( @@ -1023,7 +1024,7 @@ public final class MediaSessionConnector { } builder.putLong( MediaMetadataCompat.METADATA_KEY_DURATION, - player.isCurrentWindowDynamic() || player.getDuration() == C.TIME_UNSET + player.isCurrentMediaItemDynamic() || player.getDuration() == C.TIME_UNSET ? -1 : player.getDuration()); long activeQueueItemId = mediaController.getPlaybackState().getActiveQueueItemId(); @@ -1097,7 +1098,7 @@ public final class MediaSessionConnector { private class ComponentListener extends MediaSessionCompat.Callback implements Player.Listener { - private int currentWindowIndex; + private int currentMediaItemIndex; private int currentWindowCount; // Player.Listener implementation. @@ -1107,9 +1108,9 @@ public final class MediaSessionConnector { boolean invalidatePlaybackState = false; boolean invalidateMetadata = false; if (events.contains(Player.EVENT_POSITION_DISCONTINUITY)) { - if (currentWindowIndex != player.getCurrentWindowIndex()) { + if (currentMediaItemIndex != player.getCurrentMediaItemIndex()) { if (queueNavigator != null) { - queueNavigator.onCurrentWindowIndexChanged(player); + queueNavigator.onCurrentMediaItemIndexChanged(player); } invalidateMetadata = true; } @@ -1118,11 +1119,11 @@ public final class MediaSessionConnector { if (events.contains(Player.EVENT_TIMELINE_CHANGED)) { int windowCount = player.getCurrentTimeline().getWindowCount(); - int windowIndex = player.getCurrentWindowIndex(); + int mediaItemIndex = player.getCurrentMediaItemIndex(); if (queueNavigator != null) { queueNavigator.onTimelineChanged(player); invalidatePlaybackState = true; - } else if (currentWindowCount != windowCount || currentWindowIndex != windowIndex) { + } else if (currentWindowCount != windowCount || currentMediaItemIndex != mediaItemIndex) { // active queue item and queue navigation actions may need to be updated invalidatePlaybackState = true; } @@ -1130,8 +1131,8 @@ public final class MediaSessionConnector { invalidateMetadata = true; } - // Update currentWindowIndex after comparisons above. - currentWindowIndex = player.getCurrentWindowIndex(); + // Update currentMediaItemIndex after comparisons above. + currentMediaItemIndex = player.getCurrentMediaItemIndex(); if (events.containsAny( EVENT_PLAYBACK_STATE_CHANGED, @@ -1170,7 +1171,7 @@ public final class MediaSessionConnector { player.prepare(); } } else if (player.getPlaybackState() == Player.STATE_ENDED) { - seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); + seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); } Assertions.checkNotNull(player).play(); } @@ -1186,7 +1187,7 @@ public final class MediaSessionConnector { @Override public void onSeekTo(long positionMs) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_SEEK_TO)) { - seekTo(player, player.getCurrentWindowIndex(), positionMs); + seekTo(player, player.getCurrentMediaItemIndex(), positionMs); } } diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java index 90db27e458..4277de3c32 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java @@ -98,7 +98,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu boolean enableNext = false; Timeline timeline = player.getCurrentTimeline(); if (!timeline.isEmpty() && !player.isPlayingAd()) { - timeline.getWindow(player.getCurrentWindowIndex(), window); + timeline.getWindow(player.getCurrentMediaItemIndex(), window); enableSkipTo = timeline.getWindowCount() > 1; enablePrevious = player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) @@ -128,12 +128,12 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu } @Override - public final void onCurrentWindowIndexChanged(Player player) { + public final void onCurrentMediaItemIndexChanged(Player player) { if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { publishFloatingQueueWindow(player); } else if (!player.getCurrentTimeline().isEmpty()) { - activeQueueItemId = player.getCurrentWindowIndex(); + activeQueueItemId = player.getCurrentMediaItemIndex(); } } @@ -185,40 +185,40 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu int queueSize = min(maxQueueSize, timeline.getWindowCount()); // Add the active queue item. - int currentWindowIndex = player.getCurrentWindowIndex(); + int currentMediaItemIndex = player.getCurrentMediaItemIndex(); queue.add( new MediaSessionCompat.QueueItem( - getMediaDescription(player, currentWindowIndex), currentWindowIndex)); + getMediaDescription(player, currentMediaItemIndex), currentMediaItemIndex)); // Fill queue alternating with next and/or previous queue items. - int firstWindowIndex = currentWindowIndex; - int lastWindowIndex = currentWindowIndex; + int firstMediaItemIndex = currentMediaItemIndex; + int lastMediaItemIndex = currentMediaItemIndex; boolean shuffleModeEnabled = player.getShuffleModeEnabled(); - while ((firstWindowIndex != C.INDEX_UNSET || lastWindowIndex != C.INDEX_UNSET) + while ((firstMediaItemIndex != C.INDEX_UNSET || lastMediaItemIndex != C.INDEX_UNSET) && queue.size() < queueSize) { // Begin with next to have a longer tail than head if an even sized queue needs to be trimmed. - if (lastWindowIndex != C.INDEX_UNSET) { - lastWindowIndex = + if (lastMediaItemIndex != C.INDEX_UNSET) { + lastMediaItemIndex = timeline.getNextWindowIndex( - lastWindowIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); - if (lastWindowIndex != C.INDEX_UNSET) { + lastMediaItemIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); + if (lastMediaItemIndex != C.INDEX_UNSET) { queue.add( new MediaSessionCompat.QueueItem( - getMediaDescription(player, lastWindowIndex), lastWindowIndex)); + getMediaDescription(player, lastMediaItemIndex), lastMediaItemIndex)); } } - if (firstWindowIndex != C.INDEX_UNSET && queue.size() < queueSize) { - firstWindowIndex = + if (firstMediaItemIndex != C.INDEX_UNSET && queue.size() < queueSize) { + firstMediaItemIndex = timeline.getPreviousWindowIndex( - firstWindowIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); - if (firstWindowIndex != C.INDEX_UNSET) { + firstMediaItemIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); + if (firstMediaItemIndex != C.INDEX_UNSET) { queue.addFirst( new MediaSessionCompat.QueueItem( - getMediaDescription(player, firstWindowIndex), firstWindowIndex)); + getMediaDescription(player, firstMediaItemIndex), firstMediaItemIndex)); } } } mediaSession.setQueue(new ArrayList<>(queue)); - activeQueueItemId = currentWindowIndex; + activeQueueItemId = currentMediaItemIndex; } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java index b60e4a3a7e..2209803f3e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -118,7 +118,7 @@ public abstract class BasePlayer implements Player { @Override public final void seekToDefaultPosition() { - seekToDefaultPosition(getCurrentWindowIndex()); + seekToDefaultPosition(getCurrentMediaItemIndex()); } @Override @@ -128,7 +128,7 @@ public abstract class BasePlayer implements Player { @Override public final void seekTo(long positionMs) { - seekTo(getCurrentWindowIndex(), positionMs); + seekTo(getCurrentMediaItemIndex(), positionMs); } @Override @@ -184,13 +184,13 @@ public abstract class BasePlayer implements Player { if (timeline.isEmpty() || isPlayingAd()) { return; } - boolean hasPreviousWindow = hasPreviousWindow(); - if (isCurrentWindowLive() && !isCurrentWindowSeekable()) { - if (hasPreviousWindow) { - seekToPreviousWindow(); + boolean hasPreviousMediaItem = hasPreviousMediaItem(); + if (isCurrentMediaItemLive() && !isCurrentMediaItemSeekable()) { + if (hasPreviousMediaItem) { + seekToPreviousMediaItem(); } - } else if (hasPreviousWindow && getCurrentPosition() <= getMaxSeekToPreviousPosition()) { - seekToPreviousWindow(); + } else if (hasPreviousMediaItem && getCurrentPosition() <= getMaxSeekToPreviousPosition()) { + seekToPreviousMediaItem(); } else { seekTo(/* positionMs= */ 0); } @@ -239,9 +239,9 @@ public abstract class BasePlayer implements Player { if (timeline.isEmpty() || isPlayingAd()) { return; } - if (hasNextWindow()) { - seekToNextWindow(); - } else if (isCurrentWindowLive() && isCurrentWindowDynamic()) { + if (hasNextMediaItem()) { + seekToNextMediaItem(); + } else if (isCurrentMediaItemLive() && isCurrentMediaItemDynamic()) { seekToDefaultPosition(); } } @@ -293,7 +293,7 @@ public abstract class BasePlayer implements Player { Timeline timeline = getCurrentTimeline(); return timeline.isEmpty() ? null - : timeline.getWindow(getCurrentWindowIndex(), window).mediaItem; + : timeline.getWindow(getCurrentMediaItemIndex(), window).mediaItem; } @Override @@ -310,7 +310,9 @@ public abstract class BasePlayer implements Player { @Nullable public final Object getCurrentManifest() { Timeline timeline = getCurrentTimeline(); - return timeline.isEmpty() ? null : timeline.getWindow(getCurrentWindowIndex(), window).manifest; + return timeline.isEmpty() + ? null + : timeline.getWindow(getCurrentMediaItemIndex(), window).manifest; } @Override @@ -352,7 +354,8 @@ public abstract class BasePlayer implements Player { if (timeline.isEmpty()) { return C.TIME_UNSET; } - long windowStartTimeMs = timeline.getWindow(getCurrentWindowIndex(), window).windowStartTimeMs; + long windowStartTimeMs = + timeline.getWindow(getCurrentMediaItemIndex(), window).windowStartTimeMs; if (windowStartTimeMs == C.TIME_UNSET) { return C.TIME_UNSET; } @@ -376,7 +379,7 @@ public abstract class BasePlayer implements Player { Timeline timeline = getCurrentTimeline(); return timeline.isEmpty() ? C.TIME_UNSET - : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + : timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } /** @@ -389,22 +392,24 @@ public abstract class BasePlayer implements Player { return new Commands.Builder() .addAll(permanentAvailableCommands) .addIf(COMMAND_SEEK_TO_DEFAULT_POSITION, !isPlayingAd()) - .addIf(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, isCurrentWindowSeekable() && !isPlayingAd()) - .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousWindow() && !isPlayingAd()) + .addIf(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, isCurrentMediaItemSeekable() && !isPlayingAd()) + .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousMediaItem() && !isPlayingAd()) .addIf( COMMAND_SEEK_TO_PREVIOUS, !getCurrentTimeline().isEmpty() - && (hasPreviousWindow() || !isCurrentWindowLive() || isCurrentWindowSeekable()) + && (hasPreviousMediaItem() + || !isCurrentMediaItemLive() + || isCurrentMediaItemSeekable()) && !isPlayingAd()) - .addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextWindow() && !isPlayingAd()) + .addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextMediaItem() && !isPlayingAd()) .addIf( COMMAND_SEEK_TO_NEXT, !getCurrentTimeline().isEmpty() - && (hasNextWindow() || (isCurrentWindowLive() && isCurrentWindowDynamic())) + && (hasNextMediaItem() || (isCurrentMediaItemLive() && isCurrentMediaItemDynamic())) && !isPlayingAd()) .addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd()) - .addIf(COMMAND_SEEK_BACK, isCurrentWindowSeekable() && !isPlayingAd()) - .addIf(COMMAND_SEEK_FORWARD, isCurrentWindowSeekable() && !isPlayingAd()) + .addIf(COMMAND_SEEK_BACK, isCurrentMediaItemSeekable() && !isPlayingAd()) + .addIf(COMMAND_SEEK_FORWARD, isCurrentMediaItemSeekable() && !isPlayingAd()) .build(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index e594883c72..2143d2a1fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -1126,7 +1126,7 @@ public interface ExoPlayer extends Player { * @param mediaSources The new {@link MediaSource MediaSources}. * @param resetPosition Whether the playback position should be reset to the default position in * the first {@link Timeline.Window}. If false, playback will start from the position defined - * by {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}. + * by {@link #getCurrentMediaItemIndex()} and {@link #getCurrentPosition()}. */ void setMediaSources(List mediaSources, boolean resetPosition); @@ -1134,14 +1134,15 @@ public interface ExoPlayer extends Player { * Clears the playlist and adds the specified {@link MediaSource MediaSources}. * * @param mediaSources The new {@link MediaSource MediaSources}. - * @param startWindowIndex The window index to start playback from. If {@link C#INDEX_UNSET} is - * passed, the current position is not reset. + * @param startMediaItemIndex The media item index to start playback from. If {@link + * C#INDEX_UNSET} is passed, the current position is not reset. * @param startPositionMs The position in milliseconds to start playback from. If {@link - * C#TIME_UNSET} is passed, the default position of the given window is used. In any case, if - * {@code startWindowIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored and the - * position is not reset at all. + * C#TIME_UNSET} is passed, the default position of the given media item is used. In any case, + * if {@code startMediaItemIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored + * and the position is not reset at all. */ - void setMediaSources(List mediaSources, int startWindowIndex, long startPositionMs); + void setMediaSources( + List mediaSources, int startMediaItemIndex, long startPositionMs); /** * Clears the playlist, adds the specified {@link MediaSource} and resets the position to the @@ -1164,7 +1165,7 @@ public interface ExoPlayer extends Player { * * @param mediaSource The new {@link MediaSource}. * @param resetPosition Whether the playback position should be reset to the default position. If - * false, playback will start from the position defined by {@link #getCurrentWindowIndex()} + * false, playback will start from the position defined by {@link #getCurrentMediaItemIndex()} * and {@link #getCurrentPosition()}. */ void setMediaSource(MediaSource mediaSource, boolean resetPosition); @@ -1331,9 +1332,9 @@ public interface ExoPlayer extends Player { * will be delivered immediately without blocking on the playback thread. The default {@link * PlayerMessage#getType()} is 0 and the default {@link PlayerMessage#getPayload()} is null. If a * position is specified with {@link PlayerMessage#setPosition(long)}, the message will be - * delivered at this position in the current window defined by {@link #getCurrentWindowIndex()}. - * Alternatively, the message can be sent at a specific window using {@link - * PlayerMessage#setPosition(int, long)}. + * delivered at this position in the current media item defined by {@link + * #getCurrentMediaItemIndex()}. Alternatively, the message can be sent at a specific mediaItem + * using {@link PlayerMessage#setPosition(int, long)}. */ PlayerMessage createMessage(PlayerMessage.Target target); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 653135e59c..61a1d41b82 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -537,7 +537,7 @@ import java.util.concurrent.CopyOnWriteArraySet; playbackInfo, timeline, getPeriodPositionOrMaskWindowPosition( - timeline, getCurrentWindowIndex(), getCurrentPosition())); + timeline, getCurrentMediaItemIndex(), getCurrentPosition())); pendingOperationAcks++; this.shuffleOrder = shuffleOrder; internalPlayer.setShuffleOrder(shuffleOrder); @@ -662,7 +662,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Player.State int newPlaybackState = getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING; - int oldMaskingWindowIndex = getCurrentWindowIndex(); + int oldMaskingMediaItemIndex = getCurrentMediaItemIndex(); PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState); newPlaybackInfo = maskTimelineAndPosition( @@ -678,7 +678,7 @@ import java.util.concurrent.CopyOnWriteArraySet; /* positionDiscontinuity= */ true, /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), - oldMaskingWindowIndex); + oldMaskingMediaItemIndex); } @Override @@ -839,7 +839,7 @@ import java.util.concurrent.CopyOnWriteArraySet; internalPlayer, target, playbackInfo.timeline, - getCurrentWindowIndex(), + getCurrentMediaItemIndex(), clock, internalPlayer.getPlaybackLooper()); } @@ -910,7 +910,10 @@ import java.util.concurrent.CopyOnWriteArraySet; if (isPlayingAd()) { playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET - ? playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDefaultPositionMs() + ? playbackInfo + .timeline + .getWindow(getCurrentMediaItemIndex(), window) + .getDefaultPositionMs() : period.getPositionInWindowMs() + Util.usToMs(playbackInfo.requestedContentPositionUs); } else { return getCurrentPosition(); @@ -924,7 +927,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber != playbackInfo.periodId.windowSequenceNumber) { - return playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + return playbackInfo.timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } long contentBufferedPositionUs = playbackInfo.bufferedPositionUs; if (playbackInfo.loadingMediaPeriodId.isAd()) { @@ -1218,7 +1221,7 @@ import java.util.concurrent.CopyOnWriteArraySet; boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, long discontinuityWindowStartPositionUs, - int oldMaskingWindowIndex) { + int oldMaskingMediaItemIndex) { // Assign playback info immediately such that all getters return the right values, but keep // snapshot of previous and new state so that listener invocations are triggered correctly. @@ -1267,7 +1270,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (positionDiscontinuity) { PositionInfo previousPositionInfo = getPreviousPositionInfo( - positionDiscontinuityReason, previousPlaybackInfo, oldMaskingWindowIndex); + positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, @@ -1378,19 +1381,19 @@ import java.util.concurrent.CopyOnWriteArraySet; private PositionInfo getPreviousPositionInfo( @DiscontinuityReason int positionDiscontinuityReason, PlaybackInfo oldPlaybackInfo, - int oldMaskingWindowIndex) { + int oldMaskingMediaItemIndex) { @Nullable Object oldWindowUid = null; @Nullable Object oldPeriodUid = null; - int oldWindowIndex = oldMaskingWindowIndex; + int oldMediaItemIndex = oldMaskingMediaItemIndex; int oldPeriodIndex = C.INDEX_UNSET; @Nullable MediaItem oldMediaItem = null; Timeline.Period oldPeriod = new Timeline.Period(); if (!oldPlaybackInfo.timeline.isEmpty()) { oldPeriodUid = oldPlaybackInfo.periodId.periodUid; oldPlaybackInfo.timeline.getPeriodByUid(oldPeriodUid, oldPeriod); - oldWindowIndex = oldPeriod.windowIndex; + oldMediaItemIndex = oldPeriod.windowIndex; oldPeriodIndex = oldPlaybackInfo.timeline.getIndexOfPeriod(oldPeriodUid); - oldWindowUid = oldPlaybackInfo.timeline.getWindow(oldWindowIndex, window).uid; + oldWindowUid = oldPlaybackInfo.timeline.getWindow(oldMediaItemIndex, window).uid; oldMediaItem = window.mediaItem; } long oldPositionUs; @@ -1421,7 +1424,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } return new PositionInfo( oldWindowUid, - oldWindowIndex, + oldMediaItemIndex, oldMediaItem, oldPeriodUid, oldPeriodIndex, @@ -1434,20 +1437,20 @@ import java.util.concurrent.CopyOnWriteArraySet; private PositionInfo getPositionInfo(long discontinuityWindowStartPositionUs) { @Nullable Object newWindowUid = null; @Nullable Object newPeriodUid = null; - int newWindowIndex = getCurrentWindowIndex(); + int newMediaItemIndex = getCurrentMediaItemIndex(); int newPeriodIndex = C.INDEX_UNSET; @Nullable MediaItem newMediaItem = null; if (!playbackInfo.timeline.isEmpty()) { newPeriodUid = playbackInfo.periodId.periodUid; playbackInfo.timeline.getPeriodByUid(newPeriodUid, period); newPeriodIndex = playbackInfo.timeline.getIndexOfPeriod(newPeriodUid); - newWindowUid = playbackInfo.timeline.getWindow(newWindowIndex, window).uid; + newWindowUid = playbackInfo.timeline.getWindow(newMediaItemIndex, window).uid; newMediaItem = window.mediaItem; } long positionMs = Util.usToMs(discontinuityWindowStartPositionUs); return new PositionInfo( newWindowUid, - newWindowIndex, + newMediaItemIndex, newMediaItem, newPeriodUid, newPeriodIndex, @@ -1601,7 +1604,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private PlaybackInfo removeMediaItemsInternal(int fromIndex, int toIndex) { Assertions.checkArgument( fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size()); - int currentWindowIndex = getCurrentWindowIndex(); + int currentIndex = getCurrentMediaItemIndex(); Timeline oldTimeline = getCurrentTimeline(); int currentMediaSourceCount = mediaSourceHolderSnapshots.size(); pendingOperationAcks++; @@ -1618,7 +1621,7 @@ import java.util.concurrent.CopyOnWriteArraySet; && newPlaybackInfo.playbackState != STATE_ENDED && fromIndex < toIndex && toIndex == currentMediaSourceCount - && currentWindowIndex >= newPlaybackInfo.timeline.getWindowCount(); + && currentIndex >= newPlaybackInfo.timeline.getWindowCount(); if (transitionsToEnded) { newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(STATE_ENDED); } @@ -1753,11 +1756,11 @@ import java.util.concurrent.CopyOnWriteArraySet; isCleared ? C.INDEX_UNSET : getCurrentWindowIndexInternal(), isCleared ? C.TIME_UNSET : currentPositionMs); } - int currentWindowIndex = getCurrentWindowIndex(); + int currentMediaItemIndex = getCurrentMediaItemIndex(); @Nullable Pair oldPeriodPosition = oldTimeline.getPeriodPosition( - window, period, currentWindowIndex, Util.msToUs(currentPositionMs)); + window, period, currentMediaItemIndex, Util.msToUs(currentPositionMs)); Object periodUid = castNonNull(oldPeriodPosition).first; if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) { // The old period position is still available in the new timeline. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 9fadb56a79..d6f1e1f73a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -2718,7 +2718,7 @@ import java.util.concurrent.atomic.AtomicBoolean; newTimeline, new SeekPosition( pendingMessageInfo.message.getTimeline(), - pendingMessageInfo.message.getWindowIndex(), + pendingMessageInfo.message.getMediaItemIndex(), requestPositionUs), /* trySubsequentPeriods= */ false, repeatMode, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java index 0d591ee9f6..cc7e749fa0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java @@ -63,7 +63,7 @@ public final class PlayerMessage { private int type; @Nullable private Object payload; private Looper looper; - private int windowIndex; + private int mediaItemIndex; private long positionMs; private boolean deleteAfterDelivery; private boolean isSent; @@ -78,8 +78,8 @@ public final class PlayerMessage { * @param target The {@link Target} the message is sent to. * @param timeline The timeline used when setting the position with {@link #setPosition(long)}. If * set to {@link Timeline#EMPTY}, any position can be specified. - * @param defaultWindowIndex The default window index in the {@code timeline} when no other window - * index is specified. + * @param defaultMediaItemIndex The default media item index in the {@code timeline} when no other + * media item index is specified. * @param clock The {@link Clock}. * @param defaultLooper The default {@link Looper} to send the message on when no other looper is * specified. @@ -88,7 +88,7 @@ public final class PlayerMessage { Sender sender, Target target, Timeline timeline, - int defaultWindowIndex, + int defaultMediaItemIndex, Clock clock, Looper defaultLooper) { this.sender = sender; @@ -96,7 +96,7 @@ public final class PlayerMessage { this.timeline = timeline; this.looper = defaultLooper; this.clock = clock; - this.windowIndex = defaultWindowIndex; + this.mediaItemIndex = defaultMediaItemIndex; this.positionMs = C.TIME_UNSET; this.deleteAfterDelivery = true; } @@ -173,21 +173,21 @@ public final class PlayerMessage { } /** - * Returns position in window at {@link #getWindowIndex()} at which the message will be delivered, - * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. If {@link - * C#TIME_END_OF_SOURCE}, the message will be delivered at the end of the window at {@link - * #getWindowIndex()}. + * Returns position in the media item at {@link #getMediaItemIndex()} at which the message will be + * delivered, in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. + * If {@link C#TIME_END_OF_SOURCE}, the message will be delivered at the end of the media item at + * {@link #getMediaItemIndex()}. */ public long getPositionMs() { return positionMs; } /** - * Sets a position in the current window at which the message will be delivered. + * Sets a position in the current media item at which the message will be delivered. * - * @param positionMs The position in the current window at which the message will be sent, in + * @param positionMs The position in the current media item at which the message will be sent, in * milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the message at the end of the - * current window. + * current media item. * @return This message. * @throws IllegalStateException If {@link #send()} has already been called. */ @@ -198,31 +198,32 @@ public final class PlayerMessage { } /** - * Sets a position in a window at which the message will be delivered. + * Sets a position in a media item at which the message will be delivered. * - * @param windowIndex The index of the window at which the message will be sent. - * @param positionMs The position in the window with index {@code windowIndex} at which the + * @param mediaItemIndex The index of the media item at which the message will be sent. + * @param positionMs The position in the media item with index {@code mediaItemIndex} at which the * message will be sent, in milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the - * message at the end of the window with index {@code windowIndex}. + * message at the end of the media item with index {@code mediaItemIndex}. * @return This message. * @throws IllegalSeekPositionException If the timeline returned by {@link #getTimeline()} is not - * empty and the provided window index is not within the bounds of the timeline. + * empty and the provided media item index is not within the bounds of the timeline. * @throws IllegalStateException If {@link #send()} has already been called. */ - public PlayerMessage setPosition(int windowIndex, long positionMs) { + public PlayerMessage setPosition(int mediaItemIndex, long positionMs) { Assertions.checkState(!isSent); Assertions.checkArgument(positionMs != C.TIME_UNSET); - if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) { - throw new IllegalSeekPositionException(timeline, windowIndex, positionMs); + if (mediaItemIndex < 0 + || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { + throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs); } - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; return this; } - /** Returns window index at which the message will be delivered. */ - public int getWindowIndex() { - return windowIndex; + /** Returns media item index at which the message will be delivered. */ + public int getMediaItemIndex() { + return mediaItemIndex; } /** 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 3c6607611e..83067a500c 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 @@ -1110,9 +1110,9 @@ public class SimpleExoPlayer extends BasePlayer @Override public void setMediaSources( - List mediaSources, int startWindowIndex, long startPositionMs) { + List mediaSources, int startMediaItemIndex, long startPositionMs) { verifyApplicationThread(); - player.setMediaSources(mediaSources, startWindowIndex, startPositionMs); + player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs); } @Override @@ -1419,7 +1419,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public int getCurrentMediaItemIndex() { verifyApplicationThread(); - return player.getCurrentWindowIndex(); + return player.getCurrentMediaItemIndex(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index ae22609e29..63e87a5539 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -914,7 +914,7 @@ public class AnalyticsCollector long eventPositionMs; boolean isInCurrentWindow = timeline.equals(player.getCurrentTimeline()) - && windowIndex == player.getCurrentWindowIndex(); + && windowIndex == player.getCurrentMediaItemIndex(); if (mediaPeriodId != null && mediaPeriodId.isAd()) { boolean isCurrentAd = isInCurrentWindow @@ -939,7 +939,7 @@ public class AnalyticsCollector mediaPeriodId, eventPositionMs, player.getCurrentTimeline(), - player.getCurrentWindowIndex(), + player.getCurrentMediaItemIndex(), currentMediaPeriodId, player.getCurrentPosition(), player.getTotalBufferedDuration()); @@ -962,7 +962,7 @@ public class AnalyticsCollector ? null : mediaPeriodQueueTracker.getMediaPeriodIdTimeline(mediaPeriodId); if (mediaPeriodId == null || knownTimeline == null) { - int windowIndex = player.getCurrentWindowIndex(); + int windowIndex = player.getCurrentMediaItemIndex(); Timeline timeline = player.getCurrentTimeline(); boolean windowIsInTimeline = windowIndex < timeline.getWindowCount(); return generateEventTime( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 9887f397ea..df73a1f7f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -382,7 +382,7 @@ public interface AnalyticsListener { /** * The current window index in {@link #currentTimeline} at the time of the event, or the * prospective window index if the timeline is not yet known and empty (equivalent to {@link - * Player#getCurrentWindowIndex()}). + * Player#getCurrentMediaItemIndex()}). */ public final int currentWindowIndex; @@ -419,7 +419,7 @@ public interface AnalyticsListener { * {@link Player#getCurrentTimeline()}). * @param currentWindowIndex The current window index in {@code currentTimeline} at the time of * the event, or the prospective window index if the timeline is not yet known and empty - * (equivalent to {@link Player#getCurrentWindowIndex()}). + * (equivalent to {@link Player#getCurrentMediaItemIndex()}). * @param currentMediaPeriodId {@link MediaPeriodId Media period identifier} for the currently * playing media period at the time of the event, or {@code null} if no current media period * identifier is available. @@ -1204,9 +1204,9 @@ public interface AnalyticsListener { * {@link Player#seekTo(long)} after a {@link * AnalyticsListener#onMediaItemTransition(EventTime, MediaItem, int)}). *

  • They intend to use multiple state values together or in combination with {@link Player} - * getter methods. For example using {@link Player#getCurrentWindowIndex()} with the {@code - * timeline} provided in {@link #onTimelineChanged(EventTime, int)} is only safe from within - * this method. + * getter methods. For example using {@link Player#getCurrentMediaItemIndex()} with the + * {@code timeline} provided in {@link #onTimelineChanged(EventTime, int)} is only safe from + * within this method. *
  • They are interested in events that logically happened together (e.g {@link * #onPlaybackStateChanged(EventTime, int)} to {@link Player#STATE_BUFFERING} because of * {@link #onMediaItemTransition(EventTime, MediaItem, int)}). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java index 5eeaa060bc..77fb5c048e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java @@ -138,8 +138,8 @@ public class DebugTextViewHelper implements Player.Listener, Runnable { break; } return String.format( - "playWhenReady:%s playbackState:%s window:%s", - player.getPlayWhenReady(), playbackStateString, player.getCurrentWindowIndex()); + "playWhenReady:%s playbackState:%s item:%s", + player.getPlayWhenReady(), playbackStateString, player.getCurrentMediaItemIndex()); } /** Returns a string containing video debugging information. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 64044b2a3f..886d3c8ed9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -48,7 +48,7 @@ import static com.google.android.exoplayer2.Player.COMMAND_STOP; import static com.google.android.exoplayer2.Player.STATE_ENDED; import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilPosition; -import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfWindow; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfMediaItem; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPositionDiscontinuity; @@ -381,7 +381,7 @@ public final class ExoPlayerTest { player.play(); runUntilPositionDiscontinuity(player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); player.setForegroundMode(/* foregroundMode= */ true); - // Only the video renderer that is disabled in the second window has been reset. + // Only the video renderer that is disabled in the second media item has been reset. assertThat(audioRenderer.resetCount).isEqualTo(0); assertThat(videoRenderer.resetCount).isEqualTo(1); @@ -460,7 +460,7 @@ public final class ExoPlayerTest { // Disable text renderer by selecting a language that is not available. player.setTrackSelectionParameters( player.getTrackSelectionParameters().buildUpon().setPreferredTextLanguage("de").build()); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1000); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1000); runUntilPlaybackState(player, Player.STATE_READY); // Expect formerly enabled renderers to be reset after seek. assertThat(textRenderer.resetCount).isEqualTo(1); @@ -647,21 +647,21 @@ public final class ExoPlayerTest { player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT)); player.prepare(); runUntilTimelineChanged(player); - playUntilStartOfWindow(player, /* windowIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); player.setRepeatMode(Player.REPEAT_MODE_ONE); - playUntilStartOfWindow(player, /* windowIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); player.setRepeatMode(Player.REPEAT_MODE_OFF); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); player.setRepeatMode(Player.REPEAT_MODE_ONE); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); player.setRepeatMode(Player.REPEAT_MODE_ALL); - playUntilStartOfWindow(player, /* windowIndex= */ 0); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 0); player.setRepeatMode(Player.REPEAT_MODE_ONE); - playUntilStartOfWindow(player, /* windowIndex= */ 0); - playUntilStartOfWindow(player, /* windowIndex= */ 0); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 0); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 0); player.setRepeatMode(Player.REPEAT_MODE_OFF); - playUntilStartOfWindow(player, /* windowIndex= */ 1); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); @@ -694,9 +694,9 @@ public final class ExoPlayerTest { .pause() .waitForPlaybackState(Player.STATE_READY) .setRepeatMode(Player.REPEAT_MODE_ALL) - .playUntilStartOfWindow(/* windowIndex= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 1) .setShuffleModeEnabled(true) - .playUntilStartOfWindow(/* windowIndex= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 1) .setShuffleModeEnabled(false) .setRepeatMode(Player.REPEAT_MODE_OFF) .play() @@ -806,7 +806,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { try { - player.seekTo(/* windowIndex= */ 100, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 100, /* positionMs= */ 0); } catch (IllegalSeekPositionException e) { exception[0] = e; } @@ -1281,7 +1281,7 @@ public final class ExoPlayerTest { .play() .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 0, /* positionMs= */ 2000) + .initialSeek(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setMediaSources(mediaSource) .setActionSchedule(actionSchedule) .build() @@ -1293,7 +1293,7 @@ public final class ExoPlayerTest { @Test public void stop_withoutReset_doesNotResetPosition_correctMasking() throws Exception { - int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; @@ -1302,18 +1302,18 @@ public final class ExoPlayerTest { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[0] = player.getCurrentWindowIndex(); + currentMediaItemIndex[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); player.stop(/* reset= */ false); - currentWindowIndex[1] = player.getCurrentWindowIndex(); + currentMediaItemIndex[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -1324,7 +1324,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[2] = player.getCurrentWindowIndex(); + currentMediaItemIndex[2] = player.getCurrentMediaItemIndex(); currentPosition[2] = player.getCurrentPosition(); bufferedPosition[2] = player.getBufferedPosition(); totalBufferedDuration[2] = player.getTotalBufferedDuration(); @@ -1345,17 +1345,17 @@ public final class ExoPlayerTest { Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); - assertThat(currentWindowIndex[0]).isEqualTo(1); + assertThat(currentMediaItemIndex[0]).isEqualTo(1); assertThat(currentPosition[0]).isEqualTo(1000); assertThat(bufferedPosition[0]).isEqualTo(10000); assertThat(totalBufferedDuration[0]).isEqualTo(9000); - assertThat(currentWindowIndex[1]).isEqualTo(1); + assertThat(currentMediaItemIndex[1]).isEqualTo(1); assertThat(currentPosition[1]).isEqualTo(1000); assertThat(bufferedPosition[1]).isEqualTo(1000); assertThat(totalBufferedDuration[1]).isEqualTo(0); - assertThat(currentWindowIndex[2]).isEqualTo(1); + assertThat(currentMediaItemIndex[2]).isEqualTo(1); assertThat(currentPosition[2]).isEqualTo(1000); assertThat(bufferedPosition[2]).isEqualTo(1000); assertThat(totalBufferedDuration[2]).isEqualTo(0); @@ -1385,7 +1385,7 @@ public final class ExoPlayerTest { @Test public void stop_withReset_doesResetPosition_correctMasking() throws Exception { - int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; @@ -1394,18 +1394,18 @@ public final class ExoPlayerTest { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[0] = player.getCurrentWindowIndex(); + currentMediaItemIndex[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); player.stop(/* reset= */ true); - currentWindowIndex[1] = player.getCurrentWindowIndex(); + currentMediaItemIndex[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -1416,7 +1416,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[2] = player.getCurrentWindowIndex(); + currentMediaItemIndex[2] = player.getCurrentMediaItemIndex(); currentPosition[2] = player.getCurrentPosition(); bufferedPosition[2] = player.getBufferedPosition(); totalBufferedDuration[2] = player.getTotalBufferedDuration(); @@ -1439,17 +1439,17 @@ public final class ExoPlayerTest { testRunner.assertPositionDiscontinuityReasonsEqual( Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_REMOVE); - assertThat(currentWindowIndex[0]).isEqualTo(1); + assertThat(currentMediaItemIndex[0]).isEqualTo(1); assertThat(currentPosition[0]).isGreaterThan(0); assertThat(bufferedPosition[0]).isEqualTo(10000); assertThat(totalBufferedDuration[0]).isEqualTo(10000 - currentPosition[0]); - assertThat(currentWindowIndex[1]).isEqualTo(0); + assertThat(currentMediaItemIndex[1]).isEqualTo(0); assertThat(currentPosition[1]).isEqualTo(0); assertThat(bufferedPosition[1]).isEqualTo(0); assertThat(totalBufferedDuration[1]).isEqualTo(0); - assertThat(currentWindowIndex[2]).isEqualTo(0); + assertThat(currentMediaItemIndex[2]).isEqualTo(0); assertThat(currentPosition[2]).isEqualTo(0); assertThat(bufferedPosition[2]).isEqualTo(0); assertThat(totalBufferedDuration[2]).isEqualTo(0); @@ -1479,7 +1479,7 @@ public final class ExoPlayerTest { @Test public void release_correctMasking() throws Exception { - int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; @@ -1488,18 +1488,18 @@ public final class ExoPlayerTest { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[0] = player.getCurrentWindowIndex(); + currentMediaItemIndex[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); player.release(); - currentWindowIndex[1] = player.getCurrentWindowIndex(); + currentMediaItemIndex[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -1510,7 +1510,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[2] = player.getCurrentWindowIndex(); + currentMediaItemIndex[2] = player.getCurrentMediaItemIndex(); currentPosition[2] = player.getCurrentPosition(); bufferedPosition[2] = player.getBufferedPosition(); totalBufferedDuration[2] = player.getTotalBufferedDuration(); @@ -1525,17 +1525,17 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS); - assertThat(currentWindowIndex[0]).isEqualTo(1); + assertThat(currentMediaItemIndex[0]).isEqualTo(1); assertThat(currentPosition[0]).isGreaterThan(0); assertThat(bufferedPosition[0]).isEqualTo(10000); assertThat(totalBufferedDuration[0]).isEqualTo(10000 - currentPosition[0]); - assertThat(currentWindowIndex[1]).isEqualTo(1); + assertThat(currentMediaItemIndex[1]).isEqualTo(1); assertThat(currentPosition[1]).isEqualTo(currentPosition[0]); assertThat(bufferedPosition[1]).isEqualTo(1000); assertThat(totalBufferedDuration[1]).isEqualTo(0); - assertThat(currentWindowIndex[2]).isEqualTo(1); + assertThat(currentMediaItemIndex[2]).isEqualTo(1); assertThat(currentPosition[2]).isEqualTo(currentPosition[0]); assertThat(bufferedPosition[2]).isEqualTo(1000); assertThat(totalBufferedDuration[2]).isEqualTo(0); @@ -1547,21 +1547,21 @@ public final class ExoPlayerTest { Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 2); MediaSource secondSource = new FakeMediaSource(secondTimeline, ExoPlayerTestRunner.VIDEO_FORMAT); - AtomicInteger windowIndexAfterStop = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterStop = new AtomicInteger(); AtomicLong positionAfterStop = new AtomicLong(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_READY) .stop(/* reset= */ true) .waitForPlaybackState(Player.STATE_IDLE) - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .setMediaSources(secondSource) .prepare() .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndexAfterStop.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterStop.set(player.getCurrentMediaItemIndex()); positionAfterStop.set(player.getCurrentPosition()); } }) @@ -1594,7 +1594,7 @@ public final class ExoPlayerTest { Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, // stop(true) Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); - assertThat(windowIndexAfterStop.get()).isEqualTo(1); + assertThat(mediaItemIndexAfterStop.get()).isEqualTo(1); assertThat(positionAfterStop.get()).isAtLeast(1000L); testRunner.assertPlayedPeriodIndices(0, 1); } @@ -1621,8 +1621,8 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ 2000, secondSource) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) + .setMediaSources(/* mediaItemIndex= */ 0, /* positionMs= */ 2000, secondSource) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) .executeRunnable( @@ -1675,7 +1675,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setMediaSources(/* resetPosition= */ true, secondSource) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) @@ -1732,7 +1732,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setMediaSources(secondSource) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) @@ -1892,7 +1892,7 @@ public final class ExoPlayerTest { throws Exception { ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0)); - AtomicInteger windowIndexAfterAddingSources = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterAddingSources = new AtomicInteger(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .setShuffleModeEnabled(true) @@ -1909,7 +1909,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndexAfterAddingSources.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterAddingSources.set(player.getCurrentMediaItemIndex()); } }) .build(); @@ -1920,20 +1920,20 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndexAfterAddingSources.get()).isEqualTo(1); + assertThat(mediaItemIndexAfterAddingSources.get()).isEqualTo(1); } @Test public void playbackErrorAndReprepareDoesNotResetPosition() throws Exception { final Timeline timeline = new FakeTimeline(/* windowCount= */ 2); final long[] positionHolder = new long[3]; - final int[] windowIndexHolder = new int[3]; + final int[] mediaItemIndexHolder = new int[3]; final FakeMediaSource firstMediaSource = new FakeMediaSource(timeline); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 500) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 500) .throwPlaybackException( ExoPlaybackException.createForSource( new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED)) @@ -1944,7 +1944,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while in error state positionHolder[0] = player.getCurrentPosition(); - windowIndexHolder[0] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[0] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -1954,7 +1954,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while repreparing. positionHolder[1] = player.getCurrentPosition(); - windowIndexHolder[1] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[1] = player.getCurrentMediaItemIndex(); } }) .waitForPlaybackState(Player.STATE_READY) @@ -1964,7 +1964,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position after repreparation finished. positionHolder[2] = player.getCurrentPosition(); - windowIndexHolder[2] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[2] = player.getCurrentMediaItemIndex(); } }) .play() @@ -1983,22 +1983,22 @@ public final class ExoPlayerTest { assertThat(positionHolder[0]).isAtLeast(500L); assertThat(positionHolder[1]).isEqualTo(positionHolder[0]); assertThat(positionHolder[2]).isEqualTo(positionHolder[0]); - assertThat(windowIndexHolder[0]).isEqualTo(1); - assertThat(windowIndexHolder[1]).isEqualTo(1); - assertThat(windowIndexHolder[2]).isEqualTo(1); + assertThat(mediaItemIndexHolder[0]).isEqualTo(1); + assertThat(mediaItemIndexHolder[1]).isEqualTo(1); + assertThat(mediaItemIndexHolder[2]).isEqualTo(1); } @Test public void seekAfterPlaybackError() throws Exception { final Timeline timeline = new FakeTimeline(/* windowCount= */ 2); final long[] positionHolder = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; - final int[] windowIndexHolder = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndexHolder = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final FakeMediaSource firstMediaSource = new FakeMediaSource(timeline); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 500) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 500) .throwPlaybackException( ExoPlaybackException.createForSource( new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED)) @@ -2009,10 +2009,10 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while in error state positionHolder[0] = player.getCurrentPosition(); - windowIndexHolder[0] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[0] = player.getCurrentMediaItemIndex(); } }) - .seek(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET) .waitForPendingPlayerCommands() .executeRunnable( new PlayerRunnable() { @@ -2020,7 +2020,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while in error state positionHolder[1] = player.getCurrentPosition(); - windowIndexHolder[1] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -2030,7 +2030,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position after prepare. positionHolder[2] = player.getCurrentPosition(); - windowIndexHolder[2] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[2] = player.getCurrentMediaItemIndex(); } }) .play() @@ -2051,9 +2051,9 @@ public final class ExoPlayerTest { assertThat(positionHolder[0]).isAtLeast(500L); assertThat(positionHolder[1]).isEqualTo(0L); assertThat(positionHolder[2]).isEqualTo(0L); - assertThat(windowIndexHolder[0]).isEqualTo(1); - assertThat(windowIndexHolder[1]).isEqualTo(0); - assertThat(windowIndexHolder[2]).isEqualTo(0); + assertThat(mediaItemIndexHolder[0]).isEqualTo(1); + assertThat(mediaItemIndexHolder[1]).isEqualTo(0); + assertThat(mediaItemIndexHolder[2]).isEqualTo(0); } @Test @@ -2216,7 +2216,7 @@ public final class ExoPlayerTest { .pause() .sendMessage( (messageType, payload) -> counter.getAndIncrement(), - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ 2000, /* deleteAfterDelivery= */ false) .seek(/* positionMs= */ 2000) @@ -2290,23 +2290,23 @@ public final class ExoPlayerTest { long duration2Ms = timeline.getWindow(1, new Window()).getDurationMs(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .sendMessage(targetStartFirstPeriod, /* windowIndex= */ 0, /* positionMs= */ 0) + .sendMessage(targetStartFirstPeriod, /* mediaItemIndex= */ 0, /* positionMs= */ 0) .sendMessage( targetEndMiddlePeriodResolved, - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ duration1Ms - 1) .sendMessage( targetEndMiddlePeriodUnresolved, - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_END_OF_SOURCE) - .sendMessage(targetStartMiddlePeriod, /* windowIndex= */ 1, /* positionMs= */ 0) + .sendMessage(targetStartMiddlePeriod, /* mediaItemIndex= */ 1, /* positionMs= */ 0) .sendMessage( targetEndLastPeriodResolved, - /* windowIndex= */ 1, + /* mediaItemIndex= */ 1, /* positionMs= */ duration2Ms - 1) .sendMessage( targetEndLastPeriodUnresolved, - /* windowIndex= */ 1, + /* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_END_OF_SOURCE) .waitForMessage(targetEndLastPeriodUnresolved) .build(); @@ -2317,19 +2317,19 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(targetStartFirstPeriod.windowIndex).isEqualTo(0); + assertThat(targetStartFirstPeriod.mediaItemIndex).isEqualTo(0); assertThat(targetStartFirstPeriod.positionMs).isAtLeast(0L); - assertThat(targetEndMiddlePeriodResolved.windowIndex).isEqualTo(0); + assertThat(targetEndMiddlePeriodResolved.mediaItemIndex).isEqualTo(0); assertThat(targetEndMiddlePeriodResolved.positionMs).isAtLeast(duration1Ms - 1); - assertThat(targetEndMiddlePeriodUnresolved.windowIndex).isEqualTo(0); + assertThat(targetEndMiddlePeriodUnresolved.mediaItemIndex).isEqualTo(0); assertThat(targetEndMiddlePeriodUnresolved.positionMs).isAtLeast(duration1Ms - 1); assertThat(targetEndMiddlePeriodResolved.positionMs) .isEqualTo(targetEndMiddlePeriodUnresolved.positionMs); - assertThat(targetStartMiddlePeriod.windowIndex).isEqualTo(1); + assertThat(targetStartMiddlePeriod.mediaItemIndex).isEqualTo(1); assertThat(targetStartMiddlePeriod.positionMs).isAtLeast(0L); - assertThat(targetEndLastPeriodResolved.windowIndex).isEqualTo(1); + assertThat(targetEndLastPeriodResolved.mediaItemIndex).isEqualTo(1); assertThat(targetEndLastPeriodResolved.positionMs).isAtLeast(duration2Ms - 1); - assertThat(targetEndLastPeriodUnresolved.windowIndex).isEqualTo(1); + assertThat(targetEndLastPeriodUnresolved.mediaItemIndex).isEqualTo(1); assertThat(targetEndLastPeriodUnresolved.positionMs).isAtLeast(duration2Ms - 1); assertThat(targetEndLastPeriodResolved.positionMs) .isEqualTo(targetEndLastPeriodUnresolved.positionMs); @@ -2445,12 +2445,12 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_BUFFERING) .sendMessage( target, - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ 50, /* deleteAfterDelivery= */ false) .setRepeatMode(Player.REPEAT_MODE_ALL) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 1) - .playUntilStartOfWindow(/* windowIndex= */ 0) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 0) .setRepeatMode(Player.REPEAT_MODE_OFF) .play() .build(); @@ -2464,7 +2464,7 @@ public final class ExoPlayerTest { } @Test - public void sendMessagesMoveCurrentWindowIndex() throws Exception { + public void sendMessagesMoveCurrentMediaItemIndex() throws Exception { Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0)); final Timeline secondTimeline = @@ -2492,7 +2492,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); assertThat(target.positionMs).isAtLeast(50L); - assertThat(target.windowIndex).isEqualTo(1); + assertThat(target.mediaItemIndex).isEqualTo(1); } @Test @@ -2503,7 +2503,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50) + .sendMessage(target, /* mediaItemIndex = */ 2, /* positionMs= */ 50) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2512,7 +2512,7 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target.windowIndex).isEqualTo(2); + assertThat(target.mediaItemIndex).isEqualTo(2); assertThat(target.positionMs).isAtLeast(50L); } @@ -2525,7 +2525,7 @@ public final class ExoPlayerTest { .pause() .waitForTimelineChanged( timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50) + .sendMessage(target, /* mediaItemIndex = */ 2, /* positionMs= */ 50) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2534,12 +2534,12 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target.windowIndex).isEqualTo(2); + assertThat(target.mediaItemIndex).isEqualTo(2); assertThat(target.positionMs).isAtLeast(50L); } @Test - public void sendMessagesMoveWindowIndex() throws Exception { + public void sendMessagesMoveMediaItemIndex() throws Exception { Timeline timeline = new FakeTimeline( new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0), @@ -2556,11 +2556,11 @@ public final class ExoPlayerTest { .pause() .waitForTimelineChanged( timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .sendMessage(target, /* windowIndex = */ 1, /* positionMs= */ 50) + .sendMessage(target, /* mediaItemIndex = */ 1, /* positionMs= */ 50) .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline)) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .seek(/* windowIndex= */ 0, /* positionMs= */ 0) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 0) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2570,7 +2570,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); assertThat(target.positionMs).isAtLeast(50L); - assertThat(target.windowIndex).isEqualTo(0); + assertThat(target.mediaItemIndex).isEqualTo(0); } @Test @@ -2590,11 +2590,11 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .sendMessage(target1, /* windowIndex = */ 0, /* positionMs= */ 50) - .sendMessage(target2, /* windowIndex = */ 1, /* positionMs= */ 50) - .sendMessage(target3, /* windowIndex = */ 2, /* positionMs= */ 50) + .sendMessage(target1, /* mediaItemIndex = */ 0, /* positionMs= */ 50) + .sendMessage(target2, /* mediaItemIndex = */ 1, /* positionMs= */ 50) + .sendMessage(target3, /* mediaItemIndex = */ 2, /* positionMs= */ 50) .setShuffleModeEnabled(true) - .seek(/* windowIndex= */ 2, /* positionMs= */ 0) + .seek(/* mediaItemIndex= */ 2, /* positionMs= */ 0) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2603,9 +2603,9 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target1.windowIndex).isEqualTo(0); - assertThat(target2.windowIndex).isEqualTo(1); - assertThat(target3.windowIndex).isEqualTo(2); + assertThat(target1.mediaItemIndex).isEqualTo(0); + assertThat(target2.mediaItemIndex).isEqualTo(1); + assertThat(target3.mediaItemIndex).isEqualTo(2); } @Test @@ -2625,7 +2625,7 @@ public final class ExoPlayerTest { } }) // Play a bit to ensure message arrived in internal player. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 30) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 30) .executeRunnable(() -> message.get().cancel()) .play() .build(); @@ -2659,7 +2659,7 @@ public final class ExoPlayerTest { } }) // Play until the message has been delivered. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 51) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 51) // Seek back, cancel the message, and play past the same position again. .seek(/* positionMs= */ 0) .executeRunnable(() -> message.get().cancel()) @@ -2681,13 +2681,13 @@ public final class ExoPlayerTest { player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); player .createMessage((messageType, payload) -> {}) - .setPosition(/* windowIndex= */ 0, /* positionMs= */ 0) + .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 0) .setDeleteAfterDelivery(false) .send(); PlayerMessage.Target secondMediaItemTarget = mock(PlayerMessage.Target.class); player .createMessage(secondMediaItemTarget) - .setPosition(/* windowIndex= */ 1, /* positionMs= */ 0) + .setPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 0) .setDeleteAfterDelivery(false) .send(); @@ -2765,7 +2765,7 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_READY) // Ensure next period is pre-buffered by playing until end of first period. .playUntilPosition( - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ Util.usToMs(TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US)) .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2)) .waitForTimelineChanged( @@ -2921,12 +2921,12 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .seek(/* windowIndex= */ 0, /* positionMs= */ 9999) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 9999) // Wait after each seek until the internal player has updated its state. .waitForPendingPlayerCommands() - .seek(/* windowIndex= */ 0, /* positionMs= */ 1) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 1) .waitForPendingPlayerCommands() - .seek(/* windowIndex= */ 0, /* positionMs= */ 9999) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 9999) .waitForPendingPlayerCommands() .play() .build(); @@ -2988,7 +2988,7 @@ public final class ExoPlayerTest { } catch (InterruptedException e) { throw new IllegalStateException(e); } - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1000L); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1000L); } }) .waitForPendingPlayerCommands() @@ -3254,8 +3254,8 @@ public final class ExoPlayerTest { .setRepeatMode(Player.REPEAT_MODE_ALL) .waitForPlaybackState(Player.STATE_READY) // Play until the media repeats once. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 1) - .playUntilStartOfWindow(/* windowIndex= */ 0) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 0) .setRepeatMode(Player.REPEAT_MODE_OFF) .play() .build(); @@ -3289,7 +3289,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .seek(/* windowIndex= */ 1, /* positionMs= */ 0) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 0) .waitForPendingPlayerCommands() .play() .build(); @@ -3337,7 +3337,7 @@ public final class ExoPlayerTest { .pause() .waitForPlaybackState(Player.STATE_READY) // Play almost to end to ensure the current period is fully buffered. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 90) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 90) // Enable repeat mode to trigger the creation of new media periods. .setRepeatMode(Player.REPEAT_MODE_ALL) // Remove the media source. @@ -3852,20 +3852,20 @@ public final class ExoPlayerTest { return Timeline.EMPTY; } }; - int[] currentWindowIndices = new int[1]; + int[] currentMediaItemIndices = new int[1]; long[] currentPlaybackPositions = new long[1]; long[] windowCounts = new long[1]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .seek(/* windowIndex= */ 1, /* positionMs= */ 5000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 5000) .waitForTimelineChanged( /* expectedTimeline= */ null, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); windowCounts[0] = player.getCurrentTimeline().getWindowCount(); } @@ -3882,23 +3882,23 @@ public final class ExoPlayerTest { exoPlayerTestRunner.assertTimelineChangeReasonsEqual( Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); assertArrayEquals(new long[] {2}, windowCounts); - assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices); + assertArrayEquals(new int[] {seekToMediaItemIndex}, currentMediaItemIndices); assertArrayEquals(new long[] {5_000}, currentPlaybackPositions); } @SuppressWarnings("deprecation") @Test - public void seekTo_windowIndexIsReset_deprecated() throws Exception { + public void seekTo_mediaItemIndexIsReset_deprecated() throws Exception { FakeTimeline fakeTimeline = new FakeTimeline(); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); - final int[] windowIndex = {C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET}; final long[] positionMs = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 3000) .executeRunnable( new PlayerRunnable() { @Override @@ -3917,7 +3917,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); positionMs[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } @@ -3930,7 +3930,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(3000L); assertThat(positionMs[1]).isEqualTo(7000L); assertThat(positionMs[2]).isEqualTo(7000L); @@ -3941,17 +3941,17 @@ public final class ExoPlayerTest { } @Test - public void seekTo_windowIndexIsReset() throws Exception { + public void seekTo_mediaItemIndexIsReset() throws Exception { FakeTimeline fakeTimeline = new FakeTimeline(); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); - final int[] windowIndex = {C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET}; final long[] positionMs = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 3000) .pause() .executeRunnable( new PlayerRunnable() { @@ -3970,7 +3970,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); positionMs[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } @@ -3983,7 +3983,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(3000); assertThat(positionMs[1]).isEqualTo(7000); assertThat(positionMs[2]).isEqualTo(7000); @@ -3995,7 +3995,7 @@ public final class ExoPlayerTest { @Test public void seekTo_singlePeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4007,19 +4007,19 @@ public final class ExoPlayerTest { player.seekTo(9000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(9000); assertThat(bufferedPositions[0]).isEqualTo(9200); assertThat(totalBufferedDuration[0]).isEqualTo(200); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(9200); assertThat(totalBufferedDuration[1]).isEqualTo(200); @@ -4027,7 +4027,7 @@ public final class ExoPlayerTest { @Test public void seekTo_singlePeriod_beyondBufferedData_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4039,19 +4039,19 @@ public final class ExoPlayerTest { player.seekTo(9200); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(9200); assertThat(bufferedPositions[0]).isEqualTo(9200); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(9200); assertThat(totalBufferedDuration[1]).isEqualTo(0); @@ -4059,7 +4059,7 @@ public final class ExoPlayerTest { @Test public void seekTo_backwardsSinglePeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4071,14 +4071,14 @@ public final class ExoPlayerTest { player.seekTo(1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(1000); assertThat(bufferedPositions[0]).isEqualTo(1000); assertThat(totalBufferedDuration[0]).isEqualTo(0); @@ -4086,7 +4086,7 @@ public final class ExoPlayerTest { @Test public void seekTo_backwardsMultiplePeriods_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4098,8 +4098,8 @@ public final class ExoPlayerTest { player.seekTo(0, 1000); } }, - /* pauseWindowIndex= */ 1, - windowIndex, + /* pauseMediaItemIndex= */ 1, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4107,7 +4107,7 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(1000); assertThat(bufferedPositions[0]).isEqualTo(1000); assertThat(totalBufferedDuration[0]).isEqualTo(0); @@ -4115,7 +4115,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toUnbufferedPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4127,8 +4127,8 @@ public final class ExoPlayerTest { player.seekTo(2, 1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4136,12 +4136,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0)); - assertThat(windowIndex[0]).isEqualTo(2); + assertThat(mediaItemIndex[0]).isEqualTo(2); assertThat(positionMs[0]).isEqualTo(1000); assertThat(bufferedPositions[0]).isEqualTo(1000); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(2); + assertThat(mediaItemIndex[1]).isEqualTo(2); assertThat(positionMs[1]).isEqualTo(1000); assertThat(bufferedPositions[1]).isEqualTo(1000); assertThat(totalBufferedDuration[1]).isEqualTo(0); @@ -4149,7 +4149,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toLoadingPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4161,22 +4161,22 @@ public final class ExoPlayerTest { player.seekTo(1, 1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), new FakeMediaSource()); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(1000); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(10_000); // assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4185,7 +4185,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toLoadingPeriod_withinPartiallyBufferedData_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4197,22 +4197,22 @@ public final class ExoPlayerTest { player.seekTo(1, 1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(1000); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(1000); // assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(4000); assertThat(totalBufferedDuration[1]).isEqualTo(3000); @@ -4220,7 +4220,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toLoadingPeriod_beyondBufferedData_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4232,20 +4232,20 @@ public final class ExoPlayerTest { player.seekTo(1, 5000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(5000); assertThat(bufferedPositions[0]).isEqualTo(5000); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(1); + assertThat(mediaItemIndex[1]).isEqualTo(1); assertThat(positionMs[1]).isEqualTo(5000); assertThat(bufferedPositions[1]).isEqualTo(5000); assertThat(totalBufferedDuration[1]).isEqualTo(0); @@ -4253,7 +4253,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toInnerFullyBufferedPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4265,8 +4265,8 @@ public final class ExoPlayerTest { player.seekTo(1, 5000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4274,14 +4274,14 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(5000); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(10_000); // assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4289,7 +4289,7 @@ public final class ExoPlayerTest { @Test public void addMediaSource_withinBufferedPeriods_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4302,20 +4302,20 @@ public final class ExoPlayerTest { /* index= */ 1, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0)); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4323,7 +4323,7 @@ public final class ExoPlayerTest { @Test public void moveMediaItem_behindLoadingPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4335,8 +4335,8 @@ public final class ExoPlayerTest { player.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 2); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4344,12 +4344,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4357,7 +4357,7 @@ public final class ExoPlayerTest { @Test public void moveMediaItem_undloadedBehindPlaying_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4369,8 +4369,8 @@ public final class ExoPlayerTest { player.moveMediaItem(/* currentIndex= */ 3, /* newIndex= */ 1); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4379,12 +4379,12 @@ public final class ExoPlayerTest { createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4392,7 +4392,7 @@ public final class ExoPlayerTest { @Test public void removeMediaItem_removePlayingWindow_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4404,22 +4404,22 @@ public final class ExoPlayerTest { player.removeMediaItem(/* index= */ 0); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(0); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(4000); // assertThat(totalBufferedDuration[0]).isEqualTo(4000); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(4000); assertThat(totalBufferedDuration[1]).isEqualTo(4000); @@ -4427,7 +4427,7 @@ public final class ExoPlayerTest { @Test public void removeMediaItem_removeLoadingWindow_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4439,8 +4439,8 @@ public final class ExoPlayerTest { player.removeMediaItem(/* index= */ 2); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4448,12 +4448,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4462,7 +4462,7 @@ public final class ExoPlayerTest { @Test public void removeMediaItem_removeInnerFullyBufferedWindow_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4474,8 +4474,8 @@ public final class ExoPlayerTest { player.removeMediaItem(/* index= */ 1); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4483,12 +4483,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(0); + assertThat(mediaItemIndex[1]).isEqualTo(0); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[0]); @@ -4496,7 +4496,7 @@ public final class ExoPlayerTest { @Test public void clearMediaItems_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4508,8 +4508,8 @@ public final class ExoPlayerTest { player.clearMediaItems(); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4517,12 +4517,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(0); assertThat(bufferedPositions[0]).isEqualTo(0); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(bufferedPositions[0]); assertThat(totalBufferedDuration[1]).isEqualTo(totalBufferedDuration[0]); @@ -4530,8 +4530,8 @@ public final class ExoPlayerTest { private void runPositionMaskingCapturingActionSchedule( PlayerRunnable actionRunnable, - int pauseWindowIndex, - int[] windowIndex, + int pauseMediaItemIndex, + int[] mediaItemIndex, long[] positionMs, long[] bufferedPosition, long[] totalBufferedDuration, @@ -4539,13 +4539,13 @@ public final class ExoPlayerTest { throws Exception { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .playUntilPosition(pauseWindowIndex, /* positionMs= */ 8000) + .playUntilPosition(pauseMediaItemIndex, /* positionMs= */ 8000) .executeRunnable(actionRunnable) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); positionMs[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); @@ -4556,7 +4556,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[1] = player.getCurrentWindowIndex(); + mediaItemIndex[1] = player.getCurrentMediaItemIndex(); positionMs[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -4637,7 +4637,7 @@ public final class ExoPlayerTest { /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline); - int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] mediaItemIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] positionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET}; long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET}; long[] totalBufferedDurationMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET}; @@ -4653,7 +4653,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { player.addMediaSource(/* index= */ 1, new FakeMediaSource()); - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); isPlayingAd[0] = player.isPlayingAd(); positionMs[0] = player.getCurrentPosition(); bufferedPositionMs[0] = player.getBufferedPosition(); @@ -4665,21 +4665,21 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[1] = player.getCurrentWindowIndex(); + mediaItemIndex[1] = player.getCurrentMediaItemIndex(); isPlayingAd[1] = player.isPlayingAd(); positionMs[1] = player.getCurrentPosition(); bufferedPositionMs[1] = player.getBufferedPosition(); totalBufferedDurationMs[1] = player.getTotalBufferedDuration(); } }) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 8000) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 8000) .waitForPendingPlayerCommands() .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { player.addMediaSource(new FakeMediaSource()); - windowIndex[2] = player.getCurrentWindowIndex(); + mediaItemIndex[2] = player.getCurrentMediaItemIndex(); isPlayingAd[2] = player.isPlayingAd(); positionMs[2] = player.getCurrentPosition(); bufferedPositionMs[2] = player.getBufferedPosition(); @@ -4697,19 +4697,19 @@ public final class ExoPlayerTest { .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(isPlayingAd[0]).isTrue(); assertThat(positionMs[0]).isAtMost(adDurationMs); assertThat(bufferedPositionMs[0]).isEqualTo(adDurationMs); assertThat(totalBufferedDurationMs[0]).isAtLeast(adDurationMs - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(0); + assertThat(mediaItemIndex[1]).isEqualTo(0); assertThat(isPlayingAd[1]).isTrue(); assertThat(positionMs[1]).isAtMost(adDurationMs); assertThat(bufferedPositionMs[1]).isEqualTo(adDurationMs); assertThat(totalBufferedDurationMs[1]).isAtLeast(adDurationMs - positionMs[1]); - assertThat(windowIndex[2]).isEqualTo(0); + assertThat(mediaItemIndex[2]).isEqualTo(0); assertThat(isPlayingAd[2]).isFalse(); assertThat(positionMs[2]).isEqualTo(8000); assertThat(bufferedPositionMs[2]).isEqualTo(contentDurationMs); @@ -4738,7 +4738,7 @@ public final class ExoPlayerTest { /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline); - int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET}; + int[] mediaItemIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET}; long[] positionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDurationMs = new long[] {C.TIME_UNSET, C.TIME_UNSET}; @@ -4753,8 +4753,8 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 8000); - windowIndex[0] = player.getCurrentWindowIndex(); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 8000); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); isPlayingAd[0] = player.isPlayingAd(); positionMs[0] = player.getCurrentPosition(); bufferedPositionMs[0] = player.getBufferedPosition(); @@ -4766,7 +4766,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[1] = player.getCurrentWindowIndex(); + mediaItemIndex[1] = player.getCurrentMediaItemIndex(); isPlayingAd[1] = player.isPlayingAd(); positionMs[1] = player.getCurrentPosition(); bufferedPositionMs[1] = player.getBufferedPosition(); @@ -4784,13 +4784,13 @@ public final class ExoPlayerTest { .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(isPlayingAd[0]).isTrue(); assertThat(positionMs[0]).isEqualTo(0); assertThat(bufferedPositionMs[0]).isEqualTo(adDurationMs); assertThat(totalBufferedDurationMs[0]).isEqualTo(adDurationMs); - assertThat(windowIndex[1]).isEqualTo(0); + assertThat(mediaItemIndex[1]).isEqualTo(0); assertThat(isPlayingAd[1]).isTrue(); assertThat(positionMs[1]).isEqualTo(0); assertThat(bufferedPositionMs[1]).isEqualTo(adDurationMs); @@ -5332,7 +5332,7 @@ public final class ExoPlayerTest { .addMediaSources(new FakeMediaSource()) .executeRunnable( new PlaybackStateCollector(/* index= */ 3, playbackStates, timelineWindowCounts)) - .seek(/* windowIndex= */ 1, /* positionMs= */ 2000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 2000) .prepare() // The first expected buffering state arrives after prepare but not before. .waitForPlaybackState(Player.STATE_BUFFERING) @@ -5502,7 +5502,7 @@ public final class ExoPlayerTest { @Test public void prepareWithInvalidInitialSeek_expectEndedImmediately() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -5510,7 +5510,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -5518,7 +5518,7 @@ public final class ExoPlayerTest { ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, C.TIME_UNSET) .setActionSchedule(actionSchedule) .build() .start() @@ -5528,7 +5528,7 @@ public final class ExoPlayerTest { exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED); exoPlayerTestRunner.assertTimelinesSame(); exoPlayerTestRunner.assertTimelineChangeReasonsEqual(); - assertArrayEquals(new int[] {1}, currentWindowIndices); + assertArrayEquals(new int[] {1}, currentMediaItemIndices); } @Test @@ -5564,9 +5564,9 @@ public final class ExoPlayerTest { /* isAtomic= */ false, new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT), new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT)); - int[] currentWindowIndices = new int[1]; + int[] currentMediaItemIndices = new int[1]; long[] currentPlaybackPositions = new long[1]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_BUFFERING) @@ -5575,21 +5575,21 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); } }) .build(); new ExoPlayerTestRunner.Builder(context) .setMediaSources(concatenatingMediaSource) - .initialSeek(seekToWindowIndex, 5000) + .initialSeek(seekToMediaItemIndex, 5000) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); assertArrayEquals(new long[] {5_000}, currentPlaybackPositions); - assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices); + assertArrayEquals(new int[] {seekToMediaItemIndex}, currentMediaItemIndices); } @Test @@ -5600,10 +5600,10 @@ public final class ExoPlayerTest { /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 10_000_000)); ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false); - int[] currentWindowIndices = new int[2]; + int[] currentMediaItemIndices = new int[2]; long[] currentPlaybackPositions = new long[2]; long[] windowCounts = new long[2]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -5611,7 +5611,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); windowCounts[0] = player.getCurrentTimeline().getWindowCount(); } @@ -5627,7 +5627,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[1] = player.getCurrentPosition(); windowCounts[1] = player.getCurrentTimeline().getWindowCount(); } @@ -5635,14 +5635,15 @@ public final class ExoPlayerTest { .build(); new ExoPlayerTestRunner.Builder(context) .setMediaSources(concatenatingMediaSource) - .initialSeek(seekToWindowIndex, 5000) + .initialSeek(seekToMediaItemIndex, 5000) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); assertArrayEquals(new long[] {0, 2}, windowCounts); - assertArrayEquals(new int[] {seekToWindowIndex, seekToWindowIndex}, currentWindowIndices); + assertArrayEquals( + new int[] {seekToMediaItemIndex, seekToMediaItemIndex}, currentMediaItemIndices); assertArrayEquals(new long[] {5_000, 5_000}, currentPlaybackPositions); } @@ -5671,10 +5672,10 @@ public final class ExoPlayerTest { /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 10_000_000)); ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false); - int[] currentWindowIndices = new int[2]; + int[] currentMediaItemIndices = new int[2]; long[] currentPlaybackPositions = new long[2]; long[] windowCounts = new long[2]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -5682,7 +5683,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); windowCounts[0] = player.getCurrentTimeline().getWindowCount(); } @@ -5698,7 +5699,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[1] = player.getCurrentPosition(); windowCounts[1] = player.getCurrentTimeline().getWindowCount(); } @@ -5707,14 +5708,15 @@ public final class ExoPlayerTest { new ExoPlayerTestRunner.Builder(context) .setMediaSources(concatenatingMediaSource) .setUseLazyPreparation(/* useLazyPreparation= */ true) - .initialSeek(seekToWindowIndex, 5000) + .initialSeek(seekToMediaItemIndex, 5000) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); assertArrayEquals(new long[] {0, 2}, windowCounts); - assertArrayEquals(new int[] {seekToWindowIndex, seekToWindowIndex}, currentWindowIndices); + assertArrayEquals( + new int[] {seekToMediaItemIndex, seekToMediaItemIndex}, currentMediaItemIndices); assertArrayEquals(new long[] {5_000, 5_000}, currentPlaybackPositions); } @@ -5875,19 +5877,19 @@ public final class ExoPlayerTest { } @Test - public void setMediaSources_empty_whenEmpty_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void setMediaSources_empty_whenEmpty_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); List listOfTwo = ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()); player.addMediaSources(/* index= */ 0, listOfTwo); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -5896,7 +5898,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -5907,12 +5909,12 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {0, 0, 0}, currentMediaItemIndices); } @Test public void setMediaItems_resetPosition_resetsPosition() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) @@ -5921,14 +5923,14 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1000); - currentWindowIndices[0] = player.getCurrentWindowIndex(); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1000); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); List listOfTwo = ImmutableList.of( MediaItem.fromUri(Uri.EMPTY), MediaItem.fromUri(Uri.EMPTY)); player.setMediaItems(listOfTwo, /* resetPosition= */ true); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); } }) @@ -5942,14 +5944,14 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); assertArrayEquals(new long[] {1000, 0}, currentPositions); } @Test - public void setMediaSources_empty_whenEmpty_validInitialSeek_correctMaskingWindowIndex() + public void setMediaSources_empty_whenEmpty_validInitialSeek_correctMaskingMediaItemIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) // Wait for initial seek to be fully handled by internal player. @@ -5959,11 +5961,11 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); List listOfTwo = ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()); player.addMediaSources(/* index= */ 0, listOfTwo); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -5972,25 +5974,25 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1, 1}, currentMediaItemIndices); } @Test - public void setMediaSources_empty_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex() + public void setMediaSources_empty_whenEmpty_invalidInitialSeek_correctMaskingMediaItemIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) // Wait for initial seek to be fully handled by internal player. @@ -6000,11 +6002,11 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); List listOfTwo = ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()); player.addMediaSources(/* index= */ 0, listOfTwo); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -6013,24 +6015,26 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 4, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 4, C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {4, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {4, 0, 0}, currentMediaItemIndices); } @Test - public void setMediaSources_whenEmpty_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void setMediaSources_whenEmpty_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = { + C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET + }; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_READY) @@ -6038,18 +6042,18 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Increase current window index. + // Increase current media item index. player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Current window index is unchanged. + // Current media item index is unchanged. player.addMediaSource(/* index= */ 2, new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6059,9 +6063,9 @@ public final class ExoPlayerTest { MediaSource mediaSource = new FakeMediaSource(); ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(mediaSource, mediaSource, mediaSource); - // Increase current window with multi window source. + // Increase current media item with multi media item source. player.addMediaSource(/* index= */ 0, concatenatingMediaSource); - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6070,9 +6074,9 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(); - // Current window index is unchanged when adding empty source. + // Current media item index is unchanged when adding empty source. player.addMediaSource(/* index= */ 0, concatenatingMediaSource); - currentWindowIndices[3] = player.getCurrentWindowIndex(); + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -6083,7 +6087,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1, 4, 4}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1, 4, 4}, currentMediaItemIndices); } @Test @@ -6092,7 +6096,7 @@ public final class ExoPlayerTest { MediaSource firstMediaSource = new FakeMediaSource(firstTimeline); Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object()); MediaSource secondMediaSource = new FakeMediaSource(secondTimeline); - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = @@ -6104,12 +6108,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); - // Increase current window index. + // Increase current media item index. player.addMediaSource(/* index= */ 0, secondMediaSource); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); } @@ -6120,28 +6124,28 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); currentPositions[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, 2000) + .initialSeek(/* mediaItemIndex= */ 1, 2000) .setMediaSources(firstMediaSource) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 2, 2}, currentWindowIndices); + assertArrayEquals(new int[] {1, 2, 2}, currentMediaItemIndices); assertArrayEquals(new long[] {2000, 2000, 2000}, currentPositions); assertArrayEquals(new long[] {2000, 2000, 2000}, bufferedPositions); } @Test public void setMediaSources_whenEmpty_invalidInitialSeek_correctMasking() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = @@ -6153,12 +6157,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); - // Increase current window index. + // Increase current media item index. player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); } @@ -6169,7 +6173,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); currentPositions[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } @@ -6177,21 +6181,21 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_ENDED) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, 2000) + .initialSeek(/* mediaItemIndex= */ 1, 2000) .setMediaSources(new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {0, 1, 1}, currentMediaItemIndices); assertArrayEquals(new long[] {0, 0, 0}, currentPositions); assertArrayEquals(new long[] {0, 0, 0}, bufferedPositions); } @Test - public void setMediaSources_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void setMediaSources_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForTimelineChanged() @@ -6199,10 +6203,10 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); - // Increase current window index. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); + // Increase current media item index. player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .waitForTimelineChanged() @@ -6210,7 +6214,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -6221,7 +6225,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {0, 1, 1}, currentMediaItemIndices); } @Test @@ -6278,7 +6282,7 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED); assertArrayEquals( new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE}, @@ -6314,14 +6318,14 @@ public final class ExoPlayerTest { .build(); ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); @@ -6356,7 +6360,7 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); @@ -6392,7 +6396,7 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); @@ -6464,7 +6468,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_ENDED, Player.STATE_BUFFERING, @@ -6509,14 +6513,14 @@ public final class ExoPlayerTest { .build(); ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); exoPlayerTestRunner.assertTimelineChangeReasonsEqual( @@ -6552,7 +6556,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); @@ -6591,7 +6595,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); @@ -6625,7 +6629,8 @@ public final class ExoPlayerTest { } }) .waitForPlaybackState(Player.STATE_ENDED) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @@ -6639,7 +6644,8 @@ public final class ExoPlayerTest { } }) .waitForPlaybackState(Player.STATE_ENDED) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @@ -6652,7 +6658,8 @@ public final class ExoPlayerTest { } }) .waitForPlaybackState(Player.STATE_READY) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @@ -6676,7 +6683,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, // Ready after initial prepare. @@ -6737,7 +6744,8 @@ public final class ExoPlayerTest { } }) .waitForTimelineChanged() - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .play() .waitForPlaybackState(Player.STATE_ENDED) @@ -6745,14 +6753,14 @@ public final class ExoPlayerTest { ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) .setExpectedPlayerEndedCount(/* expectedPlayerEndedCount= */ 2) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_ENDED, // Empty source has been prepared. Player.STATE_BUFFERING, // After setting another source. @@ -6788,7 +6796,7 @@ public final class ExoPlayerTest { .build(); new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 0, /* positionMs= */ 2000) + .initialSeek(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) @@ -6800,8 +6808,8 @@ public final class ExoPlayerTest { @Test public void addMediaSources_skipSettingMediaItems_validInitialSeek_correctMasking() throws Exception { - final int[] currentWindowIndices = new int[5]; - Arrays.fill(currentWindowIndices, C.INDEX_UNSET); + final int[] currentMediaItemIndices = new int[5]; + Arrays.fill(currentMediaItemIndices, C.INDEX_UNSET); final long[] currentPositions = new long[3]; Arrays.fill(currentPositions, C.TIME_UNSET); final long[] bufferedPositions = new long[3]; @@ -6815,18 +6823,18 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); // If the timeline is empty masking variables are used. currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); player.addMediaSource(/* index= */ 0, new ConcatenatingMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); player.addMediaSource( /* index= */ 0, new FakeMediaSource(new FakeTimeline(/* windowCount= */ 2))); - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[3] = player.getCurrentWindowIndex(); + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); // With a non-empty timeline, we mask the periodId in the playback info. currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); @@ -6838,7 +6846,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[4] = player.getCurrentWindowIndex(); + currentMediaItemIndices[4] = player.getCurrentMediaItemIndex(); // Finally original playbackInfo coming from EPII is used. currentPositions[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); @@ -6847,13 +6855,13 @@ public final class ExoPlayerTest { .build(); new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 1, 2000) + .initialSeek(/* mediaItemIndex= */ 1, 2000) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1, 1, 2, 2}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1, 1, 2, 2}, currentMediaItemIndices); assertThat(currentPositions[0]).isEqualTo(2000); assertThat(currentPositions[1]).isEqualTo(2000); assertThat(currentPositions[2]).isAtLeast(2000); @@ -6864,9 +6872,9 @@ public final class ExoPlayerTest { @Test public void - testAddMediaSources_skipSettingMediaItems_invalidInitialSeek_correctMaskingWindowIndex() + testAddMediaSources_skipSettingMediaItems_invalidInitialSeek_correctMaskingMediaItemIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) // Wait for initial seek to be fully handled by internal player. @@ -6876,9 +6884,9 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); player.addMediaSource(new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -6887,28 +6895,28 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, C.TIME_UNSET) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0, 0}, currentMediaItemIndices); } @Test - public void moveMediaItems_correctMaskingWindowIndex() throws Exception { + public void moveMediaItems_correctMaskingMediaItemIndex() throws Exception { Timeline timeline = new FakeTimeline(); MediaSource firstMediaSource = new FakeMediaSource(timeline); MediaSource secondMediaSource = new FakeMediaSource(timeline); MediaSource thirdMediaSource = new FakeMediaSource(timeline); - final int[] currentWindowIndices = { + final int[] currentMediaItemIndices = { C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET }; ActionSchedule actionSchedule = @@ -6920,7 +6928,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Move the current item down in the playlist. player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 1); - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6929,17 +6937,17 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Move the current item up in the playlist. player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 0); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) - .seek(/* windowIndex= */ 2, C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 2, C.TIME_UNSET) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { // Move items from before to behind the current item. player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 1); - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6948,7 +6956,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Move items from behind to before the current item. player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 0); - currentWindowIndices[3] = player.getCurrentWindowIndex(); + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6956,20 +6964,20 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { // Move items from before to before the current item. - // No change in currentWindowIndex. + // No change in currentMediaItemIndex. player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1, /* newIndex= */ 1); - currentWindowIndices[4] = player.getCurrentWindowIndex(); + currentMediaItemIndices[4] = player.getCurrentMediaItemIndex(); } }) - .seek(/* windowIndex= */ 0, C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 0, C.TIME_UNSET) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { // Move items from behind to behind the current item. - // No change in currentWindowIndex. + // No change in currentMediaItemIndex. player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, /* newIndex= */ 2); - currentWindowIndices[5] = player.getCurrentWindowIndex(); + currentMediaItemIndices[5] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -6980,12 +6988,12 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0, 0, 2, 2, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0, 0, 2, 2, 0}, currentMediaItemIndices); } @Test - public void moveMediaItems_unprepared_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void moveMediaItems_unprepared_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForTimelineChanged() @@ -6993,10 +7001,10 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Increase current window index. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + // Increase current media item index. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); player.moveMediaItem(/* currentIndex= */ 0, /* newIndex= */ 1); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -7005,7 +7013,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -7016,12 +7024,12 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {0, 1, 1}, currentMediaItemIndices); } @Test - public void removeMediaItems_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + public void removeMediaItems_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_BUFFERING) @@ -7029,27 +7037,27 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Decrease current window index. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + // Decrease current media item index. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); player.removeMediaItem(/* index= */ 0); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); } @Test public void removeMediaItems_currentItemRemoved_correctMasking() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = @@ -7060,25 +7068,25 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { // Remove the current item. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); player.removeMediaItem(/* index= */ 1); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ 5000) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ 5000) .setMediaSources(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1}, currentMediaItemIndices); assertThat(currentPositions[0]).isAtLeast(5000L); assertThat(bufferedPositions[0]).isAtLeast(5000L); assertThat(currentPositions[1]).isEqualTo(0); @@ -7095,8 +7103,8 @@ public final class ExoPlayerTest { MediaSource thirdMediaSource = new FakeMediaSource(thirdTimeline); Timeline fourthTimeline = new FakeTimeline(/* windowCount= */ 1, 3L); MediaSource fourthMediaSource = new FakeMediaSource(fourthTimeline); - final int[] currentWindowIndices = new int[9]; - Arrays.fill(currentWindowIndices, C.INDEX_UNSET); + final int[] currentMediaItemIndices = new int[9]; + Arrays.fill(currentMediaItemIndices, C.INDEX_UNSET); final int[] maskingPlaybackStates = new int[4]; Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET); final long[] currentPositions = new long[3]; @@ -7111,14 +7119,14 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Expect the current window index to be 2 after seek. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + // Expect the current media item index to be 2 after seek. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); player.removeMediaItem(/* index= */ 2); - // Expect the current window index to be 0 + // Expect the current media item index to be 0 // (default position of timeline after not finding subsequent period). - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); // Transition to ENDED. maskingPlaybackStates[0] = player.getPlaybackState(); currentPositions[1] = player.getCurrentPosition(); @@ -7130,12 +7138,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Expects the current window index still on 0. - currentWindowIndices[2] = player.getCurrentWindowIndex(); + // Expects the current media item index still on 0. + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); // Insert an item at begin when the playlist is not empty. player.addMediaSource(/* index= */ 0, thirdMediaSource); - // Expects the current window index to be (0 + 1) after insertion at begin. - currentWindowIndices[3] = player.getCurrentWindowIndex(); + // Expects the current media item index to be (0 + 1) after insertion at begin. + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); // Remains in ENDED. maskingPlaybackStates[1] = player.getPlaybackState(); currentPositions[2] = player.getCurrentPosition(); @@ -7147,12 +7155,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[4] = player.getCurrentWindowIndex(); - // Implicit seek to the current window index, which is out of bounds in new + currentMediaItemIndices[4] = player.getCurrentMediaItemIndex(); + // Implicit seek to the current media item index, which is out of bounds in new // timeline. player.setMediaSource(fourthMediaSource, /* resetPosition= */ false); // 0 after reset. - currentWindowIndices[5] = player.getCurrentWindowIndex(); + currentMediaItemIndices[5] = player.getCurrentMediaItemIndex(); // Invalid seek, so we remain in ENDED. maskingPlaybackStates[2] = player.getPlaybackState(); } @@ -7162,11 +7170,11 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[6] = player.getCurrentWindowIndex(); + currentMediaItemIndices[6] = player.getCurrentMediaItemIndex(); // Explicit seek to (0, C.TIME_UNSET). Player transitions to BUFFERING. player.setMediaSource(fourthMediaSource, /* startPositionMs= */ 5000); // 0 after explicit seek. - currentWindowIndices[7] = player.getCurrentWindowIndex(); + currentMediaItemIndices[7] = player.getCurrentMediaItemIndex(); // Transitions from ENDED to BUFFERING after explicit seek. maskingPlaybackStates[3] = player.getPlaybackState(); } @@ -7176,14 +7184,14 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Check whether actual window index is equal masking index from above. - currentWindowIndices[8] = player.getCurrentWindowIndex(); + // Check whether actual media item index is equal masking index from above. + currentMediaItemIndices[8] = player.getCurrentMediaItemIndex(); } }) .build(); ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 2, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 2, /* positionMs= */ C.TIME_UNSET) .setExpectedPlayerEndedCount(2) .setMediaSources(firstMediaSource, secondMediaSource, thirdMediaSource) .setActionSchedule(actionSchedule) @@ -7191,23 +7199,23 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, // Ready after initial prepare. - Player.STATE_ENDED, // ended after removing current window index + Player.STATE_ENDED, // ended after removing current media item index Player.STATE_BUFFERING, // buffers after set items with seek Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals( new int[] { - Player.STATE_ENDED, // ended after removing current window index + Player.STATE_ENDED, // ended after removing current media item index Player.STATE_ENDED, // adding items does not change state Player.STATE_ENDED, // set items with seek to current position. Player.STATE_BUFFERING }, // buffers after set items with seek maskingPlaybackStates); - assertArrayEquals(new int[] {2, 0, 0, 1, 1, 0, 0, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {2, 0, 0, 1, 1, 0, 0, 0, 0}, currentMediaItemIndices); assertThat(currentPositions[0]).isEqualTo(0); assertThat(currentPositions[1]).isEqualTo(0); assertThat(currentPositions[2]).isEqualTo(0); @@ -7221,7 +7229,7 @@ public final class ExoPlayerTest { throws Exception { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .waitForPendingPlayerCommands() .removeMediaItem(/* index= */ 1) .prepare() @@ -7241,7 +7249,7 @@ public final class ExoPlayerTest { @Test public void clearMediaItems_correctMasking() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final int[] maskingPlaybackState = {C.INDEX_UNSET}; final long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET}; @@ -7249,16 +7257,16 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_BUFFERING) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 150) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 150) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); player.clearMediaItems(); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); maskingPlaybackState[0] = player.getPlaybackState(); @@ -7266,25 +7274,25 @@ public final class ExoPlayerTest { }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); assertThat(currentPosition[0]).isAtLeast(150); assertThat(currentPosition[1]).isEqualTo(0); assertThat(bufferedPosition[0]).isAtLeast(150); assertThat(bufferedPosition[1]).isEqualTo(0); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackState); } @Test - public void clearMediaItems_unprepared_correctMaskingWindowIndex_notEnded() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + public void clearMediaItems_unprepared_correctMaskingMediaItemIndex_notEnded() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final int[] currentStates = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) @@ -7295,10 +7303,10 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentStates[0] = player.getPlaybackState(); player.clearMediaItems(); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentStates[1] = player.getPlaybackState(); } }) @@ -7313,7 +7321,7 @@ public final class ExoPlayerTest { }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() @@ -7322,7 +7330,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); assertArrayEquals( new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_ENDED}, currentStates); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); } @Test @@ -7351,7 +7359,7 @@ public final class ExoPlayerTest { AtomicReference timelineAfterError = new AtomicReference<>(); AtomicReference trackInfosAfterError = new AtomicReference<>(); AtomicReference trackSelectionsAfterError = new AtomicReference<>(); - AtomicInteger windowIndexAfterError = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterError = new AtomicInteger(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .executeRunnable( @@ -7365,7 +7373,7 @@ public final class ExoPlayerTest { timelineAfterError.set(player.getCurrentTimeline()); trackInfosAfterError.set(player.getCurrentTracksInfo()); trackSelectionsAfterError.set(player.getCurrentTrackSelections()); - windowIndexAfterError.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterError.set(player.getCurrentMediaItemIndex()); } }); } @@ -7392,7 +7400,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS)); assertThat(timelineAfterError.get().getWindowCount()).isEqualTo(1); - assertThat(windowIndexAfterError.get()).isEqualTo(0); + assertThat(mediaItemIndexAfterError.get()).isEqualTo(0); assertThat(trackInfosAfterError.get().getTrackGroupInfos()).hasSize(1); assertThat(trackInfosAfterError.get().getTrackGroupInfos().get(0).getTrackGroup().getFormat(0)) .isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT); @@ -7404,7 +7412,7 @@ public final class ExoPlayerTest { public void seekToCurrentPosition_inEndedState_switchesToBufferingStateAndContinuesPlayback() throws Exception { MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount = */ 1)); - AtomicInteger windowIndexAfterFinalEndedState = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterFinalEndedState = new AtomicInteger(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -7422,7 +7430,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndexAfterFinalEndedState.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterFinalEndedState.set(player.getCurrentMediaItemIndex()); } }) .build(); @@ -7434,7 +7442,7 @@ public final class ExoPlayerTest { .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndexAfterFinalEndedState.get()).isEqualTo(1); + assertThat(mediaItemIndexAfterFinalEndedState.get()).isEqualTo(1); } @Test @@ -7448,7 +7456,7 @@ public final class ExoPlayerTest { MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(timelineWindowDefinition)); AtomicInteger playbackStateAfterPause = new AtomicInteger(C.INDEX_UNSET); AtomicLong positionAfterPause = new AtomicLong(C.TIME_UNSET); - AtomicInteger windowIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); + AtomicInteger mediaItemIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlayWhenReady(true) @@ -7458,7 +7466,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { playbackStateAfterPause.set(player.getPlaybackState()); - windowIndexAfterPause.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterPause.set(player.getCurrentMediaItemIndex()); positionAfterPause.set(player.getContentPosition()); } }) @@ -7473,7 +7481,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); assertThat(playbackStateAfterPause.get()).isEqualTo(Player.STATE_READY); - assertThat(windowIndexAfterPause.get()).isEqualTo(0); + assertThat(mediaItemIndexAfterPause.get()).isEqualTo(0); assertThat(positionAfterPause.get()).isEqualTo(10_000); } @@ -7487,7 +7495,7 @@ public final class ExoPlayerTest { MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(timelineWindowDefinition)); AtomicInteger playbackStateAfterPause = new AtomicInteger(C.INDEX_UNSET); AtomicLong positionAfterPause = new AtomicLong(C.TIME_UNSET); - AtomicInteger windowIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); + AtomicInteger mediaItemIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlayWhenReady(true) @@ -7497,7 +7505,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { playbackStateAfterPause.set(player.getPlaybackState()); - windowIndexAfterPause.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterPause.set(player.getCurrentMediaItemIndex()); positionAfterPause.set(player.getContentPosition()); } }) @@ -7512,7 +7520,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); assertThat(playbackStateAfterPause.get()).isEqualTo(Player.STATE_ENDED); - assertThat(windowIndexAfterPause.get()).isEqualTo(0); + assertThat(mediaItemIndexAfterPause.get()).isEqualTo(0); assertThat(positionAfterPause.get()).isEqualTo(10_000); } @@ -7846,7 +7854,7 @@ public final class ExoPlayerTest { firstMediaSource.setNewSourceInfo(timelineWithOffsets); // Wait until player transitions to second source (which also has non-zero offsets). runUntilPositionDiscontinuity(player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); player.release(); assertThat(rendererStreamOffsetsUs).hasSize(2); @@ -8102,7 +8110,7 @@ public final class ExoPlayerTest { player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 2000); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 2000); assertThat(reportedMediaItems) .containsExactly(mediaSource1.getMediaItem(), mediaSource2.getMediaItem()) @@ -8134,7 +8142,7 @@ public final class ExoPlayerTest { player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 2000); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 2000); assertThat(reportedMediaItems).containsExactly(mediaSource1.getMediaItem()).inOrder(); assertThat(reportedTransitionReasons) @@ -8371,7 +8379,7 @@ public final class ExoPlayerTest { player.addMediaSources( ImmutableList.of( new FakeMediaSource(), new FakeMediaSource(adTimeline), new FakeMediaSource())); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); @@ -8451,7 +8459,7 @@ public final class ExoPlayerTest { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addMediaSource(new FakeMediaSource(timelineWithUnseekableLiveWindow)); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); @@ -8507,14 +8515,14 @@ public final class ExoPlayerTest { // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 3, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @@ -8534,7 +8542,7 @@ public final class ExoPlayerTest { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); - player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 3, /* positionMs= */ 0); player.addMediaSources( ImmutableList.of( new FakeMediaSource(), @@ -8545,14 +8553,14 @@ public final class ExoPlayerTest { // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @@ -8567,8 +8575,8 @@ public final class ExoPlayerTest { player.addMediaSources(ImmutableList.of(new FakeMediaSource())); verify(mockListener).onAvailableCommandsChanged(defaultCommands); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 200); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 100); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); } @@ -8617,12 +8625,12 @@ public final class ExoPlayerTest { verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - playUntilStartOfWindow(player, /* windowIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); runUntilPendingCommandsAreFullyHandled(player); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); runUntilPendingCommandsAreFullyHandled(player); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); @@ -8714,7 +8722,7 @@ public final class ExoPlayerTest { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); - player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0); player.addMediaSources( ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); @@ -8838,7 +8846,7 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true) .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); } @Test @@ -8884,7 +8892,7 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true) .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); } @Test @@ -8947,7 +8955,7 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true) .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); } @Test @@ -8988,7 +8996,7 @@ public final class ExoPlayerTest { .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); // Verify test setup by checking that playing period was indeed different. - assertThat(player.getCurrentWindowIndex()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); } @Test @@ -9127,7 +9135,8 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtStart).isIn(Range.closed(11_900L, 12_100L)); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9173,7 +9182,8 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); long liveOffsetAtStart = player.getCurrentLiveOffset(); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9221,7 +9231,8 @@ public final class ExoPlayerTest { // Seek to a live offset of 2 seconds. player.seekTo(18_000); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9285,11 +9296,13 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtStart).isIn(Range.closed(11_900L, 12_100L)); // Play a bit and update configuration. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 55_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 55_000); fakeMediaSource.setNewSourceInfo(updatedTimeline); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9351,7 +9364,8 @@ public final class ExoPlayerTest { player.setPlaybackParameters(new PlaybackParameters(/* speed */ 2.0f)); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9399,7 +9413,8 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 1, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9463,9 +9478,10 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); // Seek to default position in second stream. - player.seekToNextWindow(); + player.seekToNextMediaItem(); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 1, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9529,9 +9545,10 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); // Seek to specific position in second stream (at 2 seconds live offset). - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 18_000); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 18_000); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 1, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9572,7 +9589,8 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); long playbackStartTimeMs = fakeClock.elapsedRealtime(); - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long playbackEndTimeMs = fakeClock.elapsedRealtime(); player.release(); @@ -9613,7 +9631,8 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtStart).isIn(Range.closed(11_900L, 12_100L)); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9751,7 +9770,7 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPositionDiscontinuity( player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); player.addMediaSource(secondMediaSource); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET); player.play(); TestPlayerRunHelper.runUntilPositionDiscontinuity( player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); @@ -9828,7 +9847,7 @@ public final class ExoPlayerTest { inOrder .verify(listener) .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)); - // Last auto transition from window 0 to window 1 not caused by repeat mode. + // Last auto transition from media item 0 to media item 1 not caused by repeat mode. inOrder .verify(listener) .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); @@ -10004,7 +10023,7 @@ public final class ExoPlayerTest { player.setMediaSource(new FakeMediaSource(new FakeTimeline(adTimeline))); player.prepare(); - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 1000); + TestPlayerRunHelper.playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 1000); player.seekTo(/* positionMs= */ 8_000); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10128,7 +10147,7 @@ public final class ExoPlayerTest { ArgumentCaptor.forClass(Player.PositionInfo.class); Window window = new Window(); InOrder inOrder = Mockito.inOrder(listener); - // from first to second window + // from first to second media item inOrder .verify(listener) .onPositionDiscontinuity( @@ -10154,7 +10173,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); - // from second window to third + // from second media item to third inOrder .verify(listener) .onPositionDiscontinuity( @@ -10180,7 +10199,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); - // from third window content to post roll ad + // from third media item content to post roll ad @Nullable Object lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10201,7 +10220,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(20_000); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(0); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(0); - // from third window post roll to third window content end + // from third media item post roll to third media item content end lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10223,7 +10242,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(19_999); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); - // from third window content end to fourth window pre roll ad + // from third media item content end to fourth media item pre roll ad lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10248,7 +10267,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(0); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(0); - // from fourth window pre roll ad to fourth window content + // from fourth media item pre roll ad to fourth media item content lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10306,7 +10325,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.setMediaSources(ImmutableList.of(secondMediaSource, secondMediaSource)); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10428,11 +10447,11 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.removeMediaItem(/* index= */ 1); player.seekTo(/* positionMs= */ 0); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 2 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 2 * C.MILLIS_PER_SECOND); // Removing the last item resets the position to 0 with an empty timeline. player.removeMediaItem(0); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10517,7 +10536,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); concatenatingMediaSource.removeMediaSource(1); TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); concatenatingMediaSource.removeMediaSource(1); @@ -10590,9 +10609,9 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); concatenatingMediaSource.removeMediaSource(1); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1234); TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); concatenatingMediaSource.removeMediaSource(0); player.play(); @@ -10708,9 +10727,9 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.seekTo(/* positionMs= */ 7 * C.MILLIS_PER_SECOND); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10753,7 +10772,7 @@ public final class ExoPlayerTest { player.addListener(listener); player.seekTo(/* positionMs= */ 7 * C.MILLIS_PER_SECOND); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); player.seekTo(/* positionMs= */ 5 * C.MILLIS_PER_SECOND); ArgumentCaptor oldPosition = @@ -10787,7 +10806,7 @@ public final class ExoPlayerTest { assertThat(newPositions.get(1).mediaItemIndex).isEqualTo(1); assertThat(newPositions.get(1).positionMs).isEqualTo(1_000); assertThat(newPositions.get(1).contentPositionMs).isEqualTo(1_000); - // a seek from masked seek position to another masked position within window + // a seek from masked seek position to another masked position within media item assertThat(oldPositions.get(2).windowUid).isNull(); assertThat(oldPositions.get(2).mediaItemIndex).isEqualTo(1); assertThat(oldPositions.get(2).mediaItem).isNull(); @@ -10815,7 +10834,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 2 * C.DEFAULT_SEEK_BACK_INCREMENT_MS); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 2 * C.DEFAULT_SEEK_BACK_INCREMENT_MS); player.seekBack(); ArgumentCaptor oldPosition = @@ -10862,7 +10881,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ C.DEFAULT_SEEK_BACK_INCREMENT_MS / 2); + player, /* mediaItemIndex= */ 0, /* positionMs= */ C.DEFAULT_SEEK_BACK_INCREMENT_MS / 2); player.seekBack(); assertThat(player.getCurrentPosition()).isEqualTo(0); @@ -10933,11 +10952,11 @@ public final class ExoPlayerTest { public void seekToPrevious_withPreviousWindowAndCloseToStart_seeksToPreviousWindow() { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); - player.seekTo(/* windowIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS); + player.seekTo(/* mediaItemIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS); player.seekToPrevious(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); assertThat(player.getCurrentPosition()).isEqualTo(0); player.release(); @@ -10947,11 +10966,11 @@ public final class ExoPlayerTest { public void seekToPrevious_notCloseToStart_seeksToZero() { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); - player.seekTo(/* windowIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS + 1); + player.seekTo(/* mediaItemIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS + 1); player.seekToPrevious(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); assertThat(player.getCurrentPosition()).isEqualTo(0); player.release(); @@ -10964,7 +10983,7 @@ public final class ExoPlayerTest { player.seekToNext(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); assertThat(player.getCurrentPosition()).isEqualTo(0); player.release(); @@ -10994,7 +11013,7 @@ public final class ExoPlayerTest { player.seekTo(/* positionMs = */ 0); player.seekToNext(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); assertThat(player.getCurrentPosition()).isEqualTo(500); player.release(); @@ -11009,7 +11028,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.stop(); verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt()); @@ -11027,7 +11046,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.stop(/* reset= */ true); ArgumentCaptor oldPosition = @@ -11071,13 +11090,13 @@ public final class ExoPlayerTest { player.setMediaSource(mediaSource); player.prepare(); - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 2000); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 2122); + TestPlayerRunHelper.playUntilPosition(player, /* mediaItemIndex= */ 1, /* positionMs= */ 2000); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 2122); // This causes a DISCONTINUITY_REASON_REMOVE between pending operations that needs to be // cancelled by the seek below. mediaSource.setNewSourceInfo(timeline2); player.play(); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 2222); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 2222); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); ArgumentCaptor oldPosition = @@ -11176,7 +11195,7 @@ public final class ExoPlayerTest { .send(); player.setMediaSource(mediaSource); player.prepare(); - playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 40_000); + playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 40_000); player.release(); // Assert that the renderer hasn't been reset despite the inserted ad group. @@ -11215,7 +11234,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.stop(); shadowOf(Looper.getMainLooper()).idle(); @@ -11360,18 +11379,18 @@ public final class ExoPlayerTest { private static final class PositionGrabbingMessageTarget extends PlayerTarget { - public int windowIndex; + public int mediaItemIndex; public long positionMs; public int messageCount; public PositionGrabbingMessageTarget() { - windowIndex = C.INDEX_UNSET; + mediaItemIndex = C.INDEX_UNSET; positionMs = C.POSITION_UNSET; } @Override public void handleMessage(ExoPlayer player, int messageType, @Nullable Object message) { - windowIndex = player.getCurrentWindowIndex(); + mediaItemIndex = player.getCurrentMediaItemIndex(); positionMs = player.getCurrentPosition(); messageCount++; } 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 6adfe54b4f..a7838377bf 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 @@ -950,7 +950,7 @@ public class PlayerControlView extends FrameLayout { int adGroupCount = 0; Timeline timeline = player.getCurrentTimeline(); if (!timeline.isEmpty()) { - int currentWindowIndex = player.getCurrentWindowIndex(); + int currentWindowIndex = player.getCurrentMediaItemIndex(); int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex; int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex; for (int i = firstWindowIndex; i <= lastWindowIndex; i++) { @@ -1110,7 +1110,7 @@ public class PlayerControlView extends FrameLayout { windowIndex++; } } else { - windowIndex = player.getCurrentWindowIndex(); + windowIndex = player.getCurrentMediaItemIndex(); } seekTo(player, windowIndex, positionMs); updateProgress(); @@ -1228,7 +1228,7 @@ public class PlayerControlView extends FrameLayout { if (state == Player.STATE_IDLE) { player.prepare(); } else if (state == Player.STATE_ENDED) { - seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); + seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); } player.play(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 26075126d0..682483cd4e 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -605,9 +605,9 @@ public class PlayerNotificationManager { public static final String ACTION_PLAY = "com.google.android.exoplayer.play"; /** The action which pauses playback. */ public static final String ACTION_PAUSE = "com.google.android.exoplayer.pause"; - /** The action which skips to the previous window. */ + /** The action which skips to the previous media item. */ public static final String ACTION_PREVIOUS = "com.google.android.exoplayer.prev"; - /** The action which skips to the next window. */ + /** The action which skips to the next media item. */ public static final String ACTION_NEXT = "com.google.android.exoplayer.next"; /** The action which fast forwards. */ public static final String ACTION_FAST_FORWARD = "com.google.android.exoplayer.ffwd"; @@ -1095,7 +1095,7 @@ public class PlayerNotificationManager { * *
      *
    • The media is {@link Player#isPlaying() actively playing}. - *
    • The media is not {@link Player#isCurrentWindowDynamic() dynamically changing its + *
    • The media is not {@link Player#isCurrentMediaItemDynamic() dynamically changing its * duration} (like for example a live stream). *
    • The media is not {@link Player#isPlayingAd() interrupted by an ad}. *
    • The media is played at {@link Player#getPlaybackParameters() regular speed}. @@ -1253,7 +1253,7 @@ public class PlayerNotificationManager { && useChronometer && player.isPlaying() && !player.isPlayingAd() - && !player.isCurrentWindowDynamic() + && !player.isCurrentMediaItemDynamic() && player.getPlaybackParameters().speed == 1f) { builder .setWhen(System.currentTimeMillis() - player.getContentPosition()) @@ -1531,7 +1531,7 @@ public class PlayerNotificationManager { if (player.getPlaybackState() == Player.STATE_IDLE) { player.prepare(); } else if (player.getPlaybackState() == Player.STATE_ENDED) { - player.seekToDefaultPosition(player.getCurrentWindowIndex()); + player.seekToDefaultPosition(player.getCurrentMediaItemIndex()); } player.play(); } else if (ACTION_PAUSE.equals(action)) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index 4836774bb2..30533dcd19 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -1501,8 +1501,8 @@ public class PlayerView extends FrameLayout implements AdViewProvider { if (lastPeriodIndexWithTracks != C.INDEX_UNSET) { int lastWindowIndexWithTracks = timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex; - if (player.getCurrentWindowIndex() == lastWindowIndexWithTracks) { - // We're in the same window. Suppress the update. + if (player.getCurrentMediaItemIndex() == lastWindowIndexWithTracks) { + // We're in the same media item. Suppress the update. return; } } 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 279e907476..c6e0aadad0 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 @@ -1269,7 +1269,7 @@ public class StyledPlayerControlView extends FrameLayout { int adGroupCount = 0; Timeline timeline = player.getCurrentTimeline(); if (!timeline.isEmpty()) { - int currentWindowIndex = player.getCurrentWindowIndex(); + int currentWindowIndex = player.getCurrentMediaItemIndex(); int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex; int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex; for (int i = firstWindowIndex; i <= lastWindowIndex; i++) { @@ -1453,7 +1453,7 @@ public class StyledPlayerControlView extends FrameLayout { windowIndex++; } } else { - windowIndex = player.getCurrentWindowIndex(); + windowIndex = player.getCurrentMediaItemIndex(); } seekTo(player, windowIndex, positionMs); updateProgress(); @@ -1616,13 +1616,12 @@ public class StyledPlayerControlView extends FrameLayout { } } - @SuppressWarnings("deprecation") private void dispatchPlay(Player player) { @State int state = player.getPlaybackState(); if (state == Player.STATE_IDLE) { player.prepare(); } else if (state == Player.STATE_ENDED) { - seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); + seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); } player.play(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index 4434aef516..b8907094fd 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -1540,8 +1540,8 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { if (lastPeriodIndexWithTracks != C.INDEX_UNSET) { int lastWindowIndexWithTracks = timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex; - if (player.getCurrentWindowIndex() == lastWindowIndexWithTracks) { - // We're in the same window. Suppress the update. + if (player.getCurrentMediaItemIndex() == lastWindowIndexWithTracks) { + // We're in the same media item. Suppress the update. return; } } diff --git a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java index efcb290579..58ed22f289 100644 --- a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java +++ b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java @@ -284,12 +284,12 @@ public class TestPlayerRunHelper { *

      If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}. * * @param player The {@link Player}. - * @param windowIndex The window. - * @param positionMs The position within the window, in milliseconds. + * @param mediaItemIndex The index of the media item. + * @param positionMs The position within the media item, in milliseconds. * @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is * exceeded. */ - public static void playUntilPosition(ExoPlayer player, int windowIndex, long positionMs) + public static void playUntilPosition(ExoPlayer player, int mediaItemIndex, long positionMs) throws TimeoutException { verifyMainTestThread(player); Looper applicationLooper = Util.getCurrentOrMainLooper(); @@ -315,7 +315,7 @@ public class TestPlayerRunHelper { // Ignore. } }) - .setPosition(windowIndex, positionMs) + .setPosition(mediaItemIndex, positionMs) .send(); player.play(); runMainLooperUntil(() -> messageHandled.get() || player.getPlayerError() != null); @@ -326,18 +326,19 @@ public class TestPlayerRunHelper { /** * Calls {@link Player#play()}, runs tasks of the main {@link Looper} until the {@code player} - * reaches the specified window or a playback error occurs, and then pauses the {@code player}. + * reaches the specified media item or a playback error occurs, and then pauses the {@code + * player}. * *

      If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}. * * @param player The {@link Player}. - * @param windowIndex The window. + * @param mediaItemIndex The index of the media item. * @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is * exceeded. */ - public static void playUntilStartOfWindow(ExoPlayer player, int windowIndex) + public static void playUntilStartOfMediaItem(ExoPlayer player, int mediaItemIndex) throws TimeoutException { - playUntilPosition(player, windowIndex, /* positionMs= */ 0); + playUntilPosition(player, mediaItemIndex, /* positionMs= */ 0); } /** diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 43d5162bd3..b5cdf77d5c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -121,7 +121,7 @@ public abstract class Action { /** Calls {@link Player#seekTo(long)} or {@link Player#seekTo(int, long)}. */ public static final class Seek extends Action { - @Nullable private final Integer windowIndex; + @Nullable private final Integer mediaItemIndex; private final long positionMs; private final boolean catchIllegalSeekException; @@ -133,7 +133,7 @@ public abstract class Action { */ public Seek(String tag, long positionMs) { super(tag, "Seek:" + positionMs); - this.windowIndex = null; + this.mediaItemIndex = null; this.positionMs = positionMs; catchIllegalSeekException = false; } @@ -142,14 +142,15 @@ public abstract class Action { * Action calls {@link Player#seekTo(int, long)}. * * @param tag A tag to use for logging. - * @param windowIndex The window to seek to. + * @param mediaItemIndex The media item to seek to. * @param positionMs The seek position. * @param catchIllegalSeekException Whether {@link IllegalSeekPositionException} should be * silently caught or not. */ - public Seek(String tag, int windowIndex, long positionMs, boolean catchIllegalSeekException) { + public Seek( + String tag, int mediaItemIndex, long positionMs, boolean catchIllegalSeekException) { super(tag, "Seek:" + positionMs); - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; this.catchIllegalSeekException = catchIllegalSeekException; } @@ -158,10 +159,10 @@ public abstract class Action { protected void doActionImpl( ExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) { try { - if (windowIndex == null) { + if (mediaItemIndex == null) { player.seekTo(positionMs); } else { - player.seekTo(windowIndex, positionMs); + player.seekTo(mediaItemIndex, positionMs); } } catch (IllegalSeekPositionException e) { if (!catchIllegalSeekException) { @@ -174,20 +175,20 @@ public abstract class Action { /** Calls {@link ExoPlayer#setMediaSources(List, int, long)}. */ public static final class SetMediaItems extends Action { - private final int windowIndex; + private final int mediaItemIndex; private final long positionMs; private final MediaSource[] mediaSources; /** * @param tag A tag to use for logging. - * @param windowIndex The window index to start playback from. + * @param mediaItemIndex The media item index to start playback from. * @param positionMs The position in milliseconds to start playback from. * @param mediaSources The media sources to populate the playlist with. */ public SetMediaItems( - String tag, int windowIndex, long positionMs, MediaSource... mediaSources) { + String tag, int mediaItemIndex, long positionMs, MediaSource... mediaSources) { super(tag, "SetMediaItems"); - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; this.mediaSources = mediaSources; } @@ -195,7 +196,7 @@ public abstract class Action { @Override protected void doActionImpl( ExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) { - player.setMediaSources(Arrays.asList(mediaSources), windowIndex, positionMs); + player.setMediaSources(Arrays.asList(mediaSources), mediaItemIndex, positionMs); } } @@ -553,7 +554,7 @@ public abstract class Action { public static final class SendMessages extends Action { private final Target target; - private final int windowIndex; + private final int mediaItemIndex; private final long positionMs; private final boolean deleteAfterDelivery; @@ -566,7 +567,7 @@ public abstract class Action { this( tag, target, - /* windowIndex= */ C.INDEX_UNSET, + /* mediaItemIndex= */ C.INDEX_UNSET, positionMs, /* deleteAfterDelivery= */ true); } @@ -574,16 +575,20 @@ public abstract class Action { /** * @param tag A tag to use for logging. * @param target A message target. - * @param windowIndex The window index at which the message should be sent, or {@link - * C#INDEX_UNSET} for the current window. + * @param mediaItemIndex The media item index at which the message should be sent, or {@link + * C#INDEX_UNSET} for the current media item. * @param positionMs The position at which the message should be sent, in milliseconds. * @param deleteAfterDelivery Whether the message will be deleted after delivery. */ public SendMessages( - String tag, Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) { + String tag, + Target target, + int mediaItemIndex, + long positionMs, + boolean deleteAfterDelivery) { super(tag, "SendMessages"); this.target = target; - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; this.deleteAfterDelivery = deleteAfterDelivery; } @@ -595,8 +600,8 @@ public abstract class Action { ((PlayerTarget) target).setPlayer(player); } PlayerMessage message = player.createMessage(target); - if (windowIndex != C.INDEX_UNSET) { - message.setPosition(windowIndex, positionMs); + if (mediaItemIndex != C.INDEX_UNSET) { + message.setPosition(mediaItemIndex, positionMs); } else { message.setPosition(positionMs); } @@ -661,17 +666,17 @@ public abstract class Action { */ public static final class PlayUntilPosition extends Action { - private final int windowIndex; + private final int mediaItemIndex; private final long positionMs; /** * @param tag A tag to use for logging. - * @param windowIndex The window index at which the player should be paused again. - * @param positionMs The position in that window at which the player should be paused again. + * @param mediaItemIndex The media item index at which the player should be paused again. + * @param positionMs The position in that media item at which the player should be paused again. */ - public PlayUntilPosition(String tag, int windowIndex, long positionMs) { - super(tag, "PlayUntilPosition:" + windowIndex + ":" + positionMs); - this.windowIndex = windowIndex; + public PlayUntilPosition(String tag, int mediaItemIndex, long positionMs) { + super(tag, "PlayUntilPosition:" + mediaItemIndex + ":" + positionMs); + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; } @@ -704,7 +709,7 @@ public abstract class Action { // Ignore. } }) - .setPosition(windowIndex, positionMs) + .setPosition(mediaItemIndex, positionMs) .send(); if (nextAction != null) { // Schedule another message on this test thread to continue action schedule. @@ -712,7 +717,7 @@ public abstract class Action { .createMessage( (messageType, payload) -> nextAction.schedule(player, trackSelector, surface, handler)) - .setPosition(windowIndex, positionMs) + .setPosition(mediaItemIndex, positionMs) .setLooper(applicationLooper) .send(); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 4590d5fd6b..0282e57a8c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -161,24 +161,25 @@ public final class ActionSchedule { /** * Schedules a seek action. * - * @param windowIndex The window to seek to. + * @param mediaItemIndex The media item to seek to. * @param positionMs The seek position. * @return The builder, for convenience. */ - public Builder seek(int windowIndex, long positionMs) { - return apply(new Seek(tag, windowIndex, positionMs, /* catchIllegalSeekException= */ false)); + public Builder seek(int mediaItemIndex, long positionMs) { + return apply( + new Seek(tag, mediaItemIndex, positionMs, /* catchIllegalSeekException= */ false)); } /** * Schedules a seek action to be executed. * - * @param windowIndex The window to seek to. + * @param mediaItemIndex The media item to seek to. * @param positionMs The seek position. * @param catchIllegalSeekException Whether an illegal seek position should be caught or not. * @return The builder, for convenience. */ - public Builder seek(int windowIndex, long positionMs, boolean catchIllegalSeekException) { - return apply(new Seek(tag, windowIndex, positionMs, catchIllegalSeekException)); + public Builder seek(int mediaItemIndex, long positionMs, boolean catchIllegalSeekException) { + return apply(new Seek(tag, mediaItemIndex, positionMs, catchIllegalSeekException)); } /** @@ -247,23 +248,23 @@ public final class ActionSchedule { * Schedules a play action, waits until the player reaches the specified position, and pauses * the player again. * - * @param windowIndex The window index at which the player should be paused again. - * @param positionMs The position in that window at which the player should be paused again. + * @param mediaItemIndex The media item index at which the player should be paused again. + * @param positionMs The position in that media item at which the player should be paused again. * @return The builder, for convenience. */ - public Builder playUntilPosition(int windowIndex, long positionMs) { - return apply(new PlayUntilPosition(tag, windowIndex, positionMs)); + public Builder playUntilPosition(int mediaItemIndex, long positionMs) { + return apply(new PlayUntilPosition(tag, mediaItemIndex, positionMs)); } /** - * Schedules a play action, waits until the player reaches the start of the specified window, - * and pauses the player again. + * Schedules a play action, waits until the player reaches the start of the specified media + * item, and pauses the player again. * - * @param windowIndex The window index at which the player should be paused again. + * @param mediaItemIndex The media item index at which the player should be paused again. * @return The builder, for convenience. */ - public Builder playUntilStartOfWindow(int windowIndex) { - return apply(new PlayUntilPosition(tag, windowIndex, /* positionMs= */ 0)); + public Builder playUntilStartOfMediaItem(int mediaItemIndex) { + return apply(new PlayUntilPosition(tag, mediaItemIndex, /* positionMs= */ 0)); } /** @@ -323,16 +324,16 @@ public final class ActionSchedule { /** * Schedules a set media items action to be executed. * - * @param windowIndex The window index to start playback from or {@link C#INDEX_UNSET} if the - * playback position should not be reset. + * @param mediaItemIndex The media item index to start playback from or {@link C#INDEX_UNSET} if + * the playback position should not be reset. * @param positionMs The position in milliseconds from where playback should start. If {@link - * C#TIME_UNSET} is passed the default position is used. In any case, if {@code windowIndex} - * is set to {@link C#INDEX_UNSET} the position is not reset at all and this parameter is - * ignored. + * C#TIME_UNSET} is passed the default position is used. In any case, if {@code + * mediaItemIndex} is set to {@link C#INDEX_UNSET} the position is not reset at all and this + * parameter is ignored. * @return The builder, for convenience. */ - public Builder setMediaSources(int windowIndex, long positionMs, MediaSource... sources) { - return apply(new Action.SetMediaItems(tag, windowIndex, positionMs, sources)); + public Builder setMediaSources(int mediaItemIndex, long positionMs, MediaSource... sources) { + return apply(new Action.SetMediaItems(tag, mediaItemIndex, positionMs, sources)); } /** @@ -354,7 +355,10 @@ public final class ActionSchedule { public Builder setMediaSources(MediaSource... mediaSources) { return apply( new Action.SetMediaItems( - tag, /* windowIndex= */ C.INDEX_UNSET, /* positionMs= */ C.TIME_UNSET, mediaSources)); + tag, + /* mediaItemIndex= */ C.INDEX_UNSET, + /* positionMs= */ C.TIME_UNSET, + mediaSources)); } /** * Schedules a add media items action to be executed. @@ -447,8 +451,8 @@ public final class ActionSchedule { /** * Schedules sending a {@link PlayerMessage}. * - * @param positionMs The position in the current window at which the message should be sent, in - * milliseconds. + * @param positionMs The position in the current media item at which the message should be sent, + * in milliseconds. * @return The builder, for convenience. */ public Builder sendMessage(Target target, long positionMs) { @@ -459,27 +463,28 @@ public final class ActionSchedule { * Schedules sending a {@link PlayerMessage}. * * @param target A message target. - * @param windowIndex The window index at which the message should be sent. + * @param mediaItemIndex The media item index at which the message should be sent. * @param positionMs The position at which the message should be sent, in milliseconds. * @return The builder, for convenience. */ - public Builder sendMessage(Target target, int windowIndex, long positionMs) { + public Builder sendMessage(Target target, int mediaItemIndex, long positionMs) { return apply( - new SendMessages(tag, target, windowIndex, positionMs, /* deleteAfterDelivery= */ true)); + new SendMessages( + tag, target, mediaItemIndex, positionMs, /* deleteAfterDelivery= */ true)); } /** * Schedules to send a {@link PlayerMessage}. * * @param target A message target. - * @param windowIndex The window index at which the message should be sent. + * @param mediaItemIndex The media item index at which the message should be sent. * @param positionMs The position at which the message should be sent, in milliseconds. * @param deleteAfterDelivery Whether the message will be deleted after delivery. * @return The builder, for convenience. */ public Builder sendMessage( - Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) { - return apply(new SendMessages(tag, target, windowIndex, positionMs, deleteAfterDelivery)); + Target target, int mediaItemIndex, long positionMs, boolean deleteAfterDelivery) { + return apply(new SendMessages(tag, target, mediaItemIndex, positionMs, deleteAfterDelivery)); } /** diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java index 6df171442d..1a305f0e35 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -85,7 +85,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul private AnalyticsListener analyticsListener; private Integer expectedPlayerEndedCount; private boolean pauseAtEndOfMediaItems; - private int initialWindowIndex; + private int initialMediaItemIndex; private long initialPositionMs; private boolean skipSettingMediaSources; @@ -93,7 +93,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul testPlayerBuilder = new TestExoPlayerBuilder(context); mediaSources = new ArrayList<>(); supportedFormats = new Format[] {VIDEO_FORMAT}; - initialWindowIndex = C.INDEX_UNSET; + initialMediaItemIndex = C.INDEX_UNSET; initialPositionMs = C.TIME_UNSET; } @@ -133,12 +133,12 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul /** * Seeks before setting the media sources and preparing the player. * - * @param windowIndex The window index to seek to. + * @param mediaItemIndex The media item index to seek to. * @param positionMs The position in milliseconds to seek to. * @return This builder. */ - public Builder initialSeek(int windowIndex, long positionMs) { - this.initialWindowIndex = windowIndex; + public Builder initialSeek(int mediaItemIndex, long positionMs) { + this.initialMediaItemIndex = mediaItemIndex; this.initialPositionMs = positionMs; return this; } @@ -343,7 +343,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul testPlayerBuilder, mediaSources, skipSettingMediaSources, - initialWindowIndex, + initialMediaItemIndex, initialPositionMs, surface, actionSchedule, @@ -357,7 +357,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul private final TestExoPlayerBuilder playerBuilder; private final List mediaSources; private final boolean skipSettingMediaSources; - private final int initialWindowIndex; + private final int initialMediaItemIndex; private final long initialPositionMs; @Nullable private final Surface surface; @Nullable private final ActionSchedule actionSchedule; @@ -386,7 +386,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul TestExoPlayerBuilder playerBuilder, List mediaSources, boolean skipSettingMediaSources, - int initialWindowIndex, + int initialMediaItemIndex, long initialPositionMs, @Nullable Surface surface, @Nullable ActionSchedule actionSchedule, @@ -397,7 +397,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul this.playerBuilder = playerBuilder; this.mediaSources = mediaSources; this.skipSettingMediaSources = skipSettingMediaSources; - this.initialWindowIndex = initialWindowIndex; + this.initialMediaItemIndex = initialMediaItemIndex; this.initialPositionMs = initialPositionMs; this.surface = surface; this.actionSchedule = actionSchedule; @@ -466,8 +466,8 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul handler, /* callback= */ ExoPlayerTestRunner.this); } - if (initialWindowIndex != C.INDEX_UNSET) { - player.seekTo(initialWindowIndex, initialPositionMs); + if (initialMediaItemIndex != C.INDEX_UNSET) { + player.seekTo(initialMediaItemIndex, initialPositionMs); } if (!skipSettingMediaSources) { player.setMediaSources(mediaSources, /* resetPosition= */ false); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index c0acae1da6..136327739f 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -162,7 +162,7 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer { @Override public void setMediaSources( - List mediaSources, int startWindowIndex, long startPositionMs) { + List mediaSources, int startMediaItemIndex, long startPositionMs) { throw new UnsupportedOperationException(); } From ee4af48a10084c0aca95dd2ed4b23a39187f62c0 Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 2 Nov 2021 10:49:18 +0000 Subject: [PATCH 086/113] Replace map with a switch statement in bandwidth meter implementations #minor-release PiperOrigin-RevId: 407042882 --- .../upstream/DefaultBandwidthMeter.java | 713 +++++++++++------- 1 file changed, 438 insertions(+), 275 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 07b3185b43..2db19fb6a7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -26,10 +26,8 @@ import com.google.android.exoplayer2.util.NetworkTypeObserver; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -42,13 +40,6 @@ import java.util.Map; */ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener { - /** - * Country groups used to determine the default initial bitrate estimate. The group assignment for - * each country is a list for [Wifi, 2G, 3G, 4G, 5G_NSA, 5G_SA]. - */ - public static final ImmutableListMultimap - DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = createInitialBitrateCountryGroupAssignment(); - /** Default initial Wifi bitrate estimate in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = ImmutableList.of(5_400_000L, 3_300_000L, 2_000_000L, 1_300_000L, 760_000L); @@ -82,17 +73,35 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /** Default maximum weight for the sliding window. */ public static final int DEFAULT_SLIDING_WINDOW_MAX_WEIGHT = 2000; - /** Index for the Wifi group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the Wifi group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_WIFI = 0; - /** Index for the 2G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 2G group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_2G = 1; - /** Index for the 3G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 3G group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_3G = 2; - /** Index for the 4G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 4G group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_4G = 3; - /** Index for the 5G-NSA group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 5G-NSA group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_5G_NSA = 4; - /** Index for the 5G-SA group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 5G-SA group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_5G_SA = 5; @Nullable private static DefaultBandwidthMeter singletonInstance; @@ -212,40 +221,33 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } private static Map getInitialBitrateEstimatesForCountry(String countryCode) { - List groupIndices = getCountryGroupIndices(countryCode); + int[] groupIndices = getInitialBitrateCountryGroupAssignment(countryCode); Map result = new HashMap<>(/* initialCapacity= */ 8); result.put(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); result.put( C.NETWORK_TYPE_WIFI, - DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices.get(COUNTRY_GROUP_INDEX_WIFI))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices[COUNTRY_GROUP_INDEX_WIFI])); result.put( C.NETWORK_TYPE_2G, - DEFAULT_INITIAL_BITRATE_ESTIMATES_2G.get(groupIndices.get(COUNTRY_GROUP_INDEX_2G))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_2G.get(groupIndices[COUNTRY_GROUP_INDEX_2G])); result.put( C.NETWORK_TYPE_3G, - DEFAULT_INITIAL_BITRATE_ESTIMATES_3G.get(groupIndices.get(COUNTRY_GROUP_INDEX_3G))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_3G.get(groupIndices[COUNTRY_GROUP_INDEX_3G])); result.put( C.NETWORK_TYPE_4G, - DEFAULT_INITIAL_BITRATE_ESTIMATES_4G.get(groupIndices.get(COUNTRY_GROUP_INDEX_4G))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_4G.get(groupIndices[COUNTRY_GROUP_INDEX_4G])); result.put( C.NETWORK_TYPE_5G_NSA, - DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA.get( - groupIndices.get(COUNTRY_GROUP_INDEX_5G_NSA))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA.get(groupIndices[COUNTRY_GROUP_INDEX_5G_NSA])); result.put( C.NETWORK_TYPE_5G_SA, - DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA.get(groupIndices.get(COUNTRY_GROUP_INDEX_5G_SA))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA.get(groupIndices[COUNTRY_GROUP_INDEX_5G_SA])); // Assume default Wifi speed for Ethernet to prevent using the slower fallback. result.put( C.NETWORK_TYPE_ETHERNET, - DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices.get(COUNTRY_GROUP_INDEX_WIFI))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices[COUNTRY_GROUP_INDEX_WIFI])); return result; } - - private static ImmutableList getCountryGroupIndices(String countryCode) { - ImmutableList groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); - // Assume median group if not found. - return groupIndices.isEmpty() ? ImmutableList.of(2, 2, 2, 2, 2, 2) : groupIndices; - } } /** @@ -461,250 +463,411 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList return isNetwork && !dataSpec.isFlagSet(DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED); } - private static ImmutableListMultimap - createInitialBitrateCountryGroupAssignment() { - return ImmutableListMultimap.builder() - .putAll("AD", 1, 2, 0, 0, 2, 2) - .putAll("AE", 1, 4, 4, 4, 3, 2) - .putAll("AF", 4, 4, 4, 4, 2, 2) - .putAll("AG", 2, 3, 1, 2, 2, 2) - .putAll("AI", 1, 2, 2, 2, 2, 2) - .putAll("AL", 1, 2, 0, 1, 2, 2) - .putAll("AM", 2, 3, 2, 4, 2, 2) - .putAll("AO", 3, 4, 3, 2, 2, 2) - .putAll("AQ", 4, 2, 2, 2, 2, 2) - .putAll("AR", 2, 4, 1, 1, 2, 2) - .putAll("AS", 2, 2, 2, 3, 2, 2) - .putAll("AT", 0, 0, 0, 0, 0, 2) - .putAll("AU", 0, 1, 0, 1, 2, 2) - .putAll("AW", 1, 2, 4, 4, 2, 2) - .putAll("AX", 0, 2, 2, 2, 2, 2) - .putAll("AZ", 3, 2, 4, 4, 2, 2) - .putAll("BA", 1, 2, 0, 1, 2, 2) - .putAll("BB", 0, 2, 0, 0, 2, 2) - .putAll("BD", 2, 1, 3, 3, 2, 2) - .putAll("BE", 0, 0, 3, 3, 2, 2) - .putAll("BF", 4, 3, 4, 3, 2, 2) - .putAll("BG", 0, 0, 0, 0, 1, 2) - .putAll("BH", 1, 2, 2, 4, 4, 2) - .putAll("BI", 4, 3, 4, 4, 2, 2) - .putAll("BJ", 4, 4, 3, 4, 2, 2) - .putAll("BL", 1, 2, 2, 2, 2, 2) - .putAll("BM", 1, 2, 0, 0, 2, 2) - .putAll("BN", 3, 2, 1, 1, 2, 2) - .putAll("BO", 1, 3, 3, 2, 2, 2) - .putAll("BQ", 1, 2, 2, 0, 2, 2) - .putAll("BR", 2, 3, 2, 2, 2, 2) - .putAll("BS", 4, 2, 2, 3, 2, 2) - .putAll("BT", 3, 1, 3, 2, 2, 2) - .putAll("BW", 3, 4, 1, 0, 2, 2) - .putAll("BY", 0, 1, 1, 3, 2, 2) - .putAll("BZ", 2, 4, 2, 2, 2, 2) - .putAll("CA", 0, 2, 1, 2, 4, 1) - .putAll("CD", 4, 2, 3, 1, 2, 2) - .putAll("CF", 4, 2, 3, 2, 2, 2) - .putAll("CG", 2, 4, 3, 4, 2, 2) - .putAll("CH", 0, 0, 0, 0, 0, 2) - .putAll("CI", 3, 3, 3, 4, 2, 2) - .putAll("CK", 2, 2, 2, 1, 2, 2) - .putAll("CL", 1, 1, 2, 2, 3, 2) - .putAll("CM", 3, 4, 3, 2, 2, 2) - .putAll("CN", 2, 0, 2, 2, 3, 1) - .putAll("CO", 2, 2, 4, 2, 2, 2) - .putAll("CR", 2, 2, 4, 4, 2, 2) - .putAll("CU", 4, 4, 3, 2, 2, 2) - .putAll("CV", 2, 3, 1, 0, 2, 2) - .putAll("CW", 2, 2, 0, 0, 2, 2) - .putAll("CX", 1, 2, 2, 2, 2, 2) - .putAll("CY", 1, 0, 0, 0, 1, 2) - .putAll("CZ", 0, 0, 0, 0, 1, 2) - .putAll("DE", 0, 0, 2, 2, 1, 2) - .putAll("DJ", 4, 1, 4, 4, 2, 2) - .putAll("DK", 0, 0, 1, 0, 0, 2) - .putAll("DM", 1, 2, 2, 2, 2, 2) - .putAll("DO", 3, 4, 4, 4, 2, 2) - .putAll("DZ", 4, 3, 4, 4, 2, 2) - .putAll("EC", 2, 4, 2, 1, 2, 2) - .putAll("EE", 0, 0, 0, 0, 2, 2) - .putAll("EG", 3, 4, 2, 3, 2, 2) - .putAll("EH", 2, 2, 2, 2, 2, 2) - .putAll("ER", 4, 2, 2, 2, 2, 2) - .putAll("ES", 0, 1, 1, 1, 2, 2) - .putAll("ET", 4, 4, 3, 1, 2, 2) - .putAll("FI", 0, 0, 0, 1, 0, 2) - .putAll("FJ", 3, 1, 3, 3, 2, 2) - .putAll("FK", 3, 2, 2, 2, 2, 2) - .putAll("FM", 3, 2, 4, 2, 2, 2) - .putAll("FO", 0, 2, 0, 0, 2, 2) - .putAll("FR", 1, 1, 2, 1, 1, 1) - .putAll("GA", 2, 3, 1, 1, 2, 2) - .putAll("GB", 0, 0, 1, 1, 2, 3) - .putAll("GD", 1, 2, 2, 2, 2, 2) - .putAll("GE", 1, 1, 1, 3, 2, 2) - .putAll("GF", 2, 1, 2, 3, 2, 2) - .putAll("GG", 0, 2, 0, 0, 2, 2) - .putAll("GH", 3, 2, 3, 2, 2, 2) - .putAll("GI", 0, 2, 2, 2, 2, 2) - .putAll("GL", 1, 2, 0, 0, 2, 2) - .putAll("GM", 4, 2, 2, 4, 2, 2) - .putAll("GN", 4, 3, 4, 2, 2, 2) - .putAll("GP", 2, 1, 2, 3, 2, 2) - .putAll("GQ", 4, 2, 3, 4, 2, 2) - .putAll("GR", 1, 0, 0, 0, 2, 2) - .putAll("GT", 2, 3, 2, 1, 2, 2) - .putAll("GU", 1, 2, 4, 4, 2, 2) - .putAll("GW", 3, 4, 3, 3, 2, 2) - .putAll("GY", 3, 4, 1, 0, 2, 2) - .putAll("HK", 0, 1, 2, 3, 2, 0) - .putAll("HN", 3, 2, 3, 3, 2, 2) - .putAll("HR", 1, 0, 0, 0, 2, 2) - .putAll("HT", 4, 4, 4, 4, 2, 2) - .putAll("HU", 0, 0, 0, 1, 3, 2) - .putAll("ID", 3, 2, 3, 3, 3, 2) - .putAll("IE", 0, 1, 1, 1, 2, 2) - .putAll("IL", 1, 1, 2, 3, 4, 2) - .putAll("IM", 0, 2, 0, 1, 2, 2) - .putAll("IN", 1, 1, 3, 2, 4, 3) - .putAll("IO", 4, 2, 2, 2, 2, 2) - .putAll("IQ", 3, 3, 3, 3, 2, 2) - .putAll("IR", 3, 0, 1, 1, 3, 0) - .putAll("IS", 0, 0, 0, 0, 0, 2) - .putAll("IT", 0, 1, 0, 1, 1, 2) - .putAll("JE", 3, 2, 1, 2, 2, 2) - .putAll("JM", 3, 4, 4, 4, 2, 2) - .putAll("JO", 1, 0, 0, 1, 2, 2) - .putAll("JP", 0, 1, 0, 1, 1, 1) - .putAll("KE", 3, 3, 2, 2, 2, 2) - .putAll("KG", 2, 1, 1, 1, 2, 2) - .putAll("KH", 1, 1, 4, 2, 2, 2) - .putAll("KI", 4, 2, 4, 3, 2, 2) - .putAll("KM", 4, 2, 4, 3, 2, 2) - .putAll("KN", 2, 2, 2, 2, 2, 2) - .putAll("KP", 3, 2, 2, 2, 2, 2) - .putAll("KR", 0, 0, 1, 3, 4, 4) - .putAll("KW", 1, 1, 0, 0, 0, 2) - .putAll("KY", 1, 2, 0, 1, 2, 2) - .putAll("KZ", 1, 1, 2, 2, 2, 2) - .putAll("LA", 2, 2, 1, 2, 2, 2) - .putAll("LB", 3, 2, 1, 4, 2, 2) - .putAll("LC", 1, 2, 0, 0, 2, 2) - .putAll("LI", 0, 2, 2, 2, 2, 2) - .putAll("LK", 3, 1, 3, 4, 4, 2) - .putAll("LR", 3, 4, 4, 3, 2, 2) - .putAll("LS", 3, 3, 4, 3, 2, 2) - .putAll("LT", 0, 0, 0, 0, 2, 2) - .putAll("LU", 1, 0, 2, 2, 2, 2) - .putAll("LV", 0, 0, 0, 0, 2, 2) - .putAll("LY", 4, 2, 4, 3, 2, 2) - .putAll("MA", 3, 2, 2, 2, 2, 2) - .putAll("MC", 0, 2, 2, 0, 2, 2) - .putAll("MD", 1, 0, 0, 0, 2, 2) - .putAll("ME", 1, 0, 0, 1, 2, 2) - .putAll("MF", 1, 2, 1, 0, 2, 2) - .putAll("MG", 3, 4, 2, 2, 2, 2) - .putAll("MH", 3, 2, 2, 4, 2, 2) - .putAll("MK", 1, 0, 0, 0, 2, 2) - .putAll("ML", 4, 3, 3, 1, 2, 2) - .putAll("MM", 2, 4, 3, 3, 2, 2) - .putAll("MN", 2, 0, 1, 2, 2, 2) - .putAll("MO", 0, 2, 4, 4, 2, 2) - .putAll("MP", 0, 2, 2, 2, 2, 2) - .putAll("MQ", 2, 1, 2, 3, 2, 2) - .putAll("MR", 4, 1, 3, 4, 2, 2) - .putAll("MS", 1, 2, 2, 2, 2, 2) - .putAll("MT", 0, 0, 0, 0, 2, 2) - .putAll("MU", 3, 1, 1, 2, 2, 2) - .putAll("MV", 3, 4, 1, 4, 2, 2) - .putAll("MW", 4, 2, 1, 0, 2, 2) - .putAll("MX", 2, 4, 3, 4, 2, 2) - .putAll("MY", 2, 1, 3, 3, 2, 2) - .putAll("MZ", 3, 2, 2, 2, 2, 2) - .putAll("NA", 4, 3, 2, 2, 2, 2) - .putAll("NC", 3, 2, 4, 4, 2, 2) - .putAll("NE", 4, 4, 4, 4, 2, 2) - .putAll("NF", 2, 2, 2, 2, 2, 2) - .putAll("NG", 3, 4, 1, 1, 2, 2) - .putAll("NI", 2, 3, 4, 3, 2, 2) - .putAll("NL", 0, 0, 3, 2, 0, 4) - .putAll("NO", 0, 0, 2, 0, 0, 2) - .putAll("NP", 2, 1, 4, 3, 2, 2) - .putAll("NR", 3, 2, 2, 0, 2, 2) - .putAll("NU", 4, 2, 2, 2, 2, 2) - .putAll("NZ", 1, 0, 1, 2, 4, 2) - .putAll("OM", 2, 3, 1, 3, 4, 2) - .putAll("PA", 1, 3, 3, 3, 2, 2) - .putAll("PE", 2, 3, 4, 4, 4, 2) - .putAll("PF", 2, 3, 3, 1, 2, 2) - .putAll("PG", 4, 4, 3, 2, 2, 2) - .putAll("PH", 2, 2, 3, 3, 3, 2) - .putAll("PK", 3, 2, 3, 3, 2, 2) - .putAll("PL", 1, 1, 2, 2, 3, 2) - .putAll("PM", 0, 2, 2, 2, 2, 2) - .putAll("PR", 2, 3, 2, 2, 3, 3) - .putAll("PS", 3, 4, 1, 2, 2, 2) - .putAll("PT", 0, 1, 0, 0, 2, 2) - .putAll("PW", 2, 2, 4, 1, 2, 2) - .putAll("PY", 2, 2, 3, 2, 2, 2) - .putAll("QA", 2, 4, 2, 4, 4, 2) - .putAll("RE", 1, 1, 1, 2, 2, 2) - .putAll("RO", 0, 0, 1, 1, 1, 2) - .putAll("RS", 1, 0, 0, 0, 2, 2) - .putAll("RU", 0, 0, 0, 1, 2, 2) - .putAll("RW", 3, 4, 3, 0, 2, 2) - .putAll("SA", 2, 2, 1, 1, 2, 2) - .putAll("SB", 4, 2, 4, 3, 2, 2) - .putAll("SC", 4, 3, 0, 2, 2, 2) - .putAll("SD", 4, 4, 4, 4, 2, 2) - .putAll("SE", 0, 0, 0, 0, 0, 2) - .putAll("SG", 1, 1, 2, 3, 1, 4) - .putAll("SH", 4, 2, 2, 2, 2, 2) - .putAll("SI", 0, 0, 0, 0, 1, 2) - .putAll("SJ", 0, 2, 2, 2, 2, 2) - .putAll("SK", 0, 0, 0, 0, 0, 2) - .putAll("SL", 4, 3, 4, 1, 2, 2) - .putAll("SM", 0, 2, 2, 2, 2, 2) - .putAll("SN", 4, 4, 4, 4, 2, 2) - .putAll("SO", 3, 2, 3, 3, 2, 2) - .putAll("SR", 2, 3, 2, 2, 2, 2) - .putAll("SS", 4, 2, 2, 2, 2, 2) - .putAll("ST", 3, 2, 2, 2, 2, 2) - .putAll("SV", 2, 2, 3, 3, 2, 2) - .putAll("SX", 2, 2, 1, 0, 2, 2) - .putAll("SY", 4, 3, 4, 4, 2, 2) - .putAll("SZ", 4, 3, 2, 4, 2, 2) - .putAll("TC", 2, 2, 1, 0, 2, 2) - .putAll("TD", 4, 4, 4, 4, 2, 2) - .putAll("TG", 3, 3, 2, 0, 2, 2) - .putAll("TH", 0, 3, 2, 3, 3, 0) - .putAll("TJ", 4, 2, 4, 4, 2, 2) - .putAll("TL", 4, 3, 4, 4, 2, 2) - .putAll("TM", 4, 2, 4, 2, 2, 2) - .putAll("TN", 2, 2, 1, 1, 2, 2) - .putAll("TO", 4, 2, 3, 3, 2, 2) - .putAll("TR", 1, 1, 0, 1, 2, 2) - .putAll("TT", 1, 4, 1, 1, 2, 2) - .putAll("TV", 4, 2, 2, 2, 2, 2) - .putAll("TW", 0, 0, 0, 0, 0, 0) - .putAll("TZ", 3, 4, 3, 3, 2, 2) - .putAll("UA", 0, 3, 1, 1, 2, 2) - .putAll("UG", 3, 3, 3, 3, 2, 2) - .putAll("US", 1, 1, 2, 2, 3, 2) - .putAll("UY", 2, 2, 1, 2, 2, 2) - .putAll("UZ", 2, 2, 3, 4, 2, 2) - .putAll("VC", 1, 2, 2, 2, 2, 2) - .putAll("VE", 4, 4, 4, 4, 2, 2) - .putAll("VG", 2, 2, 1, 1, 2, 2) - .putAll("VI", 1, 2, 1, 3, 2, 2) - .putAll("VN", 0, 3, 3, 4, 2, 2) - .putAll("VU", 4, 2, 2, 1, 2, 2) - .putAll("WF", 4, 2, 2, 4, 2, 2) - .putAll("WS", 3, 1, 2, 1, 2, 2) - .putAll("XK", 1, 1, 1, 1, 2, 2) - .putAll("YE", 4, 4, 4, 4, 2, 2) - .putAll("YT", 4, 1, 1, 1, 2, 2) - .putAll("ZA", 3, 3, 1, 1, 1, 2) - .putAll("ZM", 3, 3, 4, 2, 2, 2) - .putAll("ZW", 3, 2, 4, 3, 2, 2) - .build(); + /** + * Returns initial bitrate group assignments for a {@code country}. The initial bitrate is a list + * of indexes for [Wifi, 2G, 3G, 4G, 5G_NSA, 5G_SA]. + */ + private static int[] getInitialBitrateCountryGroupAssignment(String country) { + switch (country) { + case "AE": + return new int[] {1, 4, 4, 4, 3, 2}; + case "AG": + return new int[] {2, 3, 1, 2, 2, 2}; + case "AM": + return new int[] {2, 3, 2, 4, 2, 2}; + case "AR": + return new int[] {2, 4, 1, 1, 2, 2}; + case "AS": + return new int[] {2, 2, 2, 3, 2, 2}; + case "AU": + return new int[] {0, 1, 0, 1, 2, 2}; + case "BE": + return new int[] {0, 0, 3, 3, 2, 2}; + case "BF": + return new int[] {4, 3, 4, 3, 2, 2}; + case "BH": + return new int[] {1, 2, 2, 4, 4, 2}; + case "BJ": + return new int[] {4, 4, 3, 4, 2, 2}; + case "BN": + return new int[] {3, 2, 1, 1, 2, 2}; + case "BO": + return new int[] {1, 3, 3, 2, 2, 2}; + case "BQ": + return new int[] {1, 2, 2, 0, 2, 2}; + case "BS": + return new int[] {4, 2, 2, 3, 2, 2}; + case "BT": + return new int[] {3, 1, 3, 2, 2, 2}; + case "BY": + return new int[] {0, 1, 1, 3, 2, 2}; + case "BZ": + return new int[] {2, 4, 2, 2, 2, 2}; + case "CA": + return new int[] {0, 2, 1, 2, 4, 1}; + case "CD": + return new int[] {4, 2, 3, 1, 2, 2}; + case "CF": + return new int[] {4, 2, 3, 2, 2, 2}; + case "CI": + return new int[] {3, 3, 3, 4, 2, 2}; + case "CK": + return new int[] {2, 2, 2, 1, 2, 2}; + case "AO": + case "CM": + return new int[] {3, 4, 3, 2, 2, 2}; + case "CN": + return new int[] {2, 0, 2, 2, 3, 1}; + case "CO": + return new int[] {2, 2, 4, 2, 2, 2}; + case "CR": + return new int[] {2, 2, 4, 4, 2, 2}; + case "CV": + return new int[] {2, 3, 1, 0, 2, 2}; + case "CW": + return new int[] {2, 2, 0, 0, 2, 2}; + case "CY": + return new int[] {1, 0, 0, 0, 1, 2}; + case "DE": + return new int[] {0, 0, 2, 2, 1, 2}; + case "DJ": + return new int[] {4, 1, 4, 4, 2, 2}; + case "DK": + return new int[] {0, 0, 1, 0, 0, 2}; + case "EC": + return new int[] {2, 4, 2, 1, 2, 2}; + case "EG": + return new int[] {3, 4, 2, 3, 2, 2}; + case "ET": + return new int[] {4, 4, 3, 1, 2, 2}; + case "FI": + return new int[] {0, 0, 0, 1, 0, 2}; + case "FJ": + return new int[] {3, 1, 3, 3, 2, 2}; + case "FM": + return new int[] {3, 2, 4, 2, 2, 2}; + case "FR": + return new int[] {1, 1, 2, 1, 1, 1}; + case "GA": + return new int[] {2, 3, 1, 1, 2, 2}; + case "GB": + return new int[] {0, 0, 1, 1, 2, 3}; + case "GE": + return new int[] {1, 1, 1, 3, 2, 2}; + case "BB": + case "FO": + case "GG": + return new int[] {0, 2, 0, 0, 2, 2}; + case "GH": + return new int[] {3, 2, 3, 2, 2, 2}; + case "GN": + return new int[] {4, 3, 4, 2, 2, 2}; + case "GQ": + return new int[] {4, 2, 3, 4, 2, 2}; + case "GT": + return new int[] {2, 3, 2, 1, 2, 2}; + case "AW": + case "GU": + return new int[] {1, 2, 4, 4, 2, 2}; + case "BW": + case "GY": + return new int[] {3, 4, 1, 0, 2, 2}; + case "HK": + return new int[] {0, 1, 2, 3, 2, 0}; + case "HU": + return new int[] {0, 0, 0, 1, 3, 2}; + case "ID": + return new int[] {3, 2, 3, 3, 3, 2}; + case "ES": + case "IE": + return new int[] {0, 1, 1, 1, 2, 2}; + case "IL": + return new int[] {1, 1, 2, 3, 4, 2}; + case "IM": + return new int[] {0, 2, 0, 1, 2, 2}; + case "IN": + return new int[] {1, 1, 3, 2, 4, 3}; + case "IR": + return new int[] {3, 0, 1, 1, 3, 0}; + case "IT": + return new int[] {0, 1, 0, 1, 1, 2}; + case "JE": + return new int[] {3, 2, 1, 2, 2, 2}; + case "DO": + case "JM": + return new int[] {3, 4, 4, 4, 2, 2}; + case "JP": + return new int[] {0, 1, 0, 1, 1, 1}; + case "KE": + return new int[] {3, 3, 2, 2, 2, 2}; + case "KG": + return new int[] {2, 1, 1, 1, 2, 2}; + case "KH": + return new int[] {1, 1, 4, 2, 2, 2}; + case "KR": + return new int[] {0, 0, 1, 3, 4, 4}; + case "KW": + return new int[] {1, 1, 0, 0, 0, 2}; + case "AL": + case "BA": + case "KY": + return new int[] {1, 2, 0, 1, 2, 2}; + case "KZ": + return new int[] {1, 1, 2, 2, 2, 2}; + case "LB": + return new int[] {3, 2, 1, 4, 2, 2}; + case "AD": + case "BM": + case "GL": + case "LC": + return new int[] {1, 2, 0, 0, 2, 2}; + case "LK": + return new int[] {3, 1, 3, 4, 4, 2}; + case "LR": + return new int[] {3, 4, 4, 3, 2, 2}; + case "LS": + return new int[] {3, 3, 4, 3, 2, 2}; + case "LU": + return new int[] {1, 0, 2, 2, 2, 2}; + case "MC": + return new int[] {0, 2, 2, 0, 2, 2}; + case "JO": + case "ME": + return new int[] {1, 0, 0, 1, 2, 2}; + case "MF": + return new int[] {1, 2, 1, 0, 2, 2}; + case "MG": + return new int[] {3, 4, 2, 2, 2, 2}; + case "MH": + return new int[] {3, 2, 2, 4, 2, 2}; + case "ML": + return new int[] {4, 3, 3, 1, 2, 2}; + case "MM": + return new int[] {2, 4, 3, 3, 2, 2}; + case "MN": + return new int[] {2, 0, 1, 2, 2, 2}; + case "MO": + return new int[] {0, 2, 4, 4, 2, 2}; + case "GF": + case "GP": + case "MQ": + return new int[] {2, 1, 2, 3, 2, 2}; + case "MR": + return new int[] {4, 1, 3, 4, 2, 2}; + case "EE": + case "LT": + case "LV": + case "MT": + return new int[] {0, 0, 0, 0, 2, 2}; + case "MU": + return new int[] {3, 1, 1, 2, 2, 2}; + case "MV": + return new int[] {3, 4, 1, 4, 2, 2}; + case "MW": + return new int[] {4, 2, 1, 0, 2, 2}; + case "CG": + case "MX": + return new int[] {2, 4, 3, 4, 2, 2}; + case "BD": + case "MY": + return new int[] {2, 1, 3, 3, 2, 2}; + case "NA": + return new int[] {4, 3, 2, 2, 2, 2}; + case "AZ": + case "NC": + return new int[] {3, 2, 4, 4, 2, 2}; + case "NG": + return new int[] {3, 4, 1, 1, 2, 2}; + case "NI": + return new int[] {2, 3, 4, 3, 2, 2}; + case "NL": + return new int[] {0, 0, 3, 2, 0, 4}; + case "NO": + return new int[] {0, 0, 2, 0, 0, 2}; + case "NP": + return new int[] {2, 1, 4, 3, 2, 2}; + case "NR": + return new int[] {3, 2, 2, 0, 2, 2}; + case "NZ": + return new int[] {1, 0, 1, 2, 4, 2}; + case "OM": + return new int[] {2, 3, 1, 3, 4, 2}; + case "PA": + return new int[] {1, 3, 3, 3, 2, 2}; + case "PE": + return new int[] {2, 3, 4, 4, 4, 2}; + case "PF": + return new int[] {2, 3, 3, 1, 2, 2}; + case "CU": + case "PG": + return new int[] {4, 4, 3, 2, 2, 2}; + case "PH": + return new int[] {2, 2, 3, 3, 3, 2}; + case "PR": + return new int[] {2, 3, 2, 2, 3, 3}; + case "PS": + return new int[] {3, 4, 1, 2, 2, 2}; + case "PT": + return new int[] {0, 1, 0, 0, 2, 2}; + case "PW": + return new int[] {2, 2, 4, 1, 2, 2}; + case "PY": + return new int[] {2, 2, 3, 2, 2, 2}; + case "QA": + return new int[] {2, 4, 2, 4, 4, 2}; + case "RE": + return new int[] {1, 1, 1, 2, 2, 2}; + case "RO": + return new int[] {0, 0, 1, 1, 1, 2}; + case "GR": + case "HR": + case "MD": + case "MK": + case "RS": + return new int[] {1, 0, 0, 0, 2, 2}; + case "RU": + return new int[] {0, 0, 0, 1, 2, 2}; + case "RW": + return new int[] {3, 4, 3, 0, 2, 2}; + case "KI": + case "KM": + case "LY": + case "SB": + return new int[] {4, 2, 4, 3, 2, 2}; + case "SC": + return new int[] {4, 3, 0, 2, 2, 2}; + case "SG": + return new int[] {1, 1, 2, 3, 1, 4}; + case "BG": + case "CZ": + case "SI": + return new int[] {0, 0, 0, 0, 1, 2}; + case "AT": + case "CH": + case "IS": + case "SE": + case "SK": + return new int[] {0, 0, 0, 0, 0, 2}; + case "SL": + return new int[] {4, 3, 4, 1, 2, 2}; + case "AX": + case "GI": + case "LI": + case "MP": + case "PM": + case "SJ": + case "SM": + return new int[] {0, 2, 2, 2, 2, 2}; + case "HN": + case "PK": + case "SO": + return new int[] {3, 2, 3, 3, 2, 2}; + case "BR": + case "SR": + return new int[] {2, 3, 2, 2, 2, 2}; + case "FK": + case "KP": + case "MA": + case "MZ": + case "ST": + return new int[] {3, 2, 2, 2, 2, 2}; + case "SV": + return new int[] {2, 2, 3, 3, 2, 2}; + case "SZ": + return new int[] {4, 3, 2, 4, 2, 2}; + case "SX": + case "TC": + return new int[] {2, 2, 1, 0, 2, 2}; + case "TG": + return new int[] {3, 3, 2, 0, 2, 2}; + case "TH": + return new int[] {0, 3, 2, 3, 3, 0}; + case "TJ": + return new int[] {4, 2, 4, 4, 2, 2}; + case "BI": + case "DZ": + case "SY": + case "TL": + return new int[] {4, 3, 4, 4, 2, 2}; + case "TM": + return new int[] {4, 2, 4, 2, 2, 2}; + case "TO": + return new int[] {4, 2, 3, 3, 2, 2}; + case "TR": + return new int[] {1, 1, 0, 1, 2, 2}; + case "TT": + return new int[] {1, 4, 1, 1, 2, 2}; + case "AQ": + case "ER": + case "IO": + case "NU": + case "SH": + case "SS": + case "TV": + return new int[] {4, 2, 2, 2, 2, 2}; + case "TW": + return new int[] {0, 0, 0, 0, 0, 0}; + case "GW": + case "TZ": + return new int[] {3, 4, 3, 3, 2, 2}; + case "UA": + return new int[] {0, 3, 1, 1, 2, 2}; + case "IQ": + case "UG": + return new int[] {3, 3, 3, 3, 2, 2}; + case "CL": + case "PL": + case "US": + return new int[] {1, 1, 2, 2, 3, 2}; + case "LA": + case "UY": + return new int[] {2, 2, 1, 2, 2, 2}; + case "UZ": + return new int[] {2, 2, 3, 4, 2, 2}; + case "AI": + case "BL": + case "CX": + case "DM": + case "GD": + case "MS": + case "VC": + return new int[] {1, 2, 2, 2, 2, 2}; + case "SA": + case "TN": + case "VG": + return new int[] {2, 2, 1, 1, 2, 2}; + case "VI": + return new int[] {1, 2, 1, 3, 2, 2}; + case "VN": + return new int[] {0, 3, 3, 4, 2, 2}; + case "VU": + return new int[] {4, 2, 2, 1, 2, 2}; + case "GM": + case "WF": + return new int[] {4, 2, 2, 4, 2, 2}; + case "WS": + return new int[] {3, 1, 2, 1, 2, 2}; + case "XK": + return new int[] {1, 1, 1, 1, 2, 2}; + case "AF": + case "HT": + case "NE": + case "SD": + case "SN": + case "TD": + case "VE": + case "YE": + return new int[] {4, 4, 4, 4, 2, 2}; + case "YT": + return new int[] {4, 1, 1, 1, 2, 2}; + case "ZA": + return new int[] {3, 3, 1, 1, 1, 2}; + case "ZM": + return new int[] {3, 3, 4, 2, 2, 2}; + case "ZW": + return new int[] {3, 2, 4, 3, 2, 2}; + default: + return new int[] {2, 2, 2, 2, 2, 2}; + } } } From 623be98d536c6c9c846a56aead8b2b23bedfff20 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Nov 2021 11:28:50 +0000 Subject: [PATCH 087/113] Suppress lint warning about IntDef assignment. The values returned by the framework method are equivalent to the local IntDef values. PiperOrigin-RevId: 407048748 --- .../com/google/android/exoplayer2/drm/FrameworkMediaDrm.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index e4ccaf1853..9cc9910443 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -182,6 +182,8 @@ public final class FrameworkMediaDrm implements ExoMediaDrm { mediaDrm.closeSession(sessionId); } + // Return values of MediaDrm.KeyRequest.getRequestType are equal to KeyRequest.RequestType. + @SuppressLint("WrongConstant") @Override public KeyRequest getKeyRequest( byte[] scope, From 16548420507b92255a775685c0237612321b0856 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 2 Nov 2021 10:27:56 +0000 Subject: [PATCH 088/113] Migrate usages of Window-based Player methods Where this introduced an inconsistency (e.g. assigning to something called `windowIndex`), I generally renamed the transitive closure of identifiers to maintain consistency (meaning this change is quite large). The exception is code that interacts with Timeline and Window directly, where sometimes I kept the 'window' nomenclature. #minor-release PiperOrigin-RevId: 407040052 --- RELEASENOTES.md | 4 + .../exoplayer2/castdemo/PlayerManager.java | 12 +- .../exoplayer2/demo/PlayerActivity.java | 16 +- .../exoplayer2/ext/cast/CastPlayer.java | 18 +- .../exoplayer2/ext/ima/FakeExoPlayer.java | 2 +- .../ext/leanback/LeanbackPlayerAdapter.java | 2 +- .../exoplayer2/ext/media2/PlayerWrapper.java | 16 +- .../mediasession/MediaSessionConnector.java | 29 +- .../mediasession/TimelineQueueNavigator.java | 38 +- .../google/android/exoplayer2/BasePlayer.java | 49 +- .../google/android/exoplayer2/ExoPlayer.java | 23 +- .../android/exoplayer2/ExoPlayerImpl.java | 43 +- .../exoplayer2/ExoPlayerImplInternal.java | 2 +- .../android/exoplayer2/PlayerMessage.java | 49 +- .../android/exoplayer2/SimpleExoPlayer.java | 6 +- .../analytics/AnalyticsCollector.java | 6 +- .../analytics/AnalyticsListener.java | 10 +- .../exoplayer2/util/DebugTextViewHelper.java | 4 +- .../android/exoplayer2/ExoPlayerTest.java | 1003 +++++++++-------- .../exoplayer2/ui/PlayerControlView.java | 6 +- .../ui/PlayerNotificationManager.java | 10 +- .../android/exoplayer2/ui/PlayerView.java | 4 +- .../ui/StyledPlayerControlView.java | 7 +- .../exoplayer2/ui/StyledPlayerView.java | 4 +- .../robolectric/TestPlayerRunHelper.java | 17 +- .../android/exoplayer2/testutil/Action.java | 61 +- .../exoplayer2/testutil/ActionSchedule.java | 67 +- .../testutil/ExoPlayerTestRunner.java | 22 +- .../exoplayer2/testutil/StubExoPlayer.java | 2 +- 29 files changed, 788 insertions(+), 744 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bf708bfa32..0d8bf3fdb5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -85,6 +85,10 @@ * RTMP extension: * Upgrade to `io.antmedia:rtmp_client`, which does not rely on `jcenter()` ([#9591](https://github.com/google/ExoPlayer/issues/9591)). +* MediaSession extension: + * Rename + `MediaSessionConnector.QueueNavigator#onCurrentWindowIndexChanged` to + `onCurrentMediaItemIndexChanged`. * Remove deprecated symbols: * Remove `Renderer.VIDEO_SCALING_MODE_*` constants. Use identically named constants in `C` instead. diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index 9e66c823a0..54174b0c53 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -261,7 +261,7 @@ import java.util.ArrayList; int playbackState = currentPlayer.getPlaybackState(); maybeSetCurrentItemAndNotify( playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED - ? currentPlayer.getCurrentWindowIndex() + ? currentPlayer.getCurrentMediaItemIndex() : C.INDEX_UNSET); } @@ -281,7 +281,7 @@ import java.util.ArrayList; // Player state management. long playbackPositionMs = C.TIME_UNSET; - int windowIndex = C.INDEX_UNSET; + int currentItemIndex = C.INDEX_UNSET; boolean playWhenReady = false; Player previousPlayer = this.currentPlayer; @@ -291,10 +291,10 @@ import java.util.ArrayList; if (playbackState != Player.STATE_ENDED) { playbackPositionMs = previousPlayer.getCurrentPosition(); playWhenReady = previousPlayer.getPlayWhenReady(); - windowIndex = previousPlayer.getCurrentWindowIndex(); - if (windowIndex != currentItemIndex) { + currentItemIndex = previousPlayer.getCurrentMediaItemIndex(); + if (currentItemIndex != this.currentItemIndex) { playbackPositionMs = C.TIME_UNSET; - windowIndex = currentItemIndex; + currentItemIndex = this.currentItemIndex; } } previousPlayer.stop(); @@ -304,7 +304,7 @@ import java.util.ArrayList; this.currentPlayer = currentPlayer; // Media queue management. - currentPlayer.setMediaItems(mediaQueue, windowIndex, playbackPositionMs); + currentPlayer.setMediaItems(mediaQueue, currentItemIndex, playbackPositionMs); currentPlayer.setPlayWhenReady(playWhenReady); currentPlayer.prepare(); } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index e080c23f99..ca9fd45d42 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -65,7 +65,7 @@ public class PlayerActivity extends AppCompatActivity // Saved instance state keys. private static final String KEY_TRACK_SELECTION_PARAMETERS = "track_selection_parameters"; - private static final String KEY_WINDOW = "window"; + private static final String KEY_ITEM_INDEX = "item_index"; private static final String KEY_POSITION = "position"; private static final String KEY_AUTO_PLAY = "auto_play"; @@ -83,7 +83,7 @@ public class PlayerActivity extends AppCompatActivity private DebugTextViewHelper debugViewHelper; private TracksInfo lastSeenTracksInfo; private boolean startAutoPlay; - private int startWindow; + private int startItemIndex; private long startPosition; // For ad playback only. @@ -114,7 +114,7 @@ public class PlayerActivity extends AppCompatActivity DefaultTrackSelector.Parameters.CREATOR.fromBundle( savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS)); startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY); - startWindow = savedInstanceState.getInt(KEY_WINDOW); + startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX); startPosition = savedInstanceState.getLong(KEY_POSITION); } else { trackSelectionParameters = @@ -206,7 +206,7 @@ public class PlayerActivity extends AppCompatActivity updateStartPosition(); outState.putBundle(KEY_TRACK_SELECTION_PARAMETERS, trackSelectionParameters.toBundle()); outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay); - outState.putInt(KEY_WINDOW, startWindow); + outState.putInt(KEY_ITEM_INDEX, startItemIndex); outState.putLong(KEY_POSITION, startPosition); } @@ -282,9 +282,9 @@ public class PlayerActivity extends AppCompatActivity debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper.start(); } - boolean haveStartPosition = startWindow != C.INDEX_UNSET; + boolean haveStartPosition = startItemIndex != C.INDEX_UNSET; if (haveStartPosition) { - player.seekTo(startWindow, startPosition); + player.seekTo(startItemIndex, startPosition); } player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition); player.prepare(); @@ -382,14 +382,14 @@ public class PlayerActivity extends AppCompatActivity private void updateStartPosition() { if (player != null) { startAutoPlay = player.getPlayWhenReady(); - startWindow = player.getCurrentWindowIndex(); + startItemIndex = player.getCurrentMediaItemIndex(); startPosition = Math.max(0, player.getContentPosition()); } } protected void clearStartPosition() { startAutoPlay = true; - startWindow = C.INDEX_UNSET; + startItemIndex = C.INDEX_UNSET; startPosition = C.TIME_UNSET; } diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 1bf2cd410b..0054378727 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -318,9 +318,9 @@ public final class CastPlayer extends BasePlayer { @Override public void setMediaItems(List mediaItems, boolean resetPosition) { - int windowIndex = resetPosition ? 0 : getCurrentWindowIndex(); + int mediaItemIndex = resetPosition ? 0 : getCurrentMediaItemIndex(); long startPositionMs = resetPosition ? C.TIME_UNSET : getContentPosition(); - setMediaItems(mediaItems, windowIndex, startPositionMs); + setMediaItems(mediaItems, mediaItemIndex, startPositionMs); } @Override @@ -443,7 +443,7 @@ public final class CastPlayer extends BasePlayer { // in RemoteMediaClient. positionMs = positionMs != C.TIME_UNSET ? positionMs : 0; if (mediaStatus != null) { - if (getCurrentWindowIndex() != mediaItemIndex) { + if (getCurrentMediaItemIndex() != mediaItemIndex) { remoteMediaClient .queueJumpToItem( (int) currentTimeline.getPeriod(mediaItemIndex, period).uid, positionMs, null) @@ -636,7 +636,7 @@ public final class CastPlayer extends BasePlayer { @Override public int getCurrentPeriodIndex() { - return getCurrentWindowIndex(); + return getCurrentMediaItemIndex(); } @Override @@ -1103,15 +1103,15 @@ public final class CastPlayer extends BasePlayer { @Nullable private PendingResult setMediaItemsInternal( MediaQueueItem[] mediaQueueItems, - int startWindowIndex, + int startIndex, long startPositionMs, @RepeatMode int repeatMode) { if (remoteMediaClient == null || mediaQueueItems.length == 0) { return null; } startPositionMs = startPositionMs == C.TIME_UNSET ? 0 : startPositionMs; - if (startWindowIndex == C.INDEX_UNSET) { - startWindowIndex = getCurrentWindowIndex(); + if (startIndex == C.INDEX_UNSET) { + startIndex = getCurrentMediaItemIndex(); startPositionMs = getCurrentPosition(); } Timeline currentTimeline = getCurrentTimeline(); @@ -1120,7 +1120,7 @@ public final class CastPlayer extends BasePlayer { } return remoteMediaClient.queueLoad( mediaQueueItems, - min(startWindowIndex, mediaQueueItems.length - 1), + min(startIndex, mediaQueueItems.length - 1), getCastRepeatMode(repeatMode), startPositionMs, /* customData= */ null); @@ -1180,7 +1180,7 @@ public final class CastPlayer extends BasePlayer { } return new PositionInfo( newWindowUid, - getCurrentWindowIndex(), + getCurrentMediaItemIndex(), newMediaItem, newPeriodUid, getCurrentPeriodIndex(), diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java index fb2975920d..90e2087389 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeExoPlayer.java @@ -279,7 +279,7 @@ import com.google.android.exoplayer2.util.Util; timeline.getPeriod(0, period).getAdDurationUs(adGroupIndex, adIndexInAdGroup); return Util.usToMs(adDurationUs); } else { - return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + return timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } } 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 a914869f8f..bd70d14394 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 @@ -141,7 +141,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab if (player.getPlaybackState() == Player.STATE_IDLE) { player.prepare(); } else if (player.getPlaybackState() == Player.STATE_ENDED) { - player.seekToDefaultPosition(player.getCurrentWindowIndex()); + player.seekToDefaultPosition(player.getCurrentMediaItemIndex()); } if (player.isCommandAvailable(Player.COMMAND_PLAY_PAUSE)) { player.play(); diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java index 7891bc47f3..f69f725edb 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java @@ -253,8 +253,8 @@ import java.util.List; // checkIndex() throws IndexOutOfBoundsException which maps the RESULT_ERROR_BAD_VALUE // but RESULT_ERROR_INVALID_STATE with IllegalStateException is expected here. Assertions.checkState(0 <= index && index < timeline.getWindowCount()); - int windowIndex = player.getCurrentWindowIndex(); - if (windowIndex == index || !player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { + int currentIndex = player.getCurrentMediaItemIndex(); + if (currentIndex == index || !player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { return false; } player.seekToDefaultPosition(index); @@ -301,7 +301,7 @@ import java.util.List; } public int getCurrentMediaItemIndex() { - return media2Playlist.isEmpty() ? C.INDEX_UNSET : player.getCurrentWindowIndex(); + return media2Playlist.isEmpty() ? C.INDEX_UNSET : player.getCurrentMediaItemIndex(); } public int getPreviousMediaItemIndex() { @@ -331,7 +331,7 @@ import java.util.List; if (!player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { return false; } - player.seekTo(player.getCurrentWindowIndex(), /* positionMs= */ 0); + player.seekTo(player.getCurrentMediaItemIndex(), /* positionMs= */ 0); } boolean playWhenReady = player.getPlayWhenReady(); int suppressReason = player.getPlaybackSuppressionReason(); @@ -358,7 +358,7 @@ import java.util.List; if (!player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { return false; } - player.seekTo(player.getCurrentWindowIndex(), position); + player.seekTo(player.getCurrentMediaItemIndex(), position); return true; } @@ -493,7 +493,7 @@ import java.util.List; public boolean isCurrentMediaItemSeekable() { return getCurrentMediaItem() != null && !player.isPlayingAd() - && player.isCurrentWindowSeekable(); + && player.isCurrentMediaItemSeekable(); } public boolean canSkipToPlaylistItem() { @@ -502,11 +502,11 @@ import java.util.List; } public boolean canSkipToPreviousPlaylistItem() { - return player.hasPreviousWindow(); + return player.hasPreviousMediaItem(); } public boolean canSkipToNextPlaylistItem() { - return player.hasNextWindow(); + return player.hasNextMediaItem(); } public boolean hasError() { diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index b823d0f90f..cfda09ff0a 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -263,12 +263,13 @@ public final class MediaSessionConnector { * @param player The player connected to the media session. */ void onTimelineChanged(Player player); + /** - * Called when the current window index changed. + * Called when the current media item index changed. * * @param player The player connected to the media session. */ - void onCurrentWindowIndexChanged(Player player); + default void onCurrentMediaItemIndexChanged(Player player) {} /** * Gets the id of the currently active queue item, or {@link * MediaSessionCompat.QueueItem#UNKNOWN_ID} if the active item is unknown. @@ -969,8 +970,8 @@ public final class MediaSessionConnector { return player != null && mediaButtonEventHandler != null; } - private void seekTo(Player player, int windowIndex, long positionMs) { - player.seekTo(windowIndex, positionMs); + private void seekTo(Player player, int mediaItemIndex, long positionMs) { + player.seekTo(mediaItemIndex, positionMs); } private static int getMediaSessionPlaybackState( @@ -1023,7 +1024,7 @@ public final class MediaSessionConnector { } builder.putLong( MediaMetadataCompat.METADATA_KEY_DURATION, - player.isCurrentWindowDynamic() || player.getDuration() == C.TIME_UNSET + player.isCurrentMediaItemDynamic() || player.getDuration() == C.TIME_UNSET ? -1 : player.getDuration()); long activeQueueItemId = mediaController.getPlaybackState().getActiveQueueItemId(); @@ -1097,7 +1098,7 @@ public final class MediaSessionConnector { private class ComponentListener extends MediaSessionCompat.Callback implements Player.Listener { - private int currentWindowIndex; + private int currentMediaItemIndex; private int currentWindowCount; // Player.Listener implementation. @@ -1107,9 +1108,9 @@ public final class MediaSessionConnector { boolean invalidatePlaybackState = false; boolean invalidateMetadata = false; if (events.contains(Player.EVENT_POSITION_DISCONTINUITY)) { - if (currentWindowIndex != player.getCurrentWindowIndex()) { + if (currentMediaItemIndex != player.getCurrentMediaItemIndex()) { if (queueNavigator != null) { - queueNavigator.onCurrentWindowIndexChanged(player); + queueNavigator.onCurrentMediaItemIndexChanged(player); } invalidateMetadata = true; } @@ -1118,11 +1119,11 @@ public final class MediaSessionConnector { if (events.contains(Player.EVENT_TIMELINE_CHANGED)) { int windowCount = player.getCurrentTimeline().getWindowCount(); - int windowIndex = player.getCurrentWindowIndex(); + int mediaItemIndex = player.getCurrentMediaItemIndex(); if (queueNavigator != null) { queueNavigator.onTimelineChanged(player); invalidatePlaybackState = true; - } else if (currentWindowCount != windowCount || currentWindowIndex != windowIndex) { + } else if (currentWindowCount != windowCount || currentMediaItemIndex != mediaItemIndex) { // active queue item and queue navigation actions may need to be updated invalidatePlaybackState = true; } @@ -1130,8 +1131,8 @@ public final class MediaSessionConnector { invalidateMetadata = true; } - // Update currentWindowIndex after comparisons above. - currentWindowIndex = player.getCurrentWindowIndex(); + // Update currentMediaItemIndex after comparisons above. + currentMediaItemIndex = player.getCurrentMediaItemIndex(); if (events.containsAny( EVENT_PLAYBACK_STATE_CHANGED, @@ -1170,7 +1171,7 @@ public final class MediaSessionConnector { player.prepare(); } } else if (player.getPlaybackState() == Player.STATE_ENDED) { - seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); + seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); } Assertions.checkNotNull(player).play(); } @@ -1186,7 +1187,7 @@ public final class MediaSessionConnector { @Override public void onSeekTo(long positionMs) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_SEEK_TO)) { - seekTo(player, player.getCurrentWindowIndex(), positionMs); + seekTo(player, player.getCurrentMediaItemIndex(), positionMs); } } diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java index 90db27e458..4277de3c32 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java @@ -98,7 +98,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu boolean enableNext = false; Timeline timeline = player.getCurrentTimeline(); if (!timeline.isEmpty() && !player.isPlayingAd()) { - timeline.getWindow(player.getCurrentWindowIndex(), window); + timeline.getWindow(player.getCurrentMediaItemIndex(), window); enableSkipTo = timeline.getWindowCount() > 1; enablePrevious = player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) @@ -128,12 +128,12 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu } @Override - public final void onCurrentWindowIndexChanged(Player player) { + public final void onCurrentMediaItemIndexChanged(Player player) { if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { publishFloatingQueueWindow(player); } else if (!player.getCurrentTimeline().isEmpty()) { - activeQueueItemId = player.getCurrentWindowIndex(); + activeQueueItemId = player.getCurrentMediaItemIndex(); } } @@ -185,40 +185,40 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu int queueSize = min(maxQueueSize, timeline.getWindowCount()); // Add the active queue item. - int currentWindowIndex = player.getCurrentWindowIndex(); + int currentMediaItemIndex = player.getCurrentMediaItemIndex(); queue.add( new MediaSessionCompat.QueueItem( - getMediaDescription(player, currentWindowIndex), currentWindowIndex)); + getMediaDescription(player, currentMediaItemIndex), currentMediaItemIndex)); // Fill queue alternating with next and/or previous queue items. - int firstWindowIndex = currentWindowIndex; - int lastWindowIndex = currentWindowIndex; + int firstMediaItemIndex = currentMediaItemIndex; + int lastMediaItemIndex = currentMediaItemIndex; boolean shuffleModeEnabled = player.getShuffleModeEnabled(); - while ((firstWindowIndex != C.INDEX_UNSET || lastWindowIndex != C.INDEX_UNSET) + while ((firstMediaItemIndex != C.INDEX_UNSET || lastMediaItemIndex != C.INDEX_UNSET) && queue.size() < queueSize) { // Begin with next to have a longer tail than head if an even sized queue needs to be trimmed. - if (lastWindowIndex != C.INDEX_UNSET) { - lastWindowIndex = + if (lastMediaItemIndex != C.INDEX_UNSET) { + lastMediaItemIndex = timeline.getNextWindowIndex( - lastWindowIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); - if (lastWindowIndex != C.INDEX_UNSET) { + lastMediaItemIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); + if (lastMediaItemIndex != C.INDEX_UNSET) { queue.add( new MediaSessionCompat.QueueItem( - getMediaDescription(player, lastWindowIndex), lastWindowIndex)); + getMediaDescription(player, lastMediaItemIndex), lastMediaItemIndex)); } } - if (firstWindowIndex != C.INDEX_UNSET && queue.size() < queueSize) { - firstWindowIndex = + if (firstMediaItemIndex != C.INDEX_UNSET && queue.size() < queueSize) { + firstMediaItemIndex = timeline.getPreviousWindowIndex( - firstWindowIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); - if (firstWindowIndex != C.INDEX_UNSET) { + firstMediaItemIndex, Player.REPEAT_MODE_OFF, shuffleModeEnabled); + if (firstMediaItemIndex != C.INDEX_UNSET) { queue.addFirst( new MediaSessionCompat.QueueItem( - getMediaDescription(player, firstWindowIndex), firstWindowIndex)); + getMediaDescription(player, firstMediaItemIndex), firstMediaItemIndex)); } } } mediaSession.setQueue(new ArrayList<>(queue)); - activeQueueItemId = currentWindowIndex; + activeQueueItemId = currentMediaItemIndex; } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java index b60e4a3a7e..2209803f3e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -118,7 +118,7 @@ public abstract class BasePlayer implements Player { @Override public final void seekToDefaultPosition() { - seekToDefaultPosition(getCurrentWindowIndex()); + seekToDefaultPosition(getCurrentMediaItemIndex()); } @Override @@ -128,7 +128,7 @@ public abstract class BasePlayer implements Player { @Override public final void seekTo(long positionMs) { - seekTo(getCurrentWindowIndex(), positionMs); + seekTo(getCurrentMediaItemIndex(), positionMs); } @Override @@ -184,13 +184,13 @@ public abstract class BasePlayer implements Player { if (timeline.isEmpty() || isPlayingAd()) { return; } - boolean hasPreviousWindow = hasPreviousWindow(); - if (isCurrentWindowLive() && !isCurrentWindowSeekable()) { - if (hasPreviousWindow) { - seekToPreviousWindow(); + boolean hasPreviousMediaItem = hasPreviousMediaItem(); + if (isCurrentMediaItemLive() && !isCurrentMediaItemSeekable()) { + if (hasPreviousMediaItem) { + seekToPreviousMediaItem(); } - } else if (hasPreviousWindow && getCurrentPosition() <= getMaxSeekToPreviousPosition()) { - seekToPreviousWindow(); + } else if (hasPreviousMediaItem && getCurrentPosition() <= getMaxSeekToPreviousPosition()) { + seekToPreviousMediaItem(); } else { seekTo(/* positionMs= */ 0); } @@ -239,9 +239,9 @@ public abstract class BasePlayer implements Player { if (timeline.isEmpty() || isPlayingAd()) { return; } - if (hasNextWindow()) { - seekToNextWindow(); - } else if (isCurrentWindowLive() && isCurrentWindowDynamic()) { + if (hasNextMediaItem()) { + seekToNextMediaItem(); + } else if (isCurrentMediaItemLive() && isCurrentMediaItemDynamic()) { seekToDefaultPosition(); } } @@ -293,7 +293,7 @@ public abstract class BasePlayer implements Player { Timeline timeline = getCurrentTimeline(); return timeline.isEmpty() ? null - : timeline.getWindow(getCurrentWindowIndex(), window).mediaItem; + : timeline.getWindow(getCurrentMediaItemIndex(), window).mediaItem; } @Override @@ -310,7 +310,9 @@ public abstract class BasePlayer implements Player { @Nullable public final Object getCurrentManifest() { Timeline timeline = getCurrentTimeline(); - return timeline.isEmpty() ? null : timeline.getWindow(getCurrentWindowIndex(), window).manifest; + return timeline.isEmpty() + ? null + : timeline.getWindow(getCurrentMediaItemIndex(), window).manifest; } @Override @@ -352,7 +354,8 @@ public abstract class BasePlayer implements Player { if (timeline.isEmpty()) { return C.TIME_UNSET; } - long windowStartTimeMs = timeline.getWindow(getCurrentWindowIndex(), window).windowStartTimeMs; + long windowStartTimeMs = + timeline.getWindow(getCurrentMediaItemIndex(), window).windowStartTimeMs; if (windowStartTimeMs == C.TIME_UNSET) { return C.TIME_UNSET; } @@ -376,7 +379,7 @@ public abstract class BasePlayer implements Player { Timeline timeline = getCurrentTimeline(); return timeline.isEmpty() ? C.TIME_UNSET - : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + : timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } /** @@ -389,22 +392,24 @@ public abstract class BasePlayer implements Player { return new Commands.Builder() .addAll(permanentAvailableCommands) .addIf(COMMAND_SEEK_TO_DEFAULT_POSITION, !isPlayingAd()) - .addIf(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, isCurrentWindowSeekable() && !isPlayingAd()) - .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousWindow() && !isPlayingAd()) + .addIf(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, isCurrentMediaItemSeekable() && !isPlayingAd()) + .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousMediaItem() && !isPlayingAd()) .addIf( COMMAND_SEEK_TO_PREVIOUS, !getCurrentTimeline().isEmpty() - && (hasPreviousWindow() || !isCurrentWindowLive() || isCurrentWindowSeekable()) + && (hasPreviousMediaItem() + || !isCurrentMediaItemLive() + || isCurrentMediaItemSeekable()) && !isPlayingAd()) - .addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextWindow() && !isPlayingAd()) + .addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextMediaItem() && !isPlayingAd()) .addIf( COMMAND_SEEK_TO_NEXT, !getCurrentTimeline().isEmpty() - && (hasNextWindow() || (isCurrentWindowLive() && isCurrentWindowDynamic())) + && (hasNextMediaItem() || (isCurrentMediaItemLive() && isCurrentMediaItemDynamic())) && !isPlayingAd()) .addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd()) - .addIf(COMMAND_SEEK_BACK, isCurrentWindowSeekable() && !isPlayingAd()) - .addIf(COMMAND_SEEK_FORWARD, isCurrentWindowSeekable() && !isPlayingAd()) + .addIf(COMMAND_SEEK_BACK, isCurrentMediaItemSeekable() && !isPlayingAd()) + .addIf(COMMAND_SEEK_FORWARD, isCurrentMediaItemSeekable() && !isPlayingAd()) .build(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index e594883c72..2143d2a1fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -1126,7 +1126,7 @@ public interface ExoPlayer extends Player { * @param mediaSources The new {@link MediaSource MediaSources}. * @param resetPosition Whether the playback position should be reset to the default position in * the first {@link Timeline.Window}. If false, playback will start from the position defined - * by {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}. + * by {@link #getCurrentMediaItemIndex()} and {@link #getCurrentPosition()}. */ void setMediaSources(List mediaSources, boolean resetPosition); @@ -1134,14 +1134,15 @@ public interface ExoPlayer extends Player { * Clears the playlist and adds the specified {@link MediaSource MediaSources}. * * @param mediaSources The new {@link MediaSource MediaSources}. - * @param startWindowIndex The window index to start playback from. If {@link C#INDEX_UNSET} is - * passed, the current position is not reset. + * @param startMediaItemIndex The media item index to start playback from. If {@link + * C#INDEX_UNSET} is passed, the current position is not reset. * @param startPositionMs The position in milliseconds to start playback from. If {@link - * C#TIME_UNSET} is passed, the default position of the given window is used. In any case, if - * {@code startWindowIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored and the - * position is not reset at all. + * C#TIME_UNSET} is passed, the default position of the given media item is used. In any case, + * if {@code startMediaItemIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored + * and the position is not reset at all. */ - void setMediaSources(List mediaSources, int startWindowIndex, long startPositionMs); + void setMediaSources( + List mediaSources, int startMediaItemIndex, long startPositionMs); /** * Clears the playlist, adds the specified {@link MediaSource} and resets the position to the @@ -1164,7 +1165,7 @@ public interface ExoPlayer extends Player { * * @param mediaSource The new {@link MediaSource}. * @param resetPosition Whether the playback position should be reset to the default position. If - * false, playback will start from the position defined by {@link #getCurrentWindowIndex()} + * false, playback will start from the position defined by {@link #getCurrentMediaItemIndex()} * and {@link #getCurrentPosition()}. */ void setMediaSource(MediaSource mediaSource, boolean resetPosition); @@ -1331,9 +1332,9 @@ public interface ExoPlayer extends Player { * will be delivered immediately without blocking on the playback thread. The default {@link * PlayerMessage#getType()} is 0 and the default {@link PlayerMessage#getPayload()} is null. If a * position is specified with {@link PlayerMessage#setPosition(long)}, the message will be - * delivered at this position in the current window defined by {@link #getCurrentWindowIndex()}. - * Alternatively, the message can be sent at a specific window using {@link - * PlayerMessage#setPosition(int, long)}. + * delivered at this position in the current media item defined by {@link + * #getCurrentMediaItemIndex()}. Alternatively, the message can be sent at a specific mediaItem + * using {@link PlayerMessage#setPosition(int, long)}. */ PlayerMessage createMessage(PlayerMessage.Target target); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 653135e59c..61a1d41b82 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -537,7 +537,7 @@ import java.util.concurrent.CopyOnWriteArraySet; playbackInfo, timeline, getPeriodPositionOrMaskWindowPosition( - timeline, getCurrentWindowIndex(), getCurrentPosition())); + timeline, getCurrentMediaItemIndex(), getCurrentPosition())); pendingOperationAcks++; this.shuffleOrder = shuffleOrder; internalPlayer.setShuffleOrder(shuffleOrder); @@ -662,7 +662,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Player.State int newPlaybackState = getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING; - int oldMaskingWindowIndex = getCurrentWindowIndex(); + int oldMaskingMediaItemIndex = getCurrentMediaItemIndex(); PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState); newPlaybackInfo = maskTimelineAndPosition( @@ -678,7 +678,7 @@ import java.util.concurrent.CopyOnWriteArraySet; /* positionDiscontinuity= */ true, /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), - oldMaskingWindowIndex); + oldMaskingMediaItemIndex); } @Override @@ -839,7 +839,7 @@ import java.util.concurrent.CopyOnWriteArraySet; internalPlayer, target, playbackInfo.timeline, - getCurrentWindowIndex(), + getCurrentMediaItemIndex(), clock, internalPlayer.getPlaybackLooper()); } @@ -910,7 +910,10 @@ import java.util.concurrent.CopyOnWriteArraySet; if (isPlayingAd()) { playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET - ? playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDefaultPositionMs() + ? playbackInfo + .timeline + .getWindow(getCurrentMediaItemIndex(), window) + .getDefaultPositionMs() : period.getPositionInWindowMs() + Util.usToMs(playbackInfo.requestedContentPositionUs); } else { return getCurrentPosition(); @@ -924,7 +927,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber != playbackInfo.periodId.windowSequenceNumber) { - return playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + return playbackInfo.timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } long contentBufferedPositionUs = playbackInfo.bufferedPositionUs; if (playbackInfo.loadingMediaPeriodId.isAd()) { @@ -1218,7 +1221,7 @@ import java.util.concurrent.CopyOnWriteArraySet; boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, long discontinuityWindowStartPositionUs, - int oldMaskingWindowIndex) { + int oldMaskingMediaItemIndex) { // Assign playback info immediately such that all getters return the right values, but keep // snapshot of previous and new state so that listener invocations are triggered correctly. @@ -1267,7 +1270,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (positionDiscontinuity) { PositionInfo previousPositionInfo = getPreviousPositionInfo( - positionDiscontinuityReason, previousPlaybackInfo, oldMaskingWindowIndex); + positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, @@ -1378,19 +1381,19 @@ import java.util.concurrent.CopyOnWriteArraySet; private PositionInfo getPreviousPositionInfo( @DiscontinuityReason int positionDiscontinuityReason, PlaybackInfo oldPlaybackInfo, - int oldMaskingWindowIndex) { + int oldMaskingMediaItemIndex) { @Nullable Object oldWindowUid = null; @Nullable Object oldPeriodUid = null; - int oldWindowIndex = oldMaskingWindowIndex; + int oldMediaItemIndex = oldMaskingMediaItemIndex; int oldPeriodIndex = C.INDEX_UNSET; @Nullable MediaItem oldMediaItem = null; Timeline.Period oldPeriod = new Timeline.Period(); if (!oldPlaybackInfo.timeline.isEmpty()) { oldPeriodUid = oldPlaybackInfo.periodId.periodUid; oldPlaybackInfo.timeline.getPeriodByUid(oldPeriodUid, oldPeriod); - oldWindowIndex = oldPeriod.windowIndex; + oldMediaItemIndex = oldPeriod.windowIndex; oldPeriodIndex = oldPlaybackInfo.timeline.getIndexOfPeriod(oldPeriodUid); - oldWindowUid = oldPlaybackInfo.timeline.getWindow(oldWindowIndex, window).uid; + oldWindowUid = oldPlaybackInfo.timeline.getWindow(oldMediaItemIndex, window).uid; oldMediaItem = window.mediaItem; } long oldPositionUs; @@ -1421,7 +1424,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } return new PositionInfo( oldWindowUid, - oldWindowIndex, + oldMediaItemIndex, oldMediaItem, oldPeriodUid, oldPeriodIndex, @@ -1434,20 +1437,20 @@ import java.util.concurrent.CopyOnWriteArraySet; private PositionInfo getPositionInfo(long discontinuityWindowStartPositionUs) { @Nullable Object newWindowUid = null; @Nullable Object newPeriodUid = null; - int newWindowIndex = getCurrentWindowIndex(); + int newMediaItemIndex = getCurrentMediaItemIndex(); int newPeriodIndex = C.INDEX_UNSET; @Nullable MediaItem newMediaItem = null; if (!playbackInfo.timeline.isEmpty()) { newPeriodUid = playbackInfo.periodId.periodUid; playbackInfo.timeline.getPeriodByUid(newPeriodUid, period); newPeriodIndex = playbackInfo.timeline.getIndexOfPeriod(newPeriodUid); - newWindowUid = playbackInfo.timeline.getWindow(newWindowIndex, window).uid; + newWindowUid = playbackInfo.timeline.getWindow(newMediaItemIndex, window).uid; newMediaItem = window.mediaItem; } long positionMs = Util.usToMs(discontinuityWindowStartPositionUs); return new PositionInfo( newWindowUid, - newWindowIndex, + newMediaItemIndex, newMediaItem, newPeriodUid, newPeriodIndex, @@ -1601,7 +1604,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private PlaybackInfo removeMediaItemsInternal(int fromIndex, int toIndex) { Assertions.checkArgument( fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size()); - int currentWindowIndex = getCurrentWindowIndex(); + int currentIndex = getCurrentMediaItemIndex(); Timeline oldTimeline = getCurrentTimeline(); int currentMediaSourceCount = mediaSourceHolderSnapshots.size(); pendingOperationAcks++; @@ -1618,7 +1621,7 @@ import java.util.concurrent.CopyOnWriteArraySet; && newPlaybackInfo.playbackState != STATE_ENDED && fromIndex < toIndex && toIndex == currentMediaSourceCount - && currentWindowIndex >= newPlaybackInfo.timeline.getWindowCount(); + && currentIndex >= newPlaybackInfo.timeline.getWindowCount(); if (transitionsToEnded) { newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(STATE_ENDED); } @@ -1753,11 +1756,11 @@ import java.util.concurrent.CopyOnWriteArraySet; isCleared ? C.INDEX_UNSET : getCurrentWindowIndexInternal(), isCleared ? C.TIME_UNSET : currentPositionMs); } - int currentWindowIndex = getCurrentWindowIndex(); + int currentMediaItemIndex = getCurrentMediaItemIndex(); @Nullable Pair oldPeriodPosition = oldTimeline.getPeriodPosition( - window, period, currentWindowIndex, Util.msToUs(currentPositionMs)); + window, period, currentMediaItemIndex, Util.msToUs(currentPositionMs)); Object periodUid = castNonNull(oldPeriodPosition).first; if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) { // The old period position is still available in the new timeline. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 9fadb56a79..d6f1e1f73a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -2718,7 +2718,7 @@ import java.util.concurrent.atomic.AtomicBoolean; newTimeline, new SeekPosition( pendingMessageInfo.message.getTimeline(), - pendingMessageInfo.message.getWindowIndex(), + pendingMessageInfo.message.getMediaItemIndex(), requestPositionUs), /* trySubsequentPeriods= */ false, repeatMode, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java index 0d591ee9f6..cc7e749fa0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java @@ -63,7 +63,7 @@ public final class PlayerMessage { private int type; @Nullable private Object payload; private Looper looper; - private int windowIndex; + private int mediaItemIndex; private long positionMs; private boolean deleteAfterDelivery; private boolean isSent; @@ -78,8 +78,8 @@ public final class PlayerMessage { * @param target The {@link Target} the message is sent to. * @param timeline The timeline used when setting the position with {@link #setPosition(long)}. If * set to {@link Timeline#EMPTY}, any position can be specified. - * @param defaultWindowIndex The default window index in the {@code timeline} when no other window - * index is specified. + * @param defaultMediaItemIndex The default media item index in the {@code timeline} when no other + * media item index is specified. * @param clock The {@link Clock}. * @param defaultLooper The default {@link Looper} to send the message on when no other looper is * specified. @@ -88,7 +88,7 @@ public final class PlayerMessage { Sender sender, Target target, Timeline timeline, - int defaultWindowIndex, + int defaultMediaItemIndex, Clock clock, Looper defaultLooper) { this.sender = sender; @@ -96,7 +96,7 @@ public final class PlayerMessage { this.timeline = timeline; this.looper = defaultLooper; this.clock = clock; - this.windowIndex = defaultWindowIndex; + this.mediaItemIndex = defaultMediaItemIndex; this.positionMs = C.TIME_UNSET; this.deleteAfterDelivery = true; } @@ -173,21 +173,21 @@ public final class PlayerMessage { } /** - * Returns position in window at {@link #getWindowIndex()} at which the message will be delivered, - * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. If {@link - * C#TIME_END_OF_SOURCE}, the message will be delivered at the end of the window at {@link - * #getWindowIndex()}. + * Returns position in the media item at {@link #getMediaItemIndex()} at which the message will be + * delivered, in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. + * If {@link C#TIME_END_OF_SOURCE}, the message will be delivered at the end of the media item at + * {@link #getMediaItemIndex()}. */ public long getPositionMs() { return positionMs; } /** - * Sets a position in the current window at which the message will be delivered. + * Sets a position in the current media item at which the message will be delivered. * - * @param positionMs The position in the current window at which the message will be sent, in + * @param positionMs The position in the current media item at which the message will be sent, in * milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the message at the end of the - * current window. + * current media item. * @return This message. * @throws IllegalStateException If {@link #send()} has already been called. */ @@ -198,31 +198,32 @@ public final class PlayerMessage { } /** - * Sets a position in a window at which the message will be delivered. + * Sets a position in a media item at which the message will be delivered. * - * @param windowIndex The index of the window at which the message will be sent. - * @param positionMs The position in the window with index {@code windowIndex} at which the + * @param mediaItemIndex The index of the media item at which the message will be sent. + * @param positionMs The position in the media item with index {@code mediaItemIndex} at which the * message will be sent, in milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the - * message at the end of the window with index {@code windowIndex}. + * message at the end of the media item with index {@code mediaItemIndex}. * @return This message. * @throws IllegalSeekPositionException If the timeline returned by {@link #getTimeline()} is not - * empty and the provided window index is not within the bounds of the timeline. + * empty and the provided media item index is not within the bounds of the timeline. * @throws IllegalStateException If {@link #send()} has already been called. */ - public PlayerMessage setPosition(int windowIndex, long positionMs) { + public PlayerMessage setPosition(int mediaItemIndex, long positionMs) { Assertions.checkState(!isSent); Assertions.checkArgument(positionMs != C.TIME_UNSET); - if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) { - throw new IllegalSeekPositionException(timeline, windowIndex, positionMs); + if (mediaItemIndex < 0 + || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { + throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs); } - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; return this; } - /** Returns window index at which the message will be delivered. */ - public int getWindowIndex() { - return windowIndex; + /** Returns media item index at which the message will be delivered. */ + public int getMediaItemIndex() { + return mediaItemIndex; } /** 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 3c6607611e..83067a500c 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 @@ -1110,9 +1110,9 @@ public class SimpleExoPlayer extends BasePlayer @Override public void setMediaSources( - List mediaSources, int startWindowIndex, long startPositionMs) { + List mediaSources, int startMediaItemIndex, long startPositionMs) { verifyApplicationThread(); - player.setMediaSources(mediaSources, startWindowIndex, startPositionMs); + player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs); } @Override @@ -1419,7 +1419,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public int getCurrentMediaItemIndex() { verifyApplicationThread(); - return player.getCurrentWindowIndex(); + return player.getCurrentMediaItemIndex(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index ae22609e29..63e87a5539 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -914,7 +914,7 @@ public class AnalyticsCollector long eventPositionMs; boolean isInCurrentWindow = timeline.equals(player.getCurrentTimeline()) - && windowIndex == player.getCurrentWindowIndex(); + && windowIndex == player.getCurrentMediaItemIndex(); if (mediaPeriodId != null && mediaPeriodId.isAd()) { boolean isCurrentAd = isInCurrentWindow @@ -939,7 +939,7 @@ public class AnalyticsCollector mediaPeriodId, eventPositionMs, player.getCurrentTimeline(), - player.getCurrentWindowIndex(), + player.getCurrentMediaItemIndex(), currentMediaPeriodId, player.getCurrentPosition(), player.getTotalBufferedDuration()); @@ -962,7 +962,7 @@ public class AnalyticsCollector ? null : mediaPeriodQueueTracker.getMediaPeriodIdTimeline(mediaPeriodId); if (mediaPeriodId == null || knownTimeline == null) { - int windowIndex = player.getCurrentWindowIndex(); + int windowIndex = player.getCurrentMediaItemIndex(); Timeline timeline = player.getCurrentTimeline(); boolean windowIsInTimeline = windowIndex < timeline.getWindowCount(); return generateEventTime( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 9887f397ea..df73a1f7f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -382,7 +382,7 @@ public interface AnalyticsListener { /** * The current window index in {@link #currentTimeline} at the time of the event, or the * prospective window index if the timeline is not yet known and empty (equivalent to {@link - * Player#getCurrentWindowIndex()}). + * Player#getCurrentMediaItemIndex()}). */ public final int currentWindowIndex; @@ -419,7 +419,7 @@ public interface AnalyticsListener { * {@link Player#getCurrentTimeline()}). * @param currentWindowIndex The current window index in {@code currentTimeline} at the time of * the event, or the prospective window index if the timeline is not yet known and empty - * (equivalent to {@link Player#getCurrentWindowIndex()}). + * (equivalent to {@link Player#getCurrentMediaItemIndex()}). * @param currentMediaPeriodId {@link MediaPeriodId Media period identifier} for the currently * playing media period at the time of the event, or {@code null} if no current media period * identifier is available. @@ -1204,9 +1204,9 @@ public interface AnalyticsListener { * {@link Player#seekTo(long)} after a {@link * AnalyticsListener#onMediaItemTransition(EventTime, MediaItem, int)}). *

    • They intend to use multiple state values together or in combination with {@link Player} - * getter methods. For example using {@link Player#getCurrentWindowIndex()} with the {@code - * timeline} provided in {@link #onTimelineChanged(EventTime, int)} is only safe from within - * this method. + * getter methods. For example using {@link Player#getCurrentMediaItemIndex()} with the + * {@code timeline} provided in {@link #onTimelineChanged(EventTime, int)} is only safe from + * within this method. *
    • They are interested in events that logically happened together (e.g {@link * #onPlaybackStateChanged(EventTime, int)} to {@link Player#STATE_BUFFERING} because of * {@link #onMediaItemTransition(EventTime, MediaItem, int)}). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java index 5eeaa060bc..77fb5c048e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java @@ -138,8 +138,8 @@ public class DebugTextViewHelper implements Player.Listener, Runnable { break; } return String.format( - "playWhenReady:%s playbackState:%s window:%s", - player.getPlayWhenReady(), playbackStateString, player.getCurrentWindowIndex()); + "playWhenReady:%s playbackState:%s item:%s", + player.getPlayWhenReady(), playbackStateString, player.getCurrentMediaItemIndex()); } /** Returns a string containing video debugging information. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 64044b2a3f..886d3c8ed9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -48,7 +48,7 @@ import static com.google.android.exoplayer2.Player.COMMAND_STOP; import static com.google.android.exoplayer2.Player.STATE_ENDED; import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilPosition; -import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfWindow; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfMediaItem; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPositionDiscontinuity; @@ -381,7 +381,7 @@ public final class ExoPlayerTest { player.play(); runUntilPositionDiscontinuity(player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); player.setForegroundMode(/* foregroundMode= */ true); - // Only the video renderer that is disabled in the second window has been reset. + // Only the video renderer that is disabled in the second media item has been reset. assertThat(audioRenderer.resetCount).isEqualTo(0); assertThat(videoRenderer.resetCount).isEqualTo(1); @@ -460,7 +460,7 @@ public final class ExoPlayerTest { // Disable text renderer by selecting a language that is not available. player.setTrackSelectionParameters( player.getTrackSelectionParameters().buildUpon().setPreferredTextLanguage("de").build()); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1000); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1000); runUntilPlaybackState(player, Player.STATE_READY); // Expect formerly enabled renderers to be reset after seek. assertThat(textRenderer.resetCount).isEqualTo(1); @@ -647,21 +647,21 @@ public final class ExoPlayerTest { player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT)); player.prepare(); runUntilTimelineChanged(player); - playUntilStartOfWindow(player, /* windowIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); player.setRepeatMode(Player.REPEAT_MODE_ONE); - playUntilStartOfWindow(player, /* windowIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); player.setRepeatMode(Player.REPEAT_MODE_OFF); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); player.setRepeatMode(Player.REPEAT_MODE_ONE); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); player.setRepeatMode(Player.REPEAT_MODE_ALL); - playUntilStartOfWindow(player, /* windowIndex= */ 0); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 0); player.setRepeatMode(Player.REPEAT_MODE_ONE); - playUntilStartOfWindow(player, /* windowIndex= */ 0); - playUntilStartOfWindow(player, /* windowIndex= */ 0); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 0); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 0); player.setRepeatMode(Player.REPEAT_MODE_OFF); - playUntilStartOfWindow(player, /* windowIndex= */ 1); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); @@ -694,9 +694,9 @@ public final class ExoPlayerTest { .pause() .waitForPlaybackState(Player.STATE_READY) .setRepeatMode(Player.REPEAT_MODE_ALL) - .playUntilStartOfWindow(/* windowIndex= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 1) .setShuffleModeEnabled(true) - .playUntilStartOfWindow(/* windowIndex= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 1) .setShuffleModeEnabled(false) .setRepeatMode(Player.REPEAT_MODE_OFF) .play() @@ -806,7 +806,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { try { - player.seekTo(/* windowIndex= */ 100, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 100, /* positionMs= */ 0); } catch (IllegalSeekPositionException e) { exception[0] = e; } @@ -1281,7 +1281,7 @@ public final class ExoPlayerTest { .play() .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 0, /* positionMs= */ 2000) + .initialSeek(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setMediaSources(mediaSource) .setActionSchedule(actionSchedule) .build() @@ -1293,7 +1293,7 @@ public final class ExoPlayerTest { @Test public void stop_withoutReset_doesNotResetPosition_correctMasking() throws Exception { - int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; @@ -1302,18 +1302,18 @@ public final class ExoPlayerTest { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[0] = player.getCurrentWindowIndex(); + currentMediaItemIndex[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); player.stop(/* reset= */ false); - currentWindowIndex[1] = player.getCurrentWindowIndex(); + currentMediaItemIndex[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -1324,7 +1324,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[2] = player.getCurrentWindowIndex(); + currentMediaItemIndex[2] = player.getCurrentMediaItemIndex(); currentPosition[2] = player.getCurrentPosition(); bufferedPosition[2] = player.getBufferedPosition(); totalBufferedDuration[2] = player.getTotalBufferedDuration(); @@ -1345,17 +1345,17 @@ public final class ExoPlayerTest { Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); - assertThat(currentWindowIndex[0]).isEqualTo(1); + assertThat(currentMediaItemIndex[0]).isEqualTo(1); assertThat(currentPosition[0]).isEqualTo(1000); assertThat(bufferedPosition[0]).isEqualTo(10000); assertThat(totalBufferedDuration[0]).isEqualTo(9000); - assertThat(currentWindowIndex[1]).isEqualTo(1); + assertThat(currentMediaItemIndex[1]).isEqualTo(1); assertThat(currentPosition[1]).isEqualTo(1000); assertThat(bufferedPosition[1]).isEqualTo(1000); assertThat(totalBufferedDuration[1]).isEqualTo(0); - assertThat(currentWindowIndex[2]).isEqualTo(1); + assertThat(currentMediaItemIndex[2]).isEqualTo(1); assertThat(currentPosition[2]).isEqualTo(1000); assertThat(bufferedPosition[2]).isEqualTo(1000); assertThat(totalBufferedDuration[2]).isEqualTo(0); @@ -1385,7 +1385,7 @@ public final class ExoPlayerTest { @Test public void stop_withReset_doesResetPosition_correctMasking() throws Exception { - int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; @@ -1394,18 +1394,18 @@ public final class ExoPlayerTest { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[0] = player.getCurrentWindowIndex(); + currentMediaItemIndex[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); player.stop(/* reset= */ true); - currentWindowIndex[1] = player.getCurrentWindowIndex(); + currentMediaItemIndex[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -1416,7 +1416,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[2] = player.getCurrentWindowIndex(); + currentMediaItemIndex[2] = player.getCurrentMediaItemIndex(); currentPosition[2] = player.getCurrentPosition(); bufferedPosition[2] = player.getBufferedPosition(); totalBufferedDuration[2] = player.getTotalBufferedDuration(); @@ -1439,17 +1439,17 @@ public final class ExoPlayerTest { testRunner.assertPositionDiscontinuityReasonsEqual( Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_REMOVE); - assertThat(currentWindowIndex[0]).isEqualTo(1); + assertThat(currentMediaItemIndex[0]).isEqualTo(1); assertThat(currentPosition[0]).isGreaterThan(0); assertThat(bufferedPosition[0]).isEqualTo(10000); assertThat(totalBufferedDuration[0]).isEqualTo(10000 - currentPosition[0]); - assertThat(currentWindowIndex[1]).isEqualTo(0); + assertThat(currentMediaItemIndex[1]).isEqualTo(0); assertThat(currentPosition[1]).isEqualTo(0); assertThat(bufferedPosition[1]).isEqualTo(0); assertThat(totalBufferedDuration[1]).isEqualTo(0); - assertThat(currentWindowIndex[2]).isEqualTo(0); + assertThat(currentMediaItemIndex[2]).isEqualTo(0); assertThat(currentPosition[2]).isEqualTo(0); assertThat(bufferedPosition[2]).isEqualTo(0); assertThat(totalBufferedDuration[2]).isEqualTo(0); @@ -1479,7 +1479,7 @@ public final class ExoPlayerTest { @Test public void release_correctMasking() throws Exception { - int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; @@ -1488,18 +1488,18 @@ public final class ExoPlayerTest { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[0] = player.getCurrentWindowIndex(); + currentMediaItemIndex[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); player.release(); - currentWindowIndex[1] = player.getCurrentWindowIndex(); + currentMediaItemIndex[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -1510,7 +1510,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndex[2] = player.getCurrentWindowIndex(); + currentMediaItemIndex[2] = player.getCurrentMediaItemIndex(); currentPosition[2] = player.getCurrentPosition(); bufferedPosition[2] = player.getBufferedPosition(); totalBufferedDuration[2] = player.getTotalBufferedDuration(); @@ -1525,17 +1525,17 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS); - assertThat(currentWindowIndex[0]).isEqualTo(1); + assertThat(currentMediaItemIndex[0]).isEqualTo(1); assertThat(currentPosition[0]).isGreaterThan(0); assertThat(bufferedPosition[0]).isEqualTo(10000); assertThat(totalBufferedDuration[0]).isEqualTo(10000 - currentPosition[0]); - assertThat(currentWindowIndex[1]).isEqualTo(1); + assertThat(currentMediaItemIndex[1]).isEqualTo(1); assertThat(currentPosition[1]).isEqualTo(currentPosition[0]); assertThat(bufferedPosition[1]).isEqualTo(1000); assertThat(totalBufferedDuration[1]).isEqualTo(0); - assertThat(currentWindowIndex[2]).isEqualTo(1); + assertThat(currentMediaItemIndex[2]).isEqualTo(1); assertThat(currentPosition[2]).isEqualTo(currentPosition[0]); assertThat(bufferedPosition[2]).isEqualTo(1000); assertThat(totalBufferedDuration[2]).isEqualTo(0); @@ -1547,21 +1547,21 @@ public final class ExoPlayerTest { Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 2); MediaSource secondSource = new FakeMediaSource(secondTimeline, ExoPlayerTestRunner.VIDEO_FORMAT); - AtomicInteger windowIndexAfterStop = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterStop = new AtomicInteger(); AtomicLong positionAfterStop = new AtomicLong(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_READY) .stop(/* reset= */ true) .waitForPlaybackState(Player.STATE_IDLE) - .seek(/* windowIndex= */ 1, /* positionMs= */ 1000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 1000) .setMediaSources(secondSource) .prepare() .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndexAfterStop.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterStop.set(player.getCurrentMediaItemIndex()); positionAfterStop.set(player.getCurrentPosition()); } }) @@ -1594,7 +1594,7 @@ public final class ExoPlayerTest { Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, // stop(true) Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); - assertThat(windowIndexAfterStop.get()).isEqualTo(1); + assertThat(mediaItemIndexAfterStop.get()).isEqualTo(1); assertThat(positionAfterStop.get()).isAtLeast(1000L); testRunner.assertPlayedPeriodIndices(0, 1); } @@ -1621,8 +1621,8 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ 2000, secondSource) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) + .setMediaSources(/* mediaItemIndex= */ 0, /* positionMs= */ 2000, secondSource) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) .executeRunnable( @@ -1675,7 +1675,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setMediaSources(/* resetPosition= */ true, secondSource) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) @@ -1732,7 +1732,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setMediaSources(secondSource) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) @@ -1892,7 +1892,7 @@ public final class ExoPlayerTest { throws Exception { ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0)); - AtomicInteger windowIndexAfterAddingSources = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterAddingSources = new AtomicInteger(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .setShuffleModeEnabled(true) @@ -1909,7 +1909,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndexAfterAddingSources.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterAddingSources.set(player.getCurrentMediaItemIndex()); } }) .build(); @@ -1920,20 +1920,20 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndexAfterAddingSources.get()).isEqualTo(1); + assertThat(mediaItemIndexAfterAddingSources.get()).isEqualTo(1); } @Test public void playbackErrorAndReprepareDoesNotResetPosition() throws Exception { final Timeline timeline = new FakeTimeline(/* windowCount= */ 2); final long[] positionHolder = new long[3]; - final int[] windowIndexHolder = new int[3]; + final int[] mediaItemIndexHolder = new int[3]; final FakeMediaSource firstMediaSource = new FakeMediaSource(timeline); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 500) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 500) .throwPlaybackException( ExoPlaybackException.createForSource( new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED)) @@ -1944,7 +1944,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while in error state positionHolder[0] = player.getCurrentPosition(); - windowIndexHolder[0] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[0] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -1954,7 +1954,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while repreparing. positionHolder[1] = player.getCurrentPosition(); - windowIndexHolder[1] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[1] = player.getCurrentMediaItemIndex(); } }) .waitForPlaybackState(Player.STATE_READY) @@ -1964,7 +1964,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position after repreparation finished. positionHolder[2] = player.getCurrentPosition(); - windowIndexHolder[2] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[2] = player.getCurrentMediaItemIndex(); } }) .play() @@ -1983,22 +1983,22 @@ public final class ExoPlayerTest { assertThat(positionHolder[0]).isAtLeast(500L); assertThat(positionHolder[1]).isEqualTo(positionHolder[0]); assertThat(positionHolder[2]).isEqualTo(positionHolder[0]); - assertThat(windowIndexHolder[0]).isEqualTo(1); - assertThat(windowIndexHolder[1]).isEqualTo(1); - assertThat(windowIndexHolder[2]).isEqualTo(1); + assertThat(mediaItemIndexHolder[0]).isEqualTo(1); + assertThat(mediaItemIndexHolder[1]).isEqualTo(1); + assertThat(mediaItemIndexHolder[2]).isEqualTo(1); } @Test public void seekAfterPlaybackError() throws Exception { final Timeline timeline = new FakeTimeline(/* windowCount= */ 2); final long[] positionHolder = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; - final int[] windowIndexHolder = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndexHolder = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final FakeMediaSource firstMediaSource = new FakeMediaSource(timeline); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 500) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 500) .throwPlaybackException( ExoPlaybackException.createForSource( new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED)) @@ -2009,10 +2009,10 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while in error state positionHolder[0] = player.getCurrentPosition(); - windowIndexHolder[0] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[0] = player.getCurrentMediaItemIndex(); } }) - .seek(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET) .waitForPendingPlayerCommands() .executeRunnable( new PlayerRunnable() { @@ -2020,7 +2020,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position while in error state positionHolder[1] = player.getCurrentPosition(); - windowIndexHolder[1] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -2030,7 +2030,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Position after prepare. positionHolder[2] = player.getCurrentPosition(); - windowIndexHolder[2] = player.getCurrentWindowIndex(); + mediaItemIndexHolder[2] = player.getCurrentMediaItemIndex(); } }) .play() @@ -2051,9 +2051,9 @@ public final class ExoPlayerTest { assertThat(positionHolder[0]).isAtLeast(500L); assertThat(positionHolder[1]).isEqualTo(0L); assertThat(positionHolder[2]).isEqualTo(0L); - assertThat(windowIndexHolder[0]).isEqualTo(1); - assertThat(windowIndexHolder[1]).isEqualTo(0); - assertThat(windowIndexHolder[2]).isEqualTo(0); + assertThat(mediaItemIndexHolder[0]).isEqualTo(1); + assertThat(mediaItemIndexHolder[1]).isEqualTo(0); + assertThat(mediaItemIndexHolder[2]).isEqualTo(0); } @Test @@ -2216,7 +2216,7 @@ public final class ExoPlayerTest { .pause() .sendMessage( (messageType, payload) -> counter.getAndIncrement(), - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ 2000, /* deleteAfterDelivery= */ false) .seek(/* positionMs= */ 2000) @@ -2290,23 +2290,23 @@ public final class ExoPlayerTest { long duration2Ms = timeline.getWindow(1, new Window()).getDurationMs(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .sendMessage(targetStartFirstPeriod, /* windowIndex= */ 0, /* positionMs= */ 0) + .sendMessage(targetStartFirstPeriod, /* mediaItemIndex= */ 0, /* positionMs= */ 0) .sendMessage( targetEndMiddlePeriodResolved, - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ duration1Ms - 1) .sendMessage( targetEndMiddlePeriodUnresolved, - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_END_OF_SOURCE) - .sendMessage(targetStartMiddlePeriod, /* windowIndex= */ 1, /* positionMs= */ 0) + .sendMessage(targetStartMiddlePeriod, /* mediaItemIndex= */ 1, /* positionMs= */ 0) .sendMessage( targetEndLastPeriodResolved, - /* windowIndex= */ 1, + /* mediaItemIndex= */ 1, /* positionMs= */ duration2Ms - 1) .sendMessage( targetEndLastPeriodUnresolved, - /* windowIndex= */ 1, + /* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_END_OF_SOURCE) .waitForMessage(targetEndLastPeriodUnresolved) .build(); @@ -2317,19 +2317,19 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(targetStartFirstPeriod.windowIndex).isEqualTo(0); + assertThat(targetStartFirstPeriod.mediaItemIndex).isEqualTo(0); assertThat(targetStartFirstPeriod.positionMs).isAtLeast(0L); - assertThat(targetEndMiddlePeriodResolved.windowIndex).isEqualTo(0); + assertThat(targetEndMiddlePeriodResolved.mediaItemIndex).isEqualTo(0); assertThat(targetEndMiddlePeriodResolved.positionMs).isAtLeast(duration1Ms - 1); - assertThat(targetEndMiddlePeriodUnresolved.windowIndex).isEqualTo(0); + assertThat(targetEndMiddlePeriodUnresolved.mediaItemIndex).isEqualTo(0); assertThat(targetEndMiddlePeriodUnresolved.positionMs).isAtLeast(duration1Ms - 1); assertThat(targetEndMiddlePeriodResolved.positionMs) .isEqualTo(targetEndMiddlePeriodUnresolved.positionMs); - assertThat(targetStartMiddlePeriod.windowIndex).isEqualTo(1); + assertThat(targetStartMiddlePeriod.mediaItemIndex).isEqualTo(1); assertThat(targetStartMiddlePeriod.positionMs).isAtLeast(0L); - assertThat(targetEndLastPeriodResolved.windowIndex).isEqualTo(1); + assertThat(targetEndLastPeriodResolved.mediaItemIndex).isEqualTo(1); assertThat(targetEndLastPeriodResolved.positionMs).isAtLeast(duration2Ms - 1); - assertThat(targetEndLastPeriodUnresolved.windowIndex).isEqualTo(1); + assertThat(targetEndLastPeriodUnresolved.mediaItemIndex).isEqualTo(1); assertThat(targetEndLastPeriodUnresolved.positionMs).isAtLeast(duration2Ms - 1); assertThat(targetEndLastPeriodResolved.positionMs) .isEqualTo(targetEndLastPeriodUnresolved.positionMs); @@ -2445,12 +2445,12 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_BUFFERING) .sendMessage( target, - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ 50, /* deleteAfterDelivery= */ false) .setRepeatMode(Player.REPEAT_MODE_ALL) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 1) - .playUntilStartOfWindow(/* windowIndex= */ 0) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 0) .setRepeatMode(Player.REPEAT_MODE_OFF) .play() .build(); @@ -2464,7 +2464,7 @@ public final class ExoPlayerTest { } @Test - public void sendMessagesMoveCurrentWindowIndex() throws Exception { + public void sendMessagesMoveCurrentMediaItemIndex() throws Exception { Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0)); final Timeline secondTimeline = @@ -2492,7 +2492,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); assertThat(target.positionMs).isAtLeast(50L); - assertThat(target.windowIndex).isEqualTo(1); + assertThat(target.mediaItemIndex).isEqualTo(1); } @Test @@ -2503,7 +2503,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50) + .sendMessage(target, /* mediaItemIndex = */ 2, /* positionMs= */ 50) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2512,7 +2512,7 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target.windowIndex).isEqualTo(2); + assertThat(target.mediaItemIndex).isEqualTo(2); assertThat(target.positionMs).isAtLeast(50L); } @@ -2525,7 +2525,7 @@ public final class ExoPlayerTest { .pause() .waitForTimelineChanged( timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50) + .sendMessage(target, /* mediaItemIndex = */ 2, /* positionMs= */ 50) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2534,12 +2534,12 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target.windowIndex).isEqualTo(2); + assertThat(target.mediaItemIndex).isEqualTo(2); assertThat(target.positionMs).isAtLeast(50L); } @Test - public void sendMessagesMoveWindowIndex() throws Exception { + public void sendMessagesMoveMediaItemIndex() throws Exception { Timeline timeline = new FakeTimeline( new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0), @@ -2556,11 +2556,11 @@ public final class ExoPlayerTest { .pause() .waitForTimelineChanged( timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .sendMessage(target, /* windowIndex = */ 1, /* positionMs= */ 50) + .sendMessage(target, /* mediaItemIndex = */ 1, /* positionMs= */ 50) .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline)) .waitForTimelineChanged( secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - .seek(/* windowIndex= */ 0, /* positionMs= */ 0) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 0) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2570,7 +2570,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); assertThat(target.positionMs).isAtLeast(50L); - assertThat(target.windowIndex).isEqualTo(0); + assertThat(target.mediaItemIndex).isEqualTo(0); } @Test @@ -2590,11 +2590,11 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .sendMessage(target1, /* windowIndex = */ 0, /* positionMs= */ 50) - .sendMessage(target2, /* windowIndex = */ 1, /* positionMs= */ 50) - .sendMessage(target3, /* windowIndex = */ 2, /* positionMs= */ 50) + .sendMessage(target1, /* mediaItemIndex = */ 0, /* positionMs= */ 50) + .sendMessage(target2, /* mediaItemIndex = */ 1, /* positionMs= */ 50) + .sendMessage(target3, /* mediaItemIndex = */ 2, /* positionMs= */ 50) .setShuffleModeEnabled(true) - .seek(/* windowIndex= */ 2, /* positionMs= */ 0) + .seek(/* mediaItemIndex= */ 2, /* positionMs= */ 0) .play() .build(); new ExoPlayerTestRunner.Builder(context) @@ -2603,9 +2603,9 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target1.windowIndex).isEqualTo(0); - assertThat(target2.windowIndex).isEqualTo(1); - assertThat(target3.windowIndex).isEqualTo(2); + assertThat(target1.mediaItemIndex).isEqualTo(0); + assertThat(target2.mediaItemIndex).isEqualTo(1); + assertThat(target3.mediaItemIndex).isEqualTo(2); } @Test @@ -2625,7 +2625,7 @@ public final class ExoPlayerTest { } }) // Play a bit to ensure message arrived in internal player. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 30) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 30) .executeRunnable(() -> message.get().cancel()) .play() .build(); @@ -2659,7 +2659,7 @@ public final class ExoPlayerTest { } }) // Play until the message has been delivered. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 51) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 51) // Seek back, cancel the message, and play past the same position again. .seek(/* positionMs= */ 0) .executeRunnable(() -> message.get().cancel()) @@ -2681,13 +2681,13 @@ public final class ExoPlayerTest { player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); player .createMessage((messageType, payload) -> {}) - .setPosition(/* windowIndex= */ 0, /* positionMs= */ 0) + .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 0) .setDeleteAfterDelivery(false) .send(); PlayerMessage.Target secondMediaItemTarget = mock(PlayerMessage.Target.class); player .createMessage(secondMediaItemTarget) - .setPosition(/* windowIndex= */ 1, /* positionMs= */ 0) + .setPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 0) .setDeleteAfterDelivery(false) .send(); @@ -2765,7 +2765,7 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_READY) // Ensure next period is pre-buffered by playing until end of first period. .playUntilPosition( - /* windowIndex= */ 0, + /* mediaItemIndex= */ 0, /* positionMs= */ Util.usToMs(TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US)) .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2)) .waitForTimelineChanged( @@ -2921,12 +2921,12 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .seek(/* windowIndex= */ 0, /* positionMs= */ 9999) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 9999) // Wait after each seek until the internal player has updated its state. .waitForPendingPlayerCommands() - .seek(/* windowIndex= */ 0, /* positionMs= */ 1) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 1) .waitForPendingPlayerCommands() - .seek(/* windowIndex= */ 0, /* positionMs= */ 9999) + .seek(/* mediaItemIndex= */ 0, /* positionMs= */ 9999) .waitForPendingPlayerCommands() .play() .build(); @@ -2988,7 +2988,7 @@ public final class ExoPlayerTest { } catch (InterruptedException e) { throw new IllegalStateException(e); } - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1000L); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1000L); } }) .waitForPendingPlayerCommands() @@ -3254,8 +3254,8 @@ public final class ExoPlayerTest { .setRepeatMode(Player.REPEAT_MODE_ALL) .waitForPlaybackState(Player.STATE_READY) // Play until the media repeats once. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 1) - .playUntilStartOfWindow(/* windowIndex= */ 0) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 1) + .playUntilStartOfMediaItem(/* mediaItemIndex= */ 0) .setRepeatMode(Player.REPEAT_MODE_OFF) .play() .build(); @@ -3289,7 +3289,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) - .seek(/* windowIndex= */ 1, /* positionMs= */ 0) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 0) .waitForPendingPlayerCommands() .play() .build(); @@ -3337,7 +3337,7 @@ public final class ExoPlayerTest { .pause() .waitForPlaybackState(Player.STATE_READY) // Play almost to end to ensure the current period is fully buffered. - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 90) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 90) // Enable repeat mode to trigger the creation of new media periods. .setRepeatMode(Player.REPEAT_MODE_ALL) // Remove the media source. @@ -3852,20 +3852,20 @@ public final class ExoPlayerTest { return Timeline.EMPTY; } }; - int[] currentWindowIndices = new int[1]; + int[] currentMediaItemIndices = new int[1]; long[] currentPlaybackPositions = new long[1]; long[] windowCounts = new long[1]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .seek(/* windowIndex= */ 1, /* positionMs= */ 5000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 5000) .waitForTimelineChanged( /* expectedTimeline= */ null, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); windowCounts[0] = player.getCurrentTimeline().getWindowCount(); } @@ -3882,23 +3882,23 @@ public final class ExoPlayerTest { exoPlayerTestRunner.assertTimelineChangeReasonsEqual( Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); assertArrayEquals(new long[] {2}, windowCounts); - assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices); + assertArrayEquals(new int[] {seekToMediaItemIndex}, currentMediaItemIndices); assertArrayEquals(new long[] {5_000}, currentPlaybackPositions); } @SuppressWarnings("deprecation") @Test - public void seekTo_windowIndexIsReset_deprecated() throws Exception { + public void seekTo_mediaItemIndexIsReset_deprecated() throws Exception { FakeTimeline fakeTimeline = new FakeTimeline(); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); - final int[] windowIndex = {C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET}; final long[] positionMs = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 3000) .executeRunnable( new PlayerRunnable() { @Override @@ -3917,7 +3917,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); positionMs[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } @@ -3930,7 +3930,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(3000L); assertThat(positionMs[1]).isEqualTo(7000L); assertThat(positionMs[2]).isEqualTo(7000L); @@ -3941,17 +3941,17 @@ public final class ExoPlayerTest { } @Test - public void seekTo_windowIndexIsReset() throws Exception { + public void seekTo_mediaItemIndexIsReset() throws Exception { FakeTimeline fakeTimeline = new FakeTimeline(); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); - final int[] windowIndex = {C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET}; final long[] positionMs = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() - .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 3000) .pause() .executeRunnable( new PlayerRunnable() { @@ -3970,7 +3970,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); positionMs[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } @@ -3983,7 +3983,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(3000); assertThat(positionMs[1]).isEqualTo(7000); assertThat(positionMs[2]).isEqualTo(7000); @@ -3995,7 +3995,7 @@ public final class ExoPlayerTest { @Test public void seekTo_singlePeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4007,19 +4007,19 @@ public final class ExoPlayerTest { player.seekTo(9000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(9000); assertThat(bufferedPositions[0]).isEqualTo(9200); assertThat(totalBufferedDuration[0]).isEqualTo(200); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(9200); assertThat(totalBufferedDuration[1]).isEqualTo(200); @@ -4027,7 +4027,7 @@ public final class ExoPlayerTest { @Test public void seekTo_singlePeriod_beyondBufferedData_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4039,19 +4039,19 @@ public final class ExoPlayerTest { player.seekTo(9200); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(9200); assertThat(bufferedPositions[0]).isEqualTo(9200); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(9200); assertThat(totalBufferedDuration[1]).isEqualTo(0); @@ -4059,7 +4059,7 @@ public final class ExoPlayerTest { @Test public void seekTo_backwardsSinglePeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4071,14 +4071,14 @@ public final class ExoPlayerTest { player.seekTo(1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(1000); assertThat(bufferedPositions[0]).isEqualTo(1000); assertThat(totalBufferedDuration[0]).isEqualTo(0); @@ -4086,7 +4086,7 @@ public final class ExoPlayerTest { @Test public void seekTo_backwardsMultiplePeriods_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4098,8 +4098,8 @@ public final class ExoPlayerTest { player.seekTo(0, 1000); } }, - /* pauseWindowIndex= */ 1, - windowIndex, + /* pauseMediaItemIndex= */ 1, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4107,7 +4107,7 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(1000); assertThat(bufferedPositions[0]).isEqualTo(1000); assertThat(totalBufferedDuration[0]).isEqualTo(0); @@ -4115,7 +4115,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toUnbufferedPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4127,8 +4127,8 @@ public final class ExoPlayerTest { player.seekTo(2, 1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4136,12 +4136,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0)); - assertThat(windowIndex[0]).isEqualTo(2); + assertThat(mediaItemIndex[0]).isEqualTo(2); assertThat(positionMs[0]).isEqualTo(1000); assertThat(bufferedPositions[0]).isEqualTo(1000); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(2); + assertThat(mediaItemIndex[1]).isEqualTo(2); assertThat(positionMs[1]).isEqualTo(1000); assertThat(bufferedPositions[1]).isEqualTo(1000); assertThat(totalBufferedDuration[1]).isEqualTo(0); @@ -4149,7 +4149,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toLoadingPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4161,22 +4161,22 @@ public final class ExoPlayerTest { player.seekTo(1, 1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), new FakeMediaSource()); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(1000); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(10_000); // assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4185,7 +4185,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toLoadingPeriod_withinPartiallyBufferedData_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4197,22 +4197,22 @@ public final class ExoPlayerTest { player.seekTo(1, 1000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(1000); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(1000); // assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(4000); assertThat(totalBufferedDuration[1]).isEqualTo(3000); @@ -4220,7 +4220,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toLoadingPeriod_beyondBufferedData_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4232,20 +4232,20 @@ public final class ExoPlayerTest { player.seekTo(1, 5000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(5000); assertThat(bufferedPositions[0]).isEqualTo(5000); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(1); + assertThat(mediaItemIndex[1]).isEqualTo(1); assertThat(positionMs[1]).isEqualTo(5000); assertThat(bufferedPositions[1]).isEqualTo(5000); assertThat(totalBufferedDuration[1]).isEqualTo(0); @@ -4253,7 +4253,7 @@ public final class ExoPlayerTest { @Test public void seekTo_toInnerFullyBufferedPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4265,8 +4265,8 @@ public final class ExoPlayerTest { player.seekTo(1, 5000); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4274,14 +4274,14 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(1); + assertThat(mediaItemIndex[0]).isEqualTo(1); assertThat(positionMs[0]).isEqualTo(5000); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(10_000); // assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4289,7 +4289,7 @@ public final class ExoPlayerTest { @Test public void addMediaSource_withinBufferedPeriods_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4302,20 +4302,20 @@ public final class ExoPlayerTest { /* index= */ 1, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0)); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4323,7 +4323,7 @@ public final class ExoPlayerTest { @Test public void moveMediaItem_behindLoadingPeriod_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4335,8 +4335,8 @@ public final class ExoPlayerTest { player.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 2); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4344,12 +4344,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4357,7 +4357,7 @@ public final class ExoPlayerTest { @Test public void moveMediaItem_undloadedBehindPlaying_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4369,8 +4369,8 @@ public final class ExoPlayerTest { player.moveMediaItem(/* currentIndex= */ 3, /* newIndex= */ 1); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4379,12 +4379,12 @@ public final class ExoPlayerTest { createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4392,7 +4392,7 @@ public final class ExoPlayerTest { @Test public void removeMediaItem_removePlayingWindow_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4404,22 +4404,22 @@ public final class ExoPlayerTest { player.removeMediaItem(/* index= */ 0); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(0); // TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully // covered. // assertThat(bufferedPositions[0]).isEqualTo(4000); // assertThat(totalBufferedDuration[0]).isEqualTo(4000); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(4000); assertThat(totalBufferedDuration[1]).isEqualTo(4000); @@ -4427,7 +4427,7 @@ public final class ExoPlayerTest { @Test public void removeMediaItem_removeLoadingWindow_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4439,8 +4439,8 @@ public final class ExoPlayerTest { player.removeMediaItem(/* index= */ 2); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4448,12 +4448,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isAtLeast(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]); @@ -4462,7 +4462,7 @@ public final class ExoPlayerTest { @Test public void removeMediaItem_removeInnerFullyBufferedWindow_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4474,8 +4474,8 @@ public final class ExoPlayerTest { player.removeMediaItem(/* index= */ 1); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4483,12 +4483,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(0); + assertThat(mediaItemIndex[1]).isEqualTo(0); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(10_000); assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[0]); @@ -4496,7 +4496,7 @@ public final class ExoPlayerTest { @Test public void clearMediaItems_correctMaskingPosition() throws Exception { - final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] mediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET}; @@ -4508,8 +4508,8 @@ public final class ExoPlayerTest { player.clearMediaItems(); } }, - /* pauseWindowIndex= */ 0, - windowIndex, + /* pauseMediaItemIndex= */ 0, + mediaItemIndex, positionMs, bufferedPositions, totalBufferedDuration, @@ -4517,12 +4517,12 @@ public final class ExoPlayerTest { new FakeMediaSource(), createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(positionMs[0]).isEqualTo(0); assertThat(bufferedPositions[0]).isEqualTo(0); assertThat(totalBufferedDuration[0]).isEqualTo(0); - assertThat(windowIndex[1]).isEqualTo(windowIndex[0]); + assertThat(mediaItemIndex[1]).isEqualTo(mediaItemIndex[0]); assertThat(positionMs[1]).isEqualTo(positionMs[0]); assertThat(bufferedPositions[1]).isEqualTo(bufferedPositions[0]); assertThat(totalBufferedDuration[1]).isEqualTo(totalBufferedDuration[0]); @@ -4530,8 +4530,8 @@ public final class ExoPlayerTest { private void runPositionMaskingCapturingActionSchedule( PlayerRunnable actionRunnable, - int pauseWindowIndex, - int[] windowIndex, + int pauseMediaItemIndex, + int[] mediaItemIndex, long[] positionMs, long[] bufferedPosition, long[] totalBufferedDuration, @@ -4539,13 +4539,13 @@ public final class ExoPlayerTest { throws Exception { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .playUntilPosition(pauseWindowIndex, /* positionMs= */ 8000) + .playUntilPosition(pauseMediaItemIndex, /* positionMs= */ 8000) .executeRunnable(actionRunnable) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); positionMs[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); totalBufferedDuration[0] = player.getTotalBufferedDuration(); @@ -4556,7 +4556,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[1] = player.getCurrentWindowIndex(); + mediaItemIndex[1] = player.getCurrentMediaItemIndex(); positionMs[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); totalBufferedDuration[1] = player.getTotalBufferedDuration(); @@ -4637,7 +4637,7 @@ public final class ExoPlayerTest { /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline); - int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + int[] mediaItemIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; long[] positionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET}; long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET}; long[] totalBufferedDurationMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET}; @@ -4653,7 +4653,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { player.addMediaSource(/* index= */ 1, new FakeMediaSource()); - windowIndex[0] = player.getCurrentWindowIndex(); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); isPlayingAd[0] = player.isPlayingAd(); positionMs[0] = player.getCurrentPosition(); bufferedPositionMs[0] = player.getBufferedPosition(); @@ -4665,21 +4665,21 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[1] = player.getCurrentWindowIndex(); + mediaItemIndex[1] = player.getCurrentMediaItemIndex(); isPlayingAd[1] = player.isPlayingAd(); positionMs[1] = player.getCurrentPosition(); bufferedPositionMs[1] = player.getBufferedPosition(); totalBufferedDurationMs[1] = player.getTotalBufferedDuration(); } }) - .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 8000) + .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 8000) .waitForPendingPlayerCommands() .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { player.addMediaSource(new FakeMediaSource()); - windowIndex[2] = player.getCurrentWindowIndex(); + mediaItemIndex[2] = player.getCurrentMediaItemIndex(); isPlayingAd[2] = player.isPlayingAd(); positionMs[2] = player.getCurrentPosition(); bufferedPositionMs[2] = player.getBufferedPosition(); @@ -4697,19 +4697,19 @@ public final class ExoPlayerTest { .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(isPlayingAd[0]).isTrue(); assertThat(positionMs[0]).isAtMost(adDurationMs); assertThat(bufferedPositionMs[0]).isEqualTo(adDurationMs); assertThat(totalBufferedDurationMs[0]).isAtLeast(adDurationMs - positionMs[0]); - assertThat(windowIndex[1]).isEqualTo(0); + assertThat(mediaItemIndex[1]).isEqualTo(0); assertThat(isPlayingAd[1]).isTrue(); assertThat(positionMs[1]).isAtMost(adDurationMs); assertThat(bufferedPositionMs[1]).isEqualTo(adDurationMs); assertThat(totalBufferedDurationMs[1]).isAtLeast(adDurationMs - positionMs[1]); - assertThat(windowIndex[2]).isEqualTo(0); + assertThat(mediaItemIndex[2]).isEqualTo(0); assertThat(isPlayingAd[2]).isFalse(); assertThat(positionMs[2]).isEqualTo(8000); assertThat(bufferedPositionMs[2]).isEqualTo(contentDurationMs); @@ -4738,7 +4738,7 @@ public final class ExoPlayerTest { /* durationUs= */ Util.msToUs(contentDurationMs), adPlaybackState)); FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline); - int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET}; + int[] mediaItemIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET}; long[] positionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET}; long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET}; long[] totalBufferedDurationMs = new long[] {C.TIME_UNSET, C.TIME_UNSET}; @@ -4753,8 +4753,8 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 8000); - windowIndex[0] = player.getCurrentWindowIndex(); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 8000); + mediaItemIndex[0] = player.getCurrentMediaItemIndex(); isPlayingAd[0] = player.isPlayingAd(); positionMs[0] = player.getCurrentPosition(); bufferedPositionMs[0] = player.getBufferedPosition(); @@ -4766,7 +4766,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndex[1] = player.getCurrentWindowIndex(); + mediaItemIndex[1] = player.getCurrentMediaItemIndex(); isPlayingAd[1] = player.isPlayingAd(); positionMs[1] = player.getCurrentPosition(); bufferedPositionMs[1] = player.getBufferedPosition(); @@ -4784,13 +4784,13 @@ public final class ExoPlayerTest { .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndex[0]).isEqualTo(0); + assertThat(mediaItemIndex[0]).isEqualTo(0); assertThat(isPlayingAd[0]).isTrue(); assertThat(positionMs[0]).isEqualTo(0); assertThat(bufferedPositionMs[0]).isEqualTo(adDurationMs); assertThat(totalBufferedDurationMs[0]).isEqualTo(adDurationMs); - assertThat(windowIndex[1]).isEqualTo(0); + assertThat(mediaItemIndex[1]).isEqualTo(0); assertThat(isPlayingAd[1]).isTrue(); assertThat(positionMs[1]).isEqualTo(0); assertThat(bufferedPositionMs[1]).isEqualTo(adDurationMs); @@ -5332,7 +5332,7 @@ public final class ExoPlayerTest { .addMediaSources(new FakeMediaSource()) .executeRunnable( new PlaybackStateCollector(/* index= */ 3, playbackStates, timelineWindowCounts)) - .seek(/* windowIndex= */ 1, /* positionMs= */ 2000) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 2000) .prepare() // The first expected buffering state arrives after prepare but not before. .waitForPlaybackState(Player.STATE_BUFFERING) @@ -5502,7 +5502,7 @@ public final class ExoPlayerTest { @Test public void prepareWithInvalidInitialSeek_expectEndedImmediately() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -5510,7 +5510,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -5518,7 +5518,7 @@ public final class ExoPlayerTest { ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, C.TIME_UNSET) .setActionSchedule(actionSchedule) .build() .start() @@ -5528,7 +5528,7 @@ public final class ExoPlayerTest { exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED); exoPlayerTestRunner.assertTimelinesSame(); exoPlayerTestRunner.assertTimelineChangeReasonsEqual(); - assertArrayEquals(new int[] {1}, currentWindowIndices); + assertArrayEquals(new int[] {1}, currentMediaItemIndices); } @Test @@ -5564,9 +5564,9 @@ public final class ExoPlayerTest { /* isAtomic= */ false, new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT), new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT)); - int[] currentWindowIndices = new int[1]; + int[] currentMediaItemIndices = new int[1]; long[] currentPlaybackPositions = new long[1]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_BUFFERING) @@ -5575,21 +5575,21 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); } }) .build(); new ExoPlayerTestRunner.Builder(context) .setMediaSources(concatenatingMediaSource) - .initialSeek(seekToWindowIndex, 5000) + .initialSeek(seekToMediaItemIndex, 5000) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); assertArrayEquals(new long[] {5_000}, currentPlaybackPositions); - assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices); + assertArrayEquals(new int[] {seekToMediaItemIndex}, currentMediaItemIndices); } @Test @@ -5600,10 +5600,10 @@ public final class ExoPlayerTest { /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 10_000_000)); ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false); - int[] currentWindowIndices = new int[2]; + int[] currentMediaItemIndices = new int[2]; long[] currentPlaybackPositions = new long[2]; long[] windowCounts = new long[2]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -5611,7 +5611,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); windowCounts[0] = player.getCurrentTimeline().getWindowCount(); } @@ -5627,7 +5627,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[1] = player.getCurrentPosition(); windowCounts[1] = player.getCurrentTimeline().getWindowCount(); } @@ -5635,14 +5635,15 @@ public final class ExoPlayerTest { .build(); new ExoPlayerTestRunner.Builder(context) .setMediaSources(concatenatingMediaSource) - .initialSeek(seekToWindowIndex, 5000) + .initialSeek(seekToMediaItemIndex, 5000) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); assertArrayEquals(new long[] {0, 2}, windowCounts); - assertArrayEquals(new int[] {seekToWindowIndex, seekToWindowIndex}, currentWindowIndices); + assertArrayEquals( + new int[] {seekToMediaItemIndex, seekToMediaItemIndex}, currentMediaItemIndices); assertArrayEquals(new long[] {5_000, 5_000}, currentPlaybackPositions); } @@ -5671,10 +5672,10 @@ public final class ExoPlayerTest { /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 10_000_000)); ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false); - int[] currentWindowIndices = new int[2]; + int[] currentMediaItemIndices = new int[2]; long[] currentPlaybackPositions = new long[2]; long[] windowCounts = new long[2]; - int seekToWindowIndex = 1; + int seekToMediaItemIndex = 1; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -5682,7 +5683,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[0] = player.getCurrentPosition(); windowCounts[0] = player.getCurrentTimeline().getWindowCount(); } @@ -5698,7 +5699,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPlaybackPositions[1] = player.getCurrentPosition(); windowCounts[1] = player.getCurrentTimeline().getWindowCount(); } @@ -5707,14 +5708,15 @@ public final class ExoPlayerTest { new ExoPlayerTestRunner.Builder(context) .setMediaSources(concatenatingMediaSource) .setUseLazyPreparation(/* useLazyPreparation= */ true) - .initialSeek(seekToWindowIndex, 5000) + .initialSeek(seekToMediaItemIndex, 5000) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); assertArrayEquals(new long[] {0, 2}, windowCounts); - assertArrayEquals(new int[] {seekToWindowIndex, seekToWindowIndex}, currentWindowIndices); + assertArrayEquals( + new int[] {seekToMediaItemIndex, seekToMediaItemIndex}, currentMediaItemIndices); assertArrayEquals(new long[] {5_000, 5_000}, currentPlaybackPositions); } @@ -5875,19 +5877,19 @@ public final class ExoPlayerTest { } @Test - public void setMediaSources_empty_whenEmpty_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void setMediaSources_empty_whenEmpty_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); List listOfTwo = ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()); player.addMediaSources(/* index= */ 0, listOfTwo); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -5896,7 +5898,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -5907,12 +5909,12 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {0, 0, 0}, currentMediaItemIndices); } @Test public void setMediaItems_resetPosition_resetsPosition() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) @@ -5921,14 +5923,14 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1000); - currentWindowIndices[0] = player.getCurrentWindowIndex(); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1000); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); List listOfTwo = ImmutableList.of( MediaItem.fromUri(Uri.EMPTY), MediaItem.fromUri(Uri.EMPTY)); player.setMediaItems(listOfTwo, /* resetPosition= */ true); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); } }) @@ -5942,14 +5944,14 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); assertArrayEquals(new long[] {1000, 0}, currentPositions); } @Test - public void setMediaSources_empty_whenEmpty_validInitialSeek_correctMaskingWindowIndex() + public void setMediaSources_empty_whenEmpty_validInitialSeek_correctMaskingMediaItemIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) // Wait for initial seek to be fully handled by internal player. @@ -5959,11 +5961,11 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); List listOfTwo = ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()); player.addMediaSources(/* index= */ 0, listOfTwo); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -5972,25 +5974,25 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1, 1}, currentMediaItemIndices); } @Test - public void setMediaSources_empty_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex() + public void setMediaSources_empty_whenEmpty_invalidInitialSeek_correctMaskingMediaItemIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) // Wait for initial seek to be fully handled by internal player. @@ -6000,11 +6002,11 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); List listOfTwo = ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()); player.addMediaSources(/* index= */ 0, listOfTwo); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -6013,24 +6015,26 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 4, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 4, C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {4, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {4, 0, 0}, currentMediaItemIndices); } @Test - public void setMediaSources_whenEmpty_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void setMediaSources_whenEmpty_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = { + C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET + }; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_READY) @@ -6038,18 +6042,18 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Increase current window index. + // Increase current media item index. player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Current window index is unchanged. + // Current media item index is unchanged. player.addMediaSource(/* index= */ 2, new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6059,9 +6063,9 @@ public final class ExoPlayerTest { MediaSource mediaSource = new FakeMediaSource(); ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(mediaSource, mediaSource, mediaSource); - // Increase current window with multi window source. + // Increase current media item with multi media item source. player.addMediaSource(/* index= */ 0, concatenatingMediaSource); - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6070,9 +6074,9 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(); - // Current window index is unchanged when adding empty source. + // Current media item index is unchanged when adding empty source. player.addMediaSource(/* index= */ 0, concatenatingMediaSource); - currentWindowIndices[3] = player.getCurrentWindowIndex(); + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -6083,7 +6087,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1, 4, 4}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1, 4, 4}, currentMediaItemIndices); } @Test @@ -6092,7 +6096,7 @@ public final class ExoPlayerTest { MediaSource firstMediaSource = new FakeMediaSource(firstTimeline); Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object()); MediaSource secondMediaSource = new FakeMediaSource(secondTimeline); - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = @@ -6104,12 +6108,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); - // Increase current window index. + // Increase current media item index. player.addMediaSource(/* index= */ 0, secondMediaSource); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); } @@ -6120,28 +6124,28 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); currentPositions[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, 2000) + .initialSeek(/* mediaItemIndex= */ 1, 2000) .setMediaSources(firstMediaSource) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 2, 2}, currentWindowIndices); + assertArrayEquals(new int[] {1, 2, 2}, currentMediaItemIndices); assertArrayEquals(new long[] {2000, 2000, 2000}, currentPositions); assertArrayEquals(new long[] {2000, 2000, 2000}, bufferedPositions); } @Test public void setMediaSources_whenEmpty_invalidInitialSeek_correctMasking() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = @@ -6153,12 +6157,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); - // Increase current window index. + // Increase current media item index. player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); } @@ -6169,7 +6173,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); currentPositions[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); } @@ -6177,21 +6181,21 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_ENDED) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, 2000) + .initialSeek(/* mediaItemIndex= */ 1, 2000) .setMediaSources(new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {0, 1, 1}, currentMediaItemIndices); assertArrayEquals(new long[] {0, 0, 0}, currentPositions); assertArrayEquals(new long[] {0, 0, 0}, bufferedPositions); } @Test - public void setMediaSources_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void setMediaSources_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForTimelineChanged() @@ -6199,10 +6203,10 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); - // Increase current window index. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); + // Increase current media item index. player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .waitForTimelineChanged() @@ -6210,7 +6214,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -6221,7 +6225,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {0, 1, 1}, currentMediaItemIndices); } @Test @@ -6278,7 +6282,7 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED); assertArrayEquals( new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE}, @@ -6314,14 +6318,14 @@ public final class ExoPlayerTest { .build(); ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); @@ -6356,7 +6360,7 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); @@ -6392,7 +6396,7 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); @@ -6464,7 +6468,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_ENDED, Player.STATE_BUFFERING, @@ -6509,14 +6513,14 @@ public final class ExoPlayerTest { .build(); ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); exoPlayerTestRunner.assertTimelineChangeReasonsEqual( @@ -6552,7 +6556,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); @@ -6591,7 +6595,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); @@ -6625,7 +6629,8 @@ public final class ExoPlayerTest { } }) .waitForPlaybackState(Player.STATE_ENDED) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @@ -6639,7 +6644,8 @@ public final class ExoPlayerTest { } }) .waitForPlaybackState(Player.STATE_ENDED) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @@ -6652,7 +6658,8 @@ public final class ExoPlayerTest { } }) .waitForPlaybackState(Player.STATE_READY) - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @@ -6676,7 +6683,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, // Ready after initial prepare. @@ -6737,7 +6744,8 @@ public final class ExoPlayerTest { } }) .waitForTimelineChanged() - .setMediaSources(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) + .setMediaSources( + /* mediaItemIndex= */ 0, /* positionMs= */ C.TIME_UNSET, firstMediaSource) .waitForPlaybackState(Player.STATE_READY) .play() .waitForPlaybackState(Player.STATE_ENDED) @@ -6745,14 +6753,14 @@ public final class ExoPlayerTest { ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) .setExpectedPlayerEndedCount(/* expectedPlayerEndedCount= */ 2) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new ConcatenatingMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_ENDED, // Empty source has been prepared. Player.STATE_BUFFERING, // After setting another source. @@ -6788,7 +6796,7 @@ public final class ExoPlayerTest { .build(); new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 0, /* positionMs= */ 2000) + .initialSeek(/* mediaItemIndex= */ 0, /* positionMs= */ 2000) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) @@ -6800,8 +6808,8 @@ public final class ExoPlayerTest { @Test public void addMediaSources_skipSettingMediaItems_validInitialSeek_correctMasking() throws Exception { - final int[] currentWindowIndices = new int[5]; - Arrays.fill(currentWindowIndices, C.INDEX_UNSET); + final int[] currentMediaItemIndices = new int[5]; + Arrays.fill(currentMediaItemIndices, C.INDEX_UNSET); final long[] currentPositions = new long[3]; Arrays.fill(currentPositions, C.TIME_UNSET); final long[] bufferedPositions = new long[3]; @@ -6815,18 +6823,18 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); // If the timeline is empty masking variables are used. currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); player.addMediaSource(/* index= */ 0, new ConcatenatingMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); player.addMediaSource( /* index= */ 0, new FakeMediaSource(new FakeTimeline(/* windowCount= */ 2))); - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - currentWindowIndices[3] = player.getCurrentWindowIndex(); + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); // With a non-empty timeline, we mask the periodId in the playback info. currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); @@ -6838,7 +6846,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[4] = player.getCurrentWindowIndex(); + currentMediaItemIndices[4] = player.getCurrentMediaItemIndex(); // Finally original playbackInfo coming from EPII is used. currentPositions[2] = player.getCurrentPosition(); bufferedPositions[2] = player.getBufferedPosition(); @@ -6847,13 +6855,13 @@ public final class ExoPlayerTest { .build(); new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 1, 2000) + .initialSeek(/* mediaItemIndex= */ 1, 2000) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1, 1, 2, 2}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1, 1, 2, 2}, currentMediaItemIndices); assertThat(currentPositions[0]).isEqualTo(2000); assertThat(currentPositions[1]).isEqualTo(2000); assertThat(currentPositions[2]).isAtLeast(2000); @@ -6864,9 +6872,9 @@ public final class ExoPlayerTest { @Test public void - testAddMediaSources_skipSettingMediaItems_invalidInitialSeek_correctMaskingWindowIndex() + testAddMediaSources_skipSettingMediaItems_invalidInitialSeek_correctMaskingMediaItemIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) // Wait for initial seek to be fully handled by internal player. @@ -6876,9 +6884,9 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); player.addMediaSource(new FakeMediaSource()); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -6887,28 +6895,28 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) .skipSettingMediaSources() - .initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, C.TIME_UNSET) .setActionSchedule(actionSchedule) .build() .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0, 0}, currentMediaItemIndices); } @Test - public void moveMediaItems_correctMaskingWindowIndex() throws Exception { + public void moveMediaItems_correctMaskingMediaItemIndex() throws Exception { Timeline timeline = new FakeTimeline(); MediaSource firstMediaSource = new FakeMediaSource(timeline); MediaSource secondMediaSource = new FakeMediaSource(timeline); MediaSource thirdMediaSource = new FakeMediaSource(timeline); - final int[] currentWindowIndices = { + final int[] currentMediaItemIndices = { C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET }; ActionSchedule actionSchedule = @@ -6920,7 +6928,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Move the current item down in the playlist. player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 1); - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6929,17 +6937,17 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Move the current item up in the playlist. player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 0); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) - .seek(/* windowIndex= */ 2, C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 2, C.TIME_UNSET) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { // Move items from before to behind the current item. player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 1); - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6948,7 +6956,7 @@ public final class ExoPlayerTest { public void run(ExoPlayer player) { // Move items from behind to before the current item. player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 0); - currentWindowIndices[3] = player.getCurrentWindowIndex(); + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); } }) .executeRunnable( @@ -6956,20 +6964,20 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { // Move items from before to before the current item. - // No change in currentWindowIndex. + // No change in currentMediaItemIndex. player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1, /* newIndex= */ 1); - currentWindowIndices[4] = player.getCurrentWindowIndex(); + currentMediaItemIndices[4] = player.getCurrentMediaItemIndex(); } }) - .seek(/* windowIndex= */ 0, C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 0, C.TIME_UNSET) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { // Move items from behind to behind the current item. - // No change in currentWindowIndex. + // No change in currentMediaItemIndex. player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, /* newIndex= */ 2); - currentWindowIndices[5] = player.getCurrentWindowIndex(); + currentMediaItemIndices[5] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -6980,12 +6988,12 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0, 0, 2, 2, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0, 0, 2, 2, 0}, currentMediaItemIndices); } @Test - public void moveMediaItems_unprepared_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; + public void moveMediaItems_unprepared_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForTimelineChanged() @@ -6993,10 +7001,10 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Increase current window index. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + // Increase current media item index. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); player.moveMediaItem(/* currentIndex= */ 0, /* newIndex= */ 1); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .prepare() @@ -7005,7 +7013,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[2] = player.getCurrentWindowIndex(); + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); } }) .build(); @@ -7016,12 +7024,12 @@ public final class ExoPlayerTest { .start(/* doPrepare= */ false) .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {0, 1, 1}, currentMediaItemIndices); } @Test - public void removeMediaItems_correctMaskingWindowIndex() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + public void removeMediaItems_correctMaskingMediaItemIndex() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_BUFFERING) @@ -7029,27 +7037,27 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Decrease current window index. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + // Decrease current media item index. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); player.removeMediaItem(/* index= */ 0); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); } @Test public void removeMediaItems_currentItemRemoved_correctMasking() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET}; ActionSchedule actionSchedule = @@ -7060,25 +7068,25 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { // Remove the current item. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); player.removeMediaItem(/* index= */ 1); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPositions[1] = player.getCurrentPosition(); bufferedPositions[1] = player.getBufferedPosition(); } }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ 5000) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ 5000) .setMediaSources(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 1}, currentWindowIndices); + assertArrayEquals(new int[] {1, 1}, currentMediaItemIndices); assertThat(currentPositions[0]).isAtLeast(5000L); assertThat(bufferedPositions[0]).isAtLeast(5000L); assertThat(currentPositions[1]).isEqualTo(0); @@ -7095,8 +7103,8 @@ public final class ExoPlayerTest { MediaSource thirdMediaSource = new FakeMediaSource(thirdTimeline); Timeline fourthTimeline = new FakeTimeline(/* windowCount= */ 1, 3L); MediaSource fourthMediaSource = new FakeMediaSource(fourthTimeline); - final int[] currentWindowIndices = new int[9]; - Arrays.fill(currentWindowIndices, C.INDEX_UNSET); + final int[] currentMediaItemIndices = new int[9]; + Arrays.fill(currentMediaItemIndices, C.INDEX_UNSET); final int[] maskingPlaybackStates = new int[4]; Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET); final long[] currentPositions = new long[3]; @@ -7111,14 +7119,14 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Expect the current window index to be 2 after seek. - currentWindowIndices[0] = player.getCurrentWindowIndex(); + // Expect the current media item index to be 2 after seek. + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPositions[0] = player.getCurrentPosition(); bufferedPositions[0] = player.getBufferedPosition(); player.removeMediaItem(/* index= */ 2); - // Expect the current window index to be 0 + // Expect the current media item index to be 0 // (default position of timeline after not finding subsequent period). - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); // Transition to ENDED. maskingPlaybackStates[0] = player.getPlaybackState(); currentPositions[1] = player.getCurrentPosition(); @@ -7130,12 +7138,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Expects the current window index still on 0. - currentWindowIndices[2] = player.getCurrentWindowIndex(); + // Expects the current media item index still on 0. + currentMediaItemIndices[2] = player.getCurrentMediaItemIndex(); // Insert an item at begin when the playlist is not empty. player.addMediaSource(/* index= */ 0, thirdMediaSource); - // Expects the current window index to be (0 + 1) after insertion at begin. - currentWindowIndices[3] = player.getCurrentWindowIndex(); + // Expects the current media item index to be (0 + 1) after insertion at begin. + currentMediaItemIndices[3] = player.getCurrentMediaItemIndex(); // Remains in ENDED. maskingPlaybackStates[1] = player.getPlaybackState(); currentPositions[2] = player.getCurrentPosition(); @@ -7147,12 +7155,12 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[4] = player.getCurrentWindowIndex(); - // Implicit seek to the current window index, which is out of bounds in new + currentMediaItemIndices[4] = player.getCurrentMediaItemIndex(); + // Implicit seek to the current media item index, which is out of bounds in new // timeline. player.setMediaSource(fourthMediaSource, /* resetPosition= */ false); // 0 after reset. - currentWindowIndices[5] = player.getCurrentWindowIndex(); + currentMediaItemIndices[5] = player.getCurrentMediaItemIndex(); // Invalid seek, so we remain in ENDED. maskingPlaybackStates[2] = player.getPlaybackState(); } @@ -7162,11 +7170,11 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[6] = player.getCurrentWindowIndex(); + currentMediaItemIndices[6] = player.getCurrentMediaItemIndex(); // Explicit seek to (0, C.TIME_UNSET). Player transitions to BUFFERING. player.setMediaSource(fourthMediaSource, /* startPositionMs= */ 5000); // 0 after explicit seek. - currentWindowIndices[7] = player.getCurrentWindowIndex(); + currentMediaItemIndices[7] = player.getCurrentMediaItemIndex(); // Transitions from ENDED to BUFFERING after explicit seek. maskingPlaybackStates[3] = player.getPlaybackState(); } @@ -7176,14 +7184,14 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - // Check whether actual window index is equal masking index from above. - currentWindowIndices[8] = player.getCurrentWindowIndex(); + // Check whether actual media item index is equal masking index from above. + currentMediaItemIndices[8] = player.getCurrentMediaItemIndex(); } }) .build(); ExoPlayerTestRunner exoPlayerTestRunner = new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 2, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 2, /* positionMs= */ C.TIME_UNSET) .setExpectedPlayerEndedCount(2) .setMediaSources(firstMediaSource, secondMediaSource, thirdMediaSource) .setActionSchedule(actionSchedule) @@ -7191,23 +7199,23 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - // Expect reset of masking to first window. + // Expect reset of masking to first media item. exoPlayerTestRunner.assertPlaybackStatesEqual( Player.STATE_BUFFERING, Player.STATE_READY, // Ready after initial prepare. - Player.STATE_ENDED, // ended after removing current window index + Player.STATE_ENDED, // ended after removing current media item index Player.STATE_BUFFERING, // buffers after set items with seek Player.STATE_READY, Player.STATE_ENDED); assertArrayEquals( new int[] { - Player.STATE_ENDED, // ended after removing current window index + Player.STATE_ENDED, // ended after removing current media item index Player.STATE_ENDED, // adding items does not change state Player.STATE_ENDED, // set items with seek to current position. Player.STATE_BUFFERING }, // buffers after set items with seek maskingPlaybackStates); - assertArrayEquals(new int[] {2, 0, 0, 1, 1, 0, 0, 0, 0}, currentWindowIndices); + assertArrayEquals(new int[] {2, 0, 0, 1, 1, 0, 0, 0, 0}, currentMediaItemIndices); assertThat(currentPositions[0]).isEqualTo(0); assertThat(currentPositions[1]).isEqualTo(0); assertThat(currentPositions[2]).isEqualTo(0); @@ -7221,7 +7229,7 @@ public final class ExoPlayerTest { throws Exception { ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .seek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .waitForPendingPlayerCommands() .removeMediaItem(/* index= */ 1) .prepare() @@ -7241,7 +7249,7 @@ public final class ExoPlayerTest { @Test public void clearMediaItems_correctMasking() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final int[] maskingPlaybackState = {C.INDEX_UNSET}; final long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET}; final long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET}; @@ -7249,16 +7257,16 @@ public final class ExoPlayerTest { new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_BUFFERING) - .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 150) + .playUntilPosition(/* mediaItemIndex= */ 1, /* positionMs= */ 150) .executeRunnable( new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentPosition[0] = player.getCurrentPosition(); bufferedPosition[0] = player.getBufferedPosition(); player.clearMediaItems(); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentPosition[1] = player.getCurrentPosition(); bufferedPosition[1] = player.getBufferedPosition(); maskingPlaybackState[0] = player.getPlaybackState(); @@ -7266,25 +7274,25 @@ public final class ExoPlayerTest { }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); assertThat(currentPosition[0]).isAtLeast(150); assertThat(currentPosition[1]).isEqualTo(0); assertThat(bufferedPosition[0]).isAtLeast(150); assertThat(bufferedPosition[1]).isEqualTo(0); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackState); } @Test - public void clearMediaItems_unprepared_correctMaskingWindowIndex_notEnded() throws Exception { - final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; + public void clearMediaItems_unprepared_correctMaskingMediaItemIndex_notEnded() throws Exception { + final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final int[] currentStates = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) @@ -7295,10 +7303,10 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - currentWindowIndices[0] = player.getCurrentWindowIndex(); + currentMediaItemIndices[0] = player.getCurrentMediaItemIndex(); currentStates[0] = player.getPlaybackState(); player.clearMediaItems(); - currentWindowIndices[1] = player.getCurrentWindowIndex(); + currentMediaItemIndices[1] = player.getCurrentMediaItemIndex(); currentStates[1] = player.getPlaybackState(); } }) @@ -7313,7 +7321,7 @@ public final class ExoPlayerTest { }) .build(); new ExoPlayerTestRunner.Builder(context) - .initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) + .initialSeek(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .setMediaSources(new FakeMediaSource(), new FakeMediaSource()) .setActionSchedule(actionSchedule) .build() @@ -7322,7 +7330,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); assertArrayEquals( new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_ENDED}, currentStates); - assertArrayEquals(new int[] {1, 0}, currentWindowIndices); + assertArrayEquals(new int[] {1, 0}, currentMediaItemIndices); } @Test @@ -7351,7 +7359,7 @@ public final class ExoPlayerTest { AtomicReference timelineAfterError = new AtomicReference<>(); AtomicReference trackInfosAfterError = new AtomicReference<>(); AtomicReference trackSelectionsAfterError = new AtomicReference<>(); - AtomicInteger windowIndexAfterError = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterError = new AtomicInteger(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .executeRunnable( @@ -7365,7 +7373,7 @@ public final class ExoPlayerTest { timelineAfterError.set(player.getCurrentTimeline()); trackInfosAfterError.set(player.getCurrentTracksInfo()); trackSelectionsAfterError.set(player.getCurrentTrackSelections()); - windowIndexAfterError.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterError.set(player.getCurrentMediaItemIndex()); } }); } @@ -7392,7 +7400,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS)); assertThat(timelineAfterError.get().getWindowCount()).isEqualTo(1); - assertThat(windowIndexAfterError.get()).isEqualTo(0); + assertThat(mediaItemIndexAfterError.get()).isEqualTo(0); assertThat(trackInfosAfterError.get().getTrackGroupInfos()).hasSize(1); assertThat(trackInfosAfterError.get().getTrackGroupInfos().get(0).getTrackGroup().getFormat(0)) .isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT); @@ -7404,7 +7412,7 @@ public final class ExoPlayerTest { public void seekToCurrentPosition_inEndedState_switchesToBufferingStateAndContinuesPlayback() throws Exception { MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount = */ 1)); - AtomicInteger windowIndexAfterFinalEndedState = new AtomicInteger(); + AtomicInteger mediaItemIndexAfterFinalEndedState = new AtomicInteger(); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlaybackState(Player.STATE_ENDED) @@ -7422,7 +7430,7 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(ExoPlayer player) { - windowIndexAfterFinalEndedState.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterFinalEndedState.set(player.getCurrentMediaItemIndex()); } }) .build(); @@ -7434,7 +7442,7 @@ public final class ExoPlayerTest { .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(windowIndexAfterFinalEndedState.get()).isEqualTo(1); + assertThat(mediaItemIndexAfterFinalEndedState.get()).isEqualTo(1); } @Test @@ -7448,7 +7456,7 @@ public final class ExoPlayerTest { MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(timelineWindowDefinition)); AtomicInteger playbackStateAfterPause = new AtomicInteger(C.INDEX_UNSET); AtomicLong positionAfterPause = new AtomicLong(C.TIME_UNSET); - AtomicInteger windowIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); + AtomicInteger mediaItemIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlayWhenReady(true) @@ -7458,7 +7466,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { playbackStateAfterPause.set(player.getPlaybackState()); - windowIndexAfterPause.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterPause.set(player.getCurrentMediaItemIndex()); positionAfterPause.set(player.getContentPosition()); } }) @@ -7473,7 +7481,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); assertThat(playbackStateAfterPause.get()).isEqualTo(Player.STATE_READY); - assertThat(windowIndexAfterPause.get()).isEqualTo(0); + assertThat(mediaItemIndexAfterPause.get()).isEqualTo(0); assertThat(positionAfterPause.get()).isEqualTo(10_000); } @@ -7487,7 +7495,7 @@ public final class ExoPlayerTest { MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(timelineWindowDefinition)); AtomicInteger playbackStateAfterPause = new AtomicInteger(C.INDEX_UNSET); AtomicLong positionAfterPause = new AtomicLong(C.TIME_UNSET); - AtomicInteger windowIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); + AtomicInteger mediaItemIndexAfterPause = new AtomicInteger(C.INDEX_UNSET); ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .waitForPlayWhenReady(true) @@ -7497,7 +7505,7 @@ public final class ExoPlayerTest { @Override public void run(ExoPlayer player) { playbackStateAfterPause.set(player.getPlaybackState()); - windowIndexAfterPause.set(player.getCurrentWindowIndex()); + mediaItemIndexAfterPause.set(player.getCurrentMediaItemIndex()); positionAfterPause.set(player.getContentPosition()); } }) @@ -7512,7 +7520,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); assertThat(playbackStateAfterPause.get()).isEqualTo(Player.STATE_ENDED); - assertThat(windowIndexAfterPause.get()).isEqualTo(0); + assertThat(mediaItemIndexAfterPause.get()).isEqualTo(0); assertThat(positionAfterPause.get()).isEqualTo(10_000); } @@ -7846,7 +7854,7 @@ public final class ExoPlayerTest { firstMediaSource.setNewSourceInfo(timelineWithOffsets); // Wait until player transitions to second source (which also has non-zero offsets). runUntilPositionDiscontinuity(player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); player.release(); assertThat(rendererStreamOffsetsUs).hasSize(2); @@ -8102,7 +8110,7 @@ public final class ExoPlayerTest { player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 2000); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 2000); assertThat(reportedMediaItems) .containsExactly(mediaSource1.getMediaItem(), mediaSource2.getMediaItem()) @@ -8134,7 +8142,7 @@ public final class ExoPlayerTest { player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 2000); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 2000); assertThat(reportedMediaItems).containsExactly(mediaSource1.getMediaItem()).inOrder(); assertThat(reportedTransitionReasons) @@ -8371,7 +8379,7 @@ public final class ExoPlayerTest { player.addMediaSources( ImmutableList.of( new FakeMediaSource(), new FakeMediaSource(adTimeline), new FakeMediaSource())); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); @@ -8451,7 +8459,7 @@ public final class ExoPlayerTest { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addMediaSource(new FakeMediaSource(timelineWithUnseekableLiveWindow)); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); @@ -8507,14 +8515,14 @@ public final class ExoPlayerTest { // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 3, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @@ -8534,7 +8542,7 @@ public final class ExoPlayerTest { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); - player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 3, /* positionMs= */ 0); player.addMediaSources( ImmutableList.of( new FakeMediaSource(), @@ -8545,14 +8553,14 @@ public final class ExoPlayerTest { // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @@ -8567,8 +8575,8 @@ public final class ExoPlayerTest { player.addMediaSources(ImmutableList.of(new FakeMediaSource())); verify(mockListener).onAvailableCommandsChanged(defaultCommands); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 200); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 100); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); } @@ -8617,12 +8625,12 @@ public final class ExoPlayerTest { verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); - playUntilStartOfWindow(player, /* windowIndex= */ 1); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 1); runUntilPendingCommandsAreFullyHandled(player); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); - playUntilStartOfWindow(player, /* windowIndex= */ 2); + playUntilStartOfMediaItem(player, /* mediaItemIndex= */ 2); runUntilPendingCommandsAreFullyHandled(player); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); @@ -8714,7 +8722,7 @@ public final class ExoPlayerTest { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); - player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0); player.addMediaSources( ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); @@ -8838,7 +8846,7 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true) .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); } @Test @@ -8884,7 +8892,7 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true) .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); } @Test @@ -8947,7 +8955,7 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true) .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); } @Test @@ -8988,7 +8996,7 @@ public final class ExoPlayerTest { .uid; assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid); // Verify test setup by checking that playing period was indeed different. - assertThat(player.getCurrentWindowIndex()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); } @Test @@ -9127,7 +9135,8 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtStart).isIn(Range.closed(11_900L, 12_100L)); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9173,7 +9182,8 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); long liveOffsetAtStart = player.getCurrentLiveOffset(); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9221,7 +9231,8 @@ public final class ExoPlayerTest { // Seek to a live offset of 2 seconds. player.seekTo(18_000); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9285,11 +9296,13 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtStart).isIn(Range.closed(11_900L, 12_100L)); // Play a bit and update configuration. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 55_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 55_000); fakeMediaSource.setNewSourceInfo(updatedTimeline); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9351,7 +9364,8 @@ public final class ExoPlayerTest { player.setPlaybackParameters(new PlaybackParameters(/* speed */ 2.0f)); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9399,7 +9413,8 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 1, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9463,9 +9478,10 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); // Seek to default position in second stream. - player.seekToNextWindow(); + player.seekToNextMediaItem(); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 1, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9529,9 +9545,10 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); // Seek to specific position in second stream (at 2 seconds live offset). - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 18_000); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 18_000); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 1, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9572,7 +9589,8 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); long playbackStartTimeMs = fakeClock.elapsedRealtime(); - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long playbackEndTimeMs = fakeClock.elapsedRealtime(); player.release(); @@ -9613,7 +9631,8 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtStart).isIn(Range.closed(11_900L, 12_100L)); // Play until close to the end of the available live window. - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000); + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); long liveOffsetAtEnd = player.getCurrentLiveOffset(); player.release(); @@ -9751,7 +9770,7 @@ public final class ExoPlayerTest { TestPlayerRunHelper.runUntilPositionDiscontinuity( player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); player.addMediaSource(secondMediaSource); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET); player.play(); TestPlayerRunHelper.runUntilPositionDiscontinuity( player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); @@ -9828,7 +9847,7 @@ public final class ExoPlayerTest { inOrder .verify(listener) .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)); - // Last auto transition from window 0 to window 1 not caused by repeat mode. + // Last auto transition from media item 0 to media item 1 not caused by repeat mode. inOrder .verify(listener) .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); @@ -10004,7 +10023,7 @@ public final class ExoPlayerTest { player.setMediaSource(new FakeMediaSource(new FakeTimeline(adTimeline))); player.prepare(); - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 1000); + TestPlayerRunHelper.playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 1000); player.seekTo(/* positionMs= */ 8_000); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10128,7 +10147,7 @@ public final class ExoPlayerTest { ArgumentCaptor.forClass(Player.PositionInfo.class); Window window = new Window(); InOrder inOrder = Mockito.inOrder(listener); - // from first to second window + // from first to second media item inOrder .verify(listener) .onPositionDiscontinuity( @@ -10154,7 +10173,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); - // from second window to third + // from second media item to third inOrder .verify(listener) .onPositionDiscontinuity( @@ -10180,7 +10199,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); - // from third window content to post roll ad + // from third media item content to post roll ad @Nullable Object lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10201,7 +10220,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(20_000); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(0); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(0); - // from third window post roll to third window content end + // from third media item post roll to third media item content end lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10223,7 +10242,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(19_999); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); - // from third window content end to fourth window pre roll ad + // from third media item content end to fourth media item pre roll ad lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10248,7 +10267,7 @@ public final class ExoPlayerTest { assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); assertThat(newPosition.getValue().adGroupIndex).isEqualTo(0); assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(0); - // from fourth window pre roll ad to fourth window content + // from fourth media item pre roll ad to fourth media item content lastNewWindowUid = newPosition.getValue().windowUid; inOrder .verify(listener) @@ -10306,7 +10325,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.setMediaSources(ImmutableList.of(secondMediaSource, secondMediaSource)); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10428,11 +10447,11 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.removeMediaItem(/* index= */ 1); player.seekTo(/* positionMs= */ 0); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 2 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 2 * C.MILLIS_PER_SECOND); // Removing the last item resets the position to 0 with an empty timeline. player.removeMediaItem(0); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10517,7 +10536,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); concatenatingMediaSource.removeMediaSource(1); TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); concatenatingMediaSource.removeMediaSource(1); @@ -10590,9 +10609,9 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); concatenatingMediaSource.removeMediaSource(1); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1234); TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); concatenatingMediaSource.removeMediaSource(0); player.play(); @@ -10708,9 +10727,9 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.seekTo(/* positionMs= */ 7 * C.MILLIS_PER_SECOND); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -10753,7 +10772,7 @@ public final class ExoPlayerTest { player.addListener(listener); player.seekTo(/* positionMs= */ 7 * C.MILLIS_PER_SECOND); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); player.seekTo(/* positionMs= */ 5 * C.MILLIS_PER_SECOND); ArgumentCaptor oldPosition = @@ -10787,7 +10806,7 @@ public final class ExoPlayerTest { assertThat(newPositions.get(1).mediaItemIndex).isEqualTo(1); assertThat(newPositions.get(1).positionMs).isEqualTo(1_000); assertThat(newPositions.get(1).contentPositionMs).isEqualTo(1_000); - // a seek from masked seek position to another masked position within window + // a seek from masked seek position to another masked position within media item assertThat(oldPositions.get(2).windowUid).isNull(); assertThat(oldPositions.get(2).mediaItemIndex).isEqualTo(1); assertThat(oldPositions.get(2).mediaItem).isNull(); @@ -10815,7 +10834,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 2 * C.DEFAULT_SEEK_BACK_INCREMENT_MS); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 2 * C.DEFAULT_SEEK_BACK_INCREMENT_MS); player.seekBack(); ArgumentCaptor oldPosition = @@ -10862,7 +10881,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ C.DEFAULT_SEEK_BACK_INCREMENT_MS / 2); + player, /* mediaItemIndex= */ 0, /* positionMs= */ C.DEFAULT_SEEK_BACK_INCREMENT_MS / 2); player.seekBack(); assertThat(player.getCurrentPosition()).isEqualTo(0); @@ -10933,11 +10952,11 @@ public final class ExoPlayerTest { public void seekToPrevious_withPreviousWindowAndCloseToStart_seeksToPreviousWindow() { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); - player.seekTo(/* windowIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS); + player.seekTo(/* mediaItemIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS); player.seekToPrevious(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); assertThat(player.getCurrentPosition()).isEqualTo(0); player.release(); @@ -10947,11 +10966,11 @@ public final class ExoPlayerTest { public void seekToPrevious_notCloseToStart_seeksToZero() { ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); - player.seekTo(/* windowIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS + 1); + player.seekTo(/* mediaItemIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS + 1); player.seekToPrevious(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); assertThat(player.getCurrentPosition()).isEqualTo(0); player.release(); @@ -10964,7 +10983,7 @@ public final class ExoPlayerTest { player.seekToNext(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); assertThat(player.getCurrentPosition()).isEqualTo(0); player.release(); @@ -10994,7 +11013,7 @@ public final class ExoPlayerTest { player.seekTo(/* positionMs = */ 0); player.seekToNext(); - assertThat(player.getCurrentWindowIndex()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); assertThat(player.getCurrentPosition()).isEqualTo(500); player.release(); @@ -11009,7 +11028,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.stop(); verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt()); @@ -11027,7 +11046,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.stop(/* reset= */ true); ArgumentCaptor oldPosition = @@ -11071,13 +11090,13 @@ public final class ExoPlayerTest { player.setMediaSource(mediaSource); player.prepare(); - TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 2000); - player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 2122); + TestPlayerRunHelper.playUntilPosition(player, /* mediaItemIndex= */ 1, /* positionMs= */ 2000); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 2122); // This causes a DISCONTINUITY_REASON_REMOVE between pending operations that needs to be // cancelled by the seek below. mediaSource.setNewSourceInfo(timeline2); player.play(); - player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 2222); + player.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 2222); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); ArgumentCaptor oldPosition = @@ -11176,7 +11195,7 @@ public final class ExoPlayerTest { .send(); player.setMediaSource(mediaSource); player.prepare(); - playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 40_000); + playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 40_000); player.release(); // Assert that the renderer hasn't been reset despite the inserted ad group. @@ -11215,7 +11234,7 @@ public final class ExoPlayerTest { player.prepare(); TestPlayerRunHelper.playUntilPosition( - player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); player.stop(); shadowOf(Looper.getMainLooper()).idle(); @@ -11360,18 +11379,18 @@ public final class ExoPlayerTest { private static final class PositionGrabbingMessageTarget extends PlayerTarget { - public int windowIndex; + public int mediaItemIndex; public long positionMs; public int messageCount; public PositionGrabbingMessageTarget() { - windowIndex = C.INDEX_UNSET; + mediaItemIndex = C.INDEX_UNSET; positionMs = C.POSITION_UNSET; } @Override public void handleMessage(ExoPlayer player, int messageType, @Nullable Object message) { - windowIndex = player.getCurrentWindowIndex(); + mediaItemIndex = player.getCurrentMediaItemIndex(); positionMs = player.getCurrentPosition(); messageCount++; } 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 6adfe54b4f..a7838377bf 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 @@ -950,7 +950,7 @@ public class PlayerControlView extends FrameLayout { int adGroupCount = 0; Timeline timeline = player.getCurrentTimeline(); if (!timeline.isEmpty()) { - int currentWindowIndex = player.getCurrentWindowIndex(); + int currentWindowIndex = player.getCurrentMediaItemIndex(); int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex; int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex; for (int i = firstWindowIndex; i <= lastWindowIndex; i++) { @@ -1110,7 +1110,7 @@ public class PlayerControlView extends FrameLayout { windowIndex++; } } else { - windowIndex = player.getCurrentWindowIndex(); + windowIndex = player.getCurrentMediaItemIndex(); } seekTo(player, windowIndex, positionMs); updateProgress(); @@ -1228,7 +1228,7 @@ public class PlayerControlView extends FrameLayout { if (state == Player.STATE_IDLE) { player.prepare(); } else if (state == Player.STATE_ENDED) { - seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); + seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); } player.play(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 26075126d0..682483cd4e 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -605,9 +605,9 @@ public class PlayerNotificationManager { public static final String ACTION_PLAY = "com.google.android.exoplayer.play"; /** The action which pauses playback. */ public static final String ACTION_PAUSE = "com.google.android.exoplayer.pause"; - /** The action which skips to the previous window. */ + /** The action which skips to the previous media item. */ public static final String ACTION_PREVIOUS = "com.google.android.exoplayer.prev"; - /** The action which skips to the next window. */ + /** The action which skips to the next media item. */ public static final String ACTION_NEXT = "com.google.android.exoplayer.next"; /** The action which fast forwards. */ public static final String ACTION_FAST_FORWARD = "com.google.android.exoplayer.ffwd"; @@ -1095,7 +1095,7 @@ public class PlayerNotificationManager { * *
        *
      • The media is {@link Player#isPlaying() actively playing}. - *
      • The media is not {@link Player#isCurrentWindowDynamic() dynamically changing its + *
      • The media is not {@link Player#isCurrentMediaItemDynamic() dynamically changing its * duration} (like for example a live stream). *
      • The media is not {@link Player#isPlayingAd() interrupted by an ad}. *
      • The media is played at {@link Player#getPlaybackParameters() regular speed}. @@ -1253,7 +1253,7 @@ public class PlayerNotificationManager { && useChronometer && player.isPlaying() && !player.isPlayingAd() - && !player.isCurrentWindowDynamic() + && !player.isCurrentMediaItemDynamic() && player.getPlaybackParameters().speed == 1f) { builder .setWhen(System.currentTimeMillis() - player.getContentPosition()) @@ -1531,7 +1531,7 @@ public class PlayerNotificationManager { if (player.getPlaybackState() == Player.STATE_IDLE) { player.prepare(); } else if (player.getPlaybackState() == Player.STATE_ENDED) { - player.seekToDefaultPosition(player.getCurrentWindowIndex()); + player.seekToDefaultPosition(player.getCurrentMediaItemIndex()); } player.play(); } else if (ACTION_PAUSE.equals(action)) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index 4836774bb2..30533dcd19 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -1501,8 +1501,8 @@ public class PlayerView extends FrameLayout implements AdViewProvider { if (lastPeriodIndexWithTracks != C.INDEX_UNSET) { int lastWindowIndexWithTracks = timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex; - if (player.getCurrentWindowIndex() == lastWindowIndexWithTracks) { - // We're in the same window. Suppress the update. + if (player.getCurrentMediaItemIndex() == lastWindowIndexWithTracks) { + // We're in the same media item. Suppress the update. return; } } 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 279e907476..c6e0aadad0 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 @@ -1269,7 +1269,7 @@ public class StyledPlayerControlView extends FrameLayout { int adGroupCount = 0; Timeline timeline = player.getCurrentTimeline(); if (!timeline.isEmpty()) { - int currentWindowIndex = player.getCurrentWindowIndex(); + int currentWindowIndex = player.getCurrentMediaItemIndex(); int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex; int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex; for (int i = firstWindowIndex; i <= lastWindowIndex; i++) { @@ -1453,7 +1453,7 @@ public class StyledPlayerControlView extends FrameLayout { windowIndex++; } } else { - windowIndex = player.getCurrentWindowIndex(); + windowIndex = player.getCurrentMediaItemIndex(); } seekTo(player, windowIndex, positionMs); updateProgress(); @@ -1616,13 +1616,12 @@ public class StyledPlayerControlView extends FrameLayout { } } - @SuppressWarnings("deprecation") private void dispatchPlay(Player player) { @State int state = player.getPlaybackState(); if (state == Player.STATE_IDLE) { player.prepare(); } else if (state == Player.STATE_ENDED) { - seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); + seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); } player.play(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index 4434aef516..b8907094fd 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -1540,8 +1540,8 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { if (lastPeriodIndexWithTracks != C.INDEX_UNSET) { int lastWindowIndexWithTracks = timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex; - if (player.getCurrentWindowIndex() == lastWindowIndexWithTracks) { - // We're in the same window. Suppress the update. + if (player.getCurrentMediaItemIndex() == lastWindowIndexWithTracks) { + // We're in the same media item. Suppress the update. return; } } diff --git a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java index efcb290579..58ed22f289 100644 --- a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java +++ b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java @@ -284,12 +284,12 @@ public class TestPlayerRunHelper { *

        If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}. * * @param player The {@link Player}. - * @param windowIndex The window. - * @param positionMs The position within the window, in milliseconds. + * @param mediaItemIndex The index of the media item. + * @param positionMs The position within the media item, in milliseconds. * @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is * exceeded. */ - public static void playUntilPosition(ExoPlayer player, int windowIndex, long positionMs) + public static void playUntilPosition(ExoPlayer player, int mediaItemIndex, long positionMs) throws TimeoutException { verifyMainTestThread(player); Looper applicationLooper = Util.getCurrentOrMainLooper(); @@ -315,7 +315,7 @@ public class TestPlayerRunHelper { // Ignore. } }) - .setPosition(windowIndex, positionMs) + .setPosition(mediaItemIndex, positionMs) .send(); player.play(); runMainLooperUntil(() -> messageHandled.get() || player.getPlayerError() != null); @@ -326,18 +326,19 @@ public class TestPlayerRunHelper { /** * Calls {@link Player#play()}, runs tasks of the main {@link Looper} until the {@code player} - * reaches the specified window or a playback error occurs, and then pauses the {@code player}. + * reaches the specified media item or a playback error occurs, and then pauses the {@code + * player}. * *

        If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}. * * @param player The {@link Player}. - * @param windowIndex The window. + * @param mediaItemIndex The index of the media item. * @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is * exceeded. */ - public static void playUntilStartOfWindow(ExoPlayer player, int windowIndex) + public static void playUntilStartOfMediaItem(ExoPlayer player, int mediaItemIndex) throws TimeoutException { - playUntilPosition(player, windowIndex, /* positionMs= */ 0); + playUntilPosition(player, mediaItemIndex, /* positionMs= */ 0); } /** diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 43d5162bd3..b5cdf77d5c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -121,7 +121,7 @@ public abstract class Action { /** Calls {@link Player#seekTo(long)} or {@link Player#seekTo(int, long)}. */ public static final class Seek extends Action { - @Nullable private final Integer windowIndex; + @Nullable private final Integer mediaItemIndex; private final long positionMs; private final boolean catchIllegalSeekException; @@ -133,7 +133,7 @@ public abstract class Action { */ public Seek(String tag, long positionMs) { super(tag, "Seek:" + positionMs); - this.windowIndex = null; + this.mediaItemIndex = null; this.positionMs = positionMs; catchIllegalSeekException = false; } @@ -142,14 +142,15 @@ public abstract class Action { * Action calls {@link Player#seekTo(int, long)}. * * @param tag A tag to use for logging. - * @param windowIndex The window to seek to. + * @param mediaItemIndex The media item to seek to. * @param positionMs The seek position. * @param catchIllegalSeekException Whether {@link IllegalSeekPositionException} should be * silently caught or not. */ - public Seek(String tag, int windowIndex, long positionMs, boolean catchIllegalSeekException) { + public Seek( + String tag, int mediaItemIndex, long positionMs, boolean catchIllegalSeekException) { super(tag, "Seek:" + positionMs); - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; this.catchIllegalSeekException = catchIllegalSeekException; } @@ -158,10 +159,10 @@ public abstract class Action { protected void doActionImpl( ExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) { try { - if (windowIndex == null) { + if (mediaItemIndex == null) { player.seekTo(positionMs); } else { - player.seekTo(windowIndex, positionMs); + player.seekTo(mediaItemIndex, positionMs); } } catch (IllegalSeekPositionException e) { if (!catchIllegalSeekException) { @@ -174,20 +175,20 @@ public abstract class Action { /** Calls {@link ExoPlayer#setMediaSources(List, int, long)}. */ public static final class SetMediaItems extends Action { - private final int windowIndex; + private final int mediaItemIndex; private final long positionMs; private final MediaSource[] mediaSources; /** * @param tag A tag to use for logging. - * @param windowIndex The window index to start playback from. + * @param mediaItemIndex The media item index to start playback from. * @param positionMs The position in milliseconds to start playback from. * @param mediaSources The media sources to populate the playlist with. */ public SetMediaItems( - String tag, int windowIndex, long positionMs, MediaSource... mediaSources) { + String tag, int mediaItemIndex, long positionMs, MediaSource... mediaSources) { super(tag, "SetMediaItems"); - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; this.mediaSources = mediaSources; } @@ -195,7 +196,7 @@ public abstract class Action { @Override protected void doActionImpl( ExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) { - player.setMediaSources(Arrays.asList(mediaSources), windowIndex, positionMs); + player.setMediaSources(Arrays.asList(mediaSources), mediaItemIndex, positionMs); } } @@ -553,7 +554,7 @@ public abstract class Action { public static final class SendMessages extends Action { private final Target target; - private final int windowIndex; + private final int mediaItemIndex; private final long positionMs; private final boolean deleteAfterDelivery; @@ -566,7 +567,7 @@ public abstract class Action { this( tag, target, - /* windowIndex= */ C.INDEX_UNSET, + /* mediaItemIndex= */ C.INDEX_UNSET, positionMs, /* deleteAfterDelivery= */ true); } @@ -574,16 +575,20 @@ public abstract class Action { /** * @param tag A tag to use for logging. * @param target A message target. - * @param windowIndex The window index at which the message should be sent, or {@link - * C#INDEX_UNSET} for the current window. + * @param mediaItemIndex The media item index at which the message should be sent, or {@link + * C#INDEX_UNSET} for the current media item. * @param positionMs The position at which the message should be sent, in milliseconds. * @param deleteAfterDelivery Whether the message will be deleted after delivery. */ public SendMessages( - String tag, Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) { + String tag, + Target target, + int mediaItemIndex, + long positionMs, + boolean deleteAfterDelivery) { super(tag, "SendMessages"); this.target = target; - this.windowIndex = windowIndex; + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; this.deleteAfterDelivery = deleteAfterDelivery; } @@ -595,8 +600,8 @@ public abstract class Action { ((PlayerTarget) target).setPlayer(player); } PlayerMessage message = player.createMessage(target); - if (windowIndex != C.INDEX_UNSET) { - message.setPosition(windowIndex, positionMs); + if (mediaItemIndex != C.INDEX_UNSET) { + message.setPosition(mediaItemIndex, positionMs); } else { message.setPosition(positionMs); } @@ -661,17 +666,17 @@ public abstract class Action { */ public static final class PlayUntilPosition extends Action { - private final int windowIndex; + private final int mediaItemIndex; private final long positionMs; /** * @param tag A tag to use for logging. - * @param windowIndex The window index at which the player should be paused again. - * @param positionMs The position in that window at which the player should be paused again. + * @param mediaItemIndex The media item index at which the player should be paused again. + * @param positionMs The position in that media item at which the player should be paused again. */ - public PlayUntilPosition(String tag, int windowIndex, long positionMs) { - super(tag, "PlayUntilPosition:" + windowIndex + ":" + positionMs); - this.windowIndex = windowIndex; + public PlayUntilPosition(String tag, int mediaItemIndex, long positionMs) { + super(tag, "PlayUntilPosition:" + mediaItemIndex + ":" + positionMs); + this.mediaItemIndex = mediaItemIndex; this.positionMs = positionMs; } @@ -704,7 +709,7 @@ public abstract class Action { // Ignore. } }) - .setPosition(windowIndex, positionMs) + .setPosition(mediaItemIndex, positionMs) .send(); if (nextAction != null) { // Schedule another message on this test thread to continue action schedule. @@ -712,7 +717,7 @@ public abstract class Action { .createMessage( (messageType, payload) -> nextAction.schedule(player, trackSelector, surface, handler)) - .setPosition(windowIndex, positionMs) + .setPosition(mediaItemIndex, positionMs) .setLooper(applicationLooper) .send(); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 4590d5fd6b..0282e57a8c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -161,24 +161,25 @@ public final class ActionSchedule { /** * Schedules a seek action. * - * @param windowIndex The window to seek to. + * @param mediaItemIndex The media item to seek to. * @param positionMs The seek position. * @return The builder, for convenience. */ - public Builder seek(int windowIndex, long positionMs) { - return apply(new Seek(tag, windowIndex, positionMs, /* catchIllegalSeekException= */ false)); + public Builder seek(int mediaItemIndex, long positionMs) { + return apply( + new Seek(tag, mediaItemIndex, positionMs, /* catchIllegalSeekException= */ false)); } /** * Schedules a seek action to be executed. * - * @param windowIndex The window to seek to. + * @param mediaItemIndex The media item to seek to. * @param positionMs The seek position. * @param catchIllegalSeekException Whether an illegal seek position should be caught or not. * @return The builder, for convenience. */ - public Builder seek(int windowIndex, long positionMs, boolean catchIllegalSeekException) { - return apply(new Seek(tag, windowIndex, positionMs, catchIllegalSeekException)); + public Builder seek(int mediaItemIndex, long positionMs, boolean catchIllegalSeekException) { + return apply(new Seek(tag, mediaItemIndex, positionMs, catchIllegalSeekException)); } /** @@ -247,23 +248,23 @@ public final class ActionSchedule { * Schedules a play action, waits until the player reaches the specified position, and pauses * the player again. * - * @param windowIndex The window index at which the player should be paused again. - * @param positionMs The position in that window at which the player should be paused again. + * @param mediaItemIndex The media item index at which the player should be paused again. + * @param positionMs The position in that media item at which the player should be paused again. * @return The builder, for convenience. */ - public Builder playUntilPosition(int windowIndex, long positionMs) { - return apply(new PlayUntilPosition(tag, windowIndex, positionMs)); + public Builder playUntilPosition(int mediaItemIndex, long positionMs) { + return apply(new PlayUntilPosition(tag, mediaItemIndex, positionMs)); } /** - * Schedules a play action, waits until the player reaches the start of the specified window, - * and pauses the player again. + * Schedules a play action, waits until the player reaches the start of the specified media + * item, and pauses the player again. * - * @param windowIndex The window index at which the player should be paused again. + * @param mediaItemIndex The media item index at which the player should be paused again. * @return The builder, for convenience. */ - public Builder playUntilStartOfWindow(int windowIndex) { - return apply(new PlayUntilPosition(tag, windowIndex, /* positionMs= */ 0)); + public Builder playUntilStartOfMediaItem(int mediaItemIndex) { + return apply(new PlayUntilPosition(tag, mediaItemIndex, /* positionMs= */ 0)); } /** @@ -323,16 +324,16 @@ public final class ActionSchedule { /** * Schedules a set media items action to be executed. * - * @param windowIndex The window index to start playback from or {@link C#INDEX_UNSET} if the - * playback position should not be reset. + * @param mediaItemIndex The media item index to start playback from or {@link C#INDEX_UNSET} if + * the playback position should not be reset. * @param positionMs The position in milliseconds from where playback should start. If {@link - * C#TIME_UNSET} is passed the default position is used. In any case, if {@code windowIndex} - * is set to {@link C#INDEX_UNSET} the position is not reset at all and this parameter is - * ignored. + * C#TIME_UNSET} is passed the default position is used. In any case, if {@code + * mediaItemIndex} is set to {@link C#INDEX_UNSET} the position is not reset at all and this + * parameter is ignored. * @return The builder, for convenience. */ - public Builder setMediaSources(int windowIndex, long positionMs, MediaSource... sources) { - return apply(new Action.SetMediaItems(tag, windowIndex, positionMs, sources)); + public Builder setMediaSources(int mediaItemIndex, long positionMs, MediaSource... sources) { + return apply(new Action.SetMediaItems(tag, mediaItemIndex, positionMs, sources)); } /** @@ -354,7 +355,10 @@ public final class ActionSchedule { public Builder setMediaSources(MediaSource... mediaSources) { return apply( new Action.SetMediaItems( - tag, /* windowIndex= */ C.INDEX_UNSET, /* positionMs= */ C.TIME_UNSET, mediaSources)); + tag, + /* mediaItemIndex= */ C.INDEX_UNSET, + /* positionMs= */ C.TIME_UNSET, + mediaSources)); } /** * Schedules a add media items action to be executed. @@ -447,8 +451,8 @@ public final class ActionSchedule { /** * Schedules sending a {@link PlayerMessage}. * - * @param positionMs The position in the current window at which the message should be sent, in - * milliseconds. + * @param positionMs The position in the current media item at which the message should be sent, + * in milliseconds. * @return The builder, for convenience. */ public Builder sendMessage(Target target, long positionMs) { @@ -459,27 +463,28 @@ public final class ActionSchedule { * Schedules sending a {@link PlayerMessage}. * * @param target A message target. - * @param windowIndex The window index at which the message should be sent. + * @param mediaItemIndex The media item index at which the message should be sent. * @param positionMs The position at which the message should be sent, in milliseconds. * @return The builder, for convenience. */ - public Builder sendMessage(Target target, int windowIndex, long positionMs) { + public Builder sendMessage(Target target, int mediaItemIndex, long positionMs) { return apply( - new SendMessages(tag, target, windowIndex, positionMs, /* deleteAfterDelivery= */ true)); + new SendMessages( + tag, target, mediaItemIndex, positionMs, /* deleteAfterDelivery= */ true)); } /** * Schedules to send a {@link PlayerMessage}. * * @param target A message target. - * @param windowIndex The window index at which the message should be sent. + * @param mediaItemIndex The media item index at which the message should be sent. * @param positionMs The position at which the message should be sent, in milliseconds. * @param deleteAfterDelivery Whether the message will be deleted after delivery. * @return The builder, for convenience. */ public Builder sendMessage( - Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) { - return apply(new SendMessages(tag, target, windowIndex, positionMs, deleteAfterDelivery)); + Target target, int mediaItemIndex, long positionMs, boolean deleteAfterDelivery) { + return apply(new SendMessages(tag, target, mediaItemIndex, positionMs, deleteAfterDelivery)); } /** diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java index 6df171442d..1a305f0e35 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -85,7 +85,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul private AnalyticsListener analyticsListener; private Integer expectedPlayerEndedCount; private boolean pauseAtEndOfMediaItems; - private int initialWindowIndex; + private int initialMediaItemIndex; private long initialPositionMs; private boolean skipSettingMediaSources; @@ -93,7 +93,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul testPlayerBuilder = new TestExoPlayerBuilder(context); mediaSources = new ArrayList<>(); supportedFormats = new Format[] {VIDEO_FORMAT}; - initialWindowIndex = C.INDEX_UNSET; + initialMediaItemIndex = C.INDEX_UNSET; initialPositionMs = C.TIME_UNSET; } @@ -133,12 +133,12 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul /** * Seeks before setting the media sources and preparing the player. * - * @param windowIndex The window index to seek to. + * @param mediaItemIndex The media item index to seek to. * @param positionMs The position in milliseconds to seek to. * @return This builder. */ - public Builder initialSeek(int windowIndex, long positionMs) { - this.initialWindowIndex = windowIndex; + public Builder initialSeek(int mediaItemIndex, long positionMs) { + this.initialMediaItemIndex = mediaItemIndex; this.initialPositionMs = positionMs; return this; } @@ -343,7 +343,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul testPlayerBuilder, mediaSources, skipSettingMediaSources, - initialWindowIndex, + initialMediaItemIndex, initialPositionMs, surface, actionSchedule, @@ -357,7 +357,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul private final TestExoPlayerBuilder playerBuilder; private final List mediaSources; private final boolean skipSettingMediaSources; - private final int initialWindowIndex; + private final int initialMediaItemIndex; private final long initialPositionMs; @Nullable private final Surface surface; @Nullable private final ActionSchedule actionSchedule; @@ -386,7 +386,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul TestExoPlayerBuilder playerBuilder, List mediaSources, boolean skipSettingMediaSources, - int initialWindowIndex, + int initialMediaItemIndex, long initialPositionMs, @Nullable Surface surface, @Nullable ActionSchedule actionSchedule, @@ -397,7 +397,7 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul this.playerBuilder = playerBuilder; this.mediaSources = mediaSources; this.skipSettingMediaSources = skipSettingMediaSources; - this.initialWindowIndex = initialWindowIndex; + this.initialMediaItemIndex = initialMediaItemIndex; this.initialPositionMs = initialPositionMs; this.surface = surface; this.actionSchedule = actionSchedule; @@ -466,8 +466,8 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul handler, /* callback= */ ExoPlayerTestRunner.this); } - if (initialWindowIndex != C.INDEX_UNSET) { - player.seekTo(initialWindowIndex, initialPositionMs); + if (initialMediaItemIndex != C.INDEX_UNSET) { + player.seekTo(initialMediaItemIndex, initialPositionMs); } if (!skipSettingMediaSources) { player.setMediaSources(mediaSources, /* resetPosition= */ false); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index c0acae1da6..136327739f 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -162,7 +162,7 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer { @Override public void setMediaSources( - List mediaSources, int startWindowIndex, long startPositionMs) { + List mediaSources, int startMediaItemIndex, long startPositionMs) { throw new UnsupportedOperationException(); } From e246eccc1069974e0d2aa890798872915664208f Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 2 Nov 2021 10:49:18 +0000 Subject: [PATCH 089/113] Replace map with a switch statement in bandwidth meter implementations #minor-release PiperOrigin-RevId: 407042882 --- .../upstream/DefaultBandwidthMeter.java | 713 +++++++++++------- 1 file changed, 438 insertions(+), 275 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 07b3185b43..2db19fb6a7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -26,10 +26,8 @@ import com.google.android.exoplayer2.util.NetworkTypeObserver; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -42,13 +40,6 @@ import java.util.Map; */ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener { - /** - * Country groups used to determine the default initial bitrate estimate. The group assignment for - * each country is a list for [Wifi, 2G, 3G, 4G, 5G_NSA, 5G_SA]. - */ - public static final ImmutableListMultimap - DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = createInitialBitrateCountryGroupAssignment(); - /** Default initial Wifi bitrate estimate in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = ImmutableList.of(5_400_000L, 3_300_000L, 2_000_000L, 1_300_000L, 760_000L); @@ -82,17 +73,35 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /** Default maximum weight for the sliding window. */ public static final int DEFAULT_SLIDING_WINDOW_MAX_WEIGHT = 2000; - /** Index for the Wifi group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the Wifi group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_WIFI = 0; - /** Index for the 2G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 2G group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_2G = 1; - /** Index for the 3G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 3G group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_3G = 2; - /** Index for the 4G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 4G group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_4G = 3; - /** Index for the 5G-NSA group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 5G-NSA group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_5G_NSA = 4; - /** Index for the 5G-SA group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + /** + * Index for the 5G-SA group index in the array returned by {@link + * #getInitialBitrateCountryGroupAssignment}. + */ private static final int COUNTRY_GROUP_INDEX_5G_SA = 5; @Nullable private static DefaultBandwidthMeter singletonInstance; @@ -212,40 +221,33 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } private static Map getInitialBitrateEstimatesForCountry(String countryCode) { - List groupIndices = getCountryGroupIndices(countryCode); + int[] groupIndices = getInitialBitrateCountryGroupAssignment(countryCode); Map result = new HashMap<>(/* initialCapacity= */ 8); result.put(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); result.put( C.NETWORK_TYPE_WIFI, - DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices.get(COUNTRY_GROUP_INDEX_WIFI))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices[COUNTRY_GROUP_INDEX_WIFI])); result.put( C.NETWORK_TYPE_2G, - DEFAULT_INITIAL_BITRATE_ESTIMATES_2G.get(groupIndices.get(COUNTRY_GROUP_INDEX_2G))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_2G.get(groupIndices[COUNTRY_GROUP_INDEX_2G])); result.put( C.NETWORK_TYPE_3G, - DEFAULT_INITIAL_BITRATE_ESTIMATES_3G.get(groupIndices.get(COUNTRY_GROUP_INDEX_3G))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_3G.get(groupIndices[COUNTRY_GROUP_INDEX_3G])); result.put( C.NETWORK_TYPE_4G, - DEFAULT_INITIAL_BITRATE_ESTIMATES_4G.get(groupIndices.get(COUNTRY_GROUP_INDEX_4G))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_4G.get(groupIndices[COUNTRY_GROUP_INDEX_4G])); result.put( C.NETWORK_TYPE_5G_NSA, - DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA.get( - groupIndices.get(COUNTRY_GROUP_INDEX_5G_NSA))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA.get(groupIndices[COUNTRY_GROUP_INDEX_5G_NSA])); result.put( C.NETWORK_TYPE_5G_SA, - DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA.get(groupIndices.get(COUNTRY_GROUP_INDEX_5G_SA))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA.get(groupIndices[COUNTRY_GROUP_INDEX_5G_SA])); // Assume default Wifi speed for Ethernet to prevent using the slower fallback. result.put( C.NETWORK_TYPE_ETHERNET, - DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices.get(COUNTRY_GROUP_INDEX_WIFI))); + DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices[COUNTRY_GROUP_INDEX_WIFI])); return result; } - - private static ImmutableList getCountryGroupIndices(String countryCode) { - ImmutableList groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); - // Assume median group if not found. - return groupIndices.isEmpty() ? ImmutableList.of(2, 2, 2, 2, 2, 2) : groupIndices; - } } /** @@ -461,250 +463,411 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList return isNetwork && !dataSpec.isFlagSet(DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED); } - private static ImmutableListMultimap - createInitialBitrateCountryGroupAssignment() { - return ImmutableListMultimap.builder() - .putAll("AD", 1, 2, 0, 0, 2, 2) - .putAll("AE", 1, 4, 4, 4, 3, 2) - .putAll("AF", 4, 4, 4, 4, 2, 2) - .putAll("AG", 2, 3, 1, 2, 2, 2) - .putAll("AI", 1, 2, 2, 2, 2, 2) - .putAll("AL", 1, 2, 0, 1, 2, 2) - .putAll("AM", 2, 3, 2, 4, 2, 2) - .putAll("AO", 3, 4, 3, 2, 2, 2) - .putAll("AQ", 4, 2, 2, 2, 2, 2) - .putAll("AR", 2, 4, 1, 1, 2, 2) - .putAll("AS", 2, 2, 2, 3, 2, 2) - .putAll("AT", 0, 0, 0, 0, 0, 2) - .putAll("AU", 0, 1, 0, 1, 2, 2) - .putAll("AW", 1, 2, 4, 4, 2, 2) - .putAll("AX", 0, 2, 2, 2, 2, 2) - .putAll("AZ", 3, 2, 4, 4, 2, 2) - .putAll("BA", 1, 2, 0, 1, 2, 2) - .putAll("BB", 0, 2, 0, 0, 2, 2) - .putAll("BD", 2, 1, 3, 3, 2, 2) - .putAll("BE", 0, 0, 3, 3, 2, 2) - .putAll("BF", 4, 3, 4, 3, 2, 2) - .putAll("BG", 0, 0, 0, 0, 1, 2) - .putAll("BH", 1, 2, 2, 4, 4, 2) - .putAll("BI", 4, 3, 4, 4, 2, 2) - .putAll("BJ", 4, 4, 3, 4, 2, 2) - .putAll("BL", 1, 2, 2, 2, 2, 2) - .putAll("BM", 1, 2, 0, 0, 2, 2) - .putAll("BN", 3, 2, 1, 1, 2, 2) - .putAll("BO", 1, 3, 3, 2, 2, 2) - .putAll("BQ", 1, 2, 2, 0, 2, 2) - .putAll("BR", 2, 3, 2, 2, 2, 2) - .putAll("BS", 4, 2, 2, 3, 2, 2) - .putAll("BT", 3, 1, 3, 2, 2, 2) - .putAll("BW", 3, 4, 1, 0, 2, 2) - .putAll("BY", 0, 1, 1, 3, 2, 2) - .putAll("BZ", 2, 4, 2, 2, 2, 2) - .putAll("CA", 0, 2, 1, 2, 4, 1) - .putAll("CD", 4, 2, 3, 1, 2, 2) - .putAll("CF", 4, 2, 3, 2, 2, 2) - .putAll("CG", 2, 4, 3, 4, 2, 2) - .putAll("CH", 0, 0, 0, 0, 0, 2) - .putAll("CI", 3, 3, 3, 4, 2, 2) - .putAll("CK", 2, 2, 2, 1, 2, 2) - .putAll("CL", 1, 1, 2, 2, 3, 2) - .putAll("CM", 3, 4, 3, 2, 2, 2) - .putAll("CN", 2, 0, 2, 2, 3, 1) - .putAll("CO", 2, 2, 4, 2, 2, 2) - .putAll("CR", 2, 2, 4, 4, 2, 2) - .putAll("CU", 4, 4, 3, 2, 2, 2) - .putAll("CV", 2, 3, 1, 0, 2, 2) - .putAll("CW", 2, 2, 0, 0, 2, 2) - .putAll("CX", 1, 2, 2, 2, 2, 2) - .putAll("CY", 1, 0, 0, 0, 1, 2) - .putAll("CZ", 0, 0, 0, 0, 1, 2) - .putAll("DE", 0, 0, 2, 2, 1, 2) - .putAll("DJ", 4, 1, 4, 4, 2, 2) - .putAll("DK", 0, 0, 1, 0, 0, 2) - .putAll("DM", 1, 2, 2, 2, 2, 2) - .putAll("DO", 3, 4, 4, 4, 2, 2) - .putAll("DZ", 4, 3, 4, 4, 2, 2) - .putAll("EC", 2, 4, 2, 1, 2, 2) - .putAll("EE", 0, 0, 0, 0, 2, 2) - .putAll("EG", 3, 4, 2, 3, 2, 2) - .putAll("EH", 2, 2, 2, 2, 2, 2) - .putAll("ER", 4, 2, 2, 2, 2, 2) - .putAll("ES", 0, 1, 1, 1, 2, 2) - .putAll("ET", 4, 4, 3, 1, 2, 2) - .putAll("FI", 0, 0, 0, 1, 0, 2) - .putAll("FJ", 3, 1, 3, 3, 2, 2) - .putAll("FK", 3, 2, 2, 2, 2, 2) - .putAll("FM", 3, 2, 4, 2, 2, 2) - .putAll("FO", 0, 2, 0, 0, 2, 2) - .putAll("FR", 1, 1, 2, 1, 1, 1) - .putAll("GA", 2, 3, 1, 1, 2, 2) - .putAll("GB", 0, 0, 1, 1, 2, 3) - .putAll("GD", 1, 2, 2, 2, 2, 2) - .putAll("GE", 1, 1, 1, 3, 2, 2) - .putAll("GF", 2, 1, 2, 3, 2, 2) - .putAll("GG", 0, 2, 0, 0, 2, 2) - .putAll("GH", 3, 2, 3, 2, 2, 2) - .putAll("GI", 0, 2, 2, 2, 2, 2) - .putAll("GL", 1, 2, 0, 0, 2, 2) - .putAll("GM", 4, 2, 2, 4, 2, 2) - .putAll("GN", 4, 3, 4, 2, 2, 2) - .putAll("GP", 2, 1, 2, 3, 2, 2) - .putAll("GQ", 4, 2, 3, 4, 2, 2) - .putAll("GR", 1, 0, 0, 0, 2, 2) - .putAll("GT", 2, 3, 2, 1, 2, 2) - .putAll("GU", 1, 2, 4, 4, 2, 2) - .putAll("GW", 3, 4, 3, 3, 2, 2) - .putAll("GY", 3, 4, 1, 0, 2, 2) - .putAll("HK", 0, 1, 2, 3, 2, 0) - .putAll("HN", 3, 2, 3, 3, 2, 2) - .putAll("HR", 1, 0, 0, 0, 2, 2) - .putAll("HT", 4, 4, 4, 4, 2, 2) - .putAll("HU", 0, 0, 0, 1, 3, 2) - .putAll("ID", 3, 2, 3, 3, 3, 2) - .putAll("IE", 0, 1, 1, 1, 2, 2) - .putAll("IL", 1, 1, 2, 3, 4, 2) - .putAll("IM", 0, 2, 0, 1, 2, 2) - .putAll("IN", 1, 1, 3, 2, 4, 3) - .putAll("IO", 4, 2, 2, 2, 2, 2) - .putAll("IQ", 3, 3, 3, 3, 2, 2) - .putAll("IR", 3, 0, 1, 1, 3, 0) - .putAll("IS", 0, 0, 0, 0, 0, 2) - .putAll("IT", 0, 1, 0, 1, 1, 2) - .putAll("JE", 3, 2, 1, 2, 2, 2) - .putAll("JM", 3, 4, 4, 4, 2, 2) - .putAll("JO", 1, 0, 0, 1, 2, 2) - .putAll("JP", 0, 1, 0, 1, 1, 1) - .putAll("KE", 3, 3, 2, 2, 2, 2) - .putAll("KG", 2, 1, 1, 1, 2, 2) - .putAll("KH", 1, 1, 4, 2, 2, 2) - .putAll("KI", 4, 2, 4, 3, 2, 2) - .putAll("KM", 4, 2, 4, 3, 2, 2) - .putAll("KN", 2, 2, 2, 2, 2, 2) - .putAll("KP", 3, 2, 2, 2, 2, 2) - .putAll("KR", 0, 0, 1, 3, 4, 4) - .putAll("KW", 1, 1, 0, 0, 0, 2) - .putAll("KY", 1, 2, 0, 1, 2, 2) - .putAll("KZ", 1, 1, 2, 2, 2, 2) - .putAll("LA", 2, 2, 1, 2, 2, 2) - .putAll("LB", 3, 2, 1, 4, 2, 2) - .putAll("LC", 1, 2, 0, 0, 2, 2) - .putAll("LI", 0, 2, 2, 2, 2, 2) - .putAll("LK", 3, 1, 3, 4, 4, 2) - .putAll("LR", 3, 4, 4, 3, 2, 2) - .putAll("LS", 3, 3, 4, 3, 2, 2) - .putAll("LT", 0, 0, 0, 0, 2, 2) - .putAll("LU", 1, 0, 2, 2, 2, 2) - .putAll("LV", 0, 0, 0, 0, 2, 2) - .putAll("LY", 4, 2, 4, 3, 2, 2) - .putAll("MA", 3, 2, 2, 2, 2, 2) - .putAll("MC", 0, 2, 2, 0, 2, 2) - .putAll("MD", 1, 0, 0, 0, 2, 2) - .putAll("ME", 1, 0, 0, 1, 2, 2) - .putAll("MF", 1, 2, 1, 0, 2, 2) - .putAll("MG", 3, 4, 2, 2, 2, 2) - .putAll("MH", 3, 2, 2, 4, 2, 2) - .putAll("MK", 1, 0, 0, 0, 2, 2) - .putAll("ML", 4, 3, 3, 1, 2, 2) - .putAll("MM", 2, 4, 3, 3, 2, 2) - .putAll("MN", 2, 0, 1, 2, 2, 2) - .putAll("MO", 0, 2, 4, 4, 2, 2) - .putAll("MP", 0, 2, 2, 2, 2, 2) - .putAll("MQ", 2, 1, 2, 3, 2, 2) - .putAll("MR", 4, 1, 3, 4, 2, 2) - .putAll("MS", 1, 2, 2, 2, 2, 2) - .putAll("MT", 0, 0, 0, 0, 2, 2) - .putAll("MU", 3, 1, 1, 2, 2, 2) - .putAll("MV", 3, 4, 1, 4, 2, 2) - .putAll("MW", 4, 2, 1, 0, 2, 2) - .putAll("MX", 2, 4, 3, 4, 2, 2) - .putAll("MY", 2, 1, 3, 3, 2, 2) - .putAll("MZ", 3, 2, 2, 2, 2, 2) - .putAll("NA", 4, 3, 2, 2, 2, 2) - .putAll("NC", 3, 2, 4, 4, 2, 2) - .putAll("NE", 4, 4, 4, 4, 2, 2) - .putAll("NF", 2, 2, 2, 2, 2, 2) - .putAll("NG", 3, 4, 1, 1, 2, 2) - .putAll("NI", 2, 3, 4, 3, 2, 2) - .putAll("NL", 0, 0, 3, 2, 0, 4) - .putAll("NO", 0, 0, 2, 0, 0, 2) - .putAll("NP", 2, 1, 4, 3, 2, 2) - .putAll("NR", 3, 2, 2, 0, 2, 2) - .putAll("NU", 4, 2, 2, 2, 2, 2) - .putAll("NZ", 1, 0, 1, 2, 4, 2) - .putAll("OM", 2, 3, 1, 3, 4, 2) - .putAll("PA", 1, 3, 3, 3, 2, 2) - .putAll("PE", 2, 3, 4, 4, 4, 2) - .putAll("PF", 2, 3, 3, 1, 2, 2) - .putAll("PG", 4, 4, 3, 2, 2, 2) - .putAll("PH", 2, 2, 3, 3, 3, 2) - .putAll("PK", 3, 2, 3, 3, 2, 2) - .putAll("PL", 1, 1, 2, 2, 3, 2) - .putAll("PM", 0, 2, 2, 2, 2, 2) - .putAll("PR", 2, 3, 2, 2, 3, 3) - .putAll("PS", 3, 4, 1, 2, 2, 2) - .putAll("PT", 0, 1, 0, 0, 2, 2) - .putAll("PW", 2, 2, 4, 1, 2, 2) - .putAll("PY", 2, 2, 3, 2, 2, 2) - .putAll("QA", 2, 4, 2, 4, 4, 2) - .putAll("RE", 1, 1, 1, 2, 2, 2) - .putAll("RO", 0, 0, 1, 1, 1, 2) - .putAll("RS", 1, 0, 0, 0, 2, 2) - .putAll("RU", 0, 0, 0, 1, 2, 2) - .putAll("RW", 3, 4, 3, 0, 2, 2) - .putAll("SA", 2, 2, 1, 1, 2, 2) - .putAll("SB", 4, 2, 4, 3, 2, 2) - .putAll("SC", 4, 3, 0, 2, 2, 2) - .putAll("SD", 4, 4, 4, 4, 2, 2) - .putAll("SE", 0, 0, 0, 0, 0, 2) - .putAll("SG", 1, 1, 2, 3, 1, 4) - .putAll("SH", 4, 2, 2, 2, 2, 2) - .putAll("SI", 0, 0, 0, 0, 1, 2) - .putAll("SJ", 0, 2, 2, 2, 2, 2) - .putAll("SK", 0, 0, 0, 0, 0, 2) - .putAll("SL", 4, 3, 4, 1, 2, 2) - .putAll("SM", 0, 2, 2, 2, 2, 2) - .putAll("SN", 4, 4, 4, 4, 2, 2) - .putAll("SO", 3, 2, 3, 3, 2, 2) - .putAll("SR", 2, 3, 2, 2, 2, 2) - .putAll("SS", 4, 2, 2, 2, 2, 2) - .putAll("ST", 3, 2, 2, 2, 2, 2) - .putAll("SV", 2, 2, 3, 3, 2, 2) - .putAll("SX", 2, 2, 1, 0, 2, 2) - .putAll("SY", 4, 3, 4, 4, 2, 2) - .putAll("SZ", 4, 3, 2, 4, 2, 2) - .putAll("TC", 2, 2, 1, 0, 2, 2) - .putAll("TD", 4, 4, 4, 4, 2, 2) - .putAll("TG", 3, 3, 2, 0, 2, 2) - .putAll("TH", 0, 3, 2, 3, 3, 0) - .putAll("TJ", 4, 2, 4, 4, 2, 2) - .putAll("TL", 4, 3, 4, 4, 2, 2) - .putAll("TM", 4, 2, 4, 2, 2, 2) - .putAll("TN", 2, 2, 1, 1, 2, 2) - .putAll("TO", 4, 2, 3, 3, 2, 2) - .putAll("TR", 1, 1, 0, 1, 2, 2) - .putAll("TT", 1, 4, 1, 1, 2, 2) - .putAll("TV", 4, 2, 2, 2, 2, 2) - .putAll("TW", 0, 0, 0, 0, 0, 0) - .putAll("TZ", 3, 4, 3, 3, 2, 2) - .putAll("UA", 0, 3, 1, 1, 2, 2) - .putAll("UG", 3, 3, 3, 3, 2, 2) - .putAll("US", 1, 1, 2, 2, 3, 2) - .putAll("UY", 2, 2, 1, 2, 2, 2) - .putAll("UZ", 2, 2, 3, 4, 2, 2) - .putAll("VC", 1, 2, 2, 2, 2, 2) - .putAll("VE", 4, 4, 4, 4, 2, 2) - .putAll("VG", 2, 2, 1, 1, 2, 2) - .putAll("VI", 1, 2, 1, 3, 2, 2) - .putAll("VN", 0, 3, 3, 4, 2, 2) - .putAll("VU", 4, 2, 2, 1, 2, 2) - .putAll("WF", 4, 2, 2, 4, 2, 2) - .putAll("WS", 3, 1, 2, 1, 2, 2) - .putAll("XK", 1, 1, 1, 1, 2, 2) - .putAll("YE", 4, 4, 4, 4, 2, 2) - .putAll("YT", 4, 1, 1, 1, 2, 2) - .putAll("ZA", 3, 3, 1, 1, 1, 2) - .putAll("ZM", 3, 3, 4, 2, 2, 2) - .putAll("ZW", 3, 2, 4, 3, 2, 2) - .build(); + /** + * Returns initial bitrate group assignments for a {@code country}. The initial bitrate is a list + * of indexes for [Wifi, 2G, 3G, 4G, 5G_NSA, 5G_SA]. + */ + private static int[] getInitialBitrateCountryGroupAssignment(String country) { + switch (country) { + case "AE": + return new int[] {1, 4, 4, 4, 3, 2}; + case "AG": + return new int[] {2, 3, 1, 2, 2, 2}; + case "AM": + return new int[] {2, 3, 2, 4, 2, 2}; + case "AR": + return new int[] {2, 4, 1, 1, 2, 2}; + case "AS": + return new int[] {2, 2, 2, 3, 2, 2}; + case "AU": + return new int[] {0, 1, 0, 1, 2, 2}; + case "BE": + return new int[] {0, 0, 3, 3, 2, 2}; + case "BF": + return new int[] {4, 3, 4, 3, 2, 2}; + case "BH": + return new int[] {1, 2, 2, 4, 4, 2}; + case "BJ": + return new int[] {4, 4, 3, 4, 2, 2}; + case "BN": + return new int[] {3, 2, 1, 1, 2, 2}; + case "BO": + return new int[] {1, 3, 3, 2, 2, 2}; + case "BQ": + return new int[] {1, 2, 2, 0, 2, 2}; + case "BS": + return new int[] {4, 2, 2, 3, 2, 2}; + case "BT": + return new int[] {3, 1, 3, 2, 2, 2}; + case "BY": + return new int[] {0, 1, 1, 3, 2, 2}; + case "BZ": + return new int[] {2, 4, 2, 2, 2, 2}; + case "CA": + return new int[] {0, 2, 1, 2, 4, 1}; + case "CD": + return new int[] {4, 2, 3, 1, 2, 2}; + case "CF": + return new int[] {4, 2, 3, 2, 2, 2}; + case "CI": + return new int[] {3, 3, 3, 4, 2, 2}; + case "CK": + return new int[] {2, 2, 2, 1, 2, 2}; + case "AO": + case "CM": + return new int[] {3, 4, 3, 2, 2, 2}; + case "CN": + return new int[] {2, 0, 2, 2, 3, 1}; + case "CO": + return new int[] {2, 2, 4, 2, 2, 2}; + case "CR": + return new int[] {2, 2, 4, 4, 2, 2}; + case "CV": + return new int[] {2, 3, 1, 0, 2, 2}; + case "CW": + return new int[] {2, 2, 0, 0, 2, 2}; + case "CY": + return new int[] {1, 0, 0, 0, 1, 2}; + case "DE": + return new int[] {0, 0, 2, 2, 1, 2}; + case "DJ": + return new int[] {4, 1, 4, 4, 2, 2}; + case "DK": + return new int[] {0, 0, 1, 0, 0, 2}; + case "EC": + return new int[] {2, 4, 2, 1, 2, 2}; + case "EG": + return new int[] {3, 4, 2, 3, 2, 2}; + case "ET": + return new int[] {4, 4, 3, 1, 2, 2}; + case "FI": + return new int[] {0, 0, 0, 1, 0, 2}; + case "FJ": + return new int[] {3, 1, 3, 3, 2, 2}; + case "FM": + return new int[] {3, 2, 4, 2, 2, 2}; + case "FR": + return new int[] {1, 1, 2, 1, 1, 1}; + case "GA": + return new int[] {2, 3, 1, 1, 2, 2}; + case "GB": + return new int[] {0, 0, 1, 1, 2, 3}; + case "GE": + return new int[] {1, 1, 1, 3, 2, 2}; + case "BB": + case "FO": + case "GG": + return new int[] {0, 2, 0, 0, 2, 2}; + case "GH": + return new int[] {3, 2, 3, 2, 2, 2}; + case "GN": + return new int[] {4, 3, 4, 2, 2, 2}; + case "GQ": + return new int[] {4, 2, 3, 4, 2, 2}; + case "GT": + return new int[] {2, 3, 2, 1, 2, 2}; + case "AW": + case "GU": + return new int[] {1, 2, 4, 4, 2, 2}; + case "BW": + case "GY": + return new int[] {3, 4, 1, 0, 2, 2}; + case "HK": + return new int[] {0, 1, 2, 3, 2, 0}; + case "HU": + return new int[] {0, 0, 0, 1, 3, 2}; + case "ID": + return new int[] {3, 2, 3, 3, 3, 2}; + case "ES": + case "IE": + return new int[] {0, 1, 1, 1, 2, 2}; + case "IL": + return new int[] {1, 1, 2, 3, 4, 2}; + case "IM": + return new int[] {0, 2, 0, 1, 2, 2}; + case "IN": + return new int[] {1, 1, 3, 2, 4, 3}; + case "IR": + return new int[] {3, 0, 1, 1, 3, 0}; + case "IT": + return new int[] {0, 1, 0, 1, 1, 2}; + case "JE": + return new int[] {3, 2, 1, 2, 2, 2}; + case "DO": + case "JM": + return new int[] {3, 4, 4, 4, 2, 2}; + case "JP": + return new int[] {0, 1, 0, 1, 1, 1}; + case "KE": + return new int[] {3, 3, 2, 2, 2, 2}; + case "KG": + return new int[] {2, 1, 1, 1, 2, 2}; + case "KH": + return new int[] {1, 1, 4, 2, 2, 2}; + case "KR": + return new int[] {0, 0, 1, 3, 4, 4}; + case "KW": + return new int[] {1, 1, 0, 0, 0, 2}; + case "AL": + case "BA": + case "KY": + return new int[] {1, 2, 0, 1, 2, 2}; + case "KZ": + return new int[] {1, 1, 2, 2, 2, 2}; + case "LB": + return new int[] {3, 2, 1, 4, 2, 2}; + case "AD": + case "BM": + case "GL": + case "LC": + return new int[] {1, 2, 0, 0, 2, 2}; + case "LK": + return new int[] {3, 1, 3, 4, 4, 2}; + case "LR": + return new int[] {3, 4, 4, 3, 2, 2}; + case "LS": + return new int[] {3, 3, 4, 3, 2, 2}; + case "LU": + return new int[] {1, 0, 2, 2, 2, 2}; + case "MC": + return new int[] {0, 2, 2, 0, 2, 2}; + case "JO": + case "ME": + return new int[] {1, 0, 0, 1, 2, 2}; + case "MF": + return new int[] {1, 2, 1, 0, 2, 2}; + case "MG": + return new int[] {3, 4, 2, 2, 2, 2}; + case "MH": + return new int[] {3, 2, 2, 4, 2, 2}; + case "ML": + return new int[] {4, 3, 3, 1, 2, 2}; + case "MM": + return new int[] {2, 4, 3, 3, 2, 2}; + case "MN": + return new int[] {2, 0, 1, 2, 2, 2}; + case "MO": + return new int[] {0, 2, 4, 4, 2, 2}; + case "GF": + case "GP": + case "MQ": + return new int[] {2, 1, 2, 3, 2, 2}; + case "MR": + return new int[] {4, 1, 3, 4, 2, 2}; + case "EE": + case "LT": + case "LV": + case "MT": + return new int[] {0, 0, 0, 0, 2, 2}; + case "MU": + return new int[] {3, 1, 1, 2, 2, 2}; + case "MV": + return new int[] {3, 4, 1, 4, 2, 2}; + case "MW": + return new int[] {4, 2, 1, 0, 2, 2}; + case "CG": + case "MX": + return new int[] {2, 4, 3, 4, 2, 2}; + case "BD": + case "MY": + return new int[] {2, 1, 3, 3, 2, 2}; + case "NA": + return new int[] {4, 3, 2, 2, 2, 2}; + case "AZ": + case "NC": + return new int[] {3, 2, 4, 4, 2, 2}; + case "NG": + return new int[] {3, 4, 1, 1, 2, 2}; + case "NI": + return new int[] {2, 3, 4, 3, 2, 2}; + case "NL": + return new int[] {0, 0, 3, 2, 0, 4}; + case "NO": + return new int[] {0, 0, 2, 0, 0, 2}; + case "NP": + return new int[] {2, 1, 4, 3, 2, 2}; + case "NR": + return new int[] {3, 2, 2, 0, 2, 2}; + case "NZ": + return new int[] {1, 0, 1, 2, 4, 2}; + case "OM": + return new int[] {2, 3, 1, 3, 4, 2}; + case "PA": + return new int[] {1, 3, 3, 3, 2, 2}; + case "PE": + return new int[] {2, 3, 4, 4, 4, 2}; + case "PF": + return new int[] {2, 3, 3, 1, 2, 2}; + case "CU": + case "PG": + return new int[] {4, 4, 3, 2, 2, 2}; + case "PH": + return new int[] {2, 2, 3, 3, 3, 2}; + case "PR": + return new int[] {2, 3, 2, 2, 3, 3}; + case "PS": + return new int[] {3, 4, 1, 2, 2, 2}; + case "PT": + return new int[] {0, 1, 0, 0, 2, 2}; + case "PW": + return new int[] {2, 2, 4, 1, 2, 2}; + case "PY": + return new int[] {2, 2, 3, 2, 2, 2}; + case "QA": + return new int[] {2, 4, 2, 4, 4, 2}; + case "RE": + return new int[] {1, 1, 1, 2, 2, 2}; + case "RO": + return new int[] {0, 0, 1, 1, 1, 2}; + case "GR": + case "HR": + case "MD": + case "MK": + case "RS": + return new int[] {1, 0, 0, 0, 2, 2}; + case "RU": + return new int[] {0, 0, 0, 1, 2, 2}; + case "RW": + return new int[] {3, 4, 3, 0, 2, 2}; + case "KI": + case "KM": + case "LY": + case "SB": + return new int[] {4, 2, 4, 3, 2, 2}; + case "SC": + return new int[] {4, 3, 0, 2, 2, 2}; + case "SG": + return new int[] {1, 1, 2, 3, 1, 4}; + case "BG": + case "CZ": + case "SI": + return new int[] {0, 0, 0, 0, 1, 2}; + case "AT": + case "CH": + case "IS": + case "SE": + case "SK": + return new int[] {0, 0, 0, 0, 0, 2}; + case "SL": + return new int[] {4, 3, 4, 1, 2, 2}; + case "AX": + case "GI": + case "LI": + case "MP": + case "PM": + case "SJ": + case "SM": + return new int[] {0, 2, 2, 2, 2, 2}; + case "HN": + case "PK": + case "SO": + return new int[] {3, 2, 3, 3, 2, 2}; + case "BR": + case "SR": + return new int[] {2, 3, 2, 2, 2, 2}; + case "FK": + case "KP": + case "MA": + case "MZ": + case "ST": + return new int[] {3, 2, 2, 2, 2, 2}; + case "SV": + return new int[] {2, 2, 3, 3, 2, 2}; + case "SZ": + return new int[] {4, 3, 2, 4, 2, 2}; + case "SX": + case "TC": + return new int[] {2, 2, 1, 0, 2, 2}; + case "TG": + return new int[] {3, 3, 2, 0, 2, 2}; + case "TH": + return new int[] {0, 3, 2, 3, 3, 0}; + case "TJ": + return new int[] {4, 2, 4, 4, 2, 2}; + case "BI": + case "DZ": + case "SY": + case "TL": + return new int[] {4, 3, 4, 4, 2, 2}; + case "TM": + return new int[] {4, 2, 4, 2, 2, 2}; + case "TO": + return new int[] {4, 2, 3, 3, 2, 2}; + case "TR": + return new int[] {1, 1, 0, 1, 2, 2}; + case "TT": + return new int[] {1, 4, 1, 1, 2, 2}; + case "AQ": + case "ER": + case "IO": + case "NU": + case "SH": + case "SS": + case "TV": + return new int[] {4, 2, 2, 2, 2, 2}; + case "TW": + return new int[] {0, 0, 0, 0, 0, 0}; + case "GW": + case "TZ": + return new int[] {3, 4, 3, 3, 2, 2}; + case "UA": + return new int[] {0, 3, 1, 1, 2, 2}; + case "IQ": + case "UG": + return new int[] {3, 3, 3, 3, 2, 2}; + case "CL": + case "PL": + case "US": + return new int[] {1, 1, 2, 2, 3, 2}; + case "LA": + case "UY": + return new int[] {2, 2, 1, 2, 2, 2}; + case "UZ": + return new int[] {2, 2, 3, 4, 2, 2}; + case "AI": + case "BL": + case "CX": + case "DM": + case "GD": + case "MS": + case "VC": + return new int[] {1, 2, 2, 2, 2, 2}; + case "SA": + case "TN": + case "VG": + return new int[] {2, 2, 1, 1, 2, 2}; + case "VI": + return new int[] {1, 2, 1, 3, 2, 2}; + case "VN": + return new int[] {0, 3, 3, 4, 2, 2}; + case "VU": + return new int[] {4, 2, 2, 1, 2, 2}; + case "GM": + case "WF": + return new int[] {4, 2, 2, 4, 2, 2}; + case "WS": + return new int[] {3, 1, 2, 1, 2, 2}; + case "XK": + return new int[] {1, 1, 1, 1, 2, 2}; + case "AF": + case "HT": + case "NE": + case "SD": + case "SN": + case "TD": + case "VE": + case "YE": + return new int[] {4, 4, 4, 4, 2, 2}; + case "YT": + return new int[] {4, 1, 1, 1, 2, 2}; + case "ZA": + return new int[] {3, 3, 1, 1, 1, 2}; + case "ZM": + return new int[] {3, 3, 4, 2, 2, 2}; + case "ZW": + return new int[] {3, 2, 4, 3, 2, 2}; + default: + return new int[] {2, 2, 2, 2, 2, 2}; + } } } From 368f4d675457946568558b6b1fba96a177adbd71 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Nov 2021 11:28:50 +0000 Subject: [PATCH 090/113] Suppress lint warning about IntDef assignment. The values returned by the framework method are equivalent to the local IntDef values. PiperOrigin-RevId: 407048748 --- .../com/google/android/exoplayer2/drm/FrameworkMediaDrm.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index e4ccaf1853..9cc9910443 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -182,6 +182,8 @@ public final class FrameworkMediaDrm implements ExoMediaDrm { mediaDrm.closeSession(sessionId); } + // Return values of MediaDrm.KeyRequest.getRequestType are equal to KeyRequest.RequestType. + @SuppressLint("WrongConstant") @Override public KeyRequest getKeyRequest( byte[] scope, From 9970aaf6738c1e149e2c669aa0d7a4e703a79cc3 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Tue, 2 Nov 2021 15:18:29 +0000 Subject: [PATCH 091/113] Update the TransformerMediaClock trackTime before deducting the offset. #minor-release PiperOrigin-RevId: 407086818 --- .../exoplayer2/transformer/TransformerAudioRenderer.java | 2 +- .../exoplayer2/transformer/TransformerBaseRenderer.java | 3 +-- .../exoplayer2/transformer/TransformerMuxingVideoRenderer.java | 2 +- .../transformer/TransformerTranscodingVideoRenderer.java | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 8da9dcd2e1..f20915c120 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -279,8 +279,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; int result = readSource(getFormatHolder(), decoderInputBuffer, /* readFlags= */ 0); switch (result) { case C.RESULT_BUFFER_READ: - decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); + decoderInputBuffer.timeUs -= streamOffsetUs; decoderInputBuffer.flip(); decoder.queueInputBuffer(decoderInputBuffer); return !decoderInputBuffer.isEndOfStream(); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java index 6e19f0b9f9..d3fe72d65b 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java @@ -48,8 +48,7 @@ import com.google.android.exoplayer2.util.MimeTypes; } @Override - protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) - throws ExoPlaybackException { + protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { this.streamOffsetUs = offsetUs; } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java index d14378754e..4692a6ca81 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java @@ -117,8 +117,8 @@ import java.nio.ByteBuffer; muxerWrapper.endTrack(getTrackType()); return false; } - buffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), buffer.timeUs); + buffer.timeUs -= streamOffsetUs; ByteBuffer data = checkNotNull(buffer.data); data.flip(); if (sampleTransformer != null) { diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index 931e985a5d..f4836e49df 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -320,8 +320,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; case C.RESULT_FORMAT_READ: throw new IllegalStateException("Format changes are not supported."); case C.RESULT_BUFFER_READ: - decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); + decoderInputBuffer.timeUs -= streamOffsetUs; ByteBuffer data = checkNotNull(decoderInputBuffer.data); data.flip(); decoder.queueInputBuffer(decoderInputBuffer); From 1ed5861a725e98886d9a174f112087b3d9fb37af Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Nov 2021 17:01:13 +0000 Subject: [PATCH 092/113] Suppress lint warnings in leanback module. These warnings are caused by the fact that this is a library and the lint check doesn't see any app using the library in a TV context. PiperOrigin-RevId: 407110725 --- extensions/leanback/src/main/AndroidManifest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/leanback/src/main/AndroidManifest.xml b/extensions/leanback/src/main/AndroidManifest.xml index e385551143..1649d3c386 100644 --- a/extensions/leanback/src/main/AndroidManifest.xml +++ b/extensions/leanback/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ limitations under the License. --> - + From 41e338229af5c6e666b8006e992f8aef47fcabe4 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 2 Nov 2021 18:36:26 +0000 Subject: [PATCH 093/113] Parse HDR static metadata from MP4 files #minor-release PiperOrigin-RevId: 407136922 --- RELEASENOTES.md | 1 + .../exoplayer2/extractor/mp4/Atom.java | 6 + .../exoplayer2/extractor/mp4/AtomParsers.java | 87 +++- .../extractor/mp4/Mp4ExtractorTest.java | 6 + .../sample_with_colr_mdcv_and_clli.mp4.0.dump | 454 ++++++++++++++++++ .../sample_with_colr_mdcv_and_clli.mp4.1.dump | 398 +++++++++++++++ .../sample_with_colr_mdcv_and_clli.mp4.2.dump | 338 +++++++++++++ .../sample_with_colr_mdcv_and_clli.mp4.3.dump | 282 +++++++++++ ...colr_mdcv_and_clli.mp4.unknown_length.dump | 454 ++++++++++++++++++ .../mp4/sample_with_colr_mdcv_and_clli.mp4 | Bin 0 -> 285393 bytes 10 files changed, 2015 insertions(+), 11 deletions(-) create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump create mode 100644 testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0d8bf3fdb5..586144ca28 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -64,6 +64,7 @@ * MP4: Avoid throwing `ArrayIndexOutOfBoundsException` when parsing invalid `colr` boxes produced by some device cameras ([#9332](https://github.com/google/ExoPlayer/issues/9332)). + * MP4: Parse HDR static metadata from the `clli` and `mdcv` boxes. * TS: Correctly handle HEVC tracks with pixel aspect ratios other than 1. * TS: Map stream type 0x80 to H262 ([#9472](https://github.com/google/ExoPlayer/issues/9472)). diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index bc8633acc8..9c5de24b70 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -410,6 +410,12 @@ import java.util.List; @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_twos = 0x74776f73; + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_clli = 0x636c6c69; + + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_mdcv = 0x6d646376; + public final int type; public Atom(int type) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index bc5fa10fe3..442758716a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -45,6 +45,8 @@ import com.google.android.exoplayer2.video.DolbyVisionConfig; import com.google.android.exoplayer2.video.HevcConfig; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -1061,6 +1063,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; .build(); } + // hdrStaticInfo is allocated using allocate() in allocateHdrStaticInfo(). + @SuppressWarnings("ByteBufferBackingArray") private static void parseVideoSampleEntry( ParsableByteArray parent, int atomType, @@ -1112,7 +1116,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Nullable String codecs = null; @Nullable byte[] projectionData = null; @C.StereoMode int stereoMode = Format.NO_VALUE; - @Nullable ColorInfo colorInfo = null; + + // HDR related metadata. + @C.ColorSpace int colorSpace = Format.NO_VALUE; + @C.ColorRange int colorRange = Format.NO_VALUE; + @C.ColorTransfer int colorTransfer = Format.NO_VALUE; + // The format of HDR static info is defined in CTA-861-G:2017, Table 45. + @Nullable ByteBuffer hdrStaticInfo = null; + while (childPosition - position < size) { parent.setPosition(childPosition); int childStartPosition = parent.getPosition(); @@ -1157,6 +1168,43 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } else if (childAtomType == Atom.TYPE_av1C) { ExtractorUtil.checkContainerInput(mimeType == null, /* message= */ null); mimeType = MimeTypes.VIDEO_AV1; + } else if (childAtomType == Atom.TYPE_clli) { + if (hdrStaticInfo == null) { + hdrStaticInfo = allocateHdrStaticInfo(); + } + // The contents of the clli box occupy the last 4 bytes of the HDR static info array. Note + // that each field is read in big endian and written in little endian. + hdrStaticInfo.position(21); + hdrStaticInfo.putShort(parent.readShort()); // max_content_light_level. + hdrStaticInfo.putShort(parent.readShort()); // max_pic_average_light_level. + } else if (childAtomType == Atom.TYPE_mdcv) { + if (hdrStaticInfo == null) { + hdrStaticInfo = allocateHdrStaticInfo(); + } + // The contents of the mdcv box occupy 20 bytes after the first byte of the HDR static info + // array. Note that each field is read in big endian and written in little endian. + short displayPrimariesGX = parent.readShort(); + short displayPrimariesGY = parent.readShort(); + short displayPrimariesBX = parent.readShort(); + short displayPrimariesBY = parent.readShort(); + short displayPrimariesRX = parent.readShort(); + short displayPrimariesRY = parent.readShort(); + short whitePointX = parent.readShort(); + short whitePointY = parent.readShort(); + long maxDisplayMasteringLuminance = parent.readUnsignedInt(); + long minDisplayMasteringLuminance = parent.readUnsignedInt(); + + hdrStaticInfo.position(1); + hdrStaticInfo.putShort(displayPrimariesRX); + hdrStaticInfo.putShort(displayPrimariesRY); + hdrStaticInfo.putShort(displayPrimariesGX); + hdrStaticInfo.putShort(displayPrimariesGY); + hdrStaticInfo.putShort(displayPrimariesBX); + hdrStaticInfo.putShort(displayPrimariesBY); + hdrStaticInfo.putShort(whitePointX); + hdrStaticInfo.putShort(whitePointY); + hdrStaticInfo.putShort((short) (maxDisplayMasteringLuminance / 10000)); + hdrStaticInfo.putShort((short) (minDisplayMasteringLuminance / 10000)); } else if (childAtomType == Atom.TYPE_d263) { ExtractorUtil.checkContainerInput(mimeType == null, /* message= */ null); mimeType = MimeTypes.VIDEO_H263; @@ -1211,12 +1259,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; // size=18): https://github.com/google/ExoPlayer/issues/9332 boolean fullRangeFlag = childAtomSize == 19 && (parent.readUnsignedByte() & 0b10000000) != 0; - colorInfo = - new ColorInfo( - ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries), - fullRangeFlag ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED, - ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics), - /* hdrStaticInfo= */ null); + colorSpace = ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries); + colorRange = fullRangeFlag ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED; + colorTransfer = + ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics); } else { Log.w(TAG, "Unsupported color type: " + Atom.getAtomTypeString(colorType)); } @@ -1229,7 +1275,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; return; } - out.format = + Format.Builder formatBuilder = new Format.Builder() .setId(trackId) .setSampleMimeType(mimeType) @@ -1241,9 +1287,28 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; .setProjectionData(projectionData) .setStereoMode(stereoMode) .setInitializationData(initializationData) - .setDrmInitData(drmInitData) - .setColorInfo(colorInfo) - .build(); + .setDrmInitData(drmInitData); + if (colorSpace != Format.NO_VALUE + || colorRange != Format.NO_VALUE + || colorTransfer != Format.NO_VALUE + || hdrStaticInfo != null) { + // Note that if either mdcv or clli are missing, we leave the corresponding HDR static + // metadata bytes with value zero. See [Internal ref: b/194535665]. + formatBuilder.setColorInfo( + new ColorInfo( + colorSpace, + colorRange, + colorTransfer, + hdrStaticInfo != null ? hdrStaticInfo.array() : null)); + } + out.format = formatBuilder.build(); + } + + private static ByteBuffer allocateHdrStaticInfo() { + // For HDR static info, Android decoders expect a 25-byte array. The first byte is zero to + // represent Static Metadata Type 1, as per CTA-861-G:2017, Table 44. The following 24 bytes + // follow CTA-861-G:2017, Table 45. + return ByteBuffer.allocate(25).order(ByteOrder.LITTLE_ENDIAN); } private static void parseMetaDataSampleEntry( diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java index 911f3f477e..88aba133e3 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java @@ -119,4 +119,10 @@ public final class Mp4ExtractorTest { ExtractorAsserts.assertBehavior( Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig); } + + @Test + public void mp4SampleWithColrMdcvAndClli() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_with_colr_mdcv_and_clli.mp4", simulationConfig); + } } diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump new file mode 100644 index 0000000000..8ef4f19b16 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump @@ -0,0 +1,454 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 16638 + sample count = 44 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 0 + flags = 1 + data = length 393, hash 706D1B6F + sample 1: + time = 23219 + flags = 1 + data = length 400, hash B48107D1 + sample 2: + time = 46439 + flags = 1 + data = length 398, hash E5F4E9C1 + sample 3: + time = 69659 + flags = 1 + data = length 400, hash 4317B40D + sample 4: + time = 92879 + flags = 1 + data = length 403, hash CB949D88 + sample 5: + time = 116099 + flags = 1 + data = length 411, hash 616C8F82 + sample 6: + time = 139319 + flags = 1 + data = length 392, hash 3BA50F06 + sample 7: + time = 162539 + flags = 1 + data = length 401, hash 1C62F82C + sample 8: + time = 185759 + flags = 1 + data = length 400, hash 180FEA17 + sample 9: + time = 208979 + flags = 1 + data = length 378, hash 2F6B0AE6 + sample 10: + time = 232199 + flags = 1 + data = length 375, hash 6AE86D08 + sample 11: + time = 255419 + flags = 1 + data = length 375, hash EF2FD9CC + sample 12: + time = 278639 + flags = 1 + data = length 374, hash 97B83243 + sample 13: + time = 301859 + flags = 1 + data = length 382, hash 8BD6191C + sample 14: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 15: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 16: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 17: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 18: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 19: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 20: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 21: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 22: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 23: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 24: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 25: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 26: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 27: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 28: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 29: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 30: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 31: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 32: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 33: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 34: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 35: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 36: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 37: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 38: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 39: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 40: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 41: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 42: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 43: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump new file mode 100644 index 0000000000..1e1023afb0 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump @@ -0,0 +1,398 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 11156 + sample count = 30 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 1: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 2: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 3: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 4: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 5: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 6: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 7: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 8: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 9: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 10: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 11: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 12: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 13: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 14: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 15: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 16: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 17: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 18: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 19: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 20: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 21: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 22: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 23: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 24: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 25: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 26: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 27: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 28: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 29: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump new file mode 100644 index 0000000000..5b51396c83 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump @@ -0,0 +1,338 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 5567 + sample count = 15 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 1: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 2: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 3: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 4: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 5: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 6: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 7: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 8: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 9: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 10: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 11: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 12: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 13: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 14: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump new file mode 100644 index 0000000000..d66f9234a1 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump @@ -0,0 +1,282 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 374 + sample count = 1 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump new file mode 100644 index 0000000000..8ef4f19b16 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump @@ -0,0 +1,454 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 16638 + sample count = 44 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 0 + flags = 1 + data = length 393, hash 706D1B6F + sample 1: + time = 23219 + flags = 1 + data = length 400, hash B48107D1 + sample 2: + time = 46439 + flags = 1 + data = length 398, hash E5F4E9C1 + sample 3: + time = 69659 + flags = 1 + data = length 400, hash 4317B40D + sample 4: + time = 92879 + flags = 1 + data = length 403, hash CB949D88 + sample 5: + time = 116099 + flags = 1 + data = length 411, hash 616C8F82 + sample 6: + time = 139319 + flags = 1 + data = length 392, hash 3BA50F06 + sample 7: + time = 162539 + flags = 1 + data = length 401, hash 1C62F82C + sample 8: + time = 185759 + flags = 1 + data = length 400, hash 180FEA17 + sample 9: + time = 208979 + flags = 1 + data = length 378, hash 2F6B0AE6 + sample 10: + time = 232199 + flags = 1 + data = length 375, hash 6AE86D08 + sample 11: + time = 255419 + flags = 1 + data = length 375, hash EF2FD9CC + sample 12: + time = 278639 + flags = 1 + data = length 374, hash 97B83243 + sample 13: + time = 301859 + flags = 1 + data = length 382, hash 8BD6191C + sample 14: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 15: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 16: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 17: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 18: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 19: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 20: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 21: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 22: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 23: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 24: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 25: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 26: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 27: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 28: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 29: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 30: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 31: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 32: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 33: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 34: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 35: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 36: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 37: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 38: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 39: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 40: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 41: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 42: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 43: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 b/testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..608d6ec440cca8d7c74b8135723256c0925ab81a GIT binary patch literal 285393 zcmV(uKiDL;^uF-suPs0O%wDzyK3e07r6MWk#mZFSI{-6Mu@x1C}PA zKc_x-j|VrsmF0MoeZudMD;_#W?rFWUoar-4Eh$lIe5gp{HEzc<3!KX$aMwH0%O2!Ss0s4U@qMpRC4NcUrU0fLICm$dWWiz7EyL3W^8}6! z*}45Z%D5gN?_*gWFiVOCn}(AhlsEd%MmDW_;1ML&EqJ%=ExyIUwsPLQ2c2EWK)fqf9fq(dcN6hh&t;#)1Z^IeE zemijN5c+=Bk69ozIP%i~EzZWmX>9fERh~EeO`}*>r?}xX+NyoxInL@b;d=Jw-b4Gs27=gsfUIB^u`K0{_Ty1Is>(%G3k76rJ!}R z5xHzjY6QBO&-B9l4lZj`GOKSMllJn!>dNI%uEV-kG-b_AF=ls7X7LUYpohlj);K3i zu}e3zZJl-Rd5&nE`@szX*-hM2Em^lO{1k#;gUPFfdmWcDbUoE#3*Eve|9talM0(D? zYyUEFY+!j?;4+3Q6VZ{SDp=bg$(fUG@w>pG$W_j-oSCUJ&aU4^7g}hJ!)U7JS^;6phkJnknp`fH9? zfQ9VFs1Yod+->4#WK(8dv9=nLdqss*WBn0LdEKI<++i`hr z0KJj_O;iNQV;BRFKbJ(ug4hWcWJfZGQvoYs#B5Q*biUas9K+x?vzL{dZ{7V} zNy?rmBr|FM_*PZ)pi%`uONF5^9c+%iU|dP{UY3-=&Fsvwq#a;;WN9$i`Eg)%SplW9 zCYP$JeKCrVbg;id%LtB_gVaRB&V^YYZZY+ibiJ zmDGJ}IjkaLgqOJK2V>`%FAW5!zS%r&6sXZ5Ldh2WcpwcW5RHNkl}XGVzpf-r+5Ix^ za1c7Z&Ghd7`C+;X=uhwz*x&339n$s8t!Oc9o-|G6Bq(^5d32{z^l1wDC=P>M)J&&T zMSI?pwSF_E_8#M&ySP@acqK9M2_KR@vS0d@fGM%Y{SjfT1lN4_Ss-6cH*huaMj(m= z{>Haqy1Jd`Sy<(?0uZ;SYIO0b#_CG}oQD}Wv4?7n#RAoA)S}!yBg(nPlhZy^zXQ1B zgv6tU*pR|^->Kp=$nt=-XT@8iV~dEEg_}Hj7(R^lntf5lYE?^8d7p0weY4qsp(Gwj zxwH7s-YyPoo)t&8YjO1E30Ovpjc76c(d<+B=|MyBT^YQi?c*)QV9q||%fBkatp;SC zckj+h6+4HqHbmBJ$aw>crak~;#~Sktpf11SL-)NM>3KffxlC=2IZFrye_3z5|cH5KFqe+COFf57l@d9AvDl2L=Vwx9BM_%pbuqC+d>oEy2~sIzMj@Hp$5hk>?tMuRw)p$-TL1}|a^{P^ z{a=Xy#59meN_reC`aKYZRYd1`12#BLs+h!mP!T=FRz8dihcyuLEE+9w%-?lTLCQx` zjDFU(i!>s4MieG%OiUB8CSW7q1K;gfY5om`E3~wVT?qDAD1A`$eP_T^DDmI620r5c7Mvw17PWOrAi3 z)cdPP<1hwADYxz(&aBefSHI*_sYvtS1rq5#a8pB?;?x?OGjXqA`Q=u7XY~8zLfwu& zYHn5m!2vKaza^88=iKx&g7(-}cJYcowJqbQ1=npeJ0t-%Dq*Vk>sDC1>IskCrdvPc zC^m^zo1q%FWN6866Q4UC2Hl-Mf4|LvYlox({^Ch4nv?+cTX`u#5Zhp;b#=qy)tz7a z;Cp-NiF`(Xso6Q1Vnc`SybZ z>a2yDG4FqftSe8ShPc&RJhf&|lI3ta55xZ#=)oJBK97HqZ0l?5V=lwjBE}Ul;k$aM z9=j^HF5NHN9dmV9Y(M?G+oZCFaa46GUS=B)mKsthYf9g<6pBK^8eYtgS3Rh3Pwam!H zV&IzjIs+=7e3XI^^Wn7$A{ezaC7H|c0Ofp;mR608BBKP#%Q?3#Wt||HKL1wHc;_Sc&MD@uRoiVp+lv5= zE%Ei3b7oWdMj;2nvSM^UagR|))@6WnG5Ik!$?F2++=p`Z`2@;sDCnn&pL1b9*Ed3# zcFFg2d&WZOlCj(M@i?sWtyoVIQK(>f!B0V~$@WKeKy}uJcG${SUQowf0i*Enzl-|1 z*#f3{`IA;EJ|7;Ltgz&Iph6EU(mpJkOn+(i?!-oG*uZ zW_PAmvuiVvMVt<(OES-=#u>f0QdMJ8d?cOT<)vzPUC+|Qgh7cLR<~|2BS4NS>#&R6 zLfiU>l1>vyQv*6M%)nQcN9r&8;P`xOkGE*gDAWsazbE)f_~t++Fqk|UR~OdkNIt|< zX~=f8zFM%UI6ts*3%k97J%6RlnQFWcn%RdU{zOKCFJakMUJmT9D=)^SX|Kc+BT-sz z#96T$-5m#IPsD_sS@(1OMZ+ZF`;-)UCei6tV2RCh8VpHQ1pm8axkS2u&^Of!pcrR)C7QwLX#jTGkyJkKvv{Hz2O+v)+iRhKgR z#);kd%*S6|1ha$wKcqa*=o9XJ?6+wBDEHaO#zwnw51qPLE_Z}Z!_lXkb)k*mvlBv5 zSx}9obW+`_Nx5E)fj;V$;X_W<1c>2`>k2Jf6#DzI{hF6_Mc(ZNEfihNAX zn^k-cF-T21l-$mESNJ~IADK>=V%2?=VLkkm1ywcPzl($0P!o|ftShQ-V=NGL>4h`@ zXcOL~6fpudN?()Y6C($c6-V>Jz8Jgu{Jpq#0f@xohAt9A@pISq$5e94t*5t67R2aK zt;Ti6uPuLdllIl&TR%nAZi~zmTPPR2B|!sASpheVfzZp{rG&}ou(;nK1}pmodWvJF z6nE4|6)~3GD;Qu@#XN$?W=5?Iaatf<($_aUAG& zzr?#XMSH?wGCuxPyF1Uc#02;o`Rrq-vf)q;TX`%$?)ID?w}()9?P!Sy&Z_6191rnq z`}?ii(UFXfsm5rIYe8|ux|8-c<)7ix2P#Wwlqi>0roA@tl{TzgwGno#r1;19K@Lw# zti+c-8JX7tQx#ATzJB%D(E=QGOsZ#sl5|Rsq-n#PaS1tn$sp9{(kdy-6ZJc#NUDrS zJG81oNzxX5LPyY}O`*V-<4KR*b0HR!+YN9hm7fV;F3i>GfIw%uc4NYh5jlC4H4>Q) zZxBEa-uwpE@^>Nu4fPeY_c)9uel7d7$Q-`Gf?OA`F@8%KN^I+7I~wS~x^6qQtYuIA z=AwM^u+B7)wx9|lGjJ4}{C}ubMLY3{E4bZ*07i72ablw#594$cN{b=i9+nqG_LXuFcow&!4Q#N4hO1X8NRxM(wKqc__*=} zbfgCrZwBIbO#-X0rHIM%_l<#jCIuTuA*e`4t=8npK)L-;JikLD8iRKQbr0itH`Qo| zhQj!K0-D*BhUQ#_8I_^{SKNu4rHK-2`YEn%Y8-4vmOi=$g;|TM3O|RIrBFg^Y@-3VnGo@AWhfrqn`#)a^!V46^H{5gYaIMk702xJT_#p>0tKu38cDQPR}p6 zLt0)?#n|xAIYFD|R10hiqyL%mKK(1LcC*Gy7*&Dm0qDri;mr_bV(JljmCu6nOS1`q zmd2`h8-e;VY3gynslmidI1Fvc=(1Ge0?`l*Pl(Tp9ezzGhZcUKaWmz@m(S)b|6r9d zYsLxwnT6Zpab>(@NTA|EYNsDje@gRw*(Zg-7pZfP=|e`RYEv&NC_Oy?uf72QWYJ@t z+Ag!RihU;5DzkdRtj~a3_d7zDihzEyh=ua^xt-ZHB}wqJj4BAy*+gH|Lln-!p%pwo zD2|qBuFldh@Vqz*$I&Ue6RMgDsl#EkqCNH{+fME`^8{aO=V3b!=A;A9~@tHYsN9$(bO3?14WQuLx#b-w7oEi$?E-OG6=dXHu-n*qS=*%_BqvO&kW;I+~ z^Hj5Qy{?pGWrlT2MA9OZh(jFU0P`+8Y@c4UAD#09H~T`fHHp@>v(zbYD-rd`pjNP0 zW0mnoWPBJ~XGp9bQr~W)S$tw{ur1#gI!_2qIRo6_N}F`X!WyU>gGpQ#AsL1tODt?t zlpesKq~1I-->`nKIx8ssPtOqyo(04~P49V-%mm#QOw#!*7?AYQynVwqN) z8m+}G`!l;H8a%)#aviE9=D)VJmt?IjcB?g&Y*(I-@^9$C>_AY7hE#5j=qe3QDOm(Y zwuOL(w@eOyT04Ta)_k}Td9gli^?_#AucI0{@&V{zFI6t6QE&=dysgBapZrjk|0am3 z5jzLPN_sZVTDuc2Qw&koCPgNDsuCu93AhQ}<}ZwRS7T%)i-hR(TC7Z2xbZsyG5KR? zh2H{arxkndI~SecuSnwz3Y&A|(4Hqg7iME;W`c4I5u5V5A;lx!J14gWe$ zhos?!!NU~`CS8~tUTPvWW8&Mn_7V%!-L2oF8km&5Cjc@j{5D8bHi8bk!+SGO0I?B;*r_aW`*wK23N5iUaMh92x>pfk;viVfA0aC=s^){0A+7+v%KjMOs{q3Wd;P` zQkEcCEsA0{eDm!)KVnpiu9PhRRKJ{59`mXyA6pmckmo}Q(Wt^oQ=HwtN89RZxA+vV zqp^k4w`5s)UiPQTT@i6bsz=9eIa@{zv2~)a!qY6r=0Um)g<(OAiQ#UyPt03L#-nV- z2+}i#XPVHJA>{nOQT_P6v$hVcS)xJTc0qD;*>bli#Y44z=?%Tg+xL8Ua5Nd4OenP~&O$oth2C%aMyuJ{ zm0<-Odwpq4;HV&njP=L9-7_Rx2kn~&I(=~LBYMlQ#?BHX;VnUDPA2Y(UzK=LB5^~E z;9Rs{-+u)>lzTVtOfsBl4WVwwo9$KxQqy!*@65crc}ncJL3#)#y{j5|ZL+?kN0=Z_ z>AtTiJwVJp5{!jU8zl^le(c$Zl-et`sHgE@uW#b8?-Zo0yhb8v!JtR)&K>#ohu8J@ z_g%ayVQ4}lB5b(I)BHYj0o%hd66BClXPe%Tv@`a71m_GqPS0V6L5+~%hXhw8ouByhhu+yz#{}p_3ei6T7HNiAj#$) zh+noM9YV2c(Wti8MM?!siNxgO*1%yRE&spzJ R3mI@y@Dpx-h-GDW4x?ZGuP4m1 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%0DT>fyB|HXxYtUeH);X0N&E=-m}t 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 Cke;Ui literal 0 HcmV?d00001 From 7b9b878aeb762b04561bec19b24389c44cf5892e Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Nov 2021 20:21:34 +0000 Subject: [PATCH 094/113] Add missing RetentionPolicy for IntDef PiperOrigin-RevId: 407162673 --- .../android/exoplayer2/source/rtsp/RtspMessageChannel.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java index 526f508953..a877d72b13 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java @@ -39,6 +39,9 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.Socket; import java.nio.charset.Charset; import java.util.ArrayList; @@ -334,6 +337,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Processes RTSP messages line-by-line. */ private static final class MessageParser { + @Documented + @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_READING_FIRST_LINE, STATE_READING_HEADER, STATE_READING_BODY}) @interface ReadingState {} From a9f7b943c823b9337ec19ad2510514b93ec942c9 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 3 Nov 2021 07:57:55 +0000 Subject: [PATCH 095/113] Prepare for adding ServerSideInsertedAdsMediaSource for IMA PiperOrigin-RevId: 407274072 --- .../android/exoplayer2/ext/ima/ImaUtil.java | 30 +++++++++++++++++++ .../exoplayer2/offline/DownloadHelper.java | 3 +- .../source/DefaultMediaSourceFactory.java | 27 ++++++++++++++--- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index 13952abd72..9a40ba74de 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -41,6 +41,7 @@ import com.google.android.exoplayer2.upstream.DataSchemeDataSource; import com.google.android.exoplayer2.upstream.DataSourceUtil; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -134,6 +135,35 @@ import java.util.Set; } } + /** Stores configuration for DAI ad playback. */ + static final class DaiConfiguration { + + public final AdErrorEvent.AdErrorListener applicationAdErrorListener; + public final boolean debugModeEnabled; + + @Nullable public final List companionAdSlots; + @Nullable public final AdEvent.AdEventListener applicationAdEventListener; + @Nullable public final VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback; + @Nullable public final ImaSdkSettings imaSdkSettings; + + public DaiConfiguration( + AdErrorEvent.AdErrorListener applicationAdErrorListener, + @Nullable List companionAdSlots, + @Nullable AdEvent.AdEventListener applicationAdEventListener, + @Nullable VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback, + @Nullable ImaSdkSettings imaSdkSettings, + boolean debugModeEnabled) { + + this.applicationAdErrorListener = applicationAdErrorListener; + this.companionAdSlots = + companionAdSlots != null ? ImmutableList.copyOf(companionAdSlots) : null; + this.applicationAdEventListener = applicationAdEventListener; + this.applicationVideoAdPlayerCallback = applicationVideoAdPlayerCallback; + this.imaSdkSettings = imaSdkSettings; + this.debugModeEnabled = debugModeEnabled; + } + } + public static final int TIMEOUT_UNSET = -1; public static final int BITRATE_UNSET = -1; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 7fea1c5a43..e9d6c5829a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -891,7 +891,8 @@ public final class DownloadHelper { MediaItem mediaItem, DataSource.Factory dataSourceFactory, @Nullable DrmSessionManager drmSessionManager) { - return new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY) + return new DefaultMediaSourceFactory( + dataSourceFactory, ExtractorsFactory.EMPTY, /* serverSideDaiMediaSourceFactory= */ null) .setDrmSessionManager(drmSessionManager) .createMediaSource(mediaItem); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java index a98b8f4a1b..0cbae12170 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -119,6 +120,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { private final DataSource.Factory dataSourceFactory; private final DelegateFactoryLoader delegateFactoryLoader; + @Nullable private final MediaSourceFactory serverSideDaiMediaSourceFactory; @Nullable private AdsLoaderProvider adsLoaderProvider; @Nullable private AdViewProvider adViewProvider; @Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy; @@ -146,7 +148,10 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { * its container. */ public DefaultMediaSourceFactory(Context context, ExtractorsFactory extractorsFactory) { - this(new DefaultDataSource.Factory(context), extractorsFactory); + this( + new DefaultDataSource.Factory(context), + extractorsFactory, + /* serverSideDaiMediaSourceFactory= */ null); } /** @@ -156,7 +161,10 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { * for requesting media data. */ public DefaultMediaSourceFactory(DataSource.Factory dataSourceFactory) { - this(dataSourceFactory, new DefaultExtractorsFactory()); + this( + dataSourceFactory, + new DefaultExtractorsFactory(), + /* serverSideDaiMediaSourceFactory= */ null); } /** @@ -166,10 +174,17 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { * for requesting media data. * @param extractorsFactory An {@link ExtractorsFactory} used to extract progressive media from * its container. + * @param serverSideDaiMediaSourceFactory A {@link MediaSourceFactory} for creating server side + * inserted ad media sources. */ public DefaultMediaSourceFactory( - DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { + DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, + @Nullable MediaSourceFactory serverSideDaiMediaSourceFactory) { this.dataSourceFactory = dataSourceFactory; + // Temporary until factory registration is agreed upon. + this.serverSideDaiMediaSourceFactory = serverSideDaiMediaSourceFactory; + delegateFactoryLoader = new DelegateFactoryLoader(dataSourceFactory, extractorsFactory); liveTargetOffsetMs = C.TIME_UNSET; liveMinOffsetMs = C.TIME_UNSET; @@ -333,7 +348,11 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { @Override public MediaSource createMediaSource(MediaItem mediaItem) { - checkNotNull(mediaItem.localConfiguration); + Assertions.checkNotNull(mediaItem.localConfiguration); + @Nullable String scheme = mediaItem.localConfiguration.uri.getScheme(); + if (scheme != null && scheme.equals("imadai")) { + return checkNotNull(serverSideDaiMediaSourceFactory).createMediaSource(mediaItem); + } @C.ContentType int type = Util.inferContentTypeForUriAndMimeType( From 113939919325e271c854e939bc713c7287aa3b2d Mon Sep 17 00:00:00 2001 From: kimvde Date: Wed, 3 Nov 2021 11:01:07 +0000 Subject: [PATCH 096/113] WavExtractor: split header reading state into 2 states This refactoring is the basis to support RF64 (see Issue: google/ExoPlayer#9543). #minor-release PiperOrigin-RevId: 407301056 --- .../extractor/wav/WavExtractor.java | 135 ++++++++++-------- .../wav/{WavHeader.java => WavFormat.java} | 8 +- .../extractor/wav/WavHeaderReader.java | 57 ++++---- .../exoplayer2/extractor/wav/WavSeekMap.java | 16 +-- 4 files changed, 121 insertions(+), 95 deletions(-) rename library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/{WavHeader.java => WavFormat.java} (91%) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java index 97d98b5fcc..6d3fc254b1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java @@ -61,12 +61,18 @@ public final class WavExtractor implements Extractor { @Documented @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_USE}) - @IntDef({STATE_READING_HEADER, STATE_SKIPPING_TO_SAMPLE_DATA, STATE_READING_SAMPLE_DATA}) + @IntDef({ + STATE_READING_FILE_TYPE, + STATE_READING_FORMAT, + STATE_SKIPPING_TO_SAMPLE_DATA, + STATE_READING_SAMPLE_DATA + }) private @interface State {} - private static final int STATE_READING_HEADER = 0; - private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 1; - private static final int STATE_READING_SAMPLE_DATA = 2; + private static final int STATE_READING_FILE_TYPE = 0; + private static final int STATE_READING_FORMAT = 1; + private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 2; + private static final int STATE_READING_SAMPLE_DATA = 3; private @MonotonicNonNull ExtractorOutput extractorOutput; private @MonotonicNonNull TrackOutput trackOutput; @@ -76,14 +82,14 @@ public final class WavExtractor implements Extractor { private long dataEndPosition; public WavExtractor() { - state = STATE_READING_HEADER; + state = STATE_READING_FILE_TYPE; dataStartPosition = C.POSITION_UNSET; dataEndPosition = C.POSITION_UNSET; } @Override public boolean sniff(ExtractorInput input) throws IOException { - return WavHeaderReader.peek(input) != null; + return WavHeaderReader.checkFileType(input); } @Override @@ -95,7 +101,7 @@ public final class WavExtractor implements Extractor { @Override public void seek(long position, long timeUs) { - state = position == 0 ? STATE_READING_HEADER : STATE_READING_SAMPLE_DATA; + state = position == 0 ? STATE_READING_FILE_TYPE : STATE_READING_SAMPLE_DATA; if (outputWriter != null) { outputWriter.reset(timeUs); } @@ -111,8 +117,11 @@ public final class WavExtractor implements Extractor { public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { assertInitialized(); switch (state) { - case STATE_READING_HEADER: - readHeader(input); + case STATE_READING_FILE_TYPE: + readFileType(input); + return Extractor.RESULT_CONTINUE; + case STATE_READING_FORMAT: + readFormat(input); return Extractor.RESULT_CONTINUE; case STATE_SKIPPING_TO_SAMPLE_DATA: skipToSampleData(input); @@ -130,50 +139,54 @@ public final class WavExtractor implements Extractor { Util.castNonNull(extractorOutput); } - @RequiresNonNull({"extractorOutput", "trackOutput"}) - private void readHeader(ExtractorInput input) throws IOException { + private void readFileType(ExtractorInput input) throws IOException { Assertions.checkState(input.getPosition() == 0); if (dataStartPosition != C.POSITION_UNSET) { input.skipFully(dataStartPosition); state = STATE_READING_SAMPLE_DATA; return; } - WavHeader header = WavHeaderReader.peek(input); - if (header == null) { + if (!WavHeaderReader.checkFileType(input)) { // Should only happen if the media wasn't sniffed. throw ParserException.createForMalformedContainer( - "Unsupported or unrecognized wav header.", /* cause= */ null); + "Unsupported or unrecognized wav file type.", /* cause= */ null); } input.skipFully((int) (input.getPeekPosition() - input.getPosition())); + state = STATE_READING_FORMAT; + } - if (header.formatType == WavUtil.TYPE_IMA_ADPCM) { - outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header); - } else if (header.formatType == WavUtil.TYPE_ALAW) { + @RequiresNonNull({"extractorOutput", "trackOutput"}) + private void readFormat(ExtractorInput input) throws IOException { + WavFormat wavFormat = WavHeaderReader.readFormat(input); + if (wavFormat.formatType == WavUtil.TYPE_IMA_ADPCM) { + outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, wavFormat); + } else if (wavFormat.formatType == WavUtil.TYPE_ALAW) { outputWriter = new PassthroughOutputWriter( extractorOutput, trackOutput, - header, + wavFormat, MimeTypes.AUDIO_ALAW, /* pcmEncoding= */ Format.NO_VALUE); - } else if (header.formatType == WavUtil.TYPE_MLAW) { + } else if (wavFormat.formatType == WavUtil.TYPE_MLAW) { outputWriter = new PassthroughOutputWriter( extractorOutput, trackOutput, - header, + wavFormat, MimeTypes.AUDIO_MLAW, /* pcmEncoding= */ Format.NO_VALUE); } else { @C.PcmEncoding - int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample); + int pcmEncoding = + WavUtil.getPcmEncodingForType(wavFormat.formatType, wavFormat.bitsPerSample); if (pcmEncoding == C.ENCODING_INVALID) { throw ParserException.createForUnsupportedContainerFeature( - "Unsupported WAV format type: " + header.formatType); + "Unsupported WAV format type: " + wavFormat.formatType); } outputWriter = new PassthroughOutputWriter( - extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding); + extractorOutput, trackOutput, wavFormat, MimeTypes.AUDIO_RAW, pcmEncoding); } state = STATE_SKIPPING_TO_SAMPLE_DATA; } @@ -234,7 +247,7 @@ public final class WavExtractor implements Extractor { private final ExtractorOutput extractorOutput; private final TrackOutput trackOutput; - private final WavHeader header; + private final WavFormat wavFormat; private final Format format; /** The target size of each output sample, in bytes. */ private final int targetSampleSizeBytes; @@ -256,33 +269,33 @@ public final class WavExtractor implements Extractor { public PassthroughOutputWriter( ExtractorOutput extractorOutput, TrackOutput trackOutput, - WavHeader header, + WavFormat wavFormat, String mimeType, @C.PcmEncoding int pcmEncoding) throws ParserException { this.extractorOutput = extractorOutput; this.trackOutput = trackOutput; - this.header = header; + this.wavFormat = wavFormat; - int bytesPerFrame = header.numChannels * header.bitsPerSample / 8; - // Validate the header. Blocks are expected to correspond to single frames. - if (header.blockSize != bytesPerFrame) { + int bytesPerFrame = wavFormat.numChannels * wavFormat.bitsPerSample / 8; + // Validate the WAV format. Blocks are expected to correspond to single frames. + if (wavFormat.blockSize != bytesPerFrame) { throw ParserException.createForMalformedContainer( - "Expected block size: " + bytesPerFrame + "; got: " + header.blockSize, + "Expected block size: " + bytesPerFrame + "; got: " + wavFormat.blockSize, /* cause= */ null); } - int constantBitrate = header.frameRateHz * bytesPerFrame * 8; + int constantBitrate = wavFormat.frameRateHz * bytesPerFrame * 8; targetSampleSizeBytes = - max(bytesPerFrame, header.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND); + max(bytesPerFrame, wavFormat.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND); format = new Format.Builder() .setSampleMimeType(mimeType) .setAverageBitrate(constantBitrate) .setPeakBitrate(constantBitrate) .setMaxInputSize(targetSampleSizeBytes) - .setChannelCount(header.numChannels) - .setSampleRate(header.frameRateHz) + .setChannelCount(wavFormat.numChannels) + .setSampleRate(wavFormat.frameRateHz) .setPcmEncoding(pcmEncoding) .build(); } @@ -297,7 +310,7 @@ public final class WavExtractor implements Extractor { @Override public void init(int dataStartPosition, long dataEndPosition) { extractorOutput.seekMap( - new WavSeekMap(header, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition)); + new WavSeekMap(wavFormat, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition)); trackOutput.format(format); } @@ -318,13 +331,13 @@ public final class WavExtractor implements Extractor { // Write the corresponding sample metadata. Samples must be a whole number of frames. It's // possible that the number of pending output bytes is not a whole number of frames if the // stream ended unexpectedly. - int bytesPerFrame = header.blockSize; + int bytesPerFrame = wavFormat.blockSize; int pendingFrames = pendingOutputBytes / bytesPerFrame; if (pendingFrames > 0) { long timeUs = startTimeUs + Util.scaleLargeTimestamp( - outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz); + outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz); int size = pendingFrames * bytesPerFrame; int offset = pendingOutputBytes - size; trackOutput.sampleMetadata( @@ -354,7 +367,7 @@ public final class WavExtractor implements Extractor { private final ExtractorOutput extractorOutput; private final TrackOutput trackOutput; - private final WavHeader header; + private final WavFormat wavFormat; /** Number of frames per block of the input (yet to be decoded) data. */ private final int framesPerBlock; @@ -384,23 +397,26 @@ public final class WavExtractor implements Extractor { private long outputFrameCount; public ImaAdPcmOutputWriter( - ExtractorOutput extractorOutput, TrackOutput trackOutput, WavHeader header) + ExtractorOutput extractorOutput, TrackOutput trackOutput, WavFormat wavFormat) throws ParserException { this.extractorOutput = extractorOutput; this.trackOutput = trackOutput; - this.header = header; - targetSampleSizeFrames = max(1, header.frameRateHz / TARGET_SAMPLES_PER_SECOND); + this.wavFormat = wavFormat; + targetSampleSizeFrames = max(1, wavFormat.frameRateHz / TARGET_SAMPLES_PER_SECOND); - ParsableByteArray scratch = new ParsableByteArray(header.extraData); + ParsableByteArray scratch = new ParsableByteArray(wavFormat.extraData); scratch.readLittleEndianUnsignedShort(); framesPerBlock = scratch.readLittleEndianUnsignedShort(); - int numChannels = header.numChannels; - // Validate the header. This calculation is defined in "Microsoft Multimedia Standards Update + int numChannels = wavFormat.numChannels; + // Validate the WAV format. This calculation is defined in "Microsoft Multimedia Standards + // Update // - New Multimedia Types and Data Techniques" (1994). See the "IMA ADPCM Wave Type" and "DVI // ADPCM Wave Type" sections, and the calculation of wSamplesPerBlock in the latter. int expectedFramesPerBlock = - (((header.blockSize - (4 * numChannels)) * 8) / (header.bitsPerSample * numChannels)) + 1; + (((wavFormat.blockSize - (4 * numChannels)) * 8) + / (wavFormat.bitsPerSample * numChannels)) + + 1; if (framesPerBlock != expectedFramesPerBlock) { throw ParserException.createForMalformedContainer( "Expected frames per block: " + expectedFramesPerBlock + "; got: " + framesPerBlock, @@ -410,22 +426,22 @@ public final class WavExtractor implements Extractor { // Calculate the number of blocks we'll need to decode to obtain an output sample of the // target sample size, and allocate suitably sized buffers for input and decoded data. int maxBlocksToDecode = Util.ceilDivide(targetSampleSizeFrames, framesPerBlock); - inputData = new byte[maxBlocksToDecode * header.blockSize]; + inputData = new byte[maxBlocksToDecode * wavFormat.blockSize]; decodedData = new ParsableByteArray( maxBlocksToDecode * numOutputFramesToBytes(framesPerBlock, numChannels)); // Create the format. We calculate the bitrate of the data before decoding, since this is the // bitrate of the stream itself. - int constantBitrate = header.frameRateHz * header.blockSize * 8 / framesPerBlock; + int constantBitrate = wavFormat.frameRateHz * wavFormat.blockSize * 8 / framesPerBlock; format = new Format.Builder() .setSampleMimeType(MimeTypes.AUDIO_RAW) .setAverageBitrate(constantBitrate) .setPeakBitrate(constantBitrate) .setMaxInputSize(numOutputFramesToBytes(targetSampleSizeFrames, numChannels)) - .setChannelCount(header.numChannels) - .setSampleRate(header.frameRateHz) + .setChannelCount(wavFormat.numChannels) + .setSampleRate(wavFormat.frameRateHz) .setPcmEncoding(C.ENCODING_PCM_16BIT) .build(); } @@ -441,7 +457,7 @@ public final class WavExtractor implements Extractor { @Override public void init(int dataStartPosition, long dataEndPosition) { extractorOutput.seekMap( - new WavSeekMap(header, framesPerBlock, dataStartPosition, dataEndPosition)); + new WavSeekMap(wavFormat, framesPerBlock, dataStartPosition, dataEndPosition)); trackOutput.format(format); } @@ -453,7 +469,7 @@ public final class WavExtractor implements Extractor { targetSampleSizeFrames - numOutputBytesToFrames(pendingOutputBytes); // Calculate the whole number of blocks that we need to decode to obtain this many frames. int blocksToDecode = Util.ceilDivide(targetFramesRemaining, framesPerBlock); - int targetReadBytes = blocksToDecode * header.blockSize; + int targetReadBytes = blocksToDecode * wavFormat.blockSize; // Read input data until we've reached the target number of blocks, or the end of the data. boolean endOfSampleData = bytesLeft == 0; @@ -467,11 +483,11 @@ public final class WavExtractor implements Extractor { } } - int pendingBlockCount = pendingInputBytes / header.blockSize; + int pendingBlockCount = pendingInputBytes / wavFormat.blockSize; if (pendingBlockCount > 0) { // We have at least one whole block to decode. decode(inputData, pendingBlockCount, decodedData); - pendingInputBytes -= pendingBlockCount * header.blockSize; + pendingInputBytes -= pendingBlockCount * wavFormat.blockSize; // Write all of the decoded data to the track output. int decodedDataSize = decodedData.limit(); @@ -499,7 +515,8 @@ public final class WavExtractor implements Extractor { private void writeSampleMetadata(int sampleFrames) { long timeUs = startTimeUs - + Util.scaleLargeTimestamp(outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz); + + Util.scaleLargeTimestamp( + outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz); int size = numOutputFramesToBytes(sampleFrames); int offset = pendingOutputBytes - size; trackOutput.sampleMetadata( @@ -517,7 +534,7 @@ public final class WavExtractor implements Extractor { */ private void decode(byte[] input, int blockCount, ParsableByteArray output) { for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { - for (int channelIndex = 0; channelIndex < header.numChannels; channelIndex++) { + for (int channelIndex = 0; channelIndex < wavFormat.numChannels; channelIndex++) { decodeBlockForChannel(input, blockIndex, channelIndex, output.getData()); } } @@ -528,8 +545,8 @@ public final class WavExtractor implements Extractor { private void decodeBlockForChannel( byte[] input, int blockIndex, int channelIndex, byte[] output) { - int blockSize = header.blockSize; - int numChannels = header.numChannels; + int blockSize = wavFormat.blockSize; + int numChannels = wavFormat.numChannels; // The input data consists for a four byte header [Ci] for each of the N channels, followed // by interleaved data segments [Ci-DATAj], each of which are four bytes long. @@ -590,11 +607,11 @@ public final class WavExtractor implements Extractor { } private int numOutputBytesToFrames(int bytes) { - return bytes / (2 * header.numChannels); + return bytes / (2 * wavFormat.numChannels); } private int numOutputFramesToBytes(int frames) { - return numOutputFramesToBytes(frames, header.numChannels); + return numOutputFramesToBytes(frames, wavFormat.numChannels); } private static int numOutputFramesToBytes(int frames, int numChannels) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java similarity index 91% rename from library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java rename to library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java index ca34e32cc0..ca9e1d8dd7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java @@ -15,8 +15,8 @@ */ package com.google.android.exoplayer2.extractor.wav; -/** Header for a WAV file. */ -/* package */ final class WavHeader { +/** Format information for a WAV file. */ +/* package */ final class WavFormat { /** * The format type. Standard format types are the "WAVE form Registration Number" constants @@ -33,10 +33,10 @@ package com.google.android.exoplayer2.extractor.wav; public final int blockSize; /** Bits per sample for a single channel. */ public final int bitsPerSample; - /** Extra data appended to the format chunk of the header. */ + /** Extra data appended to the format chunk. */ public final byte[] extraData; - public WavHeader( + public WavFormat( int formatType, int numChannels, int frameRateHz, diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java index 147fba9c53..4541a305d6 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.extractor.wav; import android.util.Pair; -import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.audio.WavUtil; @@ -27,45 +26,56 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; -/** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */ +/** Reads a WAV header from an input stream; supports resuming from input failures. */ /* package */ final class WavHeaderReader { private static final String TAG = "WavHeaderReader"; /** - * Peeks and returns a {@code WavHeader}. + * Returns whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE + * tag. * - * @param input Input stream to peek the WAV header from. - * @throws ParserException If the input file is an incorrect RIFF WAV. + * @param input The input stream to peek from. The position should point to the start of the + * stream. + * @return Whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE + * tag. * @throws IOException If peeking from the input fails. - * @return A new {@code WavHeader} peeked from {@code input}, or null if the input is not a - * supported WAV format. */ - @Nullable - public static WavHeader peek(ExtractorInput input) throws IOException { - Assertions.checkNotNull(input); - - // Allocate a scratch buffer large enough to store the format chunk. - ParsableByteArray scratch = new ParsableByteArray(16); - + public static boolean checkFileType(ExtractorInput input) throws IOException { + ParsableByteArray scratch = new ParsableByteArray(ChunkHeader.SIZE_IN_BYTES); // Attempt to read the RIFF chunk. ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); if (chunkHeader.id != WavUtil.RIFF_FOURCC) { - return null; + return false; } input.peekFully(scratch.getData(), 0, 4); scratch.setPosition(0); - int riffFormat = scratch.readInt(); - if (riffFormat != WavUtil.WAVE_FOURCC) { - Log.e(TAG, "Unsupported RIFF format: " + riffFormat); - return null; + int formType = scratch.readInt(); + if (formType != WavUtil.WAVE_FOURCC) { + Log.e(TAG, "Unsupported form type: " + formType); + return false; } + return true; + } + + /** + * Reads and returns a {@code WavFormat}. + * + * @param input Input stream to read the WAV format from. The position should point to the byte + * following the WAVE tag. + * @throws IOException If reading from the input fails. + * @return A new {@code WavFormat} read from {@code input}. + */ + public static WavFormat readFormat(ExtractorInput input) throws IOException { + // Allocate a scratch buffer large enough to store the format chunk. + ParsableByteArray scratch = new ParsableByteArray(16); + // Skip chunks until we find the format chunk. - chunkHeader = ChunkHeader.peek(input, scratch); + ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); while (chunkHeader.id != WavUtil.FMT_FOURCC) { - input.advancePeekPosition((int) chunkHeader.size); + input.skipFully(ChunkHeader.SIZE_IN_BYTES + (int) chunkHeader.size); chunkHeader = ChunkHeader.peek(input, scratch); } @@ -88,7 +98,8 @@ import java.io.IOException; extraData = Util.EMPTY_BYTE_ARRAY; } - return new WavHeader( + input.skipFully((int) (input.getPeekPosition() - input.getPosition())); + return new WavFormat( audioFormatType, numChannels, frameRateHz, @@ -109,8 +120,6 @@ import java.io.IOException; * @throws IOException If reading from the input fails. */ public static Pair skipToSampleData(ExtractorInput input) throws IOException { - Assertions.checkNotNull(input); - // Make sure the peek position is set to the read position before we peek the first header. input.resetPeekPosition(); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java index 2a92c38431..1d5c8fdae1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java @@ -22,18 +22,18 @@ import com.google.android.exoplayer2.util.Util; /* package */ final class WavSeekMap implements SeekMap { - private final WavHeader wavHeader; + private final WavFormat wavFormat; private final int framesPerBlock; private final long firstBlockPosition; private final long blockCount; private final long durationUs; public WavSeekMap( - WavHeader wavHeader, int framesPerBlock, long dataStartPosition, long dataEndPosition) { - this.wavHeader = wavHeader; + WavFormat wavFormat, int framesPerBlock, long dataStartPosition, long dataEndPosition) { + this.wavFormat = wavFormat; this.framesPerBlock = framesPerBlock; this.firstBlockPosition = dataStartPosition; - this.blockCount = (dataEndPosition - dataStartPosition) / wavHeader.blockSize; + this.blockCount = (dataEndPosition - dataStartPosition) / wavFormat.blockSize; durationUs = blockIndexToTimeUs(blockCount); } @@ -50,17 +50,17 @@ import com.google.android.exoplayer2.util.Util; @Override public SeekPoints getSeekPoints(long timeUs) { // Calculate the containing block index, constraining to valid indices. - long blockIndex = (timeUs * wavHeader.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock); + long blockIndex = (timeUs * wavFormat.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock); blockIndex = Util.constrainValue(blockIndex, 0, blockCount - 1); - long seekPosition = firstBlockPosition + (blockIndex * wavHeader.blockSize); + long seekPosition = firstBlockPosition + (blockIndex * wavFormat.blockSize); long seekTimeUs = blockIndexToTimeUs(blockIndex); SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition); if (seekTimeUs >= timeUs || blockIndex == blockCount - 1) { return new SeekPoints(seekPoint); } else { long secondBlockIndex = blockIndex + 1; - long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavHeader.blockSize); + long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavFormat.blockSize); long secondSeekTimeUs = blockIndexToTimeUs(secondBlockIndex); SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition); return new SeekPoints(seekPoint, secondSeekPoint); @@ -69,6 +69,6 @@ import com.google.android.exoplayer2.util.Util; private long blockIndexToTimeUs(long blockIndex) { return Util.scaleLargeTimestamp( - blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavHeader.frameRateHz); + blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavFormat.frameRateHz); } } From d5a87d13b79004ec3039932946c63d0c4238634f Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 3 Nov 2021 11:33:57 +0000 Subject: [PATCH 097/113] Fix broken link on supported-formats dev guide page #minor-release PiperOrigin-RevId: 407305661 --- docs/supported-formats.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/supported-formats.md b/docs/supported-formats.md index 8270866bcd..70b24416a8 100644 --- a/docs/supported-formats.md +++ b/docs/supported-formats.md @@ -92,7 +92,7 @@ FFmpeg decoder name. ## Standalone subtitle formats ## ExoPlayer supports standalone subtitle files in a variety of formats. Subtitle -files can be side-loaded as described on the [Media source page][]. +files can be side-loaded as described on the [media items page][]. | Container format | Supported | MIME type | |---------------------------|:------------:|:----------| @@ -101,7 +101,7 @@ files can be side-loaded as described on the [Media source page][]. | SubRip | YES | MimeTypes.APPLICATION_SUBRIP | | SubStationAlpha (SSA/ASS) | YES | MimeTypes.TEXT_SSA | -[Media source page]: {{ site.baseurl }}/media-sources.html#side-loading-a-subtitle-file +[media items page]: {{ site.baseurl }}/media-items.html#sideloading-subtitle-tracks ## HDR video playback ## From 0c4f5ebc910667a38892c51e316a90d68fe00e77 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Wed, 3 Nov 2021 11:57:41 +0000 Subject: [PATCH 098/113] Fix END_OF_STREAM transformer timestamp matching previous. This cause the muxer to fail to stop on older devices/API levels. #minor-release PiperOrigin-RevId: 407309028 --- RELEASENOTES.md | 3 +++ .../exoplayer2/transformer/TransformerAudioRenderer.java | 1 + 2 files changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 586144ca28..cbf79d1a9d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -90,6 +90,9 @@ * Rename `MediaSessionConnector.QueueNavigator#onCurrentWindowIndexChanged` to `onCurrentMediaItemIndexChanged`. +* Transformer: + * Avoid sending a duplicate timestamp to the encoder with the end of + stream buffer. * Remove deprecated symbols: * Remove `Renderer.VIDEO_SCALING_MODE_*` constants. Use identically named constants in `C` instead. diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index f20915c120..7efa0eb780 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -316,6 +316,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private void queueEndOfStreamToEncoder(MediaCodecAdapterWrapper encoder) { checkState(checkNotNull(encoderInputBuffer.data).position() == 0); + encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs; encoderInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); encoderInputBuffer.flip(); // Queuing EOS should only occur with an empty buffer. From 2d311002642d5410dbcb83b7a9f0f2c86becdc60 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Nov 2021 12:36:06 +0000 Subject: [PATCH 099/113] Bump version to 2.16.0 PiperOrigin-RevId: 407314385 --- constants.gradle | 4 ++-- .../com/google/android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/constants.gradle b/constants.gradle index 400eba8c9f..bd4a545c4c 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.15.1' - releaseVersionCode = 2015001 + releaseVersion = '2.16.0' + releaseVersionCode = 2016000 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 9f13465861..4784575659 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.15.1"; + public static final String VERSION = "2.16.0"; /** 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.15.1"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.16.0"; /** * 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 = 2015001; + public static final int VERSION_INT = 2016000; /** Whether the library was compiled with {@link Assertions} checks enabled. */ public static final boolean ASSERTIONS_ENABLED = true; From bc3360e5d44238308d569dabbd26b472a01084fa Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Nov 2021 14:37:41 +0000 Subject: [PATCH 100/113] Update release notes for 2.16.0 PiperOrigin-RevId: 407333525 --- RELEASENOTES.md | 50 +++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cbf79d1a9d..ae006ebdde 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,38 +2,34 @@ ### dev-v2 (not yet released) +### 2.16.0 (2021-11-04) + * Core Library: + * Deprecate `SimpleExoPlayer`. All functionality has been moved to + `ExoPlayer` instead. `ExoPlayer.Builder` can be used instead of + `SimpleExoPlayer.Builder`. + * Add track selection methods to the `Player` interface, for example, + `Player.getCurrentTracksInfo` and `Player.setTrackSelectionParameters`. + These methods can be used instead of directly accessing the track + selector. * Enable MediaCodec asynchronous queueing by default on devices with API level >= 31. Add methods in `DefaultMediaCodecRendererFactory` and `DefaultRenderersFactory` to force enable or force disable asynchronous queueing ([6348](https://github.com/google/ExoPlayer/issues/6348)). - * Add 12 public method headers to `ExoPlayer` that exist in - `SimpleExoPlayer`, such that all public methods in `SimpleExoPlayer` are - overrides. + * Remove final dependency on `jcenter()`. + * Fix `mediaMetadata` being reset when media is repeated + ([#9458](https://github.com/google/ExoPlayer/issues/9458)). + * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values + input through the `MediaItem.MediaMetadata` are used above media derived + values. * Move `com.google.android.exoplayer2.device.DeviceInfo` to `com.google.android.exoplayer2.DeviceInfo`. * Move `com.google.android.exoplayer2.drm.DecryptionException` to `com.google.android.exoplayer2.decoder.CryptoException`. * Move `com.google.android.exoplayer2.upstream.cache.CachedRegionTracker` to `com.google.android.exoplayer2.upstream.CachedRegionTracker`. - * Remove `ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED`. Use - `GlUtil.glAssertionsEnabled` instead. * Move `Player.addListener(EventListener)` and `Player.removeListener(EventListener)` out of `Player` into subclasses. - * Fix `mediaMetadata` being reset when media is repeated - ([#9458](https://github.com/google/ExoPlayer/issues/9458)). - * Remove final dependency on `jcenter()`. - * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values - input through the `MediaItem.MediaMetadata` are used above media derived - values. -* Video: - * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a - released `Surface` when playing without an app-provided `Surface` - ([#9476](https://github.com/google/ExoPlayer/issues/9476)). -* DRM: - * Log an error (instead of throwing `IllegalStateException`) when calling - `DefaultDrmSession#release()` on a fully released session - ([#9392](https://github.com/google/ExoPlayer/issues/9392)). * Android 12 compatibility: * Keep `DownloadService` started and in the foreground whilst waiting for requirements to be met on Android 12. This is necessary due to new @@ -49,6 +45,14 @@ are not compatible with apps targeting Android 12, and will crash with an `IllegalArgumentException` when creating `PendingIntent`s ([#9181](https://github.com/google/ExoPlayer/issues/9181)). +* Video: + * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a + released `Surface` when playing without an app-provided `Surface` + ([#9476](https://github.com/google/ExoPlayer/issues/9476)). +* DRM: + * Log an error (instead of throwing `IllegalStateException`) when calling + `DefaultDrmSession#release()` on a fully released session + ([#9392](https://github.com/google/ExoPlayer/issues/9392)). * UI: * `SubtitleView` no longer implements `TextOutput`. `SubtitleView` implements `Player.Listener`, so can be registered to a player with @@ -74,7 +78,7 @@ requirements for downloads to continue. In both cases, `DownloadService` will now remain started and in the foreground whilst waiting for requirements to be met. - * Modify `DownlaodService` behavior when running on Android 12 and above. + * Modify `DownloadService` behavior when running on Android 12 and above. See the "Android 12 compatibility" section above. * RTSP: * Support RFC4566 SDP attribute field grammar @@ -83,6 +87,12 @@ * Populate `Format.sampleMimeType`, `width` and `height` for image `AdaptationSet` elements ([#9500](https://github.com/google/ExoPlayer/issues/9500)). +* HLS: + * Fix rounding error in HLS playlists + ([#9575](https://github.com/google/ExoPlayer/issues/9575)). + * Fix `NoSuchElementException` thrown when an HLS manifest declares + `#EXT-X-RENDITION-REPORT` at the beginning of the playlist + ([#9592](https://github.com/google/ExoPlayer/issues/9592)). * RTMP extension: * Upgrade to `io.antmedia:rtmp_client`, which does not rely on `jcenter()` ([#9591](https://github.com/google/ExoPlayer/issues/9591)). From 5a98c823fcf46699dd9757bdc4ac0f0d28a321d2 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Tue, 2 Nov 2021 15:18:29 +0000 Subject: [PATCH 101/113] Update the TransformerMediaClock trackTime before deducting the offset. #minor-release PiperOrigin-RevId: 407086818 --- .../exoplayer2/transformer/TransformerAudioRenderer.java | 2 +- .../exoplayer2/transformer/TransformerBaseRenderer.java | 3 +-- .../exoplayer2/transformer/TransformerMuxingVideoRenderer.java | 2 +- .../transformer/TransformerTranscodingVideoRenderer.java | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 8da9dcd2e1..f20915c120 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -279,8 +279,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; int result = readSource(getFormatHolder(), decoderInputBuffer, /* readFlags= */ 0); switch (result) { case C.RESULT_BUFFER_READ: - decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); + decoderInputBuffer.timeUs -= streamOffsetUs; decoderInputBuffer.flip(); decoder.queueInputBuffer(decoderInputBuffer); return !decoderInputBuffer.isEndOfStream(); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java index 6e19f0b9f9..d3fe72d65b 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java @@ -48,8 +48,7 @@ import com.google.android.exoplayer2.util.MimeTypes; } @Override - protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) - throws ExoPlaybackException { + protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { this.streamOffsetUs = offsetUs; } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java index d14378754e..4692a6ca81 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java @@ -117,8 +117,8 @@ import java.nio.ByteBuffer; muxerWrapper.endTrack(getTrackType()); return false; } - buffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), buffer.timeUs); + buffer.timeUs -= streamOffsetUs; ByteBuffer data = checkNotNull(buffer.data); data.flip(); if (sampleTransformer != null) { diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index 931e985a5d..f4836e49df 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -320,8 +320,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; case C.RESULT_FORMAT_READ: throw new IllegalStateException("Format changes are not supported."); case C.RESULT_BUFFER_READ: - decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); + decoderInputBuffer.timeUs -= streamOffsetUs; ByteBuffer data = checkNotNull(decoderInputBuffer.data); data.flip(); decoder.queueInputBuffer(decoderInputBuffer); From cddebdcf0313d901ac29b5524fcc7fd0a0bf7566 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Nov 2021 17:01:13 +0000 Subject: [PATCH 102/113] Suppress lint warnings in leanback module. These warnings are caused by the fact that this is a library and the lint check doesn't see any app using the library in a TV context. PiperOrigin-RevId: 407110725 --- extensions/leanback/src/main/AndroidManifest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/leanback/src/main/AndroidManifest.xml b/extensions/leanback/src/main/AndroidManifest.xml index e385551143..1649d3c386 100644 --- a/extensions/leanback/src/main/AndroidManifest.xml +++ b/extensions/leanback/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ limitations under the License. --> - + From be4ea151c445ed1f778f60cf8c2b6e7ead2e03de Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 2 Nov 2021 18:36:26 +0000 Subject: [PATCH 103/113] Parse HDR static metadata from MP4 files #minor-release PiperOrigin-RevId: 407136922 --- RELEASENOTES.md | 1 + .../exoplayer2/extractor/mp4/Atom.java | 6 + .../exoplayer2/extractor/mp4/AtomParsers.java | 87 +++- .../extractor/mp4/Mp4ExtractorTest.java | 6 + .../sample_with_colr_mdcv_and_clli.mp4.0.dump | 454 ++++++++++++++++++ .../sample_with_colr_mdcv_and_clli.mp4.1.dump | 398 +++++++++++++++ .../sample_with_colr_mdcv_and_clli.mp4.2.dump | 338 +++++++++++++ .../sample_with_colr_mdcv_and_clli.mp4.3.dump | 282 +++++++++++ ...colr_mdcv_and_clli.mp4.unknown_length.dump | 454 ++++++++++++++++++ .../mp4/sample_with_colr_mdcv_and_clli.mp4 | Bin 0 -> 285393 bytes 10 files changed, 2015 insertions(+), 11 deletions(-) create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump create mode 100644 testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump create mode 100644 testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0d8bf3fdb5..586144ca28 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -64,6 +64,7 @@ * MP4: Avoid throwing `ArrayIndexOutOfBoundsException` when parsing invalid `colr` boxes produced by some device cameras ([#9332](https://github.com/google/ExoPlayer/issues/9332)). + * MP4: Parse HDR static metadata from the `clli` and `mdcv` boxes. * TS: Correctly handle HEVC tracks with pixel aspect ratios other than 1. * TS: Map stream type 0x80 to H262 ([#9472](https://github.com/google/ExoPlayer/issues/9472)). diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index bc8633acc8..9c5de24b70 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -410,6 +410,12 @@ import java.util.List; @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_twos = 0x74776f73; + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_clli = 0x636c6c69; + + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_mdcv = 0x6d646376; + public final int type; public Atom(int type) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index bc5fa10fe3..442758716a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -45,6 +45,8 @@ import com.google.android.exoplayer2.video.DolbyVisionConfig; import com.google.android.exoplayer2.video.HevcConfig; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -1061,6 +1063,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; .build(); } + // hdrStaticInfo is allocated using allocate() in allocateHdrStaticInfo(). + @SuppressWarnings("ByteBufferBackingArray") private static void parseVideoSampleEntry( ParsableByteArray parent, int atomType, @@ -1112,7 +1116,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Nullable String codecs = null; @Nullable byte[] projectionData = null; @C.StereoMode int stereoMode = Format.NO_VALUE; - @Nullable ColorInfo colorInfo = null; + + // HDR related metadata. + @C.ColorSpace int colorSpace = Format.NO_VALUE; + @C.ColorRange int colorRange = Format.NO_VALUE; + @C.ColorTransfer int colorTransfer = Format.NO_VALUE; + // The format of HDR static info is defined in CTA-861-G:2017, Table 45. + @Nullable ByteBuffer hdrStaticInfo = null; + while (childPosition - position < size) { parent.setPosition(childPosition); int childStartPosition = parent.getPosition(); @@ -1157,6 +1168,43 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } else if (childAtomType == Atom.TYPE_av1C) { ExtractorUtil.checkContainerInput(mimeType == null, /* message= */ null); mimeType = MimeTypes.VIDEO_AV1; + } else if (childAtomType == Atom.TYPE_clli) { + if (hdrStaticInfo == null) { + hdrStaticInfo = allocateHdrStaticInfo(); + } + // The contents of the clli box occupy the last 4 bytes of the HDR static info array. Note + // that each field is read in big endian and written in little endian. + hdrStaticInfo.position(21); + hdrStaticInfo.putShort(parent.readShort()); // max_content_light_level. + hdrStaticInfo.putShort(parent.readShort()); // max_pic_average_light_level. + } else if (childAtomType == Atom.TYPE_mdcv) { + if (hdrStaticInfo == null) { + hdrStaticInfo = allocateHdrStaticInfo(); + } + // The contents of the mdcv box occupy 20 bytes after the first byte of the HDR static info + // array. Note that each field is read in big endian and written in little endian. + short displayPrimariesGX = parent.readShort(); + short displayPrimariesGY = parent.readShort(); + short displayPrimariesBX = parent.readShort(); + short displayPrimariesBY = parent.readShort(); + short displayPrimariesRX = parent.readShort(); + short displayPrimariesRY = parent.readShort(); + short whitePointX = parent.readShort(); + short whitePointY = parent.readShort(); + long maxDisplayMasteringLuminance = parent.readUnsignedInt(); + long minDisplayMasteringLuminance = parent.readUnsignedInt(); + + hdrStaticInfo.position(1); + hdrStaticInfo.putShort(displayPrimariesRX); + hdrStaticInfo.putShort(displayPrimariesRY); + hdrStaticInfo.putShort(displayPrimariesGX); + hdrStaticInfo.putShort(displayPrimariesGY); + hdrStaticInfo.putShort(displayPrimariesBX); + hdrStaticInfo.putShort(displayPrimariesBY); + hdrStaticInfo.putShort(whitePointX); + hdrStaticInfo.putShort(whitePointY); + hdrStaticInfo.putShort((short) (maxDisplayMasteringLuminance / 10000)); + hdrStaticInfo.putShort((short) (minDisplayMasteringLuminance / 10000)); } else if (childAtomType == Atom.TYPE_d263) { ExtractorUtil.checkContainerInput(mimeType == null, /* message= */ null); mimeType = MimeTypes.VIDEO_H263; @@ -1211,12 +1259,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; // size=18): https://github.com/google/ExoPlayer/issues/9332 boolean fullRangeFlag = childAtomSize == 19 && (parent.readUnsignedByte() & 0b10000000) != 0; - colorInfo = - new ColorInfo( - ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries), - fullRangeFlag ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED, - ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics), - /* hdrStaticInfo= */ null); + colorSpace = ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries); + colorRange = fullRangeFlag ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED; + colorTransfer = + ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics); } else { Log.w(TAG, "Unsupported color type: " + Atom.getAtomTypeString(colorType)); } @@ -1229,7 +1275,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; return; } - out.format = + Format.Builder formatBuilder = new Format.Builder() .setId(trackId) .setSampleMimeType(mimeType) @@ -1241,9 +1287,28 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; .setProjectionData(projectionData) .setStereoMode(stereoMode) .setInitializationData(initializationData) - .setDrmInitData(drmInitData) - .setColorInfo(colorInfo) - .build(); + .setDrmInitData(drmInitData); + if (colorSpace != Format.NO_VALUE + || colorRange != Format.NO_VALUE + || colorTransfer != Format.NO_VALUE + || hdrStaticInfo != null) { + // Note that if either mdcv or clli are missing, we leave the corresponding HDR static + // metadata bytes with value zero. See [Internal ref: b/194535665]. + formatBuilder.setColorInfo( + new ColorInfo( + colorSpace, + colorRange, + colorTransfer, + hdrStaticInfo != null ? hdrStaticInfo.array() : null)); + } + out.format = formatBuilder.build(); + } + + private static ByteBuffer allocateHdrStaticInfo() { + // For HDR static info, Android decoders expect a 25-byte array. The first byte is zero to + // represent Static Metadata Type 1, as per CTA-861-G:2017, Table 44. The following 24 bytes + // follow CTA-861-G:2017, Table 45. + return ByteBuffer.allocate(25).order(ByteOrder.LITTLE_ENDIAN); } private static void parseMetaDataSampleEntry( diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java index 911f3f477e..88aba133e3 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java @@ -119,4 +119,10 @@ public final class Mp4ExtractorTest { ExtractorAsserts.assertBehavior( Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig); } + + @Test + public void mp4SampleWithColrMdcvAndClli() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_with_colr_mdcv_and_clli.mp4", simulationConfig); + } } diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump new file mode 100644 index 0000000000..8ef4f19b16 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump @@ -0,0 +1,454 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 16638 + sample count = 44 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 0 + flags = 1 + data = length 393, hash 706D1B6F + sample 1: + time = 23219 + flags = 1 + data = length 400, hash B48107D1 + sample 2: + time = 46439 + flags = 1 + data = length 398, hash E5F4E9C1 + sample 3: + time = 69659 + flags = 1 + data = length 400, hash 4317B40D + sample 4: + time = 92879 + flags = 1 + data = length 403, hash CB949D88 + sample 5: + time = 116099 + flags = 1 + data = length 411, hash 616C8F82 + sample 6: + time = 139319 + flags = 1 + data = length 392, hash 3BA50F06 + sample 7: + time = 162539 + flags = 1 + data = length 401, hash 1C62F82C + sample 8: + time = 185759 + flags = 1 + data = length 400, hash 180FEA17 + sample 9: + time = 208979 + flags = 1 + data = length 378, hash 2F6B0AE6 + sample 10: + time = 232199 + flags = 1 + data = length 375, hash 6AE86D08 + sample 11: + time = 255419 + flags = 1 + data = length 375, hash EF2FD9CC + sample 12: + time = 278639 + flags = 1 + data = length 374, hash 97B83243 + sample 13: + time = 301859 + flags = 1 + data = length 382, hash 8BD6191C + sample 14: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 15: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 16: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 17: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 18: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 19: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 20: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 21: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 22: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 23: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 24: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 25: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 26: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 27: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 28: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 29: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 30: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 31: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 32: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 33: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 34: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 35: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 36: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 37: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 38: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 39: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 40: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 41: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 42: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 43: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump new file mode 100644 index 0000000000..1e1023afb0 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump @@ -0,0 +1,398 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 11156 + sample count = 30 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 1: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 2: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 3: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 4: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 5: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 6: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 7: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 8: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 9: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 10: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 11: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 12: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 13: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 14: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 15: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 16: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 17: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 18: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 19: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 20: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 21: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 22: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 23: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 24: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 25: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 26: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 27: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 28: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 29: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump new file mode 100644 index 0000000000..5b51396c83 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump @@ -0,0 +1,338 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 5567 + sample count = 15 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 1: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 2: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 3: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 4: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 5: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 6: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 7: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 8: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 9: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 10: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 11: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 12: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 13: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 14: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump new file mode 100644 index 0000000000..d66f9234a1 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump @@ -0,0 +1,282 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 374 + sample count = 1 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump new file mode 100644 index 0000000000..8ef4f19b16 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump @@ -0,0 +1,454 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 16638 + sample count = 44 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 0 + flags = 1 + data = length 393, hash 706D1B6F + sample 1: + time = 23219 + flags = 1 + data = length 400, hash B48107D1 + sample 2: + time = 46439 + flags = 1 + data = length 398, hash E5F4E9C1 + sample 3: + time = 69659 + flags = 1 + data = length 400, hash 4317B40D + sample 4: + time = 92879 + flags = 1 + data = length 403, hash CB949D88 + sample 5: + time = 116099 + flags = 1 + data = length 411, hash 616C8F82 + sample 6: + time = 139319 + flags = 1 + data = length 392, hash 3BA50F06 + sample 7: + time = 162539 + flags = 1 + data = length 401, hash 1C62F82C + sample 8: + time = 185759 + flags = 1 + data = length 400, hash 180FEA17 + sample 9: + time = 208979 + flags = 1 + data = length 378, hash 2F6B0AE6 + sample 10: + time = 232199 + flags = 1 + data = length 375, hash 6AE86D08 + sample 11: + time = 255419 + flags = 1 + data = length 375, hash EF2FD9CC + sample 12: + time = 278639 + flags = 1 + data = length 374, hash 97B83243 + sample 13: + time = 301859 + flags = 1 + data = length 382, hash 8BD6191C + sample 14: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 15: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 16: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 17: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 18: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 19: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 20: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 21: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 22: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 23: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 24: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 25: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 26: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 27: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 28: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 29: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 30: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 31: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 32: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 33: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 34: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 35: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 36: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 37: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 38: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 39: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 40: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 41: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 42: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 43: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 b/testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..608d6ec440cca8d7c74b8135723256c0925ab81a GIT binary patch literal 285393 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 R3mI@y@Dpx-h-GDW4x?ZGuP4m1 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%0DT>fyB|HXxYtUeH);X0N&E=-m}t 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 Cke;Ui literal 0 HcmV?d00001 From ac66487013ba70d0ce39f952ecdbb6962b6c2e41 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Nov 2021 20:21:34 +0000 Subject: [PATCH 104/113] Add missing RetentionPolicy for IntDef PiperOrigin-RevId: 407162673 --- .../android/exoplayer2/source/rtsp/RtspMessageChannel.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java index 526f508953..a877d72b13 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java @@ -39,6 +39,9 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.Socket; import java.nio.charset.Charset; import java.util.ArrayList; @@ -334,6 +337,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Processes RTSP messages line-by-line. */ private static final class MessageParser { + @Documented + @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_READING_FIRST_LINE, STATE_READING_HEADER, STATE_READING_BODY}) @interface ReadingState {} From 9e247d287fc0798582af3149396a05f755dd8fef Mon Sep 17 00:00:00 2001 From: kimvde Date: Wed, 3 Nov 2021 11:01:07 +0000 Subject: [PATCH 105/113] WavExtractor: split header reading state into 2 states This refactoring is the basis to support RF64 (see Issue: google/ExoPlayer#9543). #minor-release PiperOrigin-RevId: 407301056 --- .../extractor/wav/WavExtractor.java | 135 ++++++++++-------- .../wav/{WavHeader.java => WavFormat.java} | 8 +- .../extractor/wav/WavHeaderReader.java | 57 ++++---- .../exoplayer2/extractor/wav/WavSeekMap.java | 16 +-- 4 files changed, 121 insertions(+), 95 deletions(-) rename library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/{WavHeader.java => WavFormat.java} (91%) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java index 97d98b5fcc..6d3fc254b1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java @@ -61,12 +61,18 @@ public final class WavExtractor implements Extractor { @Documented @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_USE}) - @IntDef({STATE_READING_HEADER, STATE_SKIPPING_TO_SAMPLE_DATA, STATE_READING_SAMPLE_DATA}) + @IntDef({ + STATE_READING_FILE_TYPE, + STATE_READING_FORMAT, + STATE_SKIPPING_TO_SAMPLE_DATA, + STATE_READING_SAMPLE_DATA + }) private @interface State {} - private static final int STATE_READING_HEADER = 0; - private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 1; - private static final int STATE_READING_SAMPLE_DATA = 2; + private static final int STATE_READING_FILE_TYPE = 0; + private static final int STATE_READING_FORMAT = 1; + private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 2; + private static final int STATE_READING_SAMPLE_DATA = 3; private @MonotonicNonNull ExtractorOutput extractorOutput; private @MonotonicNonNull TrackOutput trackOutput; @@ -76,14 +82,14 @@ public final class WavExtractor implements Extractor { private long dataEndPosition; public WavExtractor() { - state = STATE_READING_HEADER; + state = STATE_READING_FILE_TYPE; dataStartPosition = C.POSITION_UNSET; dataEndPosition = C.POSITION_UNSET; } @Override public boolean sniff(ExtractorInput input) throws IOException { - return WavHeaderReader.peek(input) != null; + return WavHeaderReader.checkFileType(input); } @Override @@ -95,7 +101,7 @@ public final class WavExtractor implements Extractor { @Override public void seek(long position, long timeUs) { - state = position == 0 ? STATE_READING_HEADER : STATE_READING_SAMPLE_DATA; + state = position == 0 ? STATE_READING_FILE_TYPE : STATE_READING_SAMPLE_DATA; if (outputWriter != null) { outputWriter.reset(timeUs); } @@ -111,8 +117,11 @@ public final class WavExtractor implements Extractor { public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { assertInitialized(); switch (state) { - case STATE_READING_HEADER: - readHeader(input); + case STATE_READING_FILE_TYPE: + readFileType(input); + return Extractor.RESULT_CONTINUE; + case STATE_READING_FORMAT: + readFormat(input); return Extractor.RESULT_CONTINUE; case STATE_SKIPPING_TO_SAMPLE_DATA: skipToSampleData(input); @@ -130,50 +139,54 @@ public final class WavExtractor implements Extractor { Util.castNonNull(extractorOutput); } - @RequiresNonNull({"extractorOutput", "trackOutput"}) - private void readHeader(ExtractorInput input) throws IOException { + private void readFileType(ExtractorInput input) throws IOException { Assertions.checkState(input.getPosition() == 0); if (dataStartPosition != C.POSITION_UNSET) { input.skipFully(dataStartPosition); state = STATE_READING_SAMPLE_DATA; return; } - WavHeader header = WavHeaderReader.peek(input); - if (header == null) { + if (!WavHeaderReader.checkFileType(input)) { // Should only happen if the media wasn't sniffed. throw ParserException.createForMalformedContainer( - "Unsupported or unrecognized wav header.", /* cause= */ null); + "Unsupported or unrecognized wav file type.", /* cause= */ null); } input.skipFully((int) (input.getPeekPosition() - input.getPosition())); + state = STATE_READING_FORMAT; + } - if (header.formatType == WavUtil.TYPE_IMA_ADPCM) { - outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header); - } else if (header.formatType == WavUtil.TYPE_ALAW) { + @RequiresNonNull({"extractorOutput", "trackOutput"}) + private void readFormat(ExtractorInput input) throws IOException { + WavFormat wavFormat = WavHeaderReader.readFormat(input); + if (wavFormat.formatType == WavUtil.TYPE_IMA_ADPCM) { + outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, wavFormat); + } else if (wavFormat.formatType == WavUtil.TYPE_ALAW) { outputWriter = new PassthroughOutputWriter( extractorOutput, trackOutput, - header, + wavFormat, MimeTypes.AUDIO_ALAW, /* pcmEncoding= */ Format.NO_VALUE); - } else if (header.formatType == WavUtil.TYPE_MLAW) { + } else if (wavFormat.formatType == WavUtil.TYPE_MLAW) { outputWriter = new PassthroughOutputWriter( extractorOutput, trackOutput, - header, + wavFormat, MimeTypes.AUDIO_MLAW, /* pcmEncoding= */ Format.NO_VALUE); } else { @C.PcmEncoding - int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample); + int pcmEncoding = + WavUtil.getPcmEncodingForType(wavFormat.formatType, wavFormat.bitsPerSample); if (pcmEncoding == C.ENCODING_INVALID) { throw ParserException.createForUnsupportedContainerFeature( - "Unsupported WAV format type: " + header.formatType); + "Unsupported WAV format type: " + wavFormat.formatType); } outputWriter = new PassthroughOutputWriter( - extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding); + extractorOutput, trackOutput, wavFormat, MimeTypes.AUDIO_RAW, pcmEncoding); } state = STATE_SKIPPING_TO_SAMPLE_DATA; } @@ -234,7 +247,7 @@ public final class WavExtractor implements Extractor { private final ExtractorOutput extractorOutput; private final TrackOutput trackOutput; - private final WavHeader header; + private final WavFormat wavFormat; private final Format format; /** The target size of each output sample, in bytes. */ private final int targetSampleSizeBytes; @@ -256,33 +269,33 @@ public final class WavExtractor implements Extractor { public PassthroughOutputWriter( ExtractorOutput extractorOutput, TrackOutput trackOutput, - WavHeader header, + WavFormat wavFormat, String mimeType, @C.PcmEncoding int pcmEncoding) throws ParserException { this.extractorOutput = extractorOutput; this.trackOutput = trackOutput; - this.header = header; + this.wavFormat = wavFormat; - int bytesPerFrame = header.numChannels * header.bitsPerSample / 8; - // Validate the header. Blocks are expected to correspond to single frames. - if (header.blockSize != bytesPerFrame) { + int bytesPerFrame = wavFormat.numChannels * wavFormat.bitsPerSample / 8; + // Validate the WAV format. Blocks are expected to correspond to single frames. + if (wavFormat.blockSize != bytesPerFrame) { throw ParserException.createForMalformedContainer( - "Expected block size: " + bytesPerFrame + "; got: " + header.blockSize, + "Expected block size: " + bytesPerFrame + "; got: " + wavFormat.blockSize, /* cause= */ null); } - int constantBitrate = header.frameRateHz * bytesPerFrame * 8; + int constantBitrate = wavFormat.frameRateHz * bytesPerFrame * 8; targetSampleSizeBytes = - max(bytesPerFrame, header.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND); + max(bytesPerFrame, wavFormat.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND); format = new Format.Builder() .setSampleMimeType(mimeType) .setAverageBitrate(constantBitrate) .setPeakBitrate(constantBitrate) .setMaxInputSize(targetSampleSizeBytes) - .setChannelCount(header.numChannels) - .setSampleRate(header.frameRateHz) + .setChannelCount(wavFormat.numChannels) + .setSampleRate(wavFormat.frameRateHz) .setPcmEncoding(pcmEncoding) .build(); } @@ -297,7 +310,7 @@ public final class WavExtractor implements Extractor { @Override public void init(int dataStartPosition, long dataEndPosition) { extractorOutput.seekMap( - new WavSeekMap(header, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition)); + new WavSeekMap(wavFormat, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition)); trackOutput.format(format); } @@ -318,13 +331,13 @@ public final class WavExtractor implements Extractor { // Write the corresponding sample metadata. Samples must be a whole number of frames. It's // possible that the number of pending output bytes is not a whole number of frames if the // stream ended unexpectedly. - int bytesPerFrame = header.blockSize; + int bytesPerFrame = wavFormat.blockSize; int pendingFrames = pendingOutputBytes / bytesPerFrame; if (pendingFrames > 0) { long timeUs = startTimeUs + Util.scaleLargeTimestamp( - outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz); + outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz); int size = pendingFrames * bytesPerFrame; int offset = pendingOutputBytes - size; trackOutput.sampleMetadata( @@ -354,7 +367,7 @@ public final class WavExtractor implements Extractor { private final ExtractorOutput extractorOutput; private final TrackOutput trackOutput; - private final WavHeader header; + private final WavFormat wavFormat; /** Number of frames per block of the input (yet to be decoded) data. */ private final int framesPerBlock; @@ -384,23 +397,26 @@ public final class WavExtractor implements Extractor { private long outputFrameCount; public ImaAdPcmOutputWriter( - ExtractorOutput extractorOutput, TrackOutput trackOutput, WavHeader header) + ExtractorOutput extractorOutput, TrackOutput trackOutput, WavFormat wavFormat) throws ParserException { this.extractorOutput = extractorOutput; this.trackOutput = trackOutput; - this.header = header; - targetSampleSizeFrames = max(1, header.frameRateHz / TARGET_SAMPLES_PER_SECOND); + this.wavFormat = wavFormat; + targetSampleSizeFrames = max(1, wavFormat.frameRateHz / TARGET_SAMPLES_PER_SECOND); - ParsableByteArray scratch = new ParsableByteArray(header.extraData); + ParsableByteArray scratch = new ParsableByteArray(wavFormat.extraData); scratch.readLittleEndianUnsignedShort(); framesPerBlock = scratch.readLittleEndianUnsignedShort(); - int numChannels = header.numChannels; - // Validate the header. This calculation is defined in "Microsoft Multimedia Standards Update + int numChannels = wavFormat.numChannels; + // Validate the WAV format. This calculation is defined in "Microsoft Multimedia Standards + // Update // - New Multimedia Types and Data Techniques" (1994). See the "IMA ADPCM Wave Type" and "DVI // ADPCM Wave Type" sections, and the calculation of wSamplesPerBlock in the latter. int expectedFramesPerBlock = - (((header.blockSize - (4 * numChannels)) * 8) / (header.bitsPerSample * numChannels)) + 1; + (((wavFormat.blockSize - (4 * numChannels)) * 8) + / (wavFormat.bitsPerSample * numChannels)) + + 1; if (framesPerBlock != expectedFramesPerBlock) { throw ParserException.createForMalformedContainer( "Expected frames per block: " + expectedFramesPerBlock + "; got: " + framesPerBlock, @@ -410,22 +426,22 @@ public final class WavExtractor implements Extractor { // Calculate the number of blocks we'll need to decode to obtain an output sample of the // target sample size, and allocate suitably sized buffers for input and decoded data. int maxBlocksToDecode = Util.ceilDivide(targetSampleSizeFrames, framesPerBlock); - inputData = new byte[maxBlocksToDecode * header.blockSize]; + inputData = new byte[maxBlocksToDecode * wavFormat.blockSize]; decodedData = new ParsableByteArray( maxBlocksToDecode * numOutputFramesToBytes(framesPerBlock, numChannels)); // Create the format. We calculate the bitrate of the data before decoding, since this is the // bitrate of the stream itself. - int constantBitrate = header.frameRateHz * header.blockSize * 8 / framesPerBlock; + int constantBitrate = wavFormat.frameRateHz * wavFormat.blockSize * 8 / framesPerBlock; format = new Format.Builder() .setSampleMimeType(MimeTypes.AUDIO_RAW) .setAverageBitrate(constantBitrate) .setPeakBitrate(constantBitrate) .setMaxInputSize(numOutputFramesToBytes(targetSampleSizeFrames, numChannels)) - .setChannelCount(header.numChannels) - .setSampleRate(header.frameRateHz) + .setChannelCount(wavFormat.numChannels) + .setSampleRate(wavFormat.frameRateHz) .setPcmEncoding(C.ENCODING_PCM_16BIT) .build(); } @@ -441,7 +457,7 @@ public final class WavExtractor implements Extractor { @Override public void init(int dataStartPosition, long dataEndPosition) { extractorOutput.seekMap( - new WavSeekMap(header, framesPerBlock, dataStartPosition, dataEndPosition)); + new WavSeekMap(wavFormat, framesPerBlock, dataStartPosition, dataEndPosition)); trackOutput.format(format); } @@ -453,7 +469,7 @@ public final class WavExtractor implements Extractor { targetSampleSizeFrames - numOutputBytesToFrames(pendingOutputBytes); // Calculate the whole number of blocks that we need to decode to obtain this many frames. int blocksToDecode = Util.ceilDivide(targetFramesRemaining, framesPerBlock); - int targetReadBytes = blocksToDecode * header.blockSize; + int targetReadBytes = blocksToDecode * wavFormat.blockSize; // Read input data until we've reached the target number of blocks, or the end of the data. boolean endOfSampleData = bytesLeft == 0; @@ -467,11 +483,11 @@ public final class WavExtractor implements Extractor { } } - int pendingBlockCount = pendingInputBytes / header.blockSize; + int pendingBlockCount = pendingInputBytes / wavFormat.blockSize; if (pendingBlockCount > 0) { // We have at least one whole block to decode. decode(inputData, pendingBlockCount, decodedData); - pendingInputBytes -= pendingBlockCount * header.blockSize; + pendingInputBytes -= pendingBlockCount * wavFormat.blockSize; // Write all of the decoded data to the track output. int decodedDataSize = decodedData.limit(); @@ -499,7 +515,8 @@ public final class WavExtractor implements Extractor { private void writeSampleMetadata(int sampleFrames) { long timeUs = startTimeUs - + Util.scaleLargeTimestamp(outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz); + + Util.scaleLargeTimestamp( + outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz); int size = numOutputFramesToBytes(sampleFrames); int offset = pendingOutputBytes - size; trackOutput.sampleMetadata( @@ -517,7 +534,7 @@ public final class WavExtractor implements Extractor { */ private void decode(byte[] input, int blockCount, ParsableByteArray output) { for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { - for (int channelIndex = 0; channelIndex < header.numChannels; channelIndex++) { + for (int channelIndex = 0; channelIndex < wavFormat.numChannels; channelIndex++) { decodeBlockForChannel(input, blockIndex, channelIndex, output.getData()); } } @@ -528,8 +545,8 @@ public final class WavExtractor implements Extractor { private void decodeBlockForChannel( byte[] input, int blockIndex, int channelIndex, byte[] output) { - int blockSize = header.blockSize; - int numChannels = header.numChannels; + int blockSize = wavFormat.blockSize; + int numChannels = wavFormat.numChannels; // The input data consists for a four byte header [Ci] for each of the N channels, followed // by interleaved data segments [Ci-DATAj], each of which are four bytes long. @@ -590,11 +607,11 @@ public final class WavExtractor implements Extractor { } private int numOutputBytesToFrames(int bytes) { - return bytes / (2 * header.numChannels); + return bytes / (2 * wavFormat.numChannels); } private int numOutputFramesToBytes(int frames) { - return numOutputFramesToBytes(frames, header.numChannels); + return numOutputFramesToBytes(frames, wavFormat.numChannels); } private static int numOutputFramesToBytes(int frames, int numChannels) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java similarity index 91% rename from library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java rename to library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java index ca34e32cc0..ca9e1d8dd7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java @@ -15,8 +15,8 @@ */ package com.google.android.exoplayer2.extractor.wav; -/** Header for a WAV file. */ -/* package */ final class WavHeader { +/** Format information for a WAV file. */ +/* package */ final class WavFormat { /** * The format type. Standard format types are the "WAVE form Registration Number" constants @@ -33,10 +33,10 @@ package com.google.android.exoplayer2.extractor.wav; public final int blockSize; /** Bits per sample for a single channel. */ public final int bitsPerSample; - /** Extra data appended to the format chunk of the header. */ + /** Extra data appended to the format chunk. */ public final byte[] extraData; - public WavHeader( + public WavFormat( int formatType, int numChannels, int frameRateHz, diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java index 147fba9c53..4541a305d6 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.extractor.wav; import android.util.Pair; -import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.audio.WavUtil; @@ -27,45 +26,56 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; -/** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */ +/** Reads a WAV header from an input stream; supports resuming from input failures. */ /* package */ final class WavHeaderReader { private static final String TAG = "WavHeaderReader"; /** - * Peeks and returns a {@code WavHeader}. + * Returns whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE + * tag. * - * @param input Input stream to peek the WAV header from. - * @throws ParserException If the input file is an incorrect RIFF WAV. + * @param input The input stream to peek from. The position should point to the start of the + * stream. + * @return Whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE + * tag. * @throws IOException If peeking from the input fails. - * @return A new {@code WavHeader} peeked from {@code input}, or null if the input is not a - * supported WAV format. */ - @Nullable - public static WavHeader peek(ExtractorInput input) throws IOException { - Assertions.checkNotNull(input); - - // Allocate a scratch buffer large enough to store the format chunk. - ParsableByteArray scratch = new ParsableByteArray(16); - + public static boolean checkFileType(ExtractorInput input) throws IOException { + ParsableByteArray scratch = new ParsableByteArray(ChunkHeader.SIZE_IN_BYTES); // Attempt to read the RIFF chunk. ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); if (chunkHeader.id != WavUtil.RIFF_FOURCC) { - return null; + return false; } input.peekFully(scratch.getData(), 0, 4); scratch.setPosition(0); - int riffFormat = scratch.readInt(); - if (riffFormat != WavUtil.WAVE_FOURCC) { - Log.e(TAG, "Unsupported RIFF format: " + riffFormat); - return null; + int formType = scratch.readInt(); + if (formType != WavUtil.WAVE_FOURCC) { + Log.e(TAG, "Unsupported form type: " + formType); + return false; } + return true; + } + + /** + * Reads and returns a {@code WavFormat}. + * + * @param input Input stream to read the WAV format from. The position should point to the byte + * following the WAVE tag. + * @throws IOException If reading from the input fails. + * @return A new {@code WavFormat} read from {@code input}. + */ + public static WavFormat readFormat(ExtractorInput input) throws IOException { + // Allocate a scratch buffer large enough to store the format chunk. + ParsableByteArray scratch = new ParsableByteArray(16); + // Skip chunks until we find the format chunk. - chunkHeader = ChunkHeader.peek(input, scratch); + ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); while (chunkHeader.id != WavUtil.FMT_FOURCC) { - input.advancePeekPosition((int) chunkHeader.size); + input.skipFully(ChunkHeader.SIZE_IN_BYTES + (int) chunkHeader.size); chunkHeader = ChunkHeader.peek(input, scratch); } @@ -88,7 +98,8 @@ import java.io.IOException; extraData = Util.EMPTY_BYTE_ARRAY; } - return new WavHeader( + input.skipFully((int) (input.getPeekPosition() - input.getPosition())); + return new WavFormat( audioFormatType, numChannels, frameRateHz, @@ -109,8 +120,6 @@ import java.io.IOException; * @throws IOException If reading from the input fails. */ public static Pair skipToSampleData(ExtractorInput input) throws IOException { - Assertions.checkNotNull(input); - // Make sure the peek position is set to the read position before we peek the first header. input.resetPeekPosition(); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java index 2a92c38431..1d5c8fdae1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java @@ -22,18 +22,18 @@ import com.google.android.exoplayer2.util.Util; /* package */ final class WavSeekMap implements SeekMap { - private final WavHeader wavHeader; + private final WavFormat wavFormat; private final int framesPerBlock; private final long firstBlockPosition; private final long blockCount; private final long durationUs; public WavSeekMap( - WavHeader wavHeader, int framesPerBlock, long dataStartPosition, long dataEndPosition) { - this.wavHeader = wavHeader; + WavFormat wavFormat, int framesPerBlock, long dataStartPosition, long dataEndPosition) { + this.wavFormat = wavFormat; this.framesPerBlock = framesPerBlock; this.firstBlockPosition = dataStartPosition; - this.blockCount = (dataEndPosition - dataStartPosition) / wavHeader.blockSize; + this.blockCount = (dataEndPosition - dataStartPosition) / wavFormat.blockSize; durationUs = blockIndexToTimeUs(blockCount); } @@ -50,17 +50,17 @@ import com.google.android.exoplayer2.util.Util; @Override public SeekPoints getSeekPoints(long timeUs) { // Calculate the containing block index, constraining to valid indices. - long blockIndex = (timeUs * wavHeader.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock); + long blockIndex = (timeUs * wavFormat.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock); blockIndex = Util.constrainValue(blockIndex, 0, blockCount - 1); - long seekPosition = firstBlockPosition + (blockIndex * wavHeader.blockSize); + long seekPosition = firstBlockPosition + (blockIndex * wavFormat.blockSize); long seekTimeUs = blockIndexToTimeUs(blockIndex); SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition); if (seekTimeUs >= timeUs || blockIndex == blockCount - 1) { return new SeekPoints(seekPoint); } else { long secondBlockIndex = blockIndex + 1; - long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavHeader.blockSize); + long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavFormat.blockSize); long secondSeekTimeUs = blockIndexToTimeUs(secondBlockIndex); SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition); return new SeekPoints(seekPoint, secondSeekPoint); @@ -69,6 +69,6 @@ import com.google.android.exoplayer2.util.Util; private long blockIndexToTimeUs(long blockIndex) { return Util.scaleLargeTimestamp( - blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavHeader.frameRateHz); + blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavFormat.frameRateHz); } } From 293cf2f865d28a0d498fdac8e66f5925c72477d2 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 3 Nov 2021 11:33:57 +0000 Subject: [PATCH 106/113] Fix broken link on supported-formats dev guide page #minor-release PiperOrigin-RevId: 407305661 --- docs/supported-formats.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/supported-formats.md b/docs/supported-formats.md index 8270866bcd..70b24416a8 100644 --- a/docs/supported-formats.md +++ b/docs/supported-formats.md @@ -92,7 +92,7 @@ FFmpeg decoder name. ## Standalone subtitle formats ## ExoPlayer supports standalone subtitle files in a variety of formats. Subtitle -files can be side-loaded as described on the [Media source page][]. +files can be side-loaded as described on the [media items page][]. | Container format | Supported | MIME type | |---------------------------|:------------:|:----------| @@ -101,7 +101,7 @@ files can be side-loaded as described on the [Media source page][]. | SubRip | YES | MimeTypes.APPLICATION_SUBRIP | | SubStationAlpha (SSA/ASS) | YES | MimeTypes.TEXT_SSA | -[Media source page]: {{ site.baseurl }}/media-sources.html#side-loading-a-subtitle-file +[media items page]: {{ site.baseurl }}/media-items.html#sideloading-subtitle-tracks ## HDR video playback ## From f6e0790a68423a4238095d12d45618843e324a14 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Wed, 3 Nov 2021 11:57:41 +0000 Subject: [PATCH 107/113] Fix END_OF_STREAM transformer timestamp matching previous. This cause the muxer to fail to stop on older devices/API levels. #minor-release PiperOrigin-RevId: 407309028 --- RELEASENOTES.md | 3 +++ .../exoplayer2/transformer/TransformerAudioRenderer.java | 1 + 2 files changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 586144ca28..cbf79d1a9d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -90,6 +90,9 @@ * Rename `MediaSessionConnector.QueueNavigator#onCurrentWindowIndexChanged` to `onCurrentMediaItemIndexChanged`. +* Transformer: + * Avoid sending a duplicate timestamp to the encoder with the end of + stream buffer. * Remove deprecated symbols: * Remove `Renderer.VIDEO_SCALING_MODE_*` constants. Use identically named constants in `C` instead. diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index f20915c120..7efa0eb780 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -316,6 +316,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private void queueEndOfStreamToEncoder(MediaCodecAdapterWrapper encoder) { checkState(checkNotNull(encoderInputBuffer.data).position() == 0); + encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs; encoderInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); encoderInputBuffer.flip(); // Queuing EOS should only occur with an empty buffer. From de71fd6eba72721076ed94472dac7a602984a81f Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Nov 2021 12:36:06 +0000 Subject: [PATCH 108/113] Bump version to 2.16.0 PiperOrigin-RevId: 407314385 --- constants.gradle | 4 ++-- .../com/google/android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/constants.gradle b/constants.gradle index 400eba8c9f..bd4a545c4c 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.15.1' - releaseVersionCode = 2015001 + releaseVersion = '2.16.0' + releaseVersionCode = 2016000 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 9f13465861..4784575659 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.15.1"; + public static final String VERSION = "2.16.0"; /** 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.15.1"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.16.0"; /** * 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 = 2015001; + public static final int VERSION_INT = 2016000; /** Whether the library was compiled with {@link Assertions} checks enabled. */ public static final boolean ASSERTIONS_ENABLED = true; From 6388dc637623c72cd01b3c0bf6ddf2bc661ac57b Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Nov 2021 14:37:41 +0000 Subject: [PATCH 109/113] Update release notes for 2.16.0 PiperOrigin-RevId: 407333525 --- RELEASENOTES.md | 50 ++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cbf79d1a9d..f3d1f89a3c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,39 +1,33 @@ # Release notes -### dev-v2 (not yet released) +### 2.16.0 (2021-11-04) * Core Library: + * Deprecate `SimpleExoPlayer`. All functionality has been moved to + `ExoPlayer` instead. `ExoPlayer.Builder` can be used instead of + `SimpleExoPlayer.Builder`. + * Add track selection methods to the `Player` interface, for example, + `Player.getCurrentTracksInfo` and `Player.setTrackSelectionParameters`. + These methods can be used instead of directly accessing the track + selector. * Enable MediaCodec asynchronous queueing by default on devices with API level >= 31. Add methods in `DefaultMediaCodecRendererFactory` and `DefaultRenderersFactory` to force enable or force disable asynchronous queueing ([6348](https://github.com/google/ExoPlayer/issues/6348)). - * Add 12 public method headers to `ExoPlayer` that exist in - `SimpleExoPlayer`, such that all public methods in `SimpleExoPlayer` are - overrides. + * Remove final dependency on `jcenter()`. + * Fix `mediaMetadata` being reset when media is repeated + ([#9458](https://github.com/google/ExoPlayer/issues/9458)). + * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values + input through the `MediaItem.MediaMetadata` are used above media derived + values. * Move `com.google.android.exoplayer2.device.DeviceInfo` to `com.google.android.exoplayer2.DeviceInfo`. * Move `com.google.android.exoplayer2.drm.DecryptionException` to `com.google.android.exoplayer2.decoder.CryptoException`. * Move `com.google.android.exoplayer2.upstream.cache.CachedRegionTracker` to `com.google.android.exoplayer2.upstream.CachedRegionTracker`. - * Remove `ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED`. Use - `GlUtil.glAssertionsEnabled` instead. * Move `Player.addListener(EventListener)` and `Player.removeListener(EventListener)` out of `Player` into subclasses. - * Fix `mediaMetadata` being reset when media is repeated - ([#9458](https://github.com/google/ExoPlayer/issues/9458)). - * Remove final dependency on `jcenter()`. - * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values - input through the `MediaItem.MediaMetadata` are used above media derived - values. -* Video: - * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a - released `Surface` when playing without an app-provided `Surface` - ([#9476](https://github.com/google/ExoPlayer/issues/9476)). -* DRM: - * Log an error (instead of throwing `IllegalStateException`) when calling - `DefaultDrmSession#release()` on a fully released session - ([#9392](https://github.com/google/ExoPlayer/issues/9392)). * Android 12 compatibility: * Keep `DownloadService` started and in the foreground whilst waiting for requirements to be met on Android 12. This is necessary due to new @@ -49,6 +43,14 @@ are not compatible with apps targeting Android 12, and will crash with an `IllegalArgumentException` when creating `PendingIntent`s ([#9181](https://github.com/google/ExoPlayer/issues/9181)). +* Video: + * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a + released `Surface` when playing without an app-provided `Surface` + ([#9476](https://github.com/google/ExoPlayer/issues/9476)). +* DRM: + * Log an error (instead of throwing `IllegalStateException`) when calling + `DefaultDrmSession#release()` on a fully released session + ([#9392](https://github.com/google/ExoPlayer/issues/9392)). * UI: * `SubtitleView` no longer implements `TextOutput`. `SubtitleView` implements `Player.Listener`, so can be registered to a player with @@ -74,7 +76,7 @@ requirements for downloads to continue. In both cases, `DownloadService` will now remain started and in the foreground whilst waiting for requirements to be met. - * Modify `DownlaodService` behavior when running on Android 12 and above. + * Modify `DownloadService` behavior when running on Android 12 and above. See the "Android 12 compatibility" section above. * RTSP: * Support RFC4566 SDP attribute field grammar @@ -83,6 +85,12 @@ * Populate `Format.sampleMimeType`, `width` and `height` for image `AdaptationSet` elements ([#9500](https://github.com/google/ExoPlayer/issues/9500)). +* HLS: + * Fix rounding error in HLS playlists + ([#9575](https://github.com/google/ExoPlayer/issues/9575)). + * Fix `NoSuchElementException` thrown when an HLS manifest declares + `#EXT-X-RENDITION-REPORT` at the beginning of the playlist + ([#9592](https://github.com/google/ExoPlayer/issues/9592)). * RTMP extension: * Upgrade to `io.antmedia:rtmp_client`, which does not rely on `jcenter()` ([#9591](https://github.com/google/ExoPlayer/issues/9591)). From 5d2df8349672a106d7231b353688090e8277b80f Mon Sep 17 00:00:00 2001 From: christosts Date: Wed, 3 Nov 2021 15:45:07 +0000 Subject: [PATCH 110/113] Add DefaultMediaCodecFactory.getCodecAdapter() method Add protected method DefaultRenderersFactory.getCodecAdapter(), so that subclasses of DefaultRenderersFactory that override buildVideoRenderers() or buildAudioRenderers() can access the DefaultRenderersFactory codec adapter factory and pass it to MediaCodecRenderer instances they may create. #minor-release PiperOrigin-RevId: 407345431 --- RELEASENOTES.md | 7 +++++++ .../android/exoplayer2/DefaultRenderersFactory.java | 13 +++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ae006ebdde..b53b2f6226 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,13 @@ ### dev-v2 (not yet released) +* Core Library: + * Add protected method `DefaultRenderersFactory.getCodecAdapterFactory()` + so that subclasses of `DefaultRenderersFactory` that override + `buildVideoRenderers()` or `buildAudioRenderers()` can access the codec + adapter factory and pass it to `MediaCodecRenderer` instances they + create. + ### 2.16.0 (2021-11-04) * Core Library: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 03ccc297a8..ca205c2901 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.audio.DefaultAudioSink; import com.google.android.exoplayer2.audio.DefaultAudioSink.DefaultAudioProcessorChain; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.mediacodec.DefaultMediaCodecAdapterFactory; +import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.metadata.MetadataRenderer; @@ -368,7 +369,7 @@ public class DefaultRenderersFactory implements RenderersFactory { MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer( context, - codecAdapterFactory, + getCodecAdapterFactory(), mediaCodecSelector, allowedVideoJoiningTimeMs, enableDecoderFallback, @@ -488,7 +489,7 @@ public class DefaultRenderersFactory implements RenderersFactory { MediaCodecAudioRenderer audioRenderer = new MediaCodecAudioRenderer( context, - codecAdapterFactory, + getCodecAdapterFactory(), mediaCodecSelector, enableDecoderFallback, eventHandler, @@ -655,4 +656,12 @@ public class DefaultRenderersFactory implements RenderersFactory { ? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED : DefaultAudioSink.OFFLOAD_MODE_DISABLED); } + + /** + * Returns the {@link MediaCodecAdapter.Factory} that will be used when creating {@link + * com.google.android.exoplayer2.mediacodec.MediaCodecRenderer} instances. + */ + protected MediaCodecAdapter.Factory getCodecAdapterFactory() { + return codecAdapterFactory; + } } From 49a93e31d7a97cab7f14c22517f58b134e87a977 Mon Sep 17 00:00:00 2001 From: hschlueter Date: Wed, 3 Nov 2021 15:49:09 +0000 Subject: [PATCH 111/113] Write sample size to dumpfile in transformer tests. If the number of samples changes, the sizes will help us to verify whether they are just split differently or extra data was added. PiperOrigin-RevId: 407346280 --- .../exoplayer2/transformer/TestMuxer.java | 5 +- .../transformerdumps/amr/sample_nb.amr.dump | 218 ++++++++++++++++++ .../transformerdumps/mkv/sample.mkv.dump | 30 +++ .../mkv/sample_with_srt.mkv.dump | 30 +++ .../transformerdumps/mp4/sample.mp4.dump | 75 ++++++ .../mp4/sample.mp4.noaudio.dump | 30 +++ .../mp4/sample.mp4.novideo.dump | 45 ++++ .../mp4/sample_sef_slow_motion.mp4.dump | 41 ++++ 8 files changed, 473 insertions(+), 1 deletion(-) diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java index d47dd0d32f..b4836e27a2 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TestMuxer.java @@ -79,6 +79,7 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable { private final long presentationTimeUs; private final boolean isKeyFrame; private final int sampleDataHashCode; + private final int sampleSize; public DumpableSample( int trackIndex, ByteBuffer sample, boolean isKeyFrame, long presentationTimeUs) { @@ -86,7 +87,8 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable { this.presentationTimeUs = presentationTimeUs; this.isKeyFrame = isKeyFrame; int initialPosition = sample.position(); - byte[] data = new byte[sample.remaining()]; + sampleSize = sample.remaining(); + byte[] data = new byte[sampleSize]; sample.get(data); sample.position(initialPosition); sampleDataHashCode = Arrays.hashCode(data); @@ -98,6 +100,7 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable { .startBlock("sample") .add("trackIndex", trackIndex) .add("dataHashCode", sampleDataHashCode) + .add("size", sampleSize) .add("isKeyFrame", isKeyFrame) .add("presentationTimeUs", presentationTimeUs) .endBlock(); diff --git a/testdata/src/test/assets/transformerdumps/amr/sample_nb.amr.dump b/testdata/src/test/assets/transformerdumps/amr/sample_nb.amr.dump index c18193c16f..18836cbc5d 100644 --- a/testdata/src/test/assets/transformerdumps/amr/sample_nb.amr.dump +++ b/testdata/src/test/assets/transformerdumps/amr/sample_nb.amr.dump @@ -7,1091 +7,1309 @@ format 0: sample: trackIndex = 0 dataHashCode = 924517484 + size = 13 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 0 dataHashCode = -835666085 + size = 13 isKeyFrame = true presentationTimeUs = 750 sample: trackIndex = 0 dataHashCode = 430283125 + size = 13 isKeyFrame = true presentationTimeUs = 1500 sample: trackIndex = 0 dataHashCode = 1215919932 + size = 13 isKeyFrame = true presentationTimeUs = 2250 sample: trackIndex = 0 dataHashCode = -386387943 + size = 13 isKeyFrame = true presentationTimeUs = 3000 sample: trackIndex = 0 dataHashCode = -765080119 + size = 13 isKeyFrame = true presentationTimeUs = 3750 sample: trackIndex = 0 dataHashCode = -1855636054 + size = 13 isKeyFrame = true presentationTimeUs = 4500 sample: trackIndex = 0 dataHashCode = -946579722 + size = 13 isKeyFrame = true presentationTimeUs = 5250 sample: trackIndex = 0 dataHashCode = -841202654 + size = 13 isKeyFrame = true presentationTimeUs = 6000 sample: trackIndex = 0 dataHashCode = -638764303 + size = 13 isKeyFrame = true presentationTimeUs = 6750 sample: trackIndex = 0 dataHashCode = -1162388941 + size = 13 isKeyFrame = true presentationTimeUs = 7500 sample: trackIndex = 0 dataHashCode = 572634367 + size = 13 isKeyFrame = true presentationTimeUs = 8250 sample: trackIndex = 0 dataHashCode = -1774188021 + size = 13 isKeyFrame = true presentationTimeUs = 9000 sample: trackIndex = 0 dataHashCode = 92464891 + size = 13 isKeyFrame = true presentationTimeUs = 9750 sample: trackIndex = 0 dataHashCode = -991397659 + size = 13 isKeyFrame = true presentationTimeUs = 10500 sample: trackIndex = 0 dataHashCode = -934698563 + size = 13 isKeyFrame = true presentationTimeUs = 11250 sample: trackIndex = 0 dataHashCode = -811030035 + size = 13 isKeyFrame = true presentationTimeUs = 12000 sample: trackIndex = 0 dataHashCode = 1892305159 + size = 13 isKeyFrame = true presentationTimeUs = 12750 sample: trackIndex = 0 dataHashCode = -1266858924 + size = 13 isKeyFrame = true presentationTimeUs = 13500 sample: trackIndex = 0 dataHashCode = 673814721 + size = 13 isKeyFrame = true presentationTimeUs = 14250 sample: trackIndex = 0 dataHashCode = 1061124709 + size = 13 isKeyFrame = true presentationTimeUs = 15000 sample: trackIndex = 0 dataHashCode = -869356712 + size = 13 isKeyFrame = true presentationTimeUs = 15750 sample: trackIndex = 0 dataHashCode = 664729362 + size = 13 isKeyFrame = true presentationTimeUs = 16500 sample: trackIndex = 0 dataHashCode = -1439741143 + size = 13 isKeyFrame = true presentationTimeUs = 17250 sample: trackIndex = 0 dataHashCode = -151627580 + size = 13 isKeyFrame = true presentationTimeUs = 18000 sample: trackIndex = 0 dataHashCode = -673268457 + size = 13 isKeyFrame = true presentationTimeUs = 18750 sample: trackIndex = 0 dataHashCode = 1839962647 + size = 13 isKeyFrame = true presentationTimeUs = 19500 sample: trackIndex = 0 dataHashCode = 1858999665 + size = 13 isKeyFrame = true presentationTimeUs = 20250 sample: trackIndex = 0 dataHashCode = -1278193537 + size = 13 isKeyFrame = true presentationTimeUs = 21000 sample: trackIndex = 0 dataHashCode = 568547001 + size = 13 isKeyFrame = true presentationTimeUs = 21750 sample: trackIndex = 0 dataHashCode = 68217362 + size = 13 isKeyFrame = true presentationTimeUs = 22500 sample: trackIndex = 0 dataHashCode = 1396217256 + size = 13 isKeyFrame = true presentationTimeUs = 23250 sample: trackIndex = 0 dataHashCode = -971293094 + size = 13 isKeyFrame = true presentationTimeUs = 24000 sample: trackIndex = 0 dataHashCode = -1742638874 + size = 13 isKeyFrame = true presentationTimeUs = 24750 sample: trackIndex = 0 dataHashCode = 2047109317 + size = 13 isKeyFrame = true presentationTimeUs = 25500 sample: trackIndex = 0 dataHashCode = -1668945241 + size = 13 isKeyFrame = true presentationTimeUs = 26250 sample: trackIndex = 0 dataHashCode = -1229766218 + size = 13 isKeyFrame = true presentationTimeUs = 27000 sample: trackIndex = 0 dataHashCode = 1765233454 + size = 13 isKeyFrame = true presentationTimeUs = 27750 sample: trackIndex = 0 dataHashCode = -1930255456 + size = 13 isKeyFrame = true presentationTimeUs = 28500 sample: trackIndex = 0 dataHashCode = -764925242 + size = 13 isKeyFrame = true presentationTimeUs = 29250 sample: trackIndex = 0 dataHashCode = -1144688369 + size = 13 isKeyFrame = true presentationTimeUs = 30000 sample: trackIndex = 0 dataHashCode = 1493699436 + size = 13 isKeyFrame = true presentationTimeUs = 30750 sample: trackIndex = 0 dataHashCode = -468614511 + size = 13 isKeyFrame = true presentationTimeUs = 31500 sample: trackIndex = 0 dataHashCode = -1578782058 + size = 13 isKeyFrame = true presentationTimeUs = 32250 sample: trackIndex = 0 dataHashCode = -675743397 + size = 13 isKeyFrame = true presentationTimeUs = 33000 sample: trackIndex = 0 dataHashCode = -863790111 + size = 13 isKeyFrame = true presentationTimeUs = 33750 sample: trackIndex = 0 dataHashCode = -732307506 + size = 13 isKeyFrame = true presentationTimeUs = 34500 sample: trackIndex = 0 dataHashCode = -693298708 + size = 13 isKeyFrame = true presentationTimeUs = 35250 sample: trackIndex = 0 dataHashCode = -799131843 + size = 13 isKeyFrame = true presentationTimeUs = 36000 sample: trackIndex = 0 dataHashCode = 1782866119 + size = 13 isKeyFrame = true presentationTimeUs = 36750 sample: trackIndex = 0 dataHashCode = -912205505 + size = 13 isKeyFrame = true presentationTimeUs = 37500 sample: trackIndex = 0 dataHashCode = 1067981287 + size = 13 isKeyFrame = true presentationTimeUs = 38250 sample: trackIndex = 0 dataHashCode = 490520060 + size = 13 isKeyFrame = true presentationTimeUs = 39000 sample: trackIndex = 0 dataHashCode = -1950632957 + size = 13 isKeyFrame = true presentationTimeUs = 39750 sample: trackIndex = 0 dataHashCode = 565485817 + size = 13 isKeyFrame = true presentationTimeUs = 40500 sample: trackIndex = 0 dataHashCode = -1057414703 + size = 13 isKeyFrame = true presentationTimeUs = 41250 sample: trackIndex = 0 dataHashCode = 1568746155 + size = 13 isKeyFrame = true presentationTimeUs = 42000 sample: trackIndex = 0 dataHashCode = 1355412472 + size = 13 isKeyFrame = true presentationTimeUs = 42750 sample: trackIndex = 0 dataHashCode = 1546368465 + size = 13 isKeyFrame = true presentationTimeUs = 43500 sample: trackIndex = 0 dataHashCode = 1811529381 + size = 13 isKeyFrame = true presentationTimeUs = 44250 sample: trackIndex = 0 dataHashCode = 658031078 + size = 13 isKeyFrame = true presentationTimeUs = 45000 sample: trackIndex = 0 dataHashCode = 1606584486 + size = 13 isKeyFrame = true presentationTimeUs = 45750 sample: trackIndex = 0 dataHashCode = 2123252778 + size = 13 isKeyFrame = true presentationTimeUs = 46500 sample: trackIndex = 0 dataHashCode = -1364579398 + size = 13 isKeyFrame = true presentationTimeUs = 47250 sample: trackIndex = 0 dataHashCode = 1311427887 + size = 13 isKeyFrame = true presentationTimeUs = 48000 sample: trackIndex = 0 dataHashCode = -691467569 + size = 13 isKeyFrame = true presentationTimeUs = 48750 sample: trackIndex = 0 dataHashCode = 1876470084 + size = 13 isKeyFrame = true presentationTimeUs = 49500 sample: trackIndex = 0 dataHashCode = -1472873479 + size = 13 isKeyFrame = true presentationTimeUs = 50250 sample: trackIndex = 0 dataHashCode = -143574992 + size = 13 isKeyFrame = true presentationTimeUs = 51000 sample: trackIndex = 0 dataHashCode = 984180453 + size = 13 isKeyFrame = true presentationTimeUs = 51750 sample: trackIndex = 0 dataHashCode = -113645527 + size = 13 isKeyFrame = true presentationTimeUs = 52500 sample: trackIndex = 0 dataHashCode = 1987501641 + size = 13 isKeyFrame = true presentationTimeUs = 53250 sample: trackIndex = 0 dataHashCode = -1816426230 + size = 13 isKeyFrame = true presentationTimeUs = 54000 sample: trackIndex = 0 dataHashCode = -1250050360 + size = 13 isKeyFrame = true presentationTimeUs = 54750 sample: trackIndex = 0 dataHashCode = 1722852790 + size = 13 isKeyFrame = true presentationTimeUs = 55500 sample: trackIndex = 0 dataHashCode = 225656333 + size = 13 isKeyFrame = true presentationTimeUs = 56250 sample: trackIndex = 0 dataHashCode = -2137778394 + size = 13 isKeyFrame = true presentationTimeUs = 57000 sample: trackIndex = 0 dataHashCode = 1433327155 + size = 13 isKeyFrame = true presentationTimeUs = 57750 sample: trackIndex = 0 dataHashCode = -974261023 + size = 13 isKeyFrame = true presentationTimeUs = 58500 sample: trackIndex = 0 dataHashCode = 1797813317 + size = 13 isKeyFrame = true presentationTimeUs = 59250 sample: trackIndex = 0 dataHashCode = -594033497 + size = 13 isKeyFrame = true presentationTimeUs = 60000 sample: trackIndex = 0 dataHashCode = -628310540 + size = 13 isKeyFrame = true presentationTimeUs = 60750 sample: trackIndex = 0 dataHashCode = 1868627831 + size = 13 isKeyFrame = true presentationTimeUs = 61500 sample: trackIndex = 0 dataHashCode = 1051863958 + size = 13 isKeyFrame = true presentationTimeUs = 62250 sample: trackIndex = 0 dataHashCode = -1279059211 + size = 13 isKeyFrame = true presentationTimeUs = 63000 sample: trackIndex = 0 dataHashCode = 408201874 + size = 13 isKeyFrame = true presentationTimeUs = 63750 sample: trackIndex = 0 dataHashCode = 1686644299 + size = 13 isKeyFrame = true presentationTimeUs = 64500 sample: trackIndex = 0 dataHashCode = 1288226241 + size = 13 isKeyFrame = true presentationTimeUs = 65250 sample: trackIndex = 0 dataHashCode = 432829731 + size = 13 isKeyFrame = true presentationTimeUs = 66000 sample: trackIndex = 0 dataHashCode = -1679312600 + size = 13 isKeyFrame = true presentationTimeUs = 66750 sample: trackIndex = 0 dataHashCode = 1206680829 + size = 13 isKeyFrame = true presentationTimeUs = 67500 sample: trackIndex = 0 dataHashCode = -325844704 + size = 13 isKeyFrame = true presentationTimeUs = 68250 sample: trackIndex = 0 dataHashCode = 1941808848 + size = 13 isKeyFrame = true presentationTimeUs = 69000 sample: trackIndex = 0 dataHashCode = -87346412 + size = 13 isKeyFrame = true presentationTimeUs = 69750 sample: trackIndex = 0 dataHashCode = -329133765 + size = 13 isKeyFrame = true presentationTimeUs = 70500 sample: trackIndex = 0 dataHashCode = -1299416212 + size = 13 isKeyFrame = true presentationTimeUs = 71250 sample: trackIndex = 0 dataHashCode = -1314599219 + size = 13 isKeyFrame = true presentationTimeUs = 72000 sample: trackIndex = 0 dataHashCode = 1456741286 + size = 13 isKeyFrame = true presentationTimeUs = 72750 sample: trackIndex = 0 dataHashCode = 151296500 + size = 13 isKeyFrame = true presentationTimeUs = 73500 sample: trackIndex = 0 dataHashCode = 1708763603 + size = 13 isKeyFrame = true presentationTimeUs = 74250 sample: trackIndex = 0 dataHashCode = 227542220 + size = 13 isKeyFrame = true presentationTimeUs = 75000 sample: trackIndex = 0 dataHashCode = 1094305517 + size = 13 isKeyFrame = true presentationTimeUs = 75750 sample: trackIndex = 0 dataHashCode = -990377604 + size = 13 isKeyFrame = true presentationTimeUs = 76500 sample: trackIndex = 0 dataHashCode = -1798036230 + size = 13 isKeyFrame = true presentationTimeUs = 77250 sample: trackIndex = 0 dataHashCode = -1027148291 + size = 13 isKeyFrame = true presentationTimeUs = 78000 sample: trackIndex = 0 dataHashCode = 359763976 + size = 13 isKeyFrame = true presentationTimeUs = 78750 sample: trackIndex = 0 dataHashCode = 1332016420 + size = 13 isKeyFrame = true presentationTimeUs = 79500 sample: trackIndex = 0 dataHashCode = -102753250 + size = 13 isKeyFrame = true presentationTimeUs = 80250 sample: trackIndex = 0 dataHashCode = 1959063156 + size = 13 isKeyFrame = true presentationTimeUs = 81000 sample: trackIndex = 0 dataHashCode = 2129089853 + size = 13 isKeyFrame = true presentationTimeUs = 81750 sample: trackIndex = 0 dataHashCode = 1658742073 + size = 13 isKeyFrame = true presentationTimeUs = 82500 sample: trackIndex = 0 dataHashCode = 2136916514 + size = 13 isKeyFrame = true presentationTimeUs = 83250 sample: trackIndex = 0 dataHashCode = 105121407 + size = 13 isKeyFrame = true presentationTimeUs = 84000 sample: trackIndex = 0 dataHashCode = -839464484 + size = 13 isKeyFrame = true presentationTimeUs = 84750 sample: trackIndex = 0 dataHashCode = -1956791168 + size = 13 isKeyFrame = true presentationTimeUs = 85500 sample: trackIndex = 0 dataHashCode = -1387546109 + size = 13 isKeyFrame = true presentationTimeUs = 86250 sample: trackIndex = 0 dataHashCode = 128410432 + size = 13 isKeyFrame = true presentationTimeUs = 87000 sample: trackIndex = 0 dataHashCode = 907081136 + size = 13 isKeyFrame = true presentationTimeUs = 87750 sample: trackIndex = 0 dataHashCode = 1124845067 + size = 13 isKeyFrame = true presentationTimeUs = 88500 sample: trackIndex = 0 dataHashCode = -1714479962 + size = 13 isKeyFrame = true presentationTimeUs = 89250 sample: trackIndex = 0 dataHashCode = 322029323 + size = 13 isKeyFrame = true presentationTimeUs = 90000 sample: trackIndex = 0 dataHashCode = -1116281187 + size = 13 isKeyFrame = true presentationTimeUs = 90750 sample: trackIndex = 0 dataHashCode = 1571181228 + size = 13 isKeyFrame = true presentationTimeUs = 91500 sample: trackIndex = 0 dataHashCode = 997979854 + size = 13 isKeyFrame = true presentationTimeUs = 92250 sample: trackIndex = 0 dataHashCode = -1413492413 + size = 13 isKeyFrame = true presentationTimeUs = 93000 sample: trackIndex = 0 dataHashCode = -381390490 + size = 13 isKeyFrame = true presentationTimeUs = 93750 sample: trackIndex = 0 dataHashCode = -331348340 + size = 13 isKeyFrame = true presentationTimeUs = 94500 sample: trackIndex = 0 dataHashCode = -1568238592 + size = 13 isKeyFrame = true presentationTimeUs = 95250 sample: trackIndex = 0 dataHashCode = -941591445 + size = 13 isKeyFrame = true presentationTimeUs = 96000 sample: trackIndex = 0 dataHashCode = 1616911281 + size = 13 isKeyFrame = true presentationTimeUs = 96750 sample: trackIndex = 0 dataHashCode = -1755664741 + size = 13 isKeyFrame = true presentationTimeUs = 97500 sample: trackIndex = 0 dataHashCode = -1950609742 + size = 13 isKeyFrame = true presentationTimeUs = 98250 sample: trackIndex = 0 dataHashCode = 1476082149 + size = 13 isKeyFrame = true presentationTimeUs = 99000 sample: trackIndex = 0 dataHashCode = 1289547483 + size = 13 isKeyFrame = true presentationTimeUs = 99750 sample: trackIndex = 0 dataHashCode = -367599018 + size = 13 isKeyFrame = true presentationTimeUs = 100500 sample: trackIndex = 0 dataHashCode = 679378334 + size = 13 isKeyFrame = true presentationTimeUs = 101250 sample: trackIndex = 0 dataHashCode = 1437306809 + size = 13 isKeyFrame = true presentationTimeUs = 102000 sample: trackIndex = 0 dataHashCode = 311988463 + size = 13 isKeyFrame = true presentationTimeUs = 102750 sample: trackIndex = 0 dataHashCode = -1870442665 + size = 13 isKeyFrame = true presentationTimeUs = 103500 sample: trackIndex = 0 dataHashCode = 1530013920 + size = 13 isKeyFrame = true presentationTimeUs = 104250 sample: trackIndex = 0 dataHashCode = -585506443 + size = 13 isKeyFrame = true presentationTimeUs = 105000 sample: trackIndex = 0 dataHashCode = -293690558 + size = 13 isKeyFrame = true presentationTimeUs = 105750 sample: trackIndex = 0 dataHashCode = -616893325 + size = 13 isKeyFrame = true presentationTimeUs = 106500 sample: trackIndex = 0 dataHashCode = 632210495 + size = 13 isKeyFrame = true presentationTimeUs = 107250 sample: trackIndex = 0 dataHashCode = -291767937 + size = 13 isKeyFrame = true presentationTimeUs = 108000 sample: trackIndex = 0 dataHashCode = -270265 + size = 13 isKeyFrame = true presentationTimeUs = 108750 sample: trackIndex = 0 dataHashCode = -1095959376 + size = 13 isKeyFrame = true presentationTimeUs = 109500 sample: trackIndex = 0 dataHashCode = -1363867284 + size = 13 isKeyFrame = true presentationTimeUs = 110250 sample: trackIndex = 0 dataHashCode = 185415707 + size = 13 isKeyFrame = true presentationTimeUs = 111000 sample: trackIndex = 0 dataHashCode = 1033720098 + size = 13 isKeyFrame = true presentationTimeUs = 111750 sample: trackIndex = 0 dataHashCode = 1813896085 + size = 13 isKeyFrame = true presentationTimeUs = 112500 sample: trackIndex = 0 dataHashCode = -1381192241 + size = 13 isKeyFrame = true presentationTimeUs = 113250 sample: trackIndex = 0 dataHashCode = 362689054 + size = 13 isKeyFrame = true presentationTimeUs = 114000 sample: trackIndex = 0 dataHashCode = -1320787356 + size = 13 isKeyFrame = true presentationTimeUs = 114750 sample: trackIndex = 0 dataHashCode = 1306489379 + size = 13 isKeyFrame = true presentationTimeUs = 115500 sample: trackIndex = 0 dataHashCode = -910313430 + size = 13 isKeyFrame = true presentationTimeUs = 116250 sample: trackIndex = 0 dataHashCode = -1533334115 + size = 13 isKeyFrame = true presentationTimeUs = 117000 sample: trackIndex = 0 dataHashCode = -700061723 + size = 13 isKeyFrame = true presentationTimeUs = 117750 sample: trackIndex = 0 dataHashCode = 474100444 + size = 13 isKeyFrame = true presentationTimeUs = 118500 sample: trackIndex = 0 dataHashCode = -2096659943 + size = 13 isKeyFrame = true presentationTimeUs = 119250 sample: trackIndex = 0 dataHashCode = -690442126 + size = 13 isKeyFrame = true presentationTimeUs = 120000 sample: trackIndex = 0 dataHashCode = 158718784 + size = 13 isKeyFrame = true presentationTimeUs = 120750 sample: trackIndex = 0 dataHashCode = -1587553019 + size = 13 isKeyFrame = true presentationTimeUs = 121500 sample: trackIndex = 0 dataHashCode = 1266916929 + size = 13 isKeyFrame = true presentationTimeUs = 122250 sample: trackIndex = 0 dataHashCode = 1947792537 + size = 13 isKeyFrame = true presentationTimeUs = 123000 sample: trackIndex = 0 dataHashCode = 2051622372 + size = 13 isKeyFrame = true presentationTimeUs = 123750 sample: trackIndex = 0 dataHashCode = 1648973196 + size = 13 isKeyFrame = true presentationTimeUs = 124500 sample: trackIndex = 0 dataHashCode = -1119069213 + size = 13 isKeyFrame = true presentationTimeUs = 125250 sample: trackIndex = 0 dataHashCode = -1162670307 + size = 13 isKeyFrame = true presentationTimeUs = 126000 sample: trackIndex = 0 dataHashCode = 505180178 + size = 13 isKeyFrame = true presentationTimeUs = 126750 sample: trackIndex = 0 dataHashCode = -1707111799 + size = 13 isKeyFrame = true presentationTimeUs = 127500 sample: trackIndex = 0 dataHashCode = 549350779 + size = 13 isKeyFrame = true presentationTimeUs = 128250 sample: trackIndex = 0 dataHashCode = -895461091 + size = 13 isKeyFrame = true presentationTimeUs = 129000 sample: trackIndex = 0 dataHashCode = 1834306839 + size = 13 isKeyFrame = true presentationTimeUs = 129750 sample: trackIndex = 0 dataHashCode = -646169807 + size = 13 isKeyFrame = true presentationTimeUs = 130500 sample: trackIndex = 0 dataHashCode = 123454915 + size = 13 isKeyFrame = true presentationTimeUs = 131250 sample: trackIndex = 0 dataHashCode = 2074179659 + size = 13 isKeyFrame = true presentationTimeUs = 132000 sample: trackIndex = 0 dataHashCode = 488070546 + size = 13 isKeyFrame = true presentationTimeUs = 132750 sample: trackIndex = 0 dataHashCode = -1379245827 + size = 13 isKeyFrame = true presentationTimeUs = 133500 sample: trackIndex = 0 dataHashCode = 922846867 + size = 13 isKeyFrame = true presentationTimeUs = 134250 sample: trackIndex = 0 dataHashCode = 1163092079 + size = 13 isKeyFrame = true presentationTimeUs = 135000 sample: trackIndex = 0 dataHashCode = -817674907 + size = 13 isKeyFrame = true presentationTimeUs = 135750 sample: trackIndex = 0 dataHashCode = -765143209 + size = 13 isKeyFrame = true presentationTimeUs = 136500 sample: trackIndex = 0 dataHashCode = 1337234415 + size = 13 isKeyFrame = true presentationTimeUs = 137250 sample: trackIndex = 0 dataHashCode = 152696122 + size = 13 isKeyFrame = true presentationTimeUs = 138000 sample: trackIndex = 0 dataHashCode = -1037369189 + size = 13 isKeyFrame = true presentationTimeUs = 138750 sample: trackIndex = 0 dataHashCode = 93852784 + size = 13 isKeyFrame = true presentationTimeUs = 139500 sample: trackIndex = 0 dataHashCode = -1512860804 + size = 13 isKeyFrame = true presentationTimeUs = 140250 sample: trackIndex = 0 dataHashCode = -1571797975 + size = 13 isKeyFrame = true presentationTimeUs = 141000 sample: trackIndex = 0 dataHashCode = -1390710594 + size = 13 isKeyFrame = true presentationTimeUs = 141750 sample: trackIndex = 0 dataHashCode = 775548254 + size = 13 isKeyFrame = true presentationTimeUs = 142500 sample: trackIndex = 0 dataHashCode = 329825934 + size = 13 isKeyFrame = true presentationTimeUs = 143250 sample: trackIndex = 0 dataHashCode = 449672203 + size = 13 isKeyFrame = true presentationTimeUs = 144000 sample: trackIndex = 0 dataHashCode = 135215283 + size = 13 isKeyFrame = true presentationTimeUs = 144750 sample: trackIndex = 0 dataHashCode = -627202145 + size = 13 isKeyFrame = true presentationTimeUs = 145500 sample: trackIndex = 0 dataHashCode = 565795710 + size = 13 isKeyFrame = true presentationTimeUs = 146250 sample: trackIndex = 0 dataHashCode = -853390981 + size = 13 isKeyFrame = true presentationTimeUs = 147000 sample: trackIndex = 0 dataHashCode = 1904980829 + size = 13 isKeyFrame = true presentationTimeUs = 147750 sample: trackIndex = 0 dataHashCode = 1772857005 + size = 13 isKeyFrame = true presentationTimeUs = 148500 sample: trackIndex = 0 dataHashCode = -1159621303 + size = 13 isKeyFrame = true presentationTimeUs = 149250 sample: trackIndex = 0 dataHashCode = 712585139 + size = 13 isKeyFrame = true presentationTimeUs = 150000 sample: trackIndex = 0 dataHashCode = 7470296 + size = 13 isKeyFrame = true presentationTimeUs = 150750 sample: trackIndex = 0 dataHashCode = 1154659763 + size = 13 isKeyFrame = true presentationTimeUs = 151500 sample: trackIndex = 0 dataHashCode = 512209179 + size = 13 isKeyFrame = true presentationTimeUs = 152250 sample: trackIndex = 0 dataHashCode = 2026712081 + size = 13 isKeyFrame = true presentationTimeUs = 153000 sample: trackIndex = 0 dataHashCode = -1625715216 + size = 13 isKeyFrame = true presentationTimeUs = 153750 sample: trackIndex = 0 dataHashCode = -1299058326 + size = 13 isKeyFrame = true presentationTimeUs = 154500 sample: trackIndex = 0 dataHashCode = -813560096 + size = 13 isKeyFrame = true presentationTimeUs = 155250 sample: trackIndex = 0 dataHashCode = 1311045251 + size = 13 isKeyFrame = true presentationTimeUs = 156000 sample: trackIndex = 0 dataHashCode = 1388107407 + size = 13 isKeyFrame = true presentationTimeUs = 156750 sample: trackIndex = 0 dataHashCode = 1113099440 + size = 13 isKeyFrame = true presentationTimeUs = 157500 sample: trackIndex = 0 dataHashCode = -339743582 + size = 13 isKeyFrame = true presentationTimeUs = 158250 sample: trackIndex = 0 dataHashCode = -1055895345 + size = 13 isKeyFrame = true presentationTimeUs = 159000 sample: trackIndex = 0 dataHashCode = 1869841923 + size = 13 isKeyFrame = true presentationTimeUs = 159750 sample: trackIndex = 0 dataHashCode = 229443301 + size = 13 isKeyFrame = true presentationTimeUs = 160500 sample: trackIndex = 0 dataHashCode = 1526951012 + size = 13 isKeyFrame = true presentationTimeUs = 161250 sample: trackIndex = 0 dataHashCode = -1517436626 + size = 13 isKeyFrame = true presentationTimeUs = 162000 sample: trackIndex = 0 dataHashCode = -1403405700 + size = 13 isKeyFrame = true presentationTimeUs = 162750 released = true diff --git a/testdata/src/test/assets/transformerdumps/mkv/sample.mkv.dump b/testdata/src/test/assets/transformerdumps/mkv/sample.mkv.dump index 00d39b034e..095c54577e 100644 --- a/testdata/src/test/assets/transformerdumps/mkv/sample.mkv.dump +++ b/testdata/src/test/assets/transformerdumps/mkv/sample.mkv.dump @@ -13,151 +13,181 @@ format 0: sample: trackIndex = 0 dataHashCode = -252482306 + size = 36477 isKeyFrame = true presentationTimeUs = 67000 sample: trackIndex = 0 dataHashCode = 67864034 + size = 5341 isKeyFrame = false presentationTimeUs = 134000 sample: trackIndex = 0 dataHashCode = 897273234 + size = 596 isKeyFrame = false presentationTimeUs = 100000 sample: trackIndex = 0 dataHashCode = -1549870586 + size = 7704 isKeyFrame = false presentationTimeUs = 267000 sample: trackIndex = 0 dataHashCode = 672384813 + size = 989 isKeyFrame = false presentationTimeUs = 200000 sample: trackIndex = 0 dataHashCode = -988996493 + size = 721 isKeyFrame = false presentationTimeUs = 167000 sample: trackIndex = 0 dataHashCode = 1711151377 + size = 519 isKeyFrame = false presentationTimeUs = 234000 sample: trackIndex = 0 dataHashCode = -506806036 + size = 6160 isKeyFrame = false presentationTimeUs = 400000 sample: trackIndex = 0 dataHashCode = 1902167649 + size = 953 isKeyFrame = false presentationTimeUs = 334000 sample: trackIndex = 0 dataHashCode = 2054873212 + size = 620 isKeyFrame = false presentationTimeUs = 300000 sample: trackIndex = 0 dataHashCode = 1556608231 + size = 405 isKeyFrame = false presentationTimeUs = 367000 sample: trackIndex = 0 dataHashCode = -1648978019 + size = 4852 isKeyFrame = false presentationTimeUs = 500000 sample: trackIndex = 0 dataHashCode = -484808327 + size = 547 isKeyFrame = false presentationTimeUs = 467000 sample: trackIndex = 0 dataHashCode = -20706048 + size = 570 isKeyFrame = false presentationTimeUs = 434000 sample: trackIndex = 0 dataHashCode = 2085064574 + size = 5525 isKeyFrame = false presentationTimeUs = 634000 sample: trackIndex = 0 dataHashCode = -637074022 + size = 1082 isKeyFrame = false presentationTimeUs = 567000 sample: trackIndex = 0 dataHashCode = -1824027029 + size = 807 isKeyFrame = false presentationTimeUs = 534000 sample: trackIndex = 0 dataHashCode = -1701945306 + size = 744 isKeyFrame = false presentationTimeUs = 600000 sample: trackIndex = 0 dataHashCode = -952425536 + size = 4732 isKeyFrame = false presentationTimeUs = 767000 sample: trackIndex = 0 dataHashCode = -1978031576 + size = 1004 isKeyFrame = false presentationTimeUs = 700000 sample: trackIndex = 0 dataHashCode = -2128215508 + size = 794 isKeyFrame = false presentationTimeUs = 667000 sample: trackIndex = 0 dataHashCode = -259850011 + size = 645 isKeyFrame = false presentationTimeUs = 734000 sample: trackIndex = 0 dataHashCode = 1920983928 + size = 2684 isKeyFrame = false presentationTimeUs = 900000 sample: trackIndex = 0 dataHashCode = 1100642337 + size = 787 isKeyFrame = false presentationTimeUs = 834000 sample: trackIndex = 0 dataHashCode = 1544917830 + size = 649 isKeyFrame = false presentationTimeUs = 800000 sample: trackIndex = 0 dataHashCode = -116205995 + size = 509 isKeyFrame = false presentationTimeUs = 867000 sample: trackIndex = 0 dataHashCode = 696343585 + size = 1226 isKeyFrame = false presentationTimeUs = 1034000 sample: trackIndex = 0 dataHashCode = -644371190 + size = 898 isKeyFrame = false presentationTimeUs = 967000 sample: trackIndex = 0 dataHashCode = -1606273467 + size = 476 isKeyFrame = false presentationTimeUs = 934000 sample: trackIndex = 0 dataHashCode = -571265861 + size = 486 isKeyFrame = false presentationTimeUs = 1000000 released = true diff --git a/testdata/src/test/assets/transformerdumps/mkv/sample_with_srt.mkv.dump b/testdata/src/test/assets/transformerdumps/mkv/sample_with_srt.mkv.dump index 05a19cd924..bf39e2d187 100644 --- a/testdata/src/test/assets/transformerdumps/mkv/sample_with_srt.mkv.dump +++ b/testdata/src/test/assets/transformerdumps/mkv/sample_with_srt.mkv.dump @@ -13,151 +13,181 @@ format 0: sample: trackIndex = 0 dataHashCode = -252482306 + size = 36477 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 0 dataHashCode = 67864034 + size = 5341 isKeyFrame = false presentationTimeUs = 67000 sample: trackIndex = 0 dataHashCode = 897273234 + size = 596 isKeyFrame = false presentationTimeUs = 33000 sample: trackIndex = 0 dataHashCode = -1549870586 + size = 7704 isKeyFrame = false presentationTimeUs = 200000 sample: trackIndex = 0 dataHashCode = 672384813 + size = 989 isKeyFrame = false presentationTimeUs = 133000 sample: trackIndex = 0 dataHashCode = -988996493 + size = 721 isKeyFrame = false presentationTimeUs = 100000 sample: trackIndex = 0 dataHashCode = 1711151377 + size = 519 isKeyFrame = false presentationTimeUs = 167000 sample: trackIndex = 0 dataHashCode = -506806036 + size = 6160 isKeyFrame = false presentationTimeUs = 333000 sample: trackIndex = 0 dataHashCode = 1902167649 + size = 953 isKeyFrame = false presentationTimeUs = 267000 sample: trackIndex = 0 dataHashCode = 2054873212 + size = 620 isKeyFrame = false presentationTimeUs = 233000 sample: trackIndex = 0 dataHashCode = 1556608231 + size = 405 isKeyFrame = false presentationTimeUs = 300000 sample: trackIndex = 0 dataHashCode = -1648978019 + size = 4852 isKeyFrame = false presentationTimeUs = 433000 sample: trackIndex = 0 dataHashCode = -484808327 + size = 547 isKeyFrame = false presentationTimeUs = 400000 sample: trackIndex = 0 dataHashCode = -20706048 + size = 570 isKeyFrame = false presentationTimeUs = 367000 sample: trackIndex = 0 dataHashCode = 2085064574 + size = 5525 isKeyFrame = false presentationTimeUs = 567000 sample: trackIndex = 0 dataHashCode = -637074022 + size = 1082 isKeyFrame = false presentationTimeUs = 500000 sample: trackIndex = 0 dataHashCode = -1824027029 + size = 807 isKeyFrame = false presentationTimeUs = 467000 sample: trackIndex = 0 dataHashCode = -1701945306 + size = 744 isKeyFrame = false presentationTimeUs = 533000 sample: trackIndex = 0 dataHashCode = -952425536 + size = 4732 isKeyFrame = false presentationTimeUs = 700000 sample: trackIndex = 0 dataHashCode = -1978031576 + size = 1004 isKeyFrame = false presentationTimeUs = 633000 sample: trackIndex = 0 dataHashCode = -2128215508 + size = 794 isKeyFrame = false presentationTimeUs = 600000 sample: trackIndex = 0 dataHashCode = -259850011 + size = 645 isKeyFrame = false presentationTimeUs = 667000 sample: trackIndex = 0 dataHashCode = 1920983928 + size = 2684 isKeyFrame = false presentationTimeUs = 833000 sample: trackIndex = 0 dataHashCode = 1100642337 + size = 787 isKeyFrame = false presentationTimeUs = 767000 sample: trackIndex = 0 dataHashCode = 1544917830 + size = 649 isKeyFrame = false presentationTimeUs = 733000 sample: trackIndex = 0 dataHashCode = -116205995 + size = 509 isKeyFrame = false presentationTimeUs = 800000 sample: trackIndex = 0 dataHashCode = 696343585 + size = 1226 isKeyFrame = false presentationTimeUs = 967000 sample: trackIndex = 0 dataHashCode = -644371190 + size = 898 isKeyFrame = false presentationTimeUs = 900000 sample: trackIndex = 0 dataHashCode = -1606273467 + size = 476 isKeyFrame = false presentationTimeUs = 867000 sample: trackIndex = 0 dataHashCode = -571265861 + size = 486 isKeyFrame = false presentationTimeUs = 933000 released = true diff --git a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump index 3d74318819..dd820ba6ab 100644 --- a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump +++ b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump @@ -18,376 +18,451 @@ format 1: sample: trackIndex = 1 dataHashCode = -770308242 + size = 36692 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 1 dataHashCode = -732087136 + size = 5312 isKeyFrame = false presentationTimeUs = 66733 sample: trackIndex = 1 dataHashCode = 468156717 + size = 599 isKeyFrame = false presentationTimeUs = 33366 sample: trackIndex = 1 dataHashCode = 1150349584 + size = 7735 isKeyFrame = false presentationTimeUs = 200200 sample: trackIndex = 1 dataHashCode = 1443582006 + size = 987 isKeyFrame = false presentationTimeUs = 133466 sample: trackIndex = 1 dataHashCode = -310585145 + size = 673 isKeyFrame = false presentationTimeUs = 100100 sample: trackIndex = 1 dataHashCode = 807460688 + size = 523 isKeyFrame = false presentationTimeUs = 166833 sample: trackIndex = 1 dataHashCode = 1936487090 + size = 6061 isKeyFrame = false presentationTimeUs = 333666 sample: trackIndex = 1 dataHashCode = -32297181 + size = 992 isKeyFrame = false presentationTimeUs = 266933 sample: trackIndex = 1 dataHashCode = 1529616406 + size = 623 isKeyFrame = false presentationTimeUs = 233566 sample: trackIndex = 1 dataHashCode = 1949198785 + size = 421 isKeyFrame = false presentationTimeUs = 300300 sample: trackIndex = 1 dataHashCode = -147880287 + size = 4899 isKeyFrame = false presentationTimeUs = 433766 sample: trackIndex = 1 dataHashCode = 1369083472 + size = 568 isKeyFrame = false presentationTimeUs = 400400 sample: trackIndex = 1 dataHashCode = 965782073 + size = 620 isKeyFrame = false presentationTimeUs = 367033 sample: trackIndex = 1 dataHashCode = -261176150 + size = 5450 isKeyFrame = false presentationTimeUs = 567233 sample: trackIndex = 0 dataHashCode = 1205768497 + size = 23 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 0 dataHashCode = 837571078 + size = 6 isKeyFrame = true presentationTimeUs = 249 sample: trackIndex = 0 dataHashCode = -1991633045 + size = 148 isKeyFrame = true presentationTimeUs = 317 sample: trackIndex = 0 dataHashCode = -822987359 + size = 189 isKeyFrame = true presentationTimeUs = 1995 sample: trackIndex = 0 dataHashCode = -1141508176 + size = 205 isKeyFrame = true presentationTimeUs = 4126 sample: trackIndex = 0 dataHashCode = -226971245 + size = 210 isKeyFrame = true presentationTimeUs = 6438 sample: trackIndex = 0 dataHashCode = -2099636855 + size = 210 isKeyFrame = true presentationTimeUs = 8818 sample: trackIndex = 0 dataHashCode = 1541550559 + size = 207 isKeyFrame = true presentationTimeUs = 11198 sample: trackIndex = 0 dataHashCode = 411148001 + size = 225 isKeyFrame = true presentationTimeUs = 13533 sample: trackIndex = 0 dataHashCode = -897603973 + size = 215 isKeyFrame = true presentationTimeUs = 16072 sample: trackIndex = 0 dataHashCode = 1478106136 + size = 211 isKeyFrame = true presentationTimeUs = 18498 sample: trackIndex = 0 dataHashCode = -1380417145 + size = 216 isKeyFrame = true presentationTimeUs = 20878 sample: trackIndex = 0 dataHashCode = 780903644 + size = 229 isKeyFrame = true presentationTimeUs = 23326 sample: trackIndex = 0 dataHashCode = 586204432 + size = 232 isKeyFrame = true presentationTimeUs = 25911 sample: trackIndex = 0 dataHashCode = -2038771492 + size = 235 isKeyFrame = true presentationTimeUs = 28541 sample: trackIndex = 0 dataHashCode = -2065161304 + size = 231 isKeyFrame = true presentationTimeUs = 31194 sample: trackIndex = 0 dataHashCode = 468662933 + size = 226 isKeyFrame = true presentationTimeUs = 33801 sample: trackIndex = 0 dataHashCode = -358398546 + size = 216 isKeyFrame = true presentationTimeUs = 36363 sample: trackIndex = 0 dataHashCode = 1767325983 + size = 229 isKeyFrame = true presentationTimeUs = 38811 sample: trackIndex = 0 dataHashCode = 1093095458 + size = 219 isKeyFrame = true presentationTimeUs = 41396 sample: trackIndex = 0 dataHashCode = 1687543702 + size = 241 isKeyFrame = true presentationTimeUs = 43867 sample: trackIndex = 0 dataHashCode = 1675188486 + size = 228 isKeyFrame = true presentationTimeUs = 46588 sample: trackIndex = 0 dataHashCode = 888567545 + size = 238 isKeyFrame = true presentationTimeUs = 49173 sample: trackIndex = 0 dataHashCode = -439631803 + size = 234 isKeyFrame = true presentationTimeUs = 51871 sample: trackIndex = 0 dataHashCode = 1606694497 + size = 231 isKeyFrame = true presentationTimeUs = 54524 sample: trackIndex = 0 dataHashCode = 1747388653 + size = 217 isKeyFrame = true presentationTimeUs = 57131 sample: trackIndex = 0 dataHashCode = -734560004 + size = 239 isKeyFrame = true presentationTimeUs = 59579 sample: trackIndex = 0 dataHashCode = -975079040 + size = 243 isKeyFrame = true presentationTimeUs = 62277 sample: trackIndex = 0 dataHashCode = -1403504710 + size = 231 isKeyFrame = true presentationTimeUs = 65020 sample: trackIndex = 0 dataHashCode = 379512981 + size = 230 isKeyFrame = true presentationTimeUs = 67627 sample: trackIndex = 1 dataHashCode = -1830836678 + size = 1051 isKeyFrame = false presentationTimeUs = 500500 sample: trackIndex = 1 dataHashCode = 1767407540 + size = 874 isKeyFrame = false presentationTimeUs = 467133 sample: trackIndex = 1 dataHashCode = 918440283 + size = 781 isKeyFrame = false presentationTimeUs = 533866 sample: trackIndex = 1 dataHashCode = -1408463661 + size = 4725 isKeyFrame = false presentationTimeUs = 700700 sample: trackIndex = 0 dataHashCode = -997198863 + size = 238 isKeyFrame = true presentationTimeUs = 70234 sample: trackIndex = 0 dataHashCode = 1394492825 + size = 225 isKeyFrame = true presentationTimeUs = 72932 sample: trackIndex = 0 dataHashCode = -885232755 + size = 232 isKeyFrame = true presentationTimeUs = 75471 sample: trackIndex = 0 dataHashCode = 260871367 + size = 243 isKeyFrame = true presentationTimeUs = 78101 sample: trackIndex = 0 dataHashCode = -1505318960 + size = 232 isKeyFrame = true presentationTimeUs = 80844 sample: trackIndex = 0 dataHashCode = -390625371 + size = 237 isKeyFrame = true presentationTimeUs = 83474 sample: trackIndex = 0 dataHashCode = 1067950751 + size = 228 isKeyFrame = true presentationTimeUs = 86149 sample: trackIndex = 0 dataHashCode = -1179436278 + size = 235 isKeyFrame = true presentationTimeUs = 88734 sample: trackIndex = 0 dataHashCode = 1906607774 + size = 264 isKeyFrame = true presentationTimeUs = 91387 sample: trackIndex = 0 dataHashCode = -800475828 + size = 257 isKeyFrame = true presentationTimeUs = 94380 sample: trackIndex = 0 dataHashCode = 1718972977 + size = 227 isKeyFrame = true presentationTimeUs = 97282 sample: trackIndex = 0 dataHashCode = -1120448741 + size = 227 isKeyFrame = true presentationTimeUs = 99844 sample: trackIndex = 0 dataHashCode = -1718323210 + size = 235 isKeyFrame = true presentationTimeUs = 102406 sample: trackIndex = 0 dataHashCode = -422416 + size = 229 isKeyFrame = true presentationTimeUs = 105059 sample: trackIndex = 0 dataHashCode = 833757830 + size = 6 isKeyFrame = true presentationTimeUs = 107644 sample: trackIndex = 1 dataHashCode = 1569455924 + size = 1022 isKeyFrame = false presentationTimeUs = 633966 sample: trackIndex = 1 dataHashCode = -1723778407 + size = 790 isKeyFrame = false presentationTimeUs = 600600 sample: trackIndex = 1 dataHashCode = 1578275472 + size = 610 isKeyFrame = false presentationTimeUs = 667333 sample: trackIndex = 1 dataHashCode = 1989768395 + size = 2751 isKeyFrame = false presentationTimeUs = 834166 sample: trackIndex = 1 dataHashCode = -1215674502 + size = 745 isKeyFrame = false presentationTimeUs = 767433 sample: trackIndex = 1 dataHashCode = -814473606 + size = 621 isKeyFrame = false presentationTimeUs = 734066 sample: trackIndex = 1 dataHashCode = 498370894 + size = 505 isKeyFrame = false presentationTimeUs = 800800 sample: trackIndex = 1 dataHashCode = -1051506468 + size = 1268 isKeyFrame = false presentationTimeUs = 967633 sample: trackIndex = 1 dataHashCode = -1025604144 + size = 880 isKeyFrame = false presentationTimeUs = 900900 sample: trackIndex = 1 dataHashCode = -913586520 + size = 530 isKeyFrame = false presentationTimeUs = 867533 sample: trackIndex = 1 dataHashCode = 1340459242 + size = 568 isKeyFrame = false presentationTimeUs = 934266 released = true diff --git a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.noaudio.dump b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.noaudio.dump index d4484cbfb4..f18acde209 100644 --- a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.noaudio.dump +++ b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.noaudio.dump @@ -13,151 +13,181 @@ format 0: sample: trackIndex = 0 dataHashCode = -770308242 + size = 36692 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 0 dataHashCode = -732087136 + size = 5312 isKeyFrame = false presentationTimeUs = 66733 sample: trackIndex = 0 dataHashCode = 468156717 + size = 599 isKeyFrame = false presentationTimeUs = 33366 sample: trackIndex = 0 dataHashCode = 1150349584 + size = 7735 isKeyFrame = false presentationTimeUs = 200200 sample: trackIndex = 0 dataHashCode = 1443582006 + size = 987 isKeyFrame = false presentationTimeUs = 133466 sample: trackIndex = 0 dataHashCode = -310585145 + size = 673 isKeyFrame = false presentationTimeUs = 100100 sample: trackIndex = 0 dataHashCode = 807460688 + size = 523 isKeyFrame = false presentationTimeUs = 166833 sample: trackIndex = 0 dataHashCode = 1936487090 + size = 6061 isKeyFrame = false presentationTimeUs = 333666 sample: trackIndex = 0 dataHashCode = -32297181 + size = 992 isKeyFrame = false presentationTimeUs = 266933 sample: trackIndex = 0 dataHashCode = 1529616406 + size = 623 isKeyFrame = false presentationTimeUs = 233566 sample: trackIndex = 0 dataHashCode = 1949198785 + size = 421 isKeyFrame = false presentationTimeUs = 300300 sample: trackIndex = 0 dataHashCode = -147880287 + size = 4899 isKeyFrame = false presentationTimeUs = 433766 sample: trackIndex = 0 dataHashCode = 1369083472 + size = 568 isKeyFrame = false presentationTimeUs = 400400 sample: trackIndex = 0 dataHashCode = 965782073 + size = 620 isKeyFrame = false presentationTimeUs = 367033 sample: trackIndex = 0 dataHashCode = -261176150 + size = 5450 isKeyFrame = false presentationTimeUs = 567233 sample: trackIndex = 0 dataHashCode = -1830836678 + size = 1051 isKeyFrame = false presentationTimeUs = 500500 sample: trackIndex = 0 dataHashCode = 1767407540 + size = 874 isKeyFrame = false presentationTimeUs = 467133 sample: trackIndex = 0 dataHashCode = 918440283 + size = 781 isKeyFrame = false presentationTimeUs = 533866 sample: trackIndex = 0 dataHashCode = -1408463661 + size = 4725 isKeyFrame = false presentationTimeUs = 700700 sample: trackIndex = 0 dataHashCode = 1569455924 + size = 1022 isKeyFrame = false presentationTimeUs = 633966 sample: trackIndex = 0 dataHashCode = -1723778407 + size = 790 isKeyFrame = false presentationTimeUs = 600600 sample: trackIndex = 0 dataHashCode = 1578275472 + size = 610 isKeyFrame = false presentationTimeUs = 667333 sample: trackIndex = 0 dataHashCode = 1989768395 + size = 2751 isKeyFrame = false presentationTimeUs = 834166 sample: trackIndex = 0 dataHashCode = -1215674502 + size = 745 isKeyFrame = false presentationTimeUs = 767433 sample: trackIndex = 0 dataHashCode = -814473606 + size = 621 isKeyFrame = false presentationTimeUs = 734066 sample: trackIndex = 0 dataHashCode = 498370894 + size = 505 isKeyFrame = false presentationTimeUs = 800800 sample: trackIndex = 0 dataHashCode = -1051506468 + size = 1268 isKeyFrame = false presentationTimeUs = 967633 sample: trackIndex = 0 dataHashCode = -1025604144 + size = 880 isKeyFrame = false presentationTimeUs = 900900 sample: trackIndex = 0 dataHashCode = -913586520 + size = 530 isKeyFrame = false presentationTimeUs = 867533 sample: trackIndex = 0 dataHashCode = 1340459242 + size = 568 isKeyFrame = false presentationTimeUs = 934266 released = true diff --git a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump index 2e520ebb02..e94ff8bb7f 100644 --- a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump +++ b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump @@ -7,226 +7,271 @@ format 0: sample: trackIndex = 0 dataHashCode = 1205768497 + size = 23 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 0 dataHashCode = 837571078 + size = 6 isKeyFrame = true presentationTimeUs = 249 sample: trackIndex = 0 dataHashCode = -1991633045 + size = 148 isKeyFrame = true presentationTimeUs = 317 sample: trackIndex = 0 dataHashCode = -822987359 + size = 189 isKeyFrame = true presentationTimeUs = 1995 sample: trackIndex = 0 dataHashCode = -1141508176 + size = 205 isKeyFrame = true presentationTimeUs = 4126 sample: trackIndex = 0 dataHashCode = -226971245 + size = 210 isKeyFrame = true presentationTimeUs = 6438 sample: trackIndex = 0 dataHashCode = -2099636855 + size = 210 isKeyFrame = true presentationTimeUs = 8818 sample: trackIndex = 0 dataHashCode = 1541550559 + size = 207 isKeyFrame = true presentationTimeUs = 11198 sample: trackIndex = 0 dataHashCode = 411148001 + size = 225 isKeyFrame = true presentationTimeUs = 13533 sample: trackIndex = 0 dataHashCode = -897603973 + size = 215 isKeyFrame = true presentationTimeUs = 16072 sample: trackIndex = 0 dataHashCode = 1478106136 + size = 211 isKeyFrame = true presentationTimeUs = 18498 sample: trackIndex = 0 dataHashCode = -1380417145 + size = 216 isKeyFrame = true presentationTimeUs = 20878 sample: trackIndex = 0 dataHashCode = 780903644 + size = 229 isKeyFrame = true presentationTimeUs = 23326 sample: trackIndex = 0 dataHashCode = 586204432 + size = 232 isKeyFrame = true presentationTimeUs = 25911 sample: trackIndex = 0 dataHashCode = -2038771492 + size = 235 isKeyFrame = true presentationTimeUs = 28541 sample: trackIndex = 0 dataHashCode = -2065161304 + size = 231 isKeyFrame = true presentationTimeUs = 31194 sample: trackIndex = 0 dataHashCode = 468662933 + size = 226 isKeyFrame = true presentationTimeUs = 33801 sample: trackIndex = 0 dataHashCode = -358398546 + size = 216 isKeyFrame = true presentationTimeUs = 36363 sample: trackIndex = 0 dataHashCode = 1767325983 + size = 229 isKeyFrame = true presentationTimeUs = 38811 sample: trackIndex = 0 dataHashCode = 1093095458 + size = 219 isKeyFrame = true presentationTimeUs = 41396 sample: trackIndex = 0 dataHashCode = 1687543702 + size = 241 isKeyFrame = true presentationTimeUs = 43867 sample: trackIndex = 0 dataHashCode = 1675188486 + size = 228 isKeyFrame = true presentationTimeUs = 46588 sample: trackIndex = 0 dataHashCode = 888567545 + size = 238 isKeyFrame = true presentationTimeUs = 49173 sample: trackIndex = 0 dataHashCode = -439631803 + size = 234 isKeyFrame = true presentationTimeUs = 51871 sample: trackIndex = 0 dataHashCode = 1606694497 + size = 231 isKeyFrame = true presentationTimeUs = 54524 sample: trackIndex = 0 dataHashCode = 1747388653 + size = 217 isKeyFrame = true presentationTimeUs = 57131 sample: trackIndex = 0 dataHashCode = -734560004 + size = 239 isKeyFrame = true presentationTimeUs = 59579 sample: trackIndex = 0 dataHashCode = -975079040 + size = 243 isKeyFrame = true presentationTimeUs = 62277 sample: trackIndex = 0 dataHashCode = -1403504710 + size = 231 isKeyFrame = true presentationTimeUs = 65020 sample: trackIndex = 0 dataHashCode = 379512981 + size = 230 isKeyFrame = true presentationTimeUs = 67627 sample: trackIndex = 0 dataHashCode = -997198863 + size = 238 isKeyFrame = true presentationTimeUs = 70234 sample: trackIndex = 0 dataHashCode = 1394492825 + size = 225 isKeyFrame = true presentationTimeUs = 72932 sample: trackIndex = 0 dataHashCode = -885232755 + size = 232 isKeyFrame = true presentationTimeUs = 75471 sample: trackIndex = 0 dataHashCode = 260871367 + size = 243 isKeyFrame = true presentationTimeUs = 78101 sample: trackIndex = 0 dataHashCode = -1505318960 + size = 232 isKeyFrame = true presentationTimeUs = 80844 sample: trackIndex = 0 dataHashCode = -390625371 + size = 237 isKeyFrame = true presentationTimeUs = 83474 sample: trackIndex = 0 dataHashCode = 1067950751 + size = 228 isKeyFrame = true presentationTimeUs = 86149 sample: trackIndex = 0 dataHashCode = -1179436278 + size = 235 isKeyFrame = true presentationTimeUs = 88734 sample: trackIndex = 0 dataHashCode = 1906607774 + size = 264 isKeyFrame = true presentationTimeUs = 91387 sample: trackIndex = 0 dataHashCode = -800475828 + size = 257 isKeyFrame = true presentationTimeUs = 94380 sample: trackIndex = 0 dataHashCode = 1718972977 + size = 227 isKeyFrame = true presentationTimeUs = 97282 sample: trackIndex = 0 dataHashCode = -1120448741 + size = 227 isKeyFrame = true presentationTimeUs = 99844 sample: trackIndex = 0 dataHashCode = -1718323210 + size = 235 isKeyFrame = true presentationTimeUs = 102406 sample: trackIndex = 0 dataHashCode = -422416 + size = 229 isKeyFrame = true presentationTimeUs = 105059 sample: trackIndex = 0 dataHashCode = 833757830 + size = 6 isKeyFrame = true presentationTimeUs = 107644 released = true diff --git a/testdata/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump b/testdata/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump index 89f996e530..6115358157 100644 --- a/testdata/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump +++ b/testdata/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump @@ -19,206 +19,247 @@ format 1: sample: trackIndex = 1 dataHashCode = -549003117 + size = 5438 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 1 dataHashCode = 593600631 + size = 117 isKeyFrame = false presentationTimeUs = 14000 sample: trackIndex = 1 dataHashCode = -961321612 + size = 139 isKeyFrame = false presentationTimeUs = 47333 sample: trackIndex = 1 dataHashCode = -386347143 + size = 141 isKeyFrame = false presentationTimeUs = 80667 sample: trackIndex = 1 dataHashCode = -1289764147 + size = 141 isKeyFrame = false presentationTimeUs = 114000 sample: trackIndex = 1 dataHashCode = 1337088875 + size = 161 isKeyFrame = false presentationTimeUs = 147333 sample: trackIndex = 1 dataHashCode = -322406979 + size = 118 isKeyFrame = false presentationTimeUs = 180667 sample: trackIndex = 1 dataHashCode = -1688033783 + size = 112 isKeyFrame = false presentationTimeUs = 228042 sample: trackIndex = 1 dataHashCode = -700344608 + size = 118 isKeyFrame = false presentationTimeUs = 244708 sample: trackIndex = 1 dataHashCode = -1441653629 + size = 1172 isKeyFrame = false presentationTimeUs = 334083 sample: trackIndex = 1 dataHashCode = 1201357091 + size = 208 isKeyFrame = false presentationTimeUs = 267416 sample: trackIndex = 1 dataHashCode = -668484307 + size = 111 isKeyFrame = false presentationTimeUs = 234083 sample: trackIndex = 1 dataHashCode = 653508165 + size = 137 isKeyFrame = false presentationTimeUs = 300750 sample: trackIndex = 1 dataHashCode = -816848987 + size = 1266 isKeyFrame = false presentationTimeUs = 467416 sample: trackIndex = 1 dataHashCode = 1842436292 + size = 182 isKeyFrame = false presentationTimeUs = 400750 sample: trackIndex = 1 dataHashCode = -559603233 + size = 99 isKeyFrame = false presentationTimeUs = 367416 sample: trackIndex = 1 dataHashCode = -666437886 + size = 117 isKeyFrame = false presentationTimeUs = 434083 sample: trackIndex = 1 dataHashCode = 182521759 + size = 1101 isKeyFrame = false presentationTimeUs = 600750 sample: trackIndex = 0 dataHashCode = -212376212 + size = 20 isKeyFrame = true presentationTimeUs = 0 sample: trackIndex = 0 dataHashCode = -833872563 + size = 1732 isKeyFrame = true presentationTimeUs = 416 sample: trackIndex = 0 dataHashCode = -135901925 + size = 380 isKeyFrame = true presentationTimeUs = 36499 sample: trackIndex = 0 dataHashCode = 250093960 + size = 751 isKeyFrame = true presentationTimeUs = 44415 sample: trackIndex = 0 dataHashCode = 1895536226 + size = 1045 isKeyFrame = true presentationTimeUs = 59998 sample: trackIndex = 0 dataHashCode = 1723596464 + size = 947 isKeyFrame = true presentationTimeUs = 81748 sample: trackIndex = 0 dataHashCode = -978803114 + size = 946 isKeyFrame = true presentationTimeUs = 101414 sample: trackIndex = 0 dataHashCode = 387377078 + size = 946 isKeyFrame = true presentationTimeUs = 121080 sample: trackIndex = 0 dataHashCode = -132658698 + size = 901 isKeyFrame = true presentationTimeUs = 140746 sample: trackIndex = 0 dataHashCode = 1495036471 + size = 899 isKeyFrame = true presentationTimeUs = 159496 sample: trackIndex = 0 dataHashCode = 304440590 + size = 878 isKeyFrame = true presentationTimeUs = 178162 sample: trackIndex = 1 dataHashCode = 2139021989 + size = 242 isKeyFrame = false presentationTimeUs = 534083 sample: trackIndex = 1 dataHashCode = 2013165108 + size = 116 isKeyFrame = false presentationTimeUs = 500750 sample: trackIndex = 1 dataHashCode = 405675195 + size = 126 isKeyFrame = false presentationTimeUs = 567416 sample: trackIndex = 1 dataHashCode = -1893277090 + size = 1193 isKeyFrame = false presentationTimeUs = 734083 sample: trackIndex = 0 dataHashCode = -752661703 + size = 228 isKeyFrame = true presentationTimeUs = 196412 sample: trackIndex = 1 dataHashCode = -1554795381 + size = 205 isKeyFrame = false presentationTimeUs = 667416 sample: trackIndex = 1 dataHashCode = 1197099206 + size = 117 isKeyFrame = false presentationTimeUs = 634083 sample: trackIndex = 1 dataHashCode = -674808173 + size = 106 isKeyFrame = false presentationTimeUs = 700750 sample: trackIndex = 1 dataHashCode = -775517313 + size = 1002 isKeyFrame = false presentationTimeUs = 867416 sample: trackIndex = 1 dataHashCode = -2045106113 + size = 201 isKeyFrame = false presentationTimeUs = 800750 sample: trackIndex = 1 dataHashCode = 305167697 + size = 131 isKeyFrame = false presentationTimeUs = 767416 sample: trackIndex = 1 dataHashCode = 554021920 + size = 130 isKeyFrame = false presentationTimeUs = 834083 released = true From 468e4aa0c4ac7e16d95a56a3c10480a37b273dd9 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Nov 2021 18:06:31 +0000 Subject: [PATCH 112/113] Update Javadoc for 2.16.0 PiperOrigin-RevId: 407379522 --- docs/doc/reference/allclasses-index.html | 2524 ++++---- docs/doc/reference/allclasses.html | 85 +- docs/doc/reference/allpackages-index.html | 18 +- .../AbstractConcatenatedTimeline.html | 26 +- .../google/android/exoplayer2/BasePlayer.html | 591 +- .../android/exoplayer2/BaseRenderer.html | 37 +- .../google/android/exoplayer2/Bundleable.html | 2 +- .../C.AudioAllowedCapturePolicy.html | 1 + .../exoplayer2/C.AudioContentType.html | 1 + .../android/exoplayer2/C.AudioFlags.html | 1 + ...ry.html => C.AudioManagerOffloadMode.html} | 102 +- .../android/exoplayer2/C.AudioUsage.html | 1 + .../android/exoplayer2/C.CryptoType.html | 187 + .../android/exoplayer2/C.RoleFlags.html | 1 + .../android/exoplayer2/C.SelectionFlags.html | 1 + .../android/exoplayer2/C.SelectionReason.html | 186 + .../android/exoplayer2/C.StreamType.html | 2 +- .../android/exoplayer2/C.TrackType.html | 187 + ...ml => C.VideoChangeFrameRateStrategy.html} | 13 +- .../exoplayer2/C.VideoScalingMode.html | 3 +- .../google/android/exoplayer2/C.WakeMode.html | 1 + .../com/google/android/exoplayer2/C.html | 779 +-- .../android/exoplayer2/ControlDispatcher.html | 576 -- .../exoplayer2/DefaultControlDispatcher.html | 746 --- .../exoplayer2/DefaultLoadControl.html | 39 +- .../exoplayer2/DefaultRenderersFactory.html | 50 +- .../{device => }/DeviceInfo.PlaybackType.html | 42 +- .../exoplayer2/{device => }/DeviceInfo.html | 82 +- .../exoplayer2/ExoPlaybackException.html | 22 +- .../exoplayer2/ExoPlayer.AudioComponent.html | 232 +- .../android/exoplayer2/ExoPlayer.Builder.html | 574 +- .../exoplayer2/ExoPlayer.DeviceComponent.html | 163 +- .../ExoPlayer.MetadataComponent.html | 295 - .../exoplayer2/ExoPlayer.TextComponent.html | 71 +- .../exoplayer2/ExoPlayer.VideoComponent.html | 337 +- .../google/android/exoplayer2/ExoPlayer.html | 880 ++- .../exoplayer2/ExoPlayerLibraryInfo.html | 62 +- .../android/exoplayer2/Format.Builder.html | 41 +- .../com/google/android/exoplayer2/Format.html | 166 +- .../android/exoplayer2/ForwardingPlayer.html | 1046 +-- ...> MediaItem.AdsConfiguration.Builder.html} | 168 +- .../MediaItem.AdsConfiguration.html | 56 +- .../android/exoplayer2/MediaItem.Builder.html | 597 +- ...diaItem.ClippingConfiguration.Builder.html | 434 ++ .../MediaItem.ClippingConfiguration.html | 521 ++ .../MediaItem.ClippingProperties.html | 227 +- .../MediaItem.DrmConfiguration.Builder.html | 503 ++ .../MediaItem.DrmConfiguration.html | 144 +- .../MediaItem.LiveConfiguration.Builder.html | 414 ++ .../MediaItem.LiveConfiguration.html | 68 +- .../MediaItem.LocalConfiguration.html | 490 ++ .../MediaItem.PlaybackProperties.html | 267 +- .../exoplayer2/MediaItem.Subtitle.html | 317 +- ...diaItem.SubtitleConfiguration.Builder.html | 423 ++ .../MediaItem.SubtitleConfiguration.html | 471 ++ .../google/android/exoplayer2/MediaItem.html | 103 +- .../exoplayer2/MediaMetadata.Builder.html | 106 +- .../exoplayer2/MediaMetadata.FolderType.html | 1 + .../exoplayer2/MediaMetadata.PictureType.html | 1 + .../android/exoplayer2/MediaMetadata.html | 8 +- .../android/exoplayer2/NoSampleRenderer.html | 13 +- .../PlaybackException.ErrorCode.html | 1 + .../android/exoplayer2/PlaybackException.html | 30 +- .../exoplayer2/PlaybackParameters.html | 9 +- .../android/exoplayer2/Player.Command.html | 3 +- .../exoplayer2/Player.Commands.Builder.html | 36 +- .../android/exoplayer2/Player.Commands.html | 10 +- .../Player.DiscontinuityReason.html | 1 + .../android/exoplayer2/Player.Event.html | 1 + .../exoplayer2/Player.EventListener.html | 188 +- .../android/exoplayer2/Player.Events.html | 16 +- .../android/exoplayer2/Player.Listener.html | 194 +- .../Player.MediaItemTransitionReason.html | 1 + .../Player.PlayWhenReadyChangeReason.html | 1 + .../Player.PlaybackSuppressionReason.html | 1 + .../exoplayer2/Player.PositionInfo.html | 102 +- .../android/exoplayer2/Player.RepeatMode.html | 1 + .../android/exoplayer2/Player.State.html | 1 + .../Player.TimelineChangeReason.html | 1 + .../com/google/android/exoplayer2/Player.html | 1471 +++-- .../exoplayer2/PlayerMessage.Target.html | 3 +- .../android/exoplayer2/PlayerMessage.html | 72 +- .../exoplayer2/Renderer.MessageType.html | 186 + .../google/android/exoplayer2/Renderer.html | 140 +- .../exoplayer2/RendererCapabilities.html | 6 +- .../android/exoplayer2/RenderersFactory.html | 6 +- .../exoplayer2/SimpleExoPlayer.Builder.html | 660 +- .../android/exoplayer2/SimpleExoPlayer.html | 1522 +++-- .../Timeline.RemotableTimeline.html | 26 +- .../google/android/exoplayer2/Timeline.html | 137 +- .../exoplayer2/TracksInfo.TrackGroupInfo.html | 584 ++ .../google/android/exoplayer2/TracksInfo.html | 502 ++ .../analytics/AnalyticsCollector.html | 212 +- .../AnalyticsListener.EventTime.html | 6 +- .../analytics/AnalyticsListener.html | 220 +- .../DefaultPlaybackSessionManager.html | 14 +- .../analytics/PlaybackSessionManager.html | 10 +- .../analytics/PlaybackStatsListener.html | 30 +- .../android/exoplayer2/audio/AacUtil.html | 25 +- .../android/exoplayer2/audio/Ac3Util.html | 30 +- .../audio/AudioAttributes.Builder.html | 24 +- .../exoplayer2/audio/AudioAttributes.html | 16 +- .../exoplayer2/audio/AudioListener.html | 337 - .../exoplayer2/audio/AudioProcessor.html | 2 +- .../audio/DecoderAudioRenderer.html | 29 +- .../audio/MediaCodecAudioRenderer.html | 15 +- .../android/exoplayer2/audio/OpusUtil.html | 52 +- .../exoplayer2/audio/TeeAudioProcessor.html | 4 +- .../exoplayer2/audio/package-summary.html | 18 +- .../exoplayer2/audio/package-tree.html | 1 - .../exoplayer2/database/DatabaseProvider.html | 8 +- .../database/ExoDatabaseProvider.html | 176 +- .../database/StandaloneDatabaseProvider.html | 446 ++ .../exoplayer2/database/VersionTable.html | 4 +- .../exoplayer2/database/package-summary.html | 14 +- .../exoplayer2/database/package-tree.html | 6 +- .../android/exoplayer2/decoder/Buffer.html | 2 +- .../CryptoConfig.html} | 15 +- .../CryptoException.html} | 24 +- .../exoplayer2/decoder/CryptoInfo.html | 4 +- .../android/exoplayer2/decoder/Decoder.html | 2 +- .../decoder/DecoderInputBuffer.html | 24 +- ...er.html => DecoderOutputBuffer.Owner.html} | 16 +- ...utBuffer.html => DecoderOutputBuffer.html} | 20 +- .../exoplayer2/decoder/SimpleDecoder.html | 20 +- ...er.html => SimpleDecoderOutputBuffer.html} | 38 +- .../VideoDecoderOutputBuffer.html | 48 +- .../exoplayer2/decoder/package-summary.html | 42 +- .../exoplayer2/decoder/package-tree.html | 9 +- .../drm/DefaultDrmSessionManager.Builder.html | 8 +- .../drm/DefaultDrmSessionManager.html | 30 +- .../drm/DefaultDrmSessionManagerProvider.html | 4 +- .../drm/DrmSession.DrmSessionException.html | 12 +- .../android/exoplayer2/drm/DrmSession.html | 48 +- .../exoplayer2/drm/DrmSessionManager.html | 36 +- .../android/exoplayer2/drm/DrmUtil.html | 8 +- .../exoplayer2/drm/DummyExoMediaDrm.html | 80 +- .../exoplayer2/drm/ErrorStateDrmSession.html | 57 +- .../android/exoplayer2/drm/ExoMediaDrm.html | 73 +- ...Crypto.html => FrameworkCryptoConfig.html} | 32 +- .../exoplayer2/drm/FrameworkMediaDrm.html | 84 +- .../exoplayer2/drm/package-summary.html | 41 +- .../android/exoplayer2/drm/package-tree.html | 5 +- .../exoplayer2/ext/av1/Gav1Decoder.html | 50 +- .../ext/av1/Libgav1VideoRenderer.html | 36 +- .../exoplayer2/ext/cast/CastPlayer.html | 386 +- .../ext/cronet/CronetDataSource.Factory.html | 48 +- .../CronetDataSource.OpenException.html | 46 +- .../ext/cronet/CronetDataSourceFactory.html | 2 +- .../ext/cronet/CronetEngineWrapper.html | 4 +- .../exoplayer2/ext/cronet/CronetUtil.html | 39 +- .../ext/ffmpeg/FfmpegAudioRenderer.html | 22 +- .../exoplayer2/ext/flac/FlacDecoder.html | 30 +- .../ext/flac/LibflacAudioRenderer.html | 22 +- .../exoplayer2/ext/gvr/GvrAudioProcessor.html | 593 -- .../exoplayer2/ext/gvr/package-tree.html | 159 - .../ext/ima/ImaAdsLoader.Builder.html | 9 +- .../exoplayer2/ext/ima/ImaAdsLoader.html | 61 +- .../ext/leanback/LeanbackPlayerAdapter.html | 37 +- .../ext/media2/SessionPlayerConnector.html | 46 +- ...MediaSessionConnector.CaptionCallback.html | 2 +- ...MediaSessionConnector.CommandReceiver.html | 11 +- ...SessionConnector.CustomActionProvider.html | 11 +- ...sionConnector.MediaButtonEventHandler.html | 11 +- ...ediaSessionConnector.PlaybackPreparer.html | 2 +- .../MediaSessionConnector.QueueEditor.html | 2 +- .../MediaSessionConnector.QueueNavigator.html | 59 +- .../MediaSessionConnector.RatingCallback.html | 2 +- .../mediasession/MediaSessionConnector.html | 60 +- .../RepeatModeActionProvider.html | 15 +- .../ext/mediasession/TimelineQueueEditor.html | 15 +- .../mediasession/TimelineQueueNavigator.html | 80 +- .../ext/okhttp/OkHttpDataSource.Factory.html | 40 +- .../ext/okhttp/OkHttpDataSourceFactory.html | 2 +- .../ext/opus/LibopusAudioRenderer.html | 22 +- .../exoplayer2/ext/opus/OpusDecoder.html | 42 +- .../exoplayer2/ext/opus/OpusLibrary.html | 39 +- .../exoplayer2/ext/rtmp/RtmpDataSource.html | 4 +- .../ext/vp9/LibvpxVideoRenderer.html | 36 +- .../exoplayer2/ext/vp9/VpxDecoder.html | 62 +- .../exoplayer2/ext/vp9/VpxLibrary.html | 35 +- .../WorkManagerScheduler.SchedulerWorker.html | 2 +- .../extractor/ConstantBitrateSeekMap.html | 40 +- .../extractor/DefaultExtractorsFactory.html | 55 +- .../extractor/DummyExtractorOutput.html | 11 +- .../exoplayer2/extractor/Extractor.html | 2 +- .../exoplayer2/extractor/ExtractorOutput.html | 15 +- .../extractor/FlacMetadataReader.html | 2 +- .../extractor/TrueHdSampleRechunker.html | 365 ++ .../extractor/amr/AmrExtractor.Flags.html | 2 +- .../extractor/amr/AmrExtractor.html | 29 +- .../flac}/FlacConstants.html | 60 +- .../extractor/flac/package-summary.html | 6 + .../extractor/flac/package-tree.html | 1 + .../jpeg/StartOffsetExtractorOutput.html | 11 +- .../extractor/mp3/Mp3Extractor.Flags.html | 3 +- .../extractor/mp3/Mp3Extractor.html | 29 + .../exoplayer2/extractor/mp4/Track.html | 12 +- .../exoplayer2/extractor/package-summary.html | 14 +- .../exoplayer2/extractor/package-tree.html | 1 + .../extractor/ts/AdtsExtractor.Flags.html | 2 +- .../extractor/ts/AdtsExtractor.html | 29 +- .../exoplayer2/extractor/ts/TsExtractor.html | 42 +- .../extractor/wav/WavExtractor.html | 3 +- .../DefaultMediaCodecAdapterFactory.html | 411 ++ .../MediaCodecAdapter.Configuration.html | 239 +- .../mediacodec/MediaCodecAdapter.Factory.html | 2 +- .../mediacodec/MediaCodecAdapter.html | 71 +- .../mediacodec/MediaCodecRenderer.html | 189 +- .../SynchronousMediaCodecAdapter.html | 75 +- .../mediacodec/package-summary.html | 16 +- .../exoplayer2/mediacodec/package-tree.html | 1 + .../exoplayer2/metadata/Metadata.Entry.html | 2 +- .../metadata/MetadataInputBuffer.html | 2 +- .../exoplayer2/metadata/MetadataOutput.html | 8 - .../exoplayer2/metadata/MetadataRenderer.html | 6 +- .../android/exoplayer2/offline/Download.html | 10 +- .../exoplayer2/offline/DownloadManager.html | 3 +- .../exoplayer2/offline/DownloadRequest.html | 2 +- .../exoplayer2/offline/DownloadService.html | 134 +- .../android/exoplayer2/package-summary.html | 207 +- .../android/exoplayer2/package-tree.html | 64 +- .../robolectric/PlaybackOutput.html | 8 +- .../robolectric/TestPlayerRunHelper.html | 64 +- .../robolectric/package-summary.html | 4 +- ...ormScheduler.PlatformSchedulerService.html | 4 +- .../source/ClippingMediaPeriod.html | 25 +- ...tMediaSourceFactory.AdsLoaderProvider.html | 8 +- .../source/DefaultMediaSourceFactory.html | 102 +- .../exoplayer2/source/ForwardingTimeline.html | 26 +- .../exoplayer2/source/LoopingMediaSource.html | 4 +- ...askingMediaSource.PlaceholderTimeline.html | 2 +- .../exoplayer2/source/MediaLoadData.html | 26 +- .../exoplayer2/source/MediaSource.html | 8 +- ...iaSourceEventListener.EventDispatcher.html | 43 +- .../exoplayer2/source/MediaSourceFactory.html | 63 +- .../ProgressiveMediaSource.Factory.html | 25 +- .../source/SilenceMediaSource.Factory.html | 9 +- .../source/SinglePeriodTimeline.html | 4 +- .../SingleSampleMediaSource.Factory.html | 66 +- .../android/exoplayer2/source/TrackGroup.html | 81 +- .../exoplayer2/source/TrackGroupArray.html | 85 +- .../source/ads/AdPlaybackState.AdGroup.html | 17 +- .../source/ads/AdPlaybackState.html | 51 +- .../source/ads/SinglePeriodAdTimeline.html | 4 +- .../source/chunk/BaseMediaChunk.html | 1 + .../source/chunk/BaseMediaChunkOutput.html | 15 +- .../source/chunk/BundledChunkExtractor.html | 21 +- .../exoplayer2/source/chunk/Chunk.html | 11 +- .../source/chunk/ChunkExtractor.Factory.html | 8 +- .../ChunkExtractor.TrackOutputProvider.html | 11 +- .../source/chunk/ChunkSampleStream.html | 15 +- .../source/chunk/ContainerMediaChunk.html | 1 + .../exoplayer2/source/chunk/DataChunk.html | 1 + .../source/chunk/InitializationChunk.html | 1 + .../exoplayer2/source/chunk/MediaChunk.html | 1 + .../chunk/MediaParserChunkExtractor.html | 9 +- .../source/chunk/SingleSampleMediaChunk.html | 12 +- .../source/dash/DashChunkSource.Factory.html | 12 +- .../source/dash/DashMediaSource.Factory.html | 31 +- .../exoplayer2/source/dash/DashUtil.html | 8 +- .../dash/DefaultDashChunkSource.Factory.html | 16 +- .../source/dash/DefaultDashChunkSource.html | 21 +- .../source/dash/manifest/AdaptationSet.html | 20 +- .../dash/manifest/DashManifestParser.html | 46 +- .../source/dash/offline/DashDownloader.html | 2 +- .../exoplayer2/source/hls/HlsMediaPeriod.html | 9 +- .../source/hls/HlsMediaSource.Factory.html | 32 +- .../hls/HlsMediaSource.MetadataType.html | 3 +- .../source/hls/offline/HlsDownloader.html | 2 +- .../mediaparser/OutputConsumerAdapterV30.html | 15 +- .../exoplayer2/source/package-summary.html | 4 +- .../exoplayer2/source/package-tree.html | 4 +- .../source/rtsp/RtspMediaSource.Factory.html | 73 +- .../SsMediaSource.Factory.html | 25 +- .../manifest/SsManifest.StreamElement.html | 12 +- .../smoothstreaming/offline/SsDownloader.html | 2 +- .../testutil/Action.AddMediaItems.html | 18 +- .../testutil/Action.ClearMediaItems.html | 18 +- .../testutil/Action.ClearVideoSurface.html | 18 +- .../testutil/Action.ExecuteRunnable.html | 16 +- .../testutil/Action.MoveMediaItem.html | 18 +- .../testutil/Action.PlayUntilPosition.html | 38 +- .../exoplayer2/testutil/Action.Prepare.html | 16 +- .../testutil/Action.RemoveMediaItem.html | 18 +- .../testutil/Action.RemoveMediaItems.html | 18 +- .../exoplayer2/testutil/Action.Seek.html | 22 +- .../testutil/Action.SendMessages.html | 22 +- .../testutil/Action.SetAudioAttributes.html | 18 +- .../testutil/Action.SetMediaItems.html | 24 +- .../Action.SetMediaItemsResetPosition.html | 18 +- .../testutil/Action.SetPlayWhenReady.html | 16 +- .../Action.SetPlaybackParameters.html | 16 +- .../testutil/Action.SetRendererDisabled.html | 16 +- .../testutil/Action.SetRepeatMode.html | 26 +- .../Action.SetShuffleModeEnabled.html | 16 +- .../testutil/Action.SetShuffleOrder.html | 16 +- .../testutil/Action.SetVideoSurface.html | 18 +- .../exoplayer2/testutil/Action.Stop.html | 16 +- .../Action.ThrowPlaybackException.html | 16 +- .../testutil/Action.WaitForIsLoading.html | 30 +- .../testutil/Action.WaitForMessage.html | 30 +- .../Action.WaitForPendingPlayerCommands.html | 30 +- .../testutil/Action.WaitForPlayWhenReady.html | 32 +- .../testutil/Action.WaitForPlaybackState.html | 40 +- .../Action.WaitForPositionDiscontinuity.html | 32 +- .../Action.WaitForTimelineChanged.html | 40 +- .../android/exoplayer2/testutil/Action.html | 56 +- .../testutil/ActionSchedule.Builder.html | 88 +- .../ActionSchedule.PlayerRunnable.html | 6 +- .../testutil/ActionSchedule.PlayerTarget.html | 9 +- .../testutil/AdditionalFailureInfo.html | 4 +- .../testutil/AssetContentProvider.html | 535 ++ .../testutil/CapturingRenderersFactory.html | 4 +- .../DefaultRenderersFactoryAsserts.html | 8 +- .../exoplayer2/testutil/ExoHostedTest.html | 26 +- .../testutil/ExoPlayerTestRunner.Builder.html | 17 +- .../testutil/ExoPlayerTestRunner.html | 231 +- .../testutil/FakeAdaptiveMediaSource.html | 2 +- .../testutil/FakeAudioRenderer.html | 12 +- .../FakeCryptoConfig.html} | 78 +- .../testutil/FakeExoMediaDrm.Builder.html | 46 +- .../FakeExoMediaDrm.LicenseServer.html | 31 +- .../exoplayer2/testutil/FakeExoMediaDrm.html | 91 +- .../testutil/FakeExtractorOutput.html | 11 +- .../exoplayer2/testutil/FakeMediaChunk.html | 3 +- .../testutil/FakeMediaClockRenderer.html | 10 +- .../FakeMediaSource.InitialTimeline.html | 4 +- .../exoplayer2/testutil/FakeMediaSource.html | 49 +- .../testutil/FakeMediaSourceFactory.html | 570 ++ .../testutil/FakeMetadataEntry.html | 456 ++ .../exoplayer2/testutil/FakeRenderer.html | 111 +- .../exoplayer2/testutil/FakeTimeline.html | 26 +- .../testutil/FakeTrackSelection.html | 7 + .../testutil/FakeTrackSelector.html | 2 +- .../testutil/FakeVideoRenderer.html | 15 +- .../exoplayer2/testutil/HostActivity.html | 6 +- .../exoplayer2/testutil/NoUidTimeline.html | 4 +- .../exoplayer2/testutil/StubExoPlayer.html | 2360 ++----- .../exoplayer2/testutil/StubPlayer.html | 1958 ++++++ .../testutil/TestExoPlayerBuilder.html | 77 +- .../android/exoplayer2/testutil/TestUtil.html | 66 +- .../exoplayer2/testutil/TimelineAsserts.html | 34 +- .../exoplayer2/testutil/package-summary.html | 137 +- .../exoplayer2/testutil/package-tree.html | 12 + .../exoplayer2/text/Cue.AnchorType.html | 1 + .../android/exoplayer2/text/Cue.Builder.html | 78 +- .../android/exoplayer2/text/Cue.LineType.html | 1 + .../exoplayer2/text/Cue.TextSizeType.html | 1 + .../exoplayer2/text/Cue.VerticalType.html | 1 + .../google/android/exoplayer2/text/Cue.html | 78 +- .../CueDecoder.html} | 133 +- .../android/exoplayer2/text/CueEncoder.html | 312 + .../exoplayer2/text/ExoplayerCuesDecoder.html | 472 ++ .../exoplayer2/text/SubtitleDecoder.html | 2 +- .../text/SubtitleDecoderFactory.html | 1 + .../exoplayer2/text/SubtitleExtractor.html | 493 ++ .../exoplayer2/text/SubtitleInputBuffer.html | 2 +- .../exoplayer2/text/SubtitleOutputBuffer.html | 22 +- .../android/exoplayer2/text/TextOutput.html | 8 - .../android/exoplayer2/text/TextRenderer.html | 6 +- .../exoplayer2/text/package-summary.html | 24 + .../android/exoplayer2/text/package-tree.html | 6 +- .../AdaptiveTrackSelection.Factory.html | 115 +- .../AdaptiveTrackSelection.html | 67 +- .../trackselection/BaseTrackSelection.html | 15 +- .../DefaultTrackSelector.Parameters.html | 106 +- ...efaultTrackSelector.ParametersBuilder.html | 222 +- ...efaultTrackSelector.SelectionOverride.html | 92 +- .../trackselection/DefaultTrackSelector.html | 158 +- .../ExoTrackSelection.Definition.html | 12 +- .../trackselection/ExoTrackSelection.html | 7 + .../trackselection/FixedTrackSelection.html | 23 +- .../MappingTrackSelector.MappedTrackInfo.html | 14 +- .../trackselection/MappingTrackSelector.html | 2 +- .../trackselection/RandomTrackSelection.html | 7 + .../TrackSelection.Type.html} | 101 +- .../trackselection/TrackSelection.html | 33 +- .../TrackSelectionOverrides.Builder.html | 381 ++ ...ctionOverrides.TrackSelectionOverride.html | 488 ++ .../TrackSelectionOverrides.html | 484 ++ .../TrackSelectionParameters.Builder.html | 156 +- .../TrackSelectionParameters.html | 188 +- .../trackselection/TrackSelectionUtil.html | 21 +- .../trackselection/TrackSelector.html | 67 +- .../trackselection/TrackSelectorResult.html | 58 +- .../trackselection/package-summary.html | 43 +- .../trackselection/package-tree.html | 15 +- .../TranscodingTransformer.Builder.html | 620 ++ .../TranscodingTransformer.Listener.html} | 71 +- ...TranscodingTransformer.ProgressState.html} | 76 +- .../transformer/TranscodingTransformer.html | 616 ++ .../exoplayer2/transformer/Transformer.html | 14 +- .../transformer/package-summary.html | 24 + .../exoplayer2/transformer/package-tree.html | 4 + .../exoplayer2/ui/AspectRatioFrameLayout.html | 6 +- .../android/exoplayer2/ui/DefaultTimeBar.html | 4 +- .../ui/DownloadNotificationHelper.html | 47 +- .../exoplayer2/ui/PlayerControlView.html | 68 +- .../ui/PlayerNotificationManager.Builder.html | 1 + .../ui/PlayerNotificationManager.html | 74 +- .../android/exoplayer2/ui/PlayerView.html | 104 +- .../ui/StyledPlayerControlView.html | 74 +- .../exoplayer2/ui/StyledPlayerView.html | 108 +- .../android/exoplayer2/ui/SubtitleView.html | 36 +- .../exoplayer2/ui/TrackSelectionView.html | 6 +- .../android/exoplayer2/ui/package-tree.html | 2 +- ...etDataSource.AssetDataSourceException.html | 12 +- .../{cache => }/CachedRegionTracker.html | 126 +- ...DataSource.ContentDataSourceException.html | 12 +- .../exoplayer2/upstream/DataSink.Factory.html | 2 +- .../upstream/DataSource.Factory.html | 2 +- .../upstream/DataSourceException.html | 34 +- .../exoplayer2/upstream/DataSourceUtil.html | 331 + .../upstream/DefaultBandwidthMeter.html | 34 +- .../upstream/DefaultDataSource.Factory.html | 379 ++ .../upstream/DefaultDataSource.html | 22 +- .../upstream/DefaultDataSourceFactory.html | 25 +- .../DefaultHttpDataSource.Factory.html | 46 +- .../DefaultHttpDataSourceFactory.html | 474 -- .../DefaultLoadErrorHandlingPolicy.html | 6 +- ...ileDataSource.FileDataSourceException.html | 24 +- .../upstream/HttpDataSource.BaseFactory.html | 34 +- .../upstream/HttpDataSource.Factory.html | 30 +- ...ttpDataSource.HttpDataSourceException.html | 88 +- ...y.html => PriorityDataSource.Factory.html} | 73 +- .../upstream/PriorityDataSource.html | 22 +- .../upstream/PriorityDataSourceFactory.html | 20 +- ...Source.RawResourceDataSourceException.html | 16 +- .../{util => upstream}/SlidingPercentile.html | 4 +- .../UdpDataSource.UdpDataSourceException.html | 8 +- .../upstream/cache/Cache.Listener.html | 2 +- .../cache/CacheDataSourceFactory.html | 430 -- .../upstream/cache/package-summary.html | 33 +- .../upstream/cache/package-tree.html | 3 - .../upstream/crypto/AesFlushingCipher.html | 20 + .../exoplayer2/upstream/package-summary.html | 81 +- .../exoplayer2/upstream/package-tree.html | 12 +- ...ndleableUtils.html => BundleableUtil.html} | 123 +- .../util/CodecSpecificDataUtil.html | 25 +- .../exoplayer2/util/DebugTextViewHelper.html | 63 +- .../android/exoplayer2/util/EventLogger.html | 80 +- .../exoplayer2/util/GlUtil.Attribute.html | 23 +- .../exoplayer2/util/GlUtil.GlException.html | 302 + ...dOutputStream.html => GlUtil.Program.html} | 271 +- .../exoplayer2/util/GlUtil.Uniform.html | 22 +- ...GlUtil.UnsupportedEglVersionException.html | 294 + .../android/exoplayer2/util/GlUtil.html | 316 +- .../exoplayer2/util/ListenerSet.Event.html | 2 +- .../ListenerSet.IterationFinishedEvent.html | 2 +- .../android/exoplayer2/util/ListenerSet.html | 4 +- .../exoplayer2/util/MediaFormatUtil.html | 28 +- .../android/exoplayer2/util/MimeTypes.html | 176 +- .../util/NalUnitUtil.H265SpsData.html | 455 ++ .../exoplayer2/util/NalUnitUtil.SpsData.html | 14 +- .../android/exoplayer2/util/NalUnitUtil.html | 145 +- .../exoplayer2/util/RepeatModeUtil.html | 18 +- .../google/android/exoplayer2/util/Util.html | 455 +- .../exoplayer2/util/package-summary.html | 75 +- .../android/exoplayer2/util/package-tree.html | 22 +- .../android/exoplayer2/video/AvcConfig.html | 42 +- .../android/exoplayer2/video/ColorInfo.html | 71 +- .../video/DecoderVideoRenderer.html | 65 +- .../exoplayer2/video/DummySurface.html | 4 +- .../android/exoplayer2/video/HevcConfig.html | 78 +- .../video/MediaCodecVideoRenderer.html | 73 +- .../video/VideoDecoderGLSurfaceView.html | 20 +- .../video/VideoDecoderInputBuffer.html | 396 -- .../VideoDecoderOutputBufferRenderer.html | 8 +- .../video/VideoFrameReleaseHelper.html | 20 +- .../exoplayer2/video/VideoListener.html | 347 - .../exoplayer2/video/package-summary.html | 22 +- .../exoplayer2/video/package-tree.html | 17 +- .../video/spherical/CameraMotionRenderer.html | 9 +- .../spherical/SphericalGLSurfaceView.html | 6 +- docs/doc/reference/constant-values.html | 1269 ++-- docs/doc/reference/deprecated-list.html | 1429 +++-- docs/doc/reference/element-list | 2 - docs/doc/reference/index-all.html | 5614 +++++++++++------ docs/doc/reference/index.html | 156 +- docs/doc/reference/member-search-index.js | 2 +- docs/doc/reference/member-search-index.zip | Bin 136486 -> 140976 bytes docs/doc/reference/overview-tree.html | 166 +- docs/doc/reference/package-search-index.js | 2 +- docs/doc/reference/package-search-index.zip | Bin 706 -> 697 bytes docs/doc/reference/serialized-form.html | 50 +- docs/doc/reference/type-search-index.js | 2 +- docs/doc/reference/type-search-index.zip | Bin 10091 -> 10295 bytes 488 files changed, 37457 insertions(+), 21429 deletions(-) rename docs/doc/reference/com/google/android/exoplayer2/{ext/gvr/package-summary.html => C.AudioManagerOffloadMode.html} (51%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/C.CryptoType.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/C.SelectionReason.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/C.TrackType.html rename docs/doc/reference/com/google/android/exoplayer2/{Renderer.VideoScalingMode.html => C.VideoChangeFrameRateStrategy.html} (89%) delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/ControlDispatcher.html delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/DefaultControlDispatcher.html rename docs/doc/reference/com/google/android/exoplayer2/{device => }/DeviceInfo.PlaybackType.html (75%) rename docs/doc/reference/com/google/android/exoplayer2/{device => }/DeviceInfo.html (80%) delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.MetadataComponent.html rename docs/doc/reference/com/google/android/exoplayer2/{upstream/cache/CacheDataSinkFactory.html => MediaItem.AdsConfiguration.Builder.html} (60%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.Builder.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/MediaItem.LiveConfiguration.Builder.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/MediaItem.LocalConfiguration.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.Builder.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/Renderer.MessageType.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/TracksInfo.TrackGroupInfo.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/TracksInfo.html delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/audio/AudioListener.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/database/StandaloneDatabaseProvider.html rename docs/doc/reference/com/google/android/exoplayer2/{drm/ExoMediaCrypto.html => decoder/CryptoConfig.html} (88%) rename docs/doc/reference/com/google/android/exoplayer2/{drm/DecryptionException.html => decoder/CryptoException.html} (93%) rename docs/doc/reference/com/google/android/exoplayer2/decoder/{OutputBuffer.Owner.html => DecoderOutputBuffer.Owner.html} (89%) rename docs/doc/reference/com/google/android/exoplayer2/decoder/{OutputBuffer.html => DecoderOutputBuffer.html} (92%) rename docs/doc/reference/com/google/android/exoplayer2/decoder/{SimpleOutputBuffer.html => SimpleDecoderOutputBuffer.html} (86%) rename docs/doc/reference/com/google/android/exoplayer2/{video => decoder}/VideoDecoderOutputBuffer.html (87%) rename docs/doc/reference/com/google/android/exoplayer2/drm/{FrameworkMediaCrypto.html => FrameworkCryptoConfig.html} (88%) delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.html delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/ext/gvr/package-tree.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/extractor/TrueHdSampleRechunker.html rename docs/doc/reference/com/google/android/exoplayer2/{util => extractor/flac}/FlacConstants.html (82%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/mediacodec/DefaultMediaCodecAdapterFactory.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/testutil/AssetContentProvider.html rename docs/doc/reference/com/google/android/exoplayer2/{drm/UnsupportedMediaCrypto.html => testutil/FakeCryptoConfig.html} (80%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaSourceFactory.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMetadataEntry.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/testutil/StubPlayer.html rename docs/doc/reference/com/google/android/exoplayer2/{util/IntArrayQueue.html => text/CueDecoder.html} (72%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/text/CueEncoder.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/text/ExoplayerCuesDecoder.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/text/SubtitleExtractor.html rename docs/doc/reference/com/google/android/exoplayer2/{device/package-summary.html => trackselection/TrackSelection.Type.html} (66%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.Builder.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.TrackSelectionOverride.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/transformer/TranscodingTransformer.Builder.html rename docs/doc/reference/com/google/android/exoplayer2/{device/DeviceListener.html => transformer/TranscodingTransformer.Listener.html} (69%) rename docs/doc/reference/com/google/android/exoplayer2/{device/package-tree.html => transformer/TranscodingTransformer.ProgressState.html} (66%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/transformer/TranscodingTransformer.html rename docs/doc/reference/com/google/android/exoplayer2/upstream/{cache => }/CachedRegionTracker.html (65%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/upstream/DataSourceUtil.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/upstream/DefaultDataSource.Factory.html delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.html rename docs/doc/reference/com/google/android/exoplayer2/upstream/{FileDataSourceFactory.html => PriorityDataSource.Factory.html} (78%) rename docs/doc/reference/com/google/android/exoplayer2/{util => upstream}/SlidingPercentile.html (99%) delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.html rename docs/doc/reference/com/google/android/exoplayer2/util/{BundleableUtils.html => BundleableUtil.html} (58%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/util/GlUtil.GlException.html rename docs/doc/reference/com/google/android/exoplayer2/util/{ReusableBufferedOutputStream.html => GlUtil.Program.html} (52%) create mode 100644 docs/doc/reference/com/google/android/exoplayer2/util/GlUtil.UnsupportedEglVersionException.html create mode 100644 docs/doc/reference/com/google/android/exoplayer2/util/NalUnitUtil.H265SpsData.html delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/video/VideoDecoderInputBuffer.html delete mode 100644 docs/doc/reference/com/google/android/exoplayer2/video/VideoListener.html diff --git a/docs/doc/reference/allclasses-index.html b/docs/doc/reference/allclasses-index.html index 0b597a9f2a..f99c81d555 100644 --- a/docs/doc/reference/allclasses-index.html +++ b/docs/doc/reference/allclasses-index.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":2,"i1":32,"i2":2,"i3":2,"i4":2,"i5":2,"i6":2,"i7":2,"i8":32,"i9":2,"i10":2,"i11":2,"i12":2,"i13":2,"i14":2,"i15":2,"i16":2,"i17":2,"i18":2,"i19":2,"i20":2,"i21":2,"i22":2,"i23":2,"i24":2,"i25":2,"i26":2,"i27":2,"i28":2,"i29":2,"i30":2,"i31":2,"i32":2,"i33":2,"i34":2,"i35":2,"i36":2,"i37":2,"i38":2,"i39":2,"i40":2,"i41":2,"i42":2,"i43":2,"i44":2,"i45":1,"i46":2,"i47":2,"i48":1,"i49":2,"i50":2,"i51":1,"i52":2,"i53":2,"i54":2,"i55":2,"i56":2,"i57":2,"i58":32,"i59":2,"i60":2,"i61":32,"i62":1,"i63":1,"i64":2,"i65":8,"i66":32,"i67":2,"i68":32,"i69":2,"i70":1,"i71":2,"i72":2,"i73":2,"i74":2,"i75":1,"i76":2,"i77":32,"i78":2,"i79":1,"i80":32,"i81":2,"i82":2,"i83":2,"i84":2,"i85":2,"i86":2,"i87":1,"i88":32,"i89":2,"i90":2,"i91":8,"i92":2,"i93":2,"i94":2,"i95":2,"i96":2,"i97":1,"i98":1,"i99":1,"i100":2,"i101":8,"i102":1,"i103":2,"i104":1,"i105":8,"i106":8,"i107":1,"i108":32,"i109":8,"i110":8,"i111":2,"i112":2,"i113":1,"i114":1,"i115":2,"i116":2,"i117":2,"i118":2,"i119":2,"i120":2,"i121":2,"i122":2,"i123":2,"i124":2,"i125":2,"i126":2,"i127":8,"i128":2,"i129":2,"i130":2,"i131":2,"i132":2,"i133":1,"i134":2,"i135":1,"i136":2,"i137":1,"i138":1,"i139":2,"i140":2,"i141":2,"i142":2,"i143":2,"i144":2,"i145":2,"i146":2,"i147":2,"i148":32,"i149":32,"i150":32,"i151":32,"i152":32,"i153":32,"i154":32,"i155":32,"i156":32,"i157":32,"i158":32,"i159":32,"i160":32,"i161":32,"i162":32,"i163":32,"i164":32,"i165":32,"i166":32,"i167":32,"i168":32,"i169":32,"i170":32,"i171":32,"i172":1,"i173":8,"i174":1,"i175":2,"i176":2,"i177":2,"i178":8,"i179":2,"i180":2,"i181":2,"i182":32,"i183":1,"i184":2,"i185":32,"i186":2,"i187":2,"i188":1,"i189":1,"i190":2,"i191":2,"i192":1,"i193":1,"i194":2,"i195":2,"i196":32,"i197":2,"i198":2,"i199":2,"i200":2,"i201":2,"i202":2,"i203":2,"i204":2,"i205":2,"i206":1,"i207":1,"i208":1,"i209":2,"i210":2,"i211":2,"i212":1,"i213":1,"i214":2,"i215":2,"i216":8,"i217":32,"i218":1,"i219":2,"i220":2,"i221":2,"i222":2,"i223":2,"i224":2,"i225":1,"i226":2,"i227":2,"i228":2,"i229":1,"i230":2,"i231":2,"i232":8,"i233":1,"i234":2,"i235":1,"i236":2,"i237":2,"i238":2,"i239":8,"i240":2,"i241":2,"i242":2,"i243":2,"i244":2,"i245":32,"i246":2,"i247":32,"i248":32,"i249":32,"i250":1,"i251":1,"i252":2,"i253":2,"i254":2,"i255":2,"i256":8,"i257":2,"i258":2,"i259":1,"i260":2,"i261":2,"i262":8,"i263":1,"i264":2,"i265":1,"i266":2,"i267":1,"i268":1,"i269":1,"i270":1,"i271":2,"i272":2,"i273":2,"i274":2,"i275":8,"i276":2,"i277":2,"i278":2,"i279":32,"i280":32,"i281":2,"i282":1,"i283":2,"i284":2,"i285":2,"i286":8,"i287":2,"i288":32,"i289":8,"i290":2,"i291":32,"i292":32,"i293":2,"i294":8,"i295":2,"i296":2,"i297":1,"i298":2,"i299":8,"i300":32,"i301":2,"i302":2,"i303":2,"i304":2,"i305":2,"i306":2,"i307":2,"i308":2,"i309":2,"i310":2,"i311":2,"i312":2,"i313":2,"i314":2,"i315":2,"i316":2,"i317":2,"i318":8,"i319":32,"i320":2,"i321":2,"i322":2,"i323":2,"i324":2,"i325":2,"i326":2,"i327":2,"i328":2,"i329":2,"i330":2,"i331":2,"i332":2,"i333":2,"i334":2,"i335":2,"i336":2,"i337":2,"i338":2,"i339":1,"i340":2,"i341":2,"i342":32,"i343":2,"i344":2,"i345":2,"i346":2,"i347":2,"i348":2,"i349":2,"i350":2,"i351":2,"i352":2,"i353":2,"i354":2,"i355":2,"i356":2,"i357":2,"i358":32,"i359":2,"i360":2,"i361":32,"i362":1,"i363":2,"i364":2,"i365":32,"i366":32,"i367":2,"i368":1,"i369":1,"i370":1,"i371":1,"i372":8,"i373":2,"i374":1,"i375":8,"i376":1,"i377":2,"i378":1,"i379":2,"i380":2,"i381":2,"i382":2,"i383":8,"i384":2,"i385":2,"i386":2,"i387":1,"i388":8,"i389":32,"i390":1,"i391":2,"i392":1,"i393":1,"i394":1,"i395":2,"i396":32,"i397":2,"i398":2,"i399":2,"i400":2,"i401":2,"i402":2,"i403":1,"i404":2,"i405":2,"i406":2,"i407":2,"i408":1,"i409":2,"i410":2,"i411":2,"i412":1,"i413":32,"i414":2,"i415":8,"i416":32,"i417":1,"i418":1,"i419":2,"i420":1,"i421":2,"i422":2,"i423":2,"i424":2,"i425":2,"i426":2,"i427":2,"i428":2,"i429":1,"i430":1,"i431":2,"i432":2,"i433":32,"i434":2,"i435":1,"i436":1,"i437":1,"i438":1,"i439":2,"i440":8,"i441":32,"i442":1,"i443":1,"i444":1,"i445":2,"i446":1,"i447":1,"i448":1,"i449":1,"i450":2,"i451":2,"i452":2,"i453":8,"i454":32,"i455":1,"i456":2,"i457":1,"i458":1,"i459":32,"i460":2,"i461":2,"i462":2,"i463":1,"i464":2,"i465":1,"i466":1,"i467":1,"i468":2,"i469":2,"i470":2,"i471":2,"i472":2,"i473":2,"i474":2,"i475":2,"i476":2,"i477":2,"i478":2,"i479":2,"i480":2,"i481":2,"i482":2,"i483":2,"i484":2,"i485":2,"i486":2,"i487":2,"i488":2,"i489":2,"i490":8,"i491":2,"i492":2,"i493":2,"i494":2,"i495":2,"i496":1,"i497":2,"i498":2,"i499":2,"i500":2,"i501":2,"i502":2,"i503":2,"i504":2,"i505":2,"i506":1,"i507":2,"i508":2,"i509":2,"i510":2,"i511":8,"i512":2,"i513":2,"i514":2,"i515":8,"i516":2,"i517":2,"i518":32,"i519":1,"i520":2,"i521":2,"i522":2,"i523":2,"i524":2,"i525":8,"i526":2,"i527":2,"i528":32,"i529":32,"i530":2,"i531":2,"i532":2,"i533":2,"i534":2,"i535":2,"i536":2,"i537":2,"i538":2,"i539":2,"i540":2,"i541":2,"i542":2,"i543":2,"i544":2,"i545":2,"i546":2,"i547":2,"i548":2,"i549":32,"i550":2,"i551":2,"i552":2,"i553":2,"i554":8,"i555":2,"i556":2,"i557":2,"i558":2,"i559":2,"i560":2,"i561":2,"i562":2,"i563":2,"i564":2,"i565":1,"i566":1,"i567":2,"i568":2,"i569":1,"i570":2,"i571":1,"i572":2,"i573":2,"i574":2,"i575":2,"i576":1,"i577":2,"i578":2,"i579":2,"i580":32,"i581":2,"i582":2,"i583":2,"i584":2,"i585":2,"i586":2,"i587":32,"i588":2,"i589":2,"i590":8,"i591":1,"i592":1,"i593":1,"i594":1,"i595":8,"i596":8,"i597":1,"i598":2,"i599":2,"i600":2,"i601":2,"i602":1,"i603":1,"i604":2,"i605":8,"i606":1,"i607":8,"i608":32,"i609":8,"i610":8,"i611":2,"i612":2,"i613":2,"i614":2,"i615":2,"i616":2,"i617":2,"i618":2,"i619":1,"i620":2,"i621":2,"i622":2,"i623":8,"i624":2,"i625":2,"i626":2,"i627":2,"i628":2,"i629":2,"i630":2,"i631":2,"i632":8,"i633":1,"i634":2,"i635":2,"i636":2,"i637":2,"i638":2,"i639":2,"i640":2,"i641":2,"i642":2,"i643":1,"i644":1,"i645":1,"i646":1,"i647":2,"i648":1,"i649":1,"i650":2,"i651":1,"i652":8,"i653":1,"i654":2,"i655":1,"i656":2,"i657":2,"i658":32,"i659":2,"i660":2,"i661":2,"i662":2,"i663":2,"i664":2,"i665":2,"i666":2,"i667":2,"i668":1,"i669":2,"i670":2,"i671":2,"i672":32,"i673":2,"i674":2,"i675":1,"i676":1,"i677":1,"i678":2,"i679":1,"i680":1,"i681":2,"i682":8,"i683":2,"i684":2,"i685":8,"i686":1,"i687":2,"i688":8,"i689":8,"i690":2,"i691":2,"i692":1,"i693":8,"i694":2,"i695":2,"i696":2,"i697":2,"i698":2,"i699":2,"i700":2,"i701":2,"i702":2,"i703":1,"i704":1,"i705":2,"i706":2,"i707":2,"i708":32,"i709":32,"i710":2,"i711":2,"i712":2,"i713":2,"i714":1,"i715":1,"i716":2,"i717":1,"i718":2,"i719":2,"i720":1,"i721":1,"i722":1,"i723":2,"i724":1,"i725":1,"i726":32,"i727":1,"i728":1,"i729":1,"i730":1,"i731":1,"i732":2,"i733":1,"i734":1,"i735":2,"i736":1,"i737":2,"i738":2,"i739":8,"i740":32,"i741":2,"i742":1,"i743":1,"i744":1,"i745":2,"i746":1,"i747":2,"i748":2,"i749":2,"i750":2,"i751":2,"i752":2,"i753":32,"i754":2,"i755":32,"i756":2,"i757":2,"i758":2,"i759":2,"i760":2,"i761":2,"i762":2,"i763":2,"i764":2,"i765":1,"i766":32,"i767":2,"i768":2,"i769":2,"i770":32,"i771":2,"i772":2,"i773":2,"i774":2,"i775":2,"i776":2,"i777":2,"i778":8,"i779":2,"i780":2,"i781":2,"i782":1,"i783":2,"i784":2,"i785":2,"i786":2,"i787":8,"i788":2,"i789":1,"i790":2,"i791":2,"i792":2,"i793":2,"i794":2,"i795":2,"i796":2,"i797":2,"i798":8,"i799":32,"i800":32,"i801":2,"i802":2,"i803":1,"i804":1,"i805":2,"i806":2,"i807":2,"i808":2,"i809":2,"i810":1,"i811":1,"i812":32,"i813":2,"i814":2,"i815":32,"i816":32,"i817":1,"i818":2,"i819":1,"i820":32,"i821":32,"i822":32,"i823":2,"i824":32,"i825":32,"i826":32,"i827":2,"i828":1,"i829":1,"i830":2,"i831":1,"i832":2,"i833":1,"i834":1,"i835":2,"i836":2,"i837":1,"i838":1,"i839":1,"i840":32,"i841":32,"i842":2,"i843":32,"i844":2,"i845":2,"i846":2,"i847":2,"i848":8,"i849":2,"i850":2,"i851":2,"i852":2,"i853":2,"i854":1,"i855":1,"i856":2,"i857":2,"i858":2,"i859":2,"i860":2,"i861":2,"i862":2,"i863":2,"i864":2,"i865":2,"i866":2,"i867":8,"i868":1,"i869":32,"i870":32,"i871":1,"i872":1,"i873":32,"i874":32,"i875":32,"i876":32,"i877":2,"i878":1,"i879":2,"i880":2,"i881":32,"i882":2,"i883":2,"i884":2,"i885":2,"i886":32,"i887":2,"i888":1,"i889":2,"i890":2,"i891":1,"i892":2,"i893":2,"i894":2,"i895":2,"i896":2,"i897":2,"i898":2,"i899":2,"i900":2,"i901":1,"i902":1,"i903":2,"i904":2,"i905":2,"i906":8,"i907":2,"i908":2,"i909":2,"i910":1,"i911":8,"i912":1,"i913":32,"i914":32,"i915":1,"i916":1,"i917":2,"i918":1,"i919":2,"i920":2,"i921":2,"i922":2,"i923":2,"i924":2,"i925":2,"i926":2,"i927":2,"i928":2,"i929":2,"i930":2,"i931":2,"i932":1,"i933":1,"i934":2,"i935":2,"i936":2,"i937":1,"i938":2,"i939":1,"i940":1,"i941":2,"i942":1,"i943":2,"i944":1,"i945":1,"i946":1,"i947":1,"i948":2,"i949":2,"i950":1,"i951":2,"i952":2,"i953":2,"i954":2,"i955":2,"i956":2,"i957":2,"i958":2,"i959":2,"i960":2,"i961":2,"i962":2,"i963":2,"i964":2,"i965":2,"i966":2,"i967":2,"i968":2,"i969":2,"i970":2,"i971":2,"i972":2,"i973":1,"i974":2,"i975":2,"i976":1,"i977":1,"i978":1,"i979":1,"i980":1,"i981":1,"i982":1,"i983":1,"i984":1,"i985":2,"i986":2,"i987":1,"i988":2,"i989":2,"i990":2,"i991":2,"i992":2,"i993":2,"i994":2,"i995":2,"i996":2,"i997":1,"i998":1,"i999":2,"i1000":2,"i1001":2,"i1002":2,"i1003":2,"i1004":8,"i1005":2,"i1006":2,"i1007":2,"i1008":2,"i1009":2,"i1010":2,"i1011":2,"i1012":2,"i1013":2,"i1014":1,"i1015":1,"i1016":1,"i1017":2,"i1018":32,"i1019":2,"i1020":1,"i1021":1,"i1022":8,"i1023":1,"i1024":2,"i1025":2,"i1026":2,"i1027":32,"i1028":2,"i1029":2,"i1030":2,"i1031":2,"i1032":1,"i1033":2,"i1034":2,"i1035":2,"i1036":2,"i1037":2,"i1038":2,"i1039":2,"i1040":32,"i1041":2,"i1042":32,"i1043":32,"i1044":2,"i1045":1,"i1046":2,"i1047":2,"i1048":1,"i1049":1,"i1050":2,"i1051":2,"i1052":2,"i1053":2,"i1054":2,"i1055":2,"i1056":2,"i1057":1,"i1058":2,"i1059":1,"i1060":2,"i1061":2,"i1062":2,"i1063":2,"i1064":1,"i1065":2,"i1066":2,"i1067":32,"i1068":2,"i1069":2,"i1070":2,"i1071":1,"i1072":1,"i1073":2,"i1074":32,"i1075":1,"i1076":2,"i1077":2,"i1078":1,"i1079":2,"i1080":2,"i1081":2,"i1082":1,"i1083":2,"i1084":1,"i1085":2,"i1086":1,"i1087":2,"i1088":1,"i1089":2,"i1090":2,"i1091":1,"i1092":32,"i1093":2,"i1094":32,"i1095":1,"i1096":2,"i1097":2,"i1098":1,"i1099":32,"i1100":2,"i1101":2,"i1102":2,"i1103":2,"i1104":2,"i1105":8,"i1106":32,"i1107":8,"i1108":8,"i1109":32,"i1110":2,"i1111":2,"i1112":2,"i1113":2,"i1114":2,"i1115":2,"i1116":2,"i1117":2,"i1118":2,"i1119":2,"i1120":1,"i1121":1,"i1122":2,"i1123":1,"i1124":1,"i1125":2,"i1126":2,"i1127":2,"i1128":2,"i1129":2,"i1130":2,"i1131":2,"i1132":2,"i1133":2,"i1134":8,"i1135":2,"i1136":2,"i1137":2,"i1138":2,"i1139":2,"i1140":2,"i1141":2,"i1142":32,"i1143":32,"i1144":2,"i1145":2,"i1146":2,"i1147":2,"i1148":2,"i1149":2,"i1150":2,"i1151":2,"i1152":1,"i1153":2}; +var data = {"i0":2,"i1":32,"i2":2,"i3":2,"i4":2,"i5":2,"i6":2,"i7":2,"i8":32,"i9":2,"i10":2,"i11":2,"i12":2,"i13":2,"i14":2,"i15":2,"i16":2,"i17":2,"i18":2,"i19":2,"i20":2,"i21":2,"i22":2,"i23":2,"i24":2,"i25":2,"i26":2,"i27":2,"i28":2,"i29":2,"i30":2,"i31":2,"i32":2,"i33":2,"i34":2,"i35":2,"i36":2,"i37":2,"i38":2,"i39":2,"i40":2,"i41":2,"i42":2,"i43":2,"i44":2,"i45":1,"i46":2,"i47":2,"i48":1,"i49":2,"i50":2,"i51":1,"i52":2,"i53":2,"i54":2,"i55":2,"i56":2,"i57":2,"i58":32,"i59":2,"i60":2,"i61":32,"i62":1,"i63":1,"i64":2,"i65":8,"i66":32,"i67":2,"i68":32,"i69":2,"i70":1,"i71":2,"i72":2,"i73":2,"i74":2,"i75":1,"i76":2,"i77":32,"i78":2,"i79":1,"i80":32,"i81":2,"i82":2,"i83":2,"i84":2,"i85":2,"i86":2,"i87":1,"i88":32,"i89":2,"i90":2,"i91":2,"i92":8,"i93":2,"i94":2,"i95":2,"i96":2,"i97":2,"i98":1,"i99":1,"i100":2,"i101":8,"i102":1,"i103":2,"i104":1,"i105":8,"i106":8,"i107":1,"i108":32,"i109":8,"i110":8,"i111":2,"i112":2,"i113":1,"i114":1,"i115":2,"i116":2,"i117":2,"i118":2,"i119":2,"i120":2,"i121":2,"i122":2,"i123":2,"i124":2,"i125":2,"i126":2,"i127":8,"i128":2,"i129":2,"i130":2,"i131":2,"i132":2,"i133":1,"i134":2,"i135":1,"i136":2,"i137":1,"i138":1,"i139":2,"i140":2,"i141":2,"i142":2,"i143":2,"i144":2,"i145":2,"i146":2,"i147":2,"i148":32,"i149":32,"i150":32,"i151":32,"i152":32,"i153":32,"i154":32,"i155":32,"i156":32,"i157":32,"i158":32,"i159":32,"i160":32,"i161":32,"i162":32,"i163":32,"i164":32,"i165":32,"i166":32,"i167":32,"i168":32,"i169":32,"i170":32,"i171":32,"i172":32,"i173":32,"i174":32,"i175":32,"i176":32,"i177":1,"i178":8,"i179":1,"i180":2,"i181":2,"i182":2,"i183":8,"i184":2,"i185":2,"i186":32,"i187":1,"i188":2,"i189":32,"i190":2,"i191":1,"i192":1,"i193":2,"i194":2,"i195":1,"i196":1,"i197":2,"i198":2,"i199":32,"i200":2,"i201":2,"i202":2,"i203":2,"i204":2,"i205":2,"i206":2,"i207":2,"i208":2,"i209":1,"i210":1,"i211":1,"i212":2,"i213":2,"i214":2,"i215":1,"i216":1,"i217":2,"i218":2,"i219":8,"i220":32,"i221":1,"i222":2,"i223":2,"i224":2,"i225":2,"i226":2,"i227":2,"i228":1,"i229":2,"i230":2,"i231":2,"i232":1,"i233":2,"i234":2,"i235":8,"i236":1,"i237":2,"i238":2,"i239":2,"i240":2,"i241":8,"i242":2,"i243":2,"i244":2,"i245":1,"i246":8,"i247":2,"i248":2,"i249":32,"i250":2,"i251":32,"i252":32,"i253":32,"i254":2,"i255":2,"i256":1,"i257":1,"i258":2,"i259":2,"i260":2,"i261":2,"i262":8,"i263":2,"i264":2,"i265":1,"i266":2,"i267":2,"i268":8,"i269":1,"i270":2,"i271":1,"i272":2,"i273":1,"i274":1,"i275":1,"i276":1,"i277":2,"i278":2,"i279":2,"i280":2,"i281":8,"i282":2,"i283":2,"i284":2,"i285":2,"i286":32,"i287":32,"i288":2,"i289":1,"i290":2,"i291":2,"i292":2,"i293":8,"i294":2,"i295":32,"i296":8,"i297":2,"i298":1,"i299":2,"i300":32,"i301":32,"i302":2,"i303":2,"i304":2,"i305":1,"i306":2,"i307":8,"i308":32,"i309":2,"i310":2,"i311":2,"i312":2,"i313":2,"i314":2,"i315":2,"i316":2,"i317":2,"i318":2,"i319":2,"i320":2,"i321":2,"i322":2,"i323":2,"i324":2,"i325":2,"i326":8,"i327":32,"i328":2,"i329":2,"i330":2,"i331":2,"i332":2,"i333":2,"i334":2,"i335":2,"i336":2,"i337":2,"i338":2,"i339":2,"i340":2,"i341":2,"i342":2,"i343":2,"i344":2,"i345":2,"i346":2,"i347":1,"i348":2,"i349":2,"i350":32,"i351":2,"i352":2,"i353":2,"i354":2,"i355":2,"i356":2,"i357":2,"i358":2,"i359":2,"i360":2,"i361":2,"i362":2,"i363":2,"i364":2,"i365":2,"i366":32,"i367":2,"i368":2,"i369":32,"i370":2,"i371":2,"i372":32,"i373":32,"i374":2,"i375":1,"i376":1,"i377":1,"i378":1,"i379":8,"i380":2,"i381":1,"i382":8,"i383":1,"i384":2,"i385":1,"i386":2,"i387":2,"i388":2,"i389":2,"i390":8,"i391":2,"i392":2,"i393":2,"i394":1,"i395":8,"i396":32,"i397":1,"i398":2,"i399":1,"i400":1,"i401":1,"i402":2,"i403":32,"i404":2,"i405":2,"i406":2,"i407":2,"i408":2,"i409":2,"i410":1,"i411":2,"i412":2,"i413":2,"i414":2,"i415":1,"i416":2,"i417":2,"i418":2,"i419":1,"i420":32,"i421":2,"i422":8,"i423":32,"i424":1,"i425":1,"i426":2,"i427":1,"i428":2,"i429":2,"i430":2,"i431":2,"i432":2,"i433":2,"i434":2,"i435":2,"i436":1,"i437":2,"i438":2,"i439":32,"i440":2,"i441":1,"i442":1,"i443":1,"i444":1,"i445":2,"i446":8,"i447":32,"i448":1,"i449":1,"i450":1,"i451":2,"i452":1,"i453":1,"i454":1,"i455":2,"i456":2,"i457":2,"i458":2,"i459":8,"i460":32,"i461":1,"i462":2,"i463":1,"i464":1,"i465":32,"i466":2,"i467":2,"i468":2,"i469":1,"i470":2,"i471":1,"i472":1,"i473":1,"i474":2,"i475":2,"i476":2,"i477":2,"i478":2,"i479":2,"i480":2,"i481":2,"i482":2,"i483":2,"i484":2,"i485":2,"i486":2,"i487":2,"i488":2,"i489":2,"i490":2,"i491":2,"i492":2,"i493":2,"i494":2,"i495":2,"i496":2,"i497":8,"i498":2,"i499":2,"i500":2,"i501":2,"i502":2,"i503":1,"i504":2,"i505":2,"i506":2,"i507":2,"i508":2,"i509":2,"i510":2,"i511":2,"i512":2,"i513":2,"i514":2,"i515":1,"i516":2,"i517":2,"i518":2,"i519":2,"i520":8,"i521":2,"i522":2,"i523":2,"i524":8,"i525":2,"i526":32,"i527":1,"i528":2,"i529":2,"i530":2,"i531":2,"i532":2,"i533":8,"i534":2,"i535":2,"i536":32,"i537":32,"i538":2,"i539":2,"i540":2,"i541":2,"i542":2,"i543":2,"i544":2,"i545":2,"i546":2,"i547":2,"i548":2,"i549":2,"i550":2,"i551":2,"i552":2,"i553":2,"i554":2,"i555":2,"i556":2,"i557":32,"i558":2,"i559":2,"i560":2,"i561":2,"i562":8,"i563":2,"i564":2,"i565":2,"i566":2,"i567":8,"i568":2,"i569":2,"i570":8,"i571":2,"i572":2,"i573":2,"i574":2,"i575":1,"i576":1,"i577":2,"i578":2,"i579":1,"i580":2,"i581":1,"i582":2,"i583":2,"i584":2,"i585":2,"i586":1,"i587":2,"i588":2,"i589":2,"i590":32,"i591":2,"i592":2,"i593":2,"i594":2,"i595":2,"i596":2,"i597":32,"i598":2,"i599":2,"i600":8,"i601":1,"i602":1,"i603":1,"i604":1,"i605":8,"i606":8,"i607":1,"i608":2,"i609":2,"i610":2,"i611":2,"i612":1,"i613":1,"i614":2,"i615":8,"i616":1,"i617":8,"i618":32,"i619":8,"i620":8,"i621":2,"i622":2,"i623":2,"i624":2,"i625":2,"i626":2,"i627":2,"i628":2,"i629":1,"i630":2,"i631":2,"i632":2,"i633":8,"i634":2,"i635":2,"i636":2,"i637":2,"i638":2,"i639":2,"i640":2,"i641":8,"i642":1,"i643":2,"i644":2,"i645":2,"i646":2,"i647":2,"i648":2,"i649":2,"i650":2,"i651":2,"i652":1,"i653":1,"i654":1,"i655":1,"i656":2,"i657":1,"i658":1,"i659":2,"i660":1,"i661":8,"i662":1,"i663":2,"i664":1,"i665":2,"i666":2,"i667":32,"i668":2,"i669":2,"i670":2,"i671":2,"i672":2,"i673":2,"i674":2,"i675":2,"i676":2,"i677":1,"i678":2,"i679":2,"i680":2,"i681":32,"i682":2,"i683":2,"i684":1,"i685":1,"i686":1,"i687":2,"i688":1,"i689":1,"i690":2,"i691":8,"i692":2,"i693":2,"i694":8,"i695":1,"i696":2,"i697":8,"i698":8,"i699":2,"i700":2,"i701":1,"i702":8,"i703":2,"i704":2,"i705":2,"i706":2,"i707":2,"i708":2,"i709":2,"i710":2,"i711":2,"i712":2,"i713":2,"i714":2,"i715":2,"i716":2,"i717":2,"i718":2,"i719":2,"i720":1,"i721":1,"i722":2,"i723":2,"i724":2,"i725":32,"i726":32,"i727":2,"i728":2,"i729":2,"i730":2,"i731":1,"i732":1,"i733":2,"i734":1,"i735":2,"i736":2,"i737":1,"i738":1,"i739":1,"i740":2,"i741":1,"i742":1,"i743":32,"i744":1,"i745":1,"i746":1,"i747":1,"i748":1,"i749":2,"i750":1,"i751":1,"i752":2,"i753":1,"i754":2,"i755":2,"i756":8,"i757":32,"i758":2,"i759":1,"i760":1,"i761":1,"i762":2,"i763":1,"i764":2,"i765":2,"i766":2,"i767":2,"i768":2,"i769":2,"i770":32,"i771":2,"i772":32,"i773":2,"i774":2,"i775":2,"i776":2,"i777":2,"i778":2,"i779":2,"i780":2,"i781":2,"i782":2,"i783":1,"i784":32,"i785":2,"i786":2,"i787":2,"i788":32,"i789":2,"i790":2,"i791":2,"i792":2,"i793":2,"i794":2,"i795":2,"i796":8,"i797":2,"i798":2,"i799":2,"i800":2,"i801":2,"i802":2,"i803":8,"i804":2,"i805":1,"i806":2,"i807":2,"i808":2,"i809":2,"i810":2,"i811":2,"i812":2,"i813":2,"i814":8,"i815":32,"i816":32,"i817":2,"i818":2,"i819":1,"i820":1,"i821":2,"i822":2,"i823":2,"i824":2,"i825":2,"i826":1,"i827":1,"i828":32,"i829":2,"i830":2,"i831":32,"i832":32,"i833":1,"i834":2,"i835":1,"i836":32,"i837":32,"i838":32,"i839":2,"i840":32,"i841":32,"i842":32,"i843":2,"i844":1,"i845":1,"i846":2,"i847":1,"i848":2,"i849":1,"i850":1,"i851":2,"i852":2,"i853":1,"i854":1,"i855":1,"i856":32,"i857":32,"i858":2,"i859":32,"i860":2,"i861":2,"i862":2,"i863":2,"i864":2,"i865":8,"i866":2,"i867":2,"i868":2,"i869":2,"i870":2,"i871":1,"i872":1,"i873":2,"i874":2,"i875":2,"i876":2,"i877":2,"i878":2,"i879":2,"i880":2,"i881":2,"i882":2,"i883":2,"i884":8,"i885":1,"i886":32,"i887":32,"i888":1,"i889":1,"i890":32,"i891":32,"i892":32,"i893":32,"i894":2,"i895":1,"i896":2,"i897":2,"i898":32,"i899":2,"i900":2,"i901":2,"i902":2,"i903":32,"i904":2,"i905":1,"i906":2,"i907":2,"i908":1,"i909":2,"i910":2,"i911":2,"i912":2,"i913":2,"i914":2,"i915":2,"i916":2,"i917":1,"i918":1,"i919":2,"i920":2,"i921":2,"i922":8,"i923":2,"i924":2,"i925":2,"i926":1,"i927":8,"i928":1,"i929":32,"i930":32,"i931":1,"i932":1,"i933":2,"i934":1,"i935":2,"i936":2,"i937":2,"i938":2,"i939":2,"i940":2,"i941":2,"i942":2,"i943":2,"i944":2,"i945":2,"i946":2,"i947":2,"i948":1,"i949":1,"i950":2,"i951":2,"i952":2,"i953":1,"i954":2,"i955":1,"i956":1,"i957":2,"i958":1,"i959":2,"i960":1,"i961":1,"i962":1,"i963":1,"i964":2,"i965":2,"i966":1,"i967":2,"i968":2,"i969":2,"i970":2,"i971":2,"i972":2,"i973":2,"i974":2,"i975":2,"i976":2,"i977":2,"i978":2,"i979":2,"i980":2,"i981":2,"i982":2,"i983":2,"i984":2,"i985":2,"i986":2,"i987":2,"i988":2,"i989":1,"i990":2,"i991":2,"i992":1,"i993":1,"i994":1,"i995":1,"i996":1,"i997":1,"i998":1,"i999":1,"i1000":1,"i1001":2,"i1002":2,"i1003":1,"i1004":2,"i1005":2,"i1006":2,"i1007":2,"i1008":2,"i1009":2,"i1010":2,"i1011":2,"i1012":2,"i1013":1,"i1014":1,"i1015":2,"i1016":2,"i1017":2,"i1018":2,"i1019":2,"i1020":8,"i1021":2,"i1022":2,"i1023":2,"i1024":2,"i1025":2,"i1026":2,"i1027":2,"i1028":2,"i1029":2,"i1030":2,"i1031":2,"i1032":1,"i1033":1,"i1034":1,"i1035":2,"i1036":32,"i1037":2,"i1038":1,"i1039":1,"i1040":8,"i1041":1,"i1042":2,"i1043":2,"i1044":2,"i1045":2,"i1046":32,"i1047":2,"i1048":2,"i1049":2,"i1050":2,"i1051":1,"i1052":2,"i1053":2,"i1054":2,"i1055":2,"i1056":2,"i1057":2,"i1058":2,"i1059":32,"i1060":2,"i1061":32,"i1062":32,"i1063":2,"i1064":1,"i1065":2,"i1066":2,"i1067":1,"i1068":1,"i1069":2,"i1070":2,"i1071":2,"i1072":2,"i1073":2,"i1074":2,"i1075":2,"i1076":1,"i1077":2,"i1078":1,"i1079":2,"i1080":2,"i1081":2,"i1082":2,"i1083":1,"i1084":2,"i1085":2,"i1086":32,"i1087":2,"i1088":2,"i1089":2,"i1090":1,"i1091":1,"i1092":2,"i1093":32,"i1094":1,"i1095":32,"i1096":2,"i1097":2,"i1098":1,"i1099":2,"i1100":2,"i1101":2,"i1102":2,"i1103":2,"i1104":2,"i1105":1,"i1106":2,"i1107":1,"i1108":2,"i1109":1,"i1110":2,"i1111":2,"i1112":2,"i1113":2,"i1114":2,"i1115":1,"i1116":32,"i1117":1,"i1118":2,"i1119":2,"i1120":1,"i1121":32,"i1122":2,"i1123":2,"i1124":32,"i1125":1,"i1126":2,"i1127":2,"i1128":1,"i1129":32,"i1130":2,"i1131":2,"i1132":2,"i1133":2,"i1134":2,"i1135":8,"i1136":32,"i1137":8,"i1138":8,"i1139":32,"i1140":2,"i1141":2,"i1142":2,"i1143":2,"i1144":2,"i1145":2,"i1146":2,"i1147":2,"i1148":1,"i1149":1,"i1150":2,"i1151":1,"i1152":2,"i1153":2,"i1154":2,"i1155":2,"i1156":2,"i1157":2,"i1158":2,"i1159":2,"i1160":2,"i1161":8,"i1162":2,"i1163":2,"i1164":2,"i1165":2,"i1166":2,"i1167":2,"i1168":2,"i1169":32,"i1170":32,"i1171":2,"i1172":2,"i1173":2,"i1174":2,"i1175":2,"i1176":2,"i1177":2,"i1178":2,"i1179":1,"i1180":2}; var tabs = {65535:["t0","All Classes"],1:["t1","Interface Summary"],2:["t2","Class Summary"],8:["t4","Exception Summary"],32:["t6","Annotation Types Summary"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -195,19 +195,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.AddMediaItems -

        + Action.ClearMediaItems - + Action.ClearVideoSurface - + @@ -219,7 +219,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.MoveMediaItem - + @@ -238,13 +238,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.RemoveMediaItem - + Action.RemoveMediaItems - + @@ -262,19 +262,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.SetAudioAttributes - + Action.SetMediaItems - + Action.SetMediaItemsResetPosition - + @@ -299,7 +299,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.SetRepeatMode - + @@ -317,7 +317,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.SetVideoSurface - + @@ -353,27 +353,27 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.WaitForPlaybackState -
        Waits for a specified playback state, returning either immediately or after a call to Player.Listener.onPlaybackStateChanged(int).
        +
        Waits for a specified playback state, returning either immediately or after a call to Player.Listener.onPlaybackStateChanged(int).
        Action.WaitForPlayWhenReady
        Waits for a specified playWhenReady value, returning either immediately or after a call to - Player.Listener.onPlayWhenReadyChanged(boolean, int).
        + Player.Listener.onPlayWhenReadyChanged(boolean, int). Action.WaitForPositionDiscontinuity -
        Waits for Player.Listener.onPositionDiscontinuity(Player.PositionInfo, + Action.WaitForTimelineChanged - + @@ -655,61 +655,61 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +AssetContentProvider + +
        A ContentProvider for reading asset data.
        + + + AssetDataSource
        A DataSource for reading from a local asset.
        - + AssetDataSource.AssetDataSourceException
        Thrown when an IOException is encountered reading a local asset.
        - + AtomicFile
        A helper class for performing atomic operations on a file by creating a backup file until a write has successfully completed.
        - + AudioAttributes
        Attributes for audio playback, which configure the underlying platform AudioTrack.
        - + AudioAttributes.Builder
        Builder for AudioAttributes.
        - + AudioCapabilities
        Represents the set of audio formats that a device is capable of playing.
        - + AudioCapabilitiesReceiver
        Receives broadcast events indicating changes to the device's audio capabilities, notifying a AudioCapabilitiesReceiver.Listener when audio capability changes occur.
        - + AudioCapabilitiesReceiver.Listener
        Listener notified when audio capabilities change.
        - -AudioListener -Deprecated. - - - AudioProcessor @@ -960,7 +960,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); -BundleableUtils +BundleableUtil
        Utilities for Bundleable.
        @@ -1040,1041 +1040,1089 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +C.AudioManagerOffloadMode + +
        Playback offload mode.
        + + + C.AudioUsage
        Usage types for audio attributes.
        - + C.BufferFlags
        Flags which can apply to a buffer containing a media sample.
        - + C.ColorRange
        Video color range.
        - + C.ColorSpace
        Video colorspaces.
        - + C.ColorTransfer
        Video color transfer characteristics.
        - + C.ContentType
        Represents a streaming or other media type.
        - + C.CryptoMode
        Crypto modes for a codec.
        - + +C.CryptoType + +
        Types of crypto implementation.
        + + + C.DataType
        Represents a type of data.
        - + C.Encoding
        Represents an audio encoding, or an invalid or unset value.
        - + C.FormatSupport
        Level of renderer support for a format.
        - + C.NetworkType
        Network connection type.
        - + C.PcmEncoding
        Represents a PCM audio encoding, or an invalid or unset value.
        - + C.Projection
        Video projection types.
        - + C.RoleFlags
        Track role flags.
        - + C.SelectionFlags
        Track selection flags.
        - + +C.SelectionReason + +
        Represents a reason for selection.
        + + + C.StereoMode
        The stereo mode for 360/3D/VR videos.
        - + C.StreamType
        Stream types for an AudioTrack.
        - + +C.TrackType + +
        Represents a type of media track.
        + + + +C.VideoChangeFrameRateStrategy + + + + + C.VideoOutputMode
        Video decoder output modes.
        - + C.VideoScalingMode
        Video scaling modes for MediaCodec-based renderers.
        - + C.WakeMode
        Mode specifying whether the player should hold a WakeLock and a WifiLock.
        - + Cache
        A cache that supports partial caching of resources.
        - + Cache.CacheException
        Thrown when an error is encountered when writing data.
        - + Cache.Listener
        Listener of Cache events.
        - + CacheAsserts
        Assertion methods for Cache.
        - + CacheAsserts.RequestSet
        Defines a set of data requests.
        - + CacheDataSink
        Writes data into a cache.
        - + CacheDataSink.CacheDataSinkException
        Thrown when an IOException is encountered when writing data to the sink.
        - + CacheDataSink.Factory - -CacheDataSinkFactory -Deprecated. - - - - + CacheDataSource
        A DataSource that reads and writes a Cache.
        - + CacheDataSource.CacheIgnoredReason
        Reasons the cache may be ignored.
        - + CacheDataSource.EventListener
        Listener of CacheDataSource events.
        - + CacheDataSource.Factory - + CacheDataSource.Flags
        Flags controlling the CacheDataSource's behavior.
        - -CacheDataSourceFactory -Deprecated. - - - - -CachedRegionTracker + +CachedRegionTracker
        Utility class for efficiently tracking regions of data that are stored in a Cache for a given cache key.
        - + CacheEvictor
        Evicts data from a Cache.
        - + CacheKeyFactory
        Factory for cache keys.
        - + CacheSpan
        Defines a span of data that may or may not be cached (as indicated by CacheSpan.isCached).
        - + CacheWriter
        Caching related utility methods.
        - + CacheWriter.ProgressListener
        Receives progress updates during cache operations.
        - + CameraMotionListener
        Listens camera motion.
        - + CameraMotionRenderer
        A Renderer that parses the camera motion track.
        - + CaptionStyleCompat
        A compatibility wrapper for CaptioningManager.CaptionStyle.
        - + CaptionStyleCompat.EdgeType
        The type of edge, which may be none.
        - + CapturingAudioSink
        A ForwardingAudioSink that captures configuration, discontinuity and buffer events.
        - + CapturingRenderersFactory
        A RenderersFactory that captures interactions with the audio and video MediaCodecAdapter instances.
        - + CastPlayer
        Player implementation that communicates with a Cast receiver app.
        - + Cea608Decoder
        A SubtitleDecoder for CEA-608 (also known as "line 21 captions" and "EIA-608").
        - + Cea708Decoder
        A SubtitleDecoder for CEA-708 (also known as "EIA-708").
        - + CeaUtil
        Utility methods for handling CEA-608/708 messages.
        - + ChapterFrame
        Chapter information ID3 frame.
        - + ChapterTocFrame
        Chapter table of contents ID3 frame.
        - + Chunk
        An abstract base class for Loader.Loadable implementations that load chunks of data required for the playback of streams.
        - + ChunkExtractor
        Extracts samples and track Formats from chunks.
        - + ChunkExtractor.Factory
        Creates ChunkExtractor instances.
        - + ChunkExtractor.TrackOutputProvider
        Provides TrackOutput instances to be written to during extraction.
        - + ChunkHolder
        Holds a chunk or an indication that the end of the stream has been reached.
        - + ChunkIndex
        Defines chunks of samples within a media stream.
        - + ChunkSampleStream<T extends ChunkSource>
        A SampleStream that loads media in Chunks, obtained from a ChunkSource.
        - + ChunkSampleStream.ReleaseCallback<T extends ChunkSource>
        A callback to be notified when a sample stream has finished being released.
        - + ChunkSource
        A provider of Chunks for a ChunkSampleStream to load.
        - + ClippingMediaPeriod
        Wraps a MediaPeriod and clips its SampleStreams to provide a subsequence of their samples.
        - + ClippingMediaSource
        MediaSource that wraps a source and clips its timeline based on specified start/end positions.
        - + ClippingMediaSource.IllegalClippingException
        Thrown when a ClippingMediaSource cannot clip its wrapped source.
        - + ClippingMediaSource.IllegalClippingException.Reason
        The reason clipping failed.
        - + Clock
        An interface through which system clocks can be read and HandlerWrappers created.
        - + CodecSpecificDataUtil
        Provides utilities for handling various types of codec-specific data.
        - + ColorInfo
        Stores color info.
        - + ColorParser
        Parser for color expressions found in styling formats, e.g.
        - + CommentFrame
        Comment ID3 frame.
        - + CompositeMediaSource<T>
        Composite MediaSource consisting of multiple child sources.
        - + CompositeSequenceableLoader
        A SequenceableLoader that encapsulates multiple other SequenceableLoaders.
        - + CompositeSequenceableLoaderFactory
        A factory to create composite SequenceableLoaders.
        - + ConcatenatingMediaSource
        Concatenates multiple MediaSources.
        - + ConditionVariable
        An interruptible condition variable.
        - + ConstantBitrateSeekMap
        A SeekMap implementation that assumes the stream has a constant bitrate and consists of multiple independent frames of the same size.
        - + Consumer<T>
        Represents an operation that accepts a single input argument and returns no result.
        - + ContainerMediaChunk
        A BaseMediaChunk that uses an Extractor to decode sample data.
        - + ContentDataSource
        A DataSource for reading from a content URI.
        - + ContentDataSource.ContentDataSourceException
        Thrown when an IOException is encountered reading from a content URI.
        - + ContentMetadata
        Interface for an immutable snapshot of keyed metadata.
        - + ContentMetadataMutations
        Defines multiple mutations on metadata value which are applied atomically.
        - -ControlDispatcher -Deprecated. -
        Use a ForwardingPlayer or configure the player to customize operations.
        - - - + CopyOnWriteMultiset<E>
        An unordered collection of elements that allows duplicates, but also allows access to a set of unique elements.
        - + CronetDataSource
        DataSource without intermediate buffer based on Cronet API set using UrlRequest.
        - + CronetDataSource.Factory - + CronetDataSource.OpenException
        Thrown when an error is encountered when trying to open a CronetDataSource.
        - + CronetDataSourceFactory Deprecated. - + CronetEngineWrapper Deprecated.
        Use CronetEngine directly.
        - + CronetUtil
        Cronet utility methods.
        - -CryptoInfo + +CryptoConfig -
        Compatibility wrapper for MediaCodec.CryptoInfo.
        +
        Configuration for a decoder to allow it to decode encrypted media data.
        - + +CryptoException + +
        Thrown when a non-platform component fails to decrypt data.
        + + + +CryptoInfo + +
        Metadata describing the structure of an encrypted input sample.
        + + + Cue
        Contains information about a specific cue, including textual content and formatting data.
        - + Cue.AnchorType
        The type of anchor, which may be unset.
        - + Cue.Builder
        A builder for Cue objects.
        - + Cue.LineType
        The type of line, which may be unset.
        - + Cue.TextSizeType
        The type of default text size for this cue, which may be unset.
        - + Cue.VerticalType
        The type of vertical layout for this cue, which may be unset (i.e.
        - + +CueDecoder + +
        Decodes data encoded by CueEncoder.
        + + + +CueEncoder + +
        Encodes data that can be decoded by CueDecoder.
        + + + DashChunkSource
        A ChunkSource for DASH streams.
        - + DashChunkSource.Factory
        Factory for DashChunkSources.
        - + DashDownloader
        A downloader for DASH streams.
        - + DashManifest
        Represents a DASH media presentation description (mpd), as defined by ISO/IEC 23009-1:2014 Section 5.3.1.2.
        - + DashManifestParser
        A parser of media presentation description files.
        - + DashManifestParser.RepresentationInfo
        A parsed Representation element.
        - + DashManifestStaleException
        Thrown when a live playback's manifest is stale and a new manifest could not be loaded.
        - + DashMediaSource
        A DASH MediaSource.
        - + DashMediaSource.Factory
        Factory for DashMediaSources.
        - + DashSegmentIndex
        Indexes the segments within a media stream.
        - + DashUtil
        Utility methods for DASH streams.
        - + DashWrappingSegmentIndex
        An implementation of DashSegmentIndex that wraps a ChunkIndex parsed from a media stream.
        - + DatabaseIOException
        An IOException whose cause is an SQLException.
        - + DatabaseProvider -
        Provides SQLiteDatabase instances to ExoPlayer components, which may read and write +
        Provides SQLiteDatabase instances to media library components, which may read and write tables prefixed with DatabaseProvider.TABLE_PREFIX.
        - + DataChunk
        A base class for Chunk implementations where the data should be loaded into a byte[] before being consumed.
        - + DataReader
        Reads bytes from a data stream.
        - + DataSchemeDataSource
        A DataSource for reading data URLs, as defined by RFC 2397.
        - + DataSink
        A component to which streams of data can be written.
        - + DataSink.Factory
        A factory for DataSink instances.
        - + DataSource
        Reads data from URI-identified resources.
        - + DataSource.Factory
        A factory for DataSource instances.
        - + DataSourceContractTest
        A collection of contract tests for DataSource implementations.
        - + DataSourceContractTest.FakeTransferListener
        A TransferListener that only keeps track of the transferred bytes.
        - + DataSourceContractTest.TestResource
        Information about a resource that can be used to test the DataSource instance.
        - + DataSourceContractTest.TestResource.Builder - + DataSourceException
        Used to specify reason of a DataSource error.
        - + DataSourceInputStream
        Allows data corresponding to a given DataSpec to be read from a DataSource and consumed through an InputStream.
        - + +DataSourceUtil + +
        Utility methods for DataSource.
        + + + DataSpec
        Defines a region of data in a resource.
        - + DataSpec.Builder
        Builds DataSpec instances.
        - + DataSpec.Flags
        The flags that apply to any request for data.
        - + DataSpec.HttpMethod
        HTTP methods supported by ExoPlayer HttpDataSources.
        - + DebugTextViewHelper
        A helper class for periodically updating a TextView with debug information obtained from - a SimpleExoPlayer.
        + an ExoPlayer.
        - + Decoder<I,​O,​E extends DecoderException>
        A media decoder.
        - -DecoderAudioRenderer<T extends Decoder<DecoderInputBuffer,​? extends SimpleOutputBuffer,​? extends DecoderException>> + +DecoderAudioRenderer<T extends Decoder<DecoderInputBuffer,​? extends SimpleDecoderOutputBuffer,​? extends DecoderException>>
        Decodes and renders audio using a Decoder.
        - + DecoderCounters
        Maintains decoder event counts, for debugging purposes only.
        - + DecoderCountersUtil
        Assertions for DecoderCounters.
        - + DecoderException
        Thrown when a Decoder error occurs.
        - + DecoderInputBuffer
        Holds input for a decoder.
        - + DecoderInputBuffer.BufferReplacementMode
        The buffer replacement mode.
        - + DecoderInputBuffer.InsufficientCapacityException
        Thrown when an attempt is made to write into a DecoderInputBuffer whose DecoderInputBuffer.bufferReplacementMode is DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED and who DecoderInputBuffer.data capacity is smaller than required.
        - + +DecoderOutputBuffer + +
        Output buffer decoded by a Decoder.
        + + + +DecoderOutputBuffer.Owner<S extends DecoderOutputBuffer> + +
        Buffer owner.
        + + + DecoderReuseEvaluation
        The result of an evaluation to determine whether a decoder can be reused for a new input format.
        - + DecoderReuseEvaluation.DecoderDiscardReasons
        Possible reasons why reuse is not possible.
        - + DecoderReuseEvaluation.DecoderReuseResult
        Possible outcomes of the evaluation.
        - + DecoderVideoRenderer
        Decodes and renders video using a Decoder.
        - -DecryptionException - -
        Thrown when a non-platform component fails to decrypt data.
        - - - + DefaultAllocator
        Default implementation of Allocator.
        - + DefaultAudioSink
        Plays audio data.
        - + DefaultAudioSink.AudioProcessorChain
        Provides a chain of audio processors, which are used for any user-defined processing and applying playback parameters (if supported).
        - + DefaultAudioSink.DefaultAudioProcessorChain
        The default audio processor chain, which applies a (possibly empty) chain of user-defined audio processors followed by SilenceSkippingAudioProcessor and SonicAudioProcessor.
        - + DefaultAudioSink.InvalidAudioTrackTimestampException
        Thrown when the audio track has provided a spurious timestamp, if DefaultAudioSink.failOnSpuriousAudioTimestamp is set.
        - + DefaultAudioSink.OffloadMode
        Audio offload mode configuration.
        - + DefaultBandwidthMeter
        Estimates bandwidth by listening to data transfers.
        - + DefaultBandwidthMeter.Builder
        Builder for a bandwidth meter.
        - + DefaultCastOptionsProvider
        A convenience OptionsProvider to target the default cast receiver app.
        - + DefaultCompositeSequenceableLoaderFactory
        Default implementation of CompositeSequenceableLoaderFactory.
        - + DefaultContentMetadata
        Default implementation of ContentMetadata.
        - -DefaultControlDispatcher -Deprecated. -
        Use a ForwardingPlayer or configure the player to customize operations.
        - - - + DefaultDashChunkSource
        A default DashChunkSource implementation.
        - + DefaultDashChunkSource.Factory   - + DefaultDashChunkSource.RepresentationHolder
        Holds information about a snapshot of a single Representation.
        - + DefaultDashChunkSource.RepresentationSegmentIterator - + DefaultDatabaseProvider
        A DatabaseProvider that provides instances obtained from a SQLiteOpenHelper.
        - + DefaultDataSource
        A DataSource that supports multiple URI schemes.
        - -DefaultDataSourceFactory + +DefaultDataSource.Factory -
        A DataSource.Factory that produces DefaultDataSource instances that delegate to DefaultHttpDataSources for non-file/asset/content URIs.
        + - + +DefaultDataSourceFactory +Deprecated. + + + + DefaultDownloaderFactory
        Default DownloaderFactory, supporting creation of progressive, DASH, HLS and SmoothStreaming downloaders.
        - + DefaultDownloadIndex
        A DownloadIndex that uses SQLite to persist Downloads.
        - + DefaultDrmSessionManager
        A DrmSessionManager that supports playbacks using ExoMediaDrm.
        - + DefaultDrmSessionManager.Builder
        Builder for DefaultDrmSessionManager instances.
        - + DefaultDrmSessionManager.MissingSchemeDataException - + DefaultDrmSessionManager.Mode
        Determines the action to be done after a session acquired.
        - + DefaultDrmSessionManagerProvider
        Default implementation of DrmSessionManagerProvider.
        - + DefaultExtractorInput
        An ExtractorInput that wraps a DataReader.
        - + DefaultExtractorsFactory
        An ExtractorsFactory that provides an array of extractors for the following formats: @@ -2099,1745 +2147,1760 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); com.google.android.exoplayer2.ext.flac.FlacExtractor is used.
        - + DefaultHlsDataSourceFactory
        Default implementation of HlsDataSourceFactory.
        - + DefaultHlsExtractorFactory
        Default HlsExtractorFactory implementation.
        - + DefaultHlsPlaylistParserFactory
        Default implementation for HlsPlaylistParserFactory.
        - + DefaultHlsPlaylistTracker
        Default implementation for HlsPlaylistTracker.
        - + DefaultHttpDataSource
        An HttpDataSource that uses Android's HttpURLConnection.
        - + DefaultHttpDataSource.Factory - -DefaultHttpDataSourceFactory -Deprecated. - - - - + DefaultLivePlaybackSpeedControl
        A LivePlaybackSpeedControl that adjusts the playback speed using a proportional controller.
        - + DefaultLivePlaybackSpeedControl.Builder - + DefaultLoadControl
        The default LoadControl implementation.
        - + DefaultLoadControl.Builder
        Builder for DefaultLoadControl.
        - + DefaultLoadErrorHandlingPolicy
        Default implementation of LoadErrorHandlingPolicy.
        - + +DefaultMediaCodecAdapterFactory + + + + + DefaultMediaDescriptionAdapter - + DefaultMediaItemConverter
        Default MediaItemConverter implementation.
        - + DefaultMediaItemConverter
        Default implementation of MediaItemConverter.
        - + DefaultMediaSourceFactory
        The default MediaSourceFactory implementation.
        - + DefaultMediaSourceFactory.AdsLoaderProvider -
        Provides AdsLoader instances for media items that have ad tag URIs.
        +
        Provides AdsLoader instances for media items that have ad tag URIs.
        - + DefaultPlaybackSessionManager
        Default PlaybackSessionManager which instantiates a new session for each window in the timeline and also for each ad within the windows.
        - + DefaultRenderersFactory
        Default RenderersFactory implementation.
        - + DefaultRenderersFactory.ExtensionRendererMode
        Modes for using extension renderers.
        - + DefaultRenderersFactoryAsserts
        Assertions for DefaultRenderersFactory.
        - + DefaultRtpPayloadReaderFactory
        Default RtpPayloadReader.Factory implementation.
        - + DefaultSsChunkSource
        A default SsChunkSource implementation.
        - + DefaultSsChunkSource.Factory   - + DefaultTimeBar
        A time bar that shows a current position, buffered position, duration and ad markers.
        - + DefaultTrackNameProvider - + DefaultTrackSelector
        A default TrackSelector suitable for most use cases.
        - + DefaultTrackSelector.AudioTrackScore
        Represents how well an audio track matches the selection DefaultTrackSelector.Parameters.
        - + DefaultTrackSelector.OtherTrackScore
        Represents how well any other track (non video, audio or text) matches the selection DefaultTrackSelector.Parameters.
        - + DefaultTrackSelector.Parameters
        Extends DefaultTrackSelector.Parameters by adding fields that are specific to DefaultTrackSelector.
        - + DefaultTrackSelector.ParametersBuilder - + DefaultTrackSelector.SelectionOverride
        A track selection override.
        - + DefaultTrackSelector.TextTrackScore
        Represents how well a text track matches the selection DefaultTrackSelector.Parameters.
        - + DefaultTrackSelector.VideoTrackScore
        Represents how well a video track matches the selection DefaultTrackSelector.Parameters.
        - + DefaultTsPayloadReaderFactory
        Default TsPayloadReader.Factory implementation.
        - + DefaultTsPayloadReaderFactory.Flags
        Flags controlling elementary stream readers' behavior.
        - + Descriptor
        A descriptor, as defined by ISO 23009-1, 2nd edition, 5.8.2.
        - -DeviceInfo + +DeviceInfo
        Information about the playback device.
        - -DeviceInfo.PlaybackType + +DeviceInfo.PlaybackType
        Types of playback.
        - -DeviceListener -Deprecated. - - - - + DolbyVisionConfig
        Dolby Vision configuration data.
        - + Download
        Represents state of a download.
        - + Download.FailureReason
        Failure reasons.
        - + Download.State
        Download states.
        - + DownloadBuilder
        Builder for Download.
        - + DownloadCursor
        Provides random read-write access to the result set returned by a database query.
        - + Downloader
        Downloads and removes a piece of content.
        - + Downloader.ProgressListener
        Receives progress updates during download operations.
        - + DownloaderFactory
        Creates Downloaders for given DownloadRequests.
        - + DownloadException
        Thrown on an error during downloading.
        - + DownloadHelper
        A helper for initializing and removing downloads.
        - + DownloadHelper.Callback
        A callback to be notified when the DownloadHelper is prepared.
        - + DownloadHelper.LiveContentUnsupportedException
        Thrown at an attempt to download live content.
        - + DownloadIndex
        An index of Downloads.
        - + DownloadManager
        Manages downloads.
        - + DownloadManager.Listener
        Listener for DownloadManager events.
        - + DownloadNotificationHelper
        Helper for creating download notifications.
        - + DownloadProgress
        Mutable Download progress.
        - + DownloadRequest
        Defines content to be downloaded.
        - + DownloadRequest.Builder
        A builder for download requests.
        - + DownloadRequest.UnsupportedRequestException
        Thrown when the encoded request data belongs to an unsupported request type.
        - + DownloadService
        A Service for downloading media.
        - + DrmInitData
        Initialization data for one or more DRM schemes.
        - + DrmInitData.SchemeData
        Scheme initialization data.
        - + DrmSession
        A DRM session.
        - + DrmSession.DrmSessionException
        Wraps the throwable which is the cause of the error state.
        - + DrmSession.State
        The state of the DRM session.
        - + DrmSessionEventListener
        Listener of DrmSessionManager events.
        - + DrmSessionEventListener.EventDispatcher
        Dispatches events to DrmSessionEventListeners.
        - + DrmSessionManager
        Manages a DRM session.
        - + DrmSessionManager.DrmSessionReference
        Represents a single reference count of a DrmSession, while deliberately not giving access to the underlying session.
        - + DrmSessionManagerProvider
        A provider to obtain a DrmSessionManager suitable for playing the content described by a MediaItem.
        - + DrmUtil
        DRM-related utility methods.
        - + DrmUtil.ErrorSource
        Identifies the operation which caused a DRM-related error.
        - + DtsReader
        Parses a continuous DTS byte stream and extracts individual samples.
        - + DtsUtil
        Utility methods for parsing DTS frames.
        - + DummyDataSource
        A DataSource which provides no data.
        - + DummyExoMediaDrm
        An ExoMediaDrm that does not support any protection schemes.
        - + DummyExtractorOutput
        A fake ExtractorOutput implementation.
        - + DummyMainThread
        Helper class to simulate main/UI thread in tests.
        - + DummyMainThread.TestRunnable
        Runnable variant which can throw a checked exception.
        - + DummySurface
        A dummy Surface.
        - + DummyTrackOutput
        A fake TrackOutput implementation.
        - + DumpableFormat
        Wraps a Format to allow dumping it.
        - + Dumper
        Helper utility to dump field values.
        - + Dumper.Dumpable
        Provides custom dump method.
        - + DumpFileAsserts
        Helper class to enable assertions based on golden-data dump files.
        - + DvbDecoder
        A SimpleSubtitleDecoder for DVB subtitles.
        - + DvbSubtitleReader
        Parses DVB subtitle data and extracts individual frames.
        - + EbmlProcessor
        Defines EBML element IDs/types and processes events.
        - + EbmlProcessor.ElementType
        EBML element types.
        - + EGLSurfaceTexture
        Generates a SurfaceTexture using EGL/GLES functions.
        - + EGLSurfaceTexture.GlException
        A runtime exception to be thrown if some EGL operations failed.
        - + EGLSurfaceTexture.SecureMode
        Secure mode to be used by the EGL surface and context.
        - + EGLSurfaceTexture.TextureImageListener
        Listener to be called when the texture image on SurfaceTexture has been updated.
        - + ElementaryStreamReader
        Extracts individual samples from an elementary media stream, preserving original order.
        - + EmptySampleStream
        An empty SampleStream.
        - + ErrorMessageProvider<T extends Throwable>
        Converts throwables into error codes and user readable error messages.
        - + ErrorStateDrmSession
        A DrmSession that's in a terminal error state.
        - + EventLogger
        Logs events from Player and other core components using Log.
        - + EventMessage
        An Event Message (emsg) as defined in ISO 23009-1.
        - + EventMessageDecoder
        Decodes data encoded by EventMessageEncoder.
        - + EventMessageEncoder
        Encodes data that can be decoded by EventMessageDecoder.
        - + EventStream
        A DASH in-MPD EventStream element, as defined by ISO/IEC 23009-1, 2nd edition, section 5.10.
        - + ExoDatabaseProvider - -
        An SQLiteOpenHelper that provides instances of a standalone ExoPlayer database.
        +Deprecated. + - + ExoHostedTest
        A HostActivity.HostedTest for ExoPlayer playback tests.
        - -ExoMediaCrypto - -
        Enables decoding of encrypted data using keys in a DRM session.
        - - - + ExoMediaDrm
        Used to obtain keys for decrypting protected media streams.
        - + ExoMediaDrm.AppManagedProvider
        Provides an ExoMediaDrm instance owned by the app.
        - + ExoMediaDrm.KeyRequest
        Contains data used to request keys from a license server.
        - + ExoMediaDrm.KeyRequest.RequestType
        Key request types.
        - + ExoMediaDrm.KeyStatus
        Defines the status of a key.
        - + ExoMediaDrm.OnEventListener
        Called when a DRM event occurs.
        - + ExoMediaDrm.OnExpirationUpdateListener
        Called when a session expiration update occurs.
        - + ExoMediaDrm.OnKeyStatusChangeListener
        Called when the keys in a DRM session change state.
        - + ExoMediaDrm.Provider
        Provider for ExoMediaDrm instances.
        - + ExoMediaDrm.ProvisionRequest
        Contains data to request a certificate from a provisioning server.
        - + ExoPlaybackException
        Thrown when a non locally recoverable playback failure occurs.
        - + ExoPlaybackException.Type
        The type of source that produced the error.
        - + ExoPlayer
        An extensible media player that plays MediaSources.
        - + ExoPlayer.AudioComponent - -
        The audio component of an ExoPlayer.
        +Deprecated. +
        Use ExoPlayer, as the ExoPlayer.AudioComponent methods are defined by that + interface.
        - + ExoPlayer.AudioOffloadListener
        A listener for audio offload events.
        - + ExoPlayer.Builder -Deprecated. - + +
        A builder for ExoPlayer instances.
        - + ExoPlayer.DeviceComponent - -
        The device component of an ExoPlayer.
        +Deprecated. +
        Use Player, as the ExoPlayer.DeviceComponent methods are defined by that + interface.
        - -ExoPlayer.MetadataComponent - -
        The metadata component of an ExoPlayer.
        - - - + ExoPlayer.TextComponent - -
        The text component of an ExoPlayer.
        +Deprecated. +
        Use Player, as the ExoPlayer.TextComponent methods are defined by that + interface.
        - + ExoPlayer.VideoComponent - -
        The video component of an ExoPlayer.
        +Deprecated. +
        Use ExoPlayer, as the ExoPlayer.VideoComponent methods are defined by that + interface.
        - + +ExoplayerCuesDecoder + +
        A SubtitleDecoder that decodes subtitle samples of type MimeTypes.TEXT_EXOPLAYER_CUES
        + + + ExoPlayerLibraryInfo -
        Information about the ExoPlayer library.
        +
        Information about the media libraries.
        - + ExoPlayerTestRunner
        Helper class to run an ExoPlayer test.
        - + ExoPlayerTestRunner.Builder -
        Builder to set-up a ExoPlayerTestRunner.
        +
        Builder to set-up an ExoPlayerTestRunner.
        - + ExoTimeoutException
        A timeout of an operation on the ExoPlayer playback thread.
        - + ExoTimeoutException.TimeoutOperation
        The operation which produced the timeout error.
        - + ExoTrackSelection - + ExoTrackSelection.Definition
        Contains of a subset of selected tracks belonging to a TrackGroup.
        - + ExoTrackSelection.Factory
        Factory for ExoTrackSelection instances.
        - + Extractor
        Extracts media data from a container format.
        - + Extractor.ReadResult
        Result values that can be returned by Extractor.read(ExtractorInput, PositionHolder).
        - + ExtractorAsserts
        Assertion methods for Extractor.
        - + ExtractorAsserts.AssertionConfig
        A config for the assertions made (e.g.
        - + ExtractorAsserts.AssertionConfig.Builder
        Builder for ExtractorAsserts.AssertionConfig instances.
        - + ExtractorAsserts.ExtractorFactory
        A factory for Extractor instances.
        - + ExtractorAsserts.SimulationConfig
        A config of different environments to simulate and extractor behaviours to test.
        - + ExtractorInput
        Provides data to be consumed by an Extractor.
        - + ExtractorOutput
        Receives stream level data extracted by an Extractor.
        - + ExtractorsFactory
        Factory for arrays of Extractor instances.
        - + ExtractorUtil
        Extractor related utility methods.
        - + FailOnCloseDataSink
        A DataSink that can simulate caching the bytes being written to it, and then failing to persist them when FailOnCloseDataSink.close() is called.
        - + FailOnCloseDataSink.Factory
        Factory to create a FailOnCloseDataSink.
        - + FakeAdaptiveDataSet
        Fake data set emulating the data of an adaptive media source.
        - + FakeAdaptiveDataSet.Factory
        Factory for FakeAdaptiveDataSets.
        - + FakeAdaptiveDataSet.Iterator
        MediaChunkIterator for the chunks defined by a fake adaptive data set.
        - + FakeAdaptiveMediaPeriod
        Fake MediaPeriod that provides tracks from the given TrackGroupArray.
        - + FakeAdaptiveMediaSource
        Fake MediaSource that provides a given timeline.
        - + FakeAudioRenderer - + FakeChunkSource
        Fake ChunkSource with adaptive media chunks of a given duration.
        - + FakeChunkSource.Factory
        Factory for a FakeChunkSource.
        - + FakeClock
        Fake Clock implementation that allows to advance the time manually to trigger pending timed messages.
        - + +FakeCryptoConfig + + + + + FakeDataSet
        Collection of FakeDataSet.FakeData to be served by a FakeDataSource.
        - + FakeDataSet.FakeData
        Container of fake data to be served by a FakeDataSource.
        - + FakeDataSet.FakeData.Segment
        A segment of FakeDataSet.FakeData.
        - + FakeDataSource
        A fake DataSource capable of simulating various scenarios.
        - + FakeDataSource.Factory
        Factory to create a FakeDataSource.
        - + FakeExoMediaDrm
        A fake implementation of ExoMediaDrm for use in tests.
        - + FakeExoMediaDrm.Builder
        Builder for FakeExoMediaDrm instances.
        - + FakeExoMediaDrm.LicenseServer
        An license server implementation to interact with FakeExoMediaDrm.
        - + FakeExtractorInput
        A fake ExtractorInput capable of simulating various scenarios.
        - + FakeExtractorInput.Builder
        Builder of FakeExtractorInput instances.
        - + FakeExtractorInput.SimulatedIOException
        Thrown when simulating an IOException.
        - + FakeExtractorOutput - + FakeMediaChunk - + FakeMediaChunkIterator - + FakeMediaClockRenderer
        Fake abstract Renderer which is also a MediaClock.
        - + FakeMediaPeriod
        Fake MediaPeriod that provides tracks from the given TrackGroupArray.
        - + FakeMediaPeriod.TrackDataFactory
        A factory to create the test data for a particular track.
        - + FakeMediaSource
        Fake MediaSource that provides a given timeline.
        - + FakeMediaSource.InitialTimeline
        A forwarding timeline to provide an initial timeline for fake multi window sources.
        - + +FakeMediaSourceFactory + +
        Fake MediaSourceFactory that creates a FakeMediaSource.
        + + + +FakeMetadataEntry + + + + + FakeRenderer
        Fake Renderer that supports any format with the matching track type.
        - + FakeSampleStream
        Fake SampleStream that outputs a given Format and any amount of items.
        - + FakeSampleStream.FakeSampleStreamItem - + FakeShuffleOrder
        Fake ShuffleOrder which returns a reverse order.
        - + FakeTimeline
        Fake Timeline which can be setup to return custom FakeTimeline.TimelineWindowDefinitions.
        - + FakeTimeline.TimelineWindowDefinition
        Definition used to define a FakeTimeline.
        - + FakeTrackOutput
        A fake TrackOutput.
        - + FakeTrackOutput.Factory
        Factory for FakeTrackOutput instances.
        - + FakeTrackSelection
        A fake ExoTrackSelection that only returns 1 fixed track, and allows querying the number of calls to its methods.
        - + FakeTrackSelector - + FakeVideoRenderer - + FfmpegAudioRenderer
        Decodes and renders audio using FFmpeg.
        - + FfmpegDecoderException
        Thrown when an FFmpeg decoder error occurs.
        - + FfmpegLibrary
        Configures and queries the underlying native library.
        - + FileDataSource
        A DataSource for reading local files.
        - + FileDataSource.Factory - + FileDataSource.FileDataSourceException
        Thrown when a FileDataSource encounters an error reading a file.
        - -FileDataSourceFactory -Deprecated. - - - - + FileTypes
        Defines common file type constants and helper methods.
        - + FileTypes.Type
        File types.
        - + FilterableManifest<T>
        A manifest that can generate copies of itself including only the streams specified by the given keys.
        - + FilteringHlsPlaylistParserFactory
        A HlsPlaylistParserFactory that includes only the streams identified by the given stream keys.
        - + FilteringManifestParser<T extends FilterableManifest<T>>
        A manifest parser that includes only the streams identified by the given stream keys.
        - + FixedTrackSelection
        A TrackSelection consisting of a single track.
        - -FlacConstants + +FlacConstants
        Defines constants used by the FLAC extractor.
        - + FlacDecoder
        Flac decoder.
        - + FlacDecoderException
        Thrown when an Flac decoder error occurs.
        - + FlacExtractor
        Facilitates the extraction of data from the FLAC container format.
        - + FlacExtractor
        Extracts data from FLAC container format.
        - + FlacExtractor.Flags
        Flags controlling the behavior of the extractor.
        - + FlacExtractor.Flags
        Flags controlling the behavior of the extractor.
        - + FlacFrameReader
        Reads and peeks FLAC frame elements according to the FLAC format specification.
        - + FlacFrameReader.SampleNumberHolder
        Holds a sample number.
        - + FlacLibrary
        Configures and queries the underlying native library.
        - + FlacMetadataReader
        Reads and peeks FLAC stream metadata elements according to the FLAC format specification.
        - + FlacMetadataReader.FlacStreamMetadataHolder - + FlacSeekTableSeekMap
        A SeekMap implementation for FLAC streams that contain a seek table.
        - + FlacStreamMetadata
        Holder for FLAC metadata.
        - + FlacStreamMetadata.SeekTable
        A FLAC seek table.
        - + FlagSet
        A set of integer flags.
        - + FlagSet.Builder
        A builder for FlagSet instances.
        - + FlvExtractor
        Extracts data from the FLV container format.
        - + Format
        Represents a media format.
        - + Format.Builder
        Builds Format instances.
        - + FormatHolder
        Holds a Format.
        - + ForwardingAudioSink
        An overridable AudioSink implementation forwarding all methods to another sink.
        - + ForwardingExtractorInput
        An overridable ExtractorInput implementation forwarding all methods to another input.
        - + ForwardingPlayer
        A Player that forwards operations to another Player.
        - + ForwardingTimeline
        An overridable Timeline implementation forwarding all methods to another timeline.
        - + FragmentedMp4Extractor
        Extracts data from the FMP4 container format.
        - + FragmentedMp4Extractor.Flags
        Flags controlling the behavior of the extractor.
        - -FrameworkMediaCrypto + +FrameworkCryptoConfig -
        An ExoMediaCrypto implementation that contains the necessary information to build or - update a framework MediaCrypto.
        + - + FrameworkMediaDrm
        An ExoMediaDrm implementation that wraps the framework MediaDrm.
        - + GaplessInfoHolder
        Holder for gapless playback information.
        - + Gav1Decoder
        Gav1 decoder.
        - + Gav1DecoderException
        Thrown when a libgav1 decoder error occurs.
        - + Gav1Library
        Configures and queries the underlying native library.
        - + GeobFrame
        GEOB (General Encapsulated Object) ID3 frame.
        - + GlUtil
        GL utilities.
        - + GlUtil.Attribute
        GL attribute, which can be attached to a buffer with GlUtil.Attribute.setBuffer(float[], int).
        - + +GlUtil.GlException + +
        Thrown when an OpenGL error occurs and GlUtil.glAssertionsEnabled is true.
        + + + +GlUtil.Program + +
        GL program.
        + + + GlUtil.Uniform
        GL uniform, which can be attached to a sampler using GlUtil.Uniform.setSamplerTexId(int, int).
        - -GvrAudioProcessor -Deprecated. -
        If you still need this component, please contact us by filing an issue on our issue tracker.
        + +GlUtil.UnsupportedEglVersionException + +
        Thrown when the required EGL version is not supported by the device.
        - + H262Reader
        Parses a continuous H262 byte stream and extracts individual frames.
        - + H263Reader
        Parses an ISO/IEC 14496-2 (MPEG-4 Part 2) or ITU-T Recommendation H.263 byte stream and extracts individual frames.
        - + H264Reader
        Parses a continuous H264 byte stream and extracts individual frames.
        - + H265Reader
        Parses a continuous H.265 byte stream and extracts individual frames.
        - + HandlerWrapper
        An interface to call through to a Handler.
        - + HandlerWrapper.Message
        A message obtained from the handler.
        - + HeartRating
        A rating expressed as "heart" or "no heart".
        - + HevcConfig
        HEVC configuration data.
        - + HlsDataSourceFactory
        Creates DataSources for HLS playlists, encryption and media chunks.
        - + HlsDownloader
        A downloader for HLS streams.
        - + HlsExtractorFactory
        Factory for HLS media chunk extractors.
        - + HlsManifest
        Holds a master playlist along with a snapshot of one of its media playlists.
        - + HlsMasterPlaylist
        Represents an HLS master playlist.
        - + HlsMasterPlaylist.Rendition
        A rendition (i.e.
        - + HlsMasterPlaylist.Variant
        A variant (i.e.
        - + HlsMediaChunkExtractor
        Extracts samples and track Formats from HlsMediaChunks.
        - + HlsMediaPeriod
        A MediaPeriod that loads an HLS stream.
        - + HlsMediaPlaylist
        Represents an HLS media playlist.
        - + HlsMediaPlaylist.Part
        A media part.
        - + HlsMediaPlaylist.PlaylistType
        Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE.
        - + HlsMediaPlaylist.RenditionReport
        A rendition report for an alternative rendition defined in another media playlist.
        - + HlsMediaPlaylist.Segment
        Media segment reference.
        - + HlsMediaPlaylist.SegmentBase
        The base for a HlsMediaPlaylist.Segment or a HlsMediaPlaylist.Part required for playback.
        - + HlsMediaPlaylist.ServerControl
        Server control attributes.
        - + HlsMediaSource
        An HLS MediaSource.
        - + HlsMediaSource.Factory
        Factory for HlsMediaSources.
        - + HlsMediaSource.MetadataType
        The types of metadata that can be extracted from HLS streams.
        - + HlsPlaylist
        Represents an HLS playlist.
        - + HlsPlaylistParser
        HLS playlists parsing logic.
        - + HlsPlaylistParser.DeltaUpdateException
        Exception thrown when merging a delta update fails.
        - + HlsPlaylistParserFactory
        Factory for HlsPlaylist parsers.
        - + HlsPlaylistTracker
        Tracks playlists associated to an HLS stream and provides snapshots.
        - + HlsPlaylistTracker.Factory
        Factory for HlsPlaylistTracker instances.
        - + HlsPlaylistTracker.PlaylistEventListener
        Called on playlist loading events.
        - + HlsPlaylistTracker.PlaylistResetException
        Thrown when the media sequence of a new snapshot indicates the server has reset.
        - + HlsPlaylistTracker.PlaylistStuckException
        Thrown when a playlist is considered to be stuck due to a server side error.
        - + HlsPlaylistTracker.PrimaryPlaylistListener
        Listener for primary playlist changes.
        - + HlsTrackMetadataEntry
        Holds metadata associated to an HLS media track.
        - + HlsTrackMetadataEntry.VariantInfo
        Holds attributes defined in an EXT-X-STREAM-INF tag.
        - + HorizontalTextInVerticalContextSpan
        A styling span for horizontal text in a vertical context.
        - + HostActivity
        A host activity for performing playback tests.
        - + HostActivity.HostedTest
        Interface for tests that run inside of a HostActivity.
        - + HttpDataSource
        An HTTP DataSource.
        - + HttpDataSource.BaseFactory
        Base implementation of HttpDataSource.Factory that sets default request properties.
        - + HttpDataSource.CleartextNotPermittedException
        Thrown when cleartext HTTP traffic is not permitted.
        - + HttpDataSource.Factory
        A factory for HttpDataSource instances.
        - + HttpDataSource.HttpDataSourceException
        Thrown when an error is encountered when trying to read from a HttpDataSource.
        - + HttpDataSource.HttpDataSourceException.Type
        The type of operation that produced the error.
        - + HttpDataSource.InvalidContentTypeException
        Thrown when the content type is invalid.
        - + HttpDataSource.InvalidResponseCodeException
        Thrown when an attempt to open a connection results in a response code not in the 2xx range.
        - + HttpDataSource.RequestProperties
        Stores HTTP request properties (aka HTTP headers) and provides methods to modify the headers in @@ -3845,330 +3908,324 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); state.
        - + HttpDataSourceTestEnv
        A JUnit Rule that creates test resources for HttpDataSource contract tests.
        - + HttpMediaDrmCallback
        A MediaDrmCallback that makes requests using HttpDataSource instances.
        - + HttpUtil
        Utility methods for HTTP.
        - + IcyDecoder
        Decodes ICY stream information.
        - + IcyHeaders
        ICY headers.
        - + IcyInfo
        ICY in-stream information.
        - + Id3Decoder
        Decodes ID3 tags.
        - + Id3Decoder.FramePredicate
        A predicate for determining whether individual frames should be decoded.
        - + Id3Frame
        Base class for ID3 frames.
        - + Id3Peeker
        Peeks data from the beginning of an ExtractorInput to determine if there is any ID3 tag.
        - + Id3Reader
        Parses ID3 data and extracts individual text information frames.
        - + IllegalSeekPositionException
        Thrown when an attempt is made to seek to a position that does not exist in the player's Timeline.
        - + ImaAdsLoader
        AdsLoader using the IMA SDK.
        - + ImaAdsLoader.Builder
        Builder for ImaAdsLoader.
        - + IndexSeekMap
        A SeekMap implementation based on a mapping between times and positions in the input stream.
        - + InitializationChunk
        A Chunk that uses an Extractor to decode initialization data for single track.
        - + InputReaderAdapterV30
        MediaParser.SeekableInputReader implementation wrapping a DataReader.
        - -IntArrayQueue - -
        Array-based unbounded queue for int primitives with amortized O(1) add and remove.
        - - - + InternalFrame
        Internal ID3 frame that is intended for use by the player.
        - + JpegExtractor
        Extracts JPEG image using the Exif format.
        - + KeysExpiredException
        Thrown when the drm keys loaded into an open session expire.
        - + LanguageFeatureSpan
        Marker interface for span classes that carry language features rather than style information.
        - + LatmReader
        Parses and extracts samples from an AAC/LATM elementary stream.
        - + LeanbackPlayerAdapter
        Leanback PlayerAdapter implementation for Player.
        - + LeastRecentlyUsedCacheEvictor
        Evicts least recently used cache files first.
        - + LibflacAudioRenderer
        Decodes and renders audio using the native Flac decoder.
        - + Libgav1VideoRenderer
        Decodes and renders video using libgav1 decoder.
        - + LibopusAudioRenderer
        Decodes and renders audio using the native Opus decoder.
        - + LibraryLoader
        Configurable loader for native libraries.
        - + LibvpxVideoRenderer
        Decodes and renders video using the native VP9 decoder.
        - -ListenerSet<T> + +ListenerSet<T extends @NonNull Object>
        A set of listeners.
        - + ListenerSet.Event<T>
        An event sent to a listener.
        - + ListenerSet.IterationFinishedEvent<T>
        An event sent to a listener when all other events sent during one Looper message queue iteration were handled by the listener.
        - + LivePlaybackSpeedControl
        Controls the playback speed while playing live content in order to maintain a steady target live offset.
        - + LoadControl
        Controls buffering of media.
        - + Loader
        Manages the background loading of Loader.Loadables.
        - + Loader.Callback<T extends Loader.Loadable>
        A callback to be notified of Loader events.
        - + Loader.Loadable
        An object that can be loaded using a Loader.
        - + Loader.LoadErrorAction - + Loader.ReleaseCallback
        A callback to be notified when a Loader has finished being released.
        - + Loader.UnexpectedLoaderException
        Thrown when an unexpected exception or error is encountered during loading.
        - + LoaderErrorThrower
        Conditionally throws errors affecting a Loader.
        - + LoaderErrorThrower.Dummy
        A LoaderErrorThrower that never throws.
        - + LoadErrorHandlingPolicy
        A policy that defines how load errors are handled.
        - + LoadErrorHandlingPolicy.FallbackOptions
        Holds information about the available fallback options.
        - + LoadErrorHandlingPolicy.FallbackSelection
        A selected fallback option.
        - + LoadErrorHandlingPolicy.FallbackType
        Fallback type.
        - + LoadErrorHandlingPolicy.LoadErrorInfo
        Holds information about a load task error.
        - + LoadEventInfo
        MediaSource load event information.
        - + LocalMediaDrmCallback
        A MediaDrmCallback that provides a fixed response to key requests.
        - + Log
        Wrapper around Log which allows to set the log level.
        - + LongArray
        An append-only, auto-growing long[].
        - + LoopingMediaSource Deprecated. -
        To loop a MediaSource indefinitely, use Player.setRepeatMode(int) +
        To loop a MediaSource indefinitely, use Player.setRepeatMode(int) instead of this class.
        - + MappingTrackSelector
        Base class for TrackSelectors that first establish a mapping between TrackGroups @@ -4176,1667 +4233,1708 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); renderer.
        - + MappingTrackSelector.MappedTrackInfo
        Provides mapped track information for each renderer.
        - + MaskingMediaPeriod
        Media period that defers calling MediaSource.createPeriod(MediaPeriodId, Allocator, long) on a given source until MaskingMediaPeriod.createPeriod(MediaPeriodId) has been called.
        - + MaskingMediaPeriod.PrepareListener
        Listener for preparation events.
        - + MaskingMediaSource
        A MediaSource that masks the Timeline with a placeholder until the actual media structure is known.
        - + MaskingMediaSource.PlaceholderTimeline
        A timeline with one dynamic window with a period of indeterminate duration.
        - + MatroskaExtractor
        Extracts data from the Matroska and WebM container formats.
        - + MatroskaExtractor.Flags
        Flags controlling the behavior of the extractor.
        - + MdtaMetadataEntry
        Stores extensible metadata with handler type 'mdta'.
        - + MediaChunk
        An abstract base class for Chunks that contain media samples.
        - + MediaChunkIterator
        Iterator for media chunk sequences.
        - + MediaClock
        Tracks the progression of media time.
        - + MediaCodecAdapter
        Abstracts MediaCodec operations.
        - + MediaCodecAdapter.Configuration
        Configuration parameters for a MediaCodecAdapter.
        - + MediaCodecAdapter.Factory
        A factory for MediaCodecAdapter instances.
        - + MediaCodecAdapter.OnFrameRenderedListener
        Listener to be called when an output frame has rendered on the output surface.
        - + MediaCodecAudioRenderer
        Decodes and renders audio using MediaCodec and an AudioSink.
        - + MediaCodecDecoderException
        Thrown when a failure occurs in a MediaCodec decoder.
        - + MediaCodecInfo
        Information about a MediaCodec for a given mime type.
        - + MediaCodecRenderer
        An abstract renderer that uses MediaCodec to decode samples for rendering.
        - + MediaCodecRenderer.DecoderInitializationException
        Thrown when a failure occurs instantiating a decoder.
        - + MediaCodecSelector
        Selector of MediaCodec instances.
        - + MediaCodecUtil
        A utility class for querying the available codecs.
        - + MediaCodecUtil.DecoderQueryException
        Thrown when an error occurs querying the device for its underlying media capabilities.
        - + MediaCodecVideoDecoderException
        Thrown when a failure occurs in a MediaCodec video decoder.
        - + MediaCodecVideoRenderer
        Decodes and renders video using MediaCodec.
        - + MediaCodecVideoRenderer.CodecMaxValues   - + MediaDrmCallback
        Performs ExoMediaDrm key and provisioning requests.
        - + MediaDrmCallbackException
        Thrown when an error occurs while executing a DRM key or provisioning request.
        - + MediaFormatUtil
        Helper class containing utility methods for managing MediaFormat instances.
        - + MediaItem
        Representation of a media item.
        - + MediaItem.AdsConfiguration
        Configuration for playing back linear ads with a media item.
        - + +MediaItem.AdsConfiguration.Builder + +
        Builder for MediaItem.AdsConfiguration instances.
        + + + MediaItem.Builder
        A builder for MediaItem instances.
        - -MediaItem.ClippingProperties + +MediaItem.ClippingConfiguration
        Optionally clips the media item to a custom start and end position.
        - + +MediaItem.ClippingConfiguration.Builder + +
        Builder for MediaItem.ClippingConfiguration instances.
        + + + +MediaItem.ClippingProperties +Deprecated. + + + + MediaItem.DrmConfiguration
        DRM configuration for a media item.
        - + +MediaItem.DrmConfiguration.Builder + + + + + MediaItem.LiveConfiguration
        Live playback configuration.
        - -MediaItem.PlaybackProperties + +MediaItem.LiveConfiguration.Builder + +
        Builder for MediaItem.LiveConfiguration instances.
        + + + +MediaItem.LocalConfiguration
        Properties for local playback.
        - + +MediaItem.PlaybackProperties +Deprecated. + + + + MediaItem.Subtitle +Deprecated. + + + + +MediaItem.SubtitleConfiguration
        Properties for a text track.
        - + +MediaItem.SubtitleConfiguration.Builder + +
        Builder for MediaItem.SubtitleConfiguration instances.
        + + + MediaItemConverter
        Converts between MediaItem and the Cast SDK's MediaQueueItem.
        - + MediaItemConverter
        Converts between Media2 MediaItem and ExoPlayer MediaItem.
        - + MediaLoadData
        Descriptor for data being loaded or selected by a MediaSource.
        - + MediaMetadata
        Metadata of a MediaItem, playlist, or a combination of multiple sources of Metadata.
        - + MediaMetadata.Builder
        A builder for MediaMetadata instances.
        - + MediaMetadata.FolderType
        The folder type of the media item.
        - + MediaMetadata.PictureType
        The picture type of the artwork.
        - + MediaParserChunkExtractor
        ChunkExtractor implemented on top of the platform's MediaParser.
        - + MediaParserExtractorAdapter
        ProgressiveMediaExtractor implemented on top of the platform's MediaParser.
        - + MediaParserHlsMediaChunkExtractor
        HlsMediaChunkExtractor implemented on top of the platform's MediaParser.
        - + MediaParserUtil
        Miscellaneous constants and utility methods related to the MediaParser integration.
        - + MediaPeriod
        Loads media corresponding to a Timeline.Period, and allows that media to be read.
        - + MediaPeriod.Callback
        A callback to be notified of MediaPeriod events.
        - + MediaPeriodAsserts
        Assertion methods for MediaPeriod.
        - + MediaPeriodAsserts.FilterableManifestMediaPeriodFactory<T extends FilterableManifest<T>>
        Interface to create media periods for testing based on a FilterableManifest.
        - + MediaPeriodId
        Identifies a specific playback of a Timeline.Period.
        - + MediaSessionConnector
        Connects a MediaSessionCompat to a Player.
        - + MediaSessionConnector.CaptionCallback
        Handles requests for enabling or disabling captions.
        - + MediaSessionConnector.CommandReceiver
        Receiver of media commands sent by a media controller.
        - + MediaSessionConnector.CustomActionProvider
        Provides a PlaybackStateCompat.CustomAction to be published and handles the action when sent by a media controller.
        - + MediaSessionConnector.DefaultMediaMetadataProvider
        Provides a default MediaMetadataCompat with properties and extras taken from the MediaDescriptionCompat of the MediaSessionCompat.QueueItem of the active queue item.
        - + MediaSessionConnector.MediaButtonEventHandler
        Handles a media button event.
        - + MediaSessionConnector.MediaMetadataProvider
        Provides a MediaMetadataCompat for a given player state.
        - + MediaSessionConnector.PlaybackActions
        Playback actions supported by the connector.
        - + MediaSessionConnector.PlaybackPreparer
        Interface to which playback preparation and play actions are delegated.
        - + MediaSessionConnector.QueueEditor
        Handles media session queue edits.
        - + MediaSessionConnector.QueueNavigator
        Handles queue navigation actions, and updates the media session queue by calling MediaSessionCompat.setQueue().
        - + MediaSessionConnector.RatingCallback
        Callback receiving a user rating for the active media item.
        - + MediaSource
        Defines and provides media to be played by an ExoPlayer.
        - + MediaSource.MediaPeriodId
        Identifier for a MediaPeriod.
        - + MediaSource.MediaSourceCaller
        A caller of media sources, which will be notified of source events.
        - + MediaSourceEventListener
        Interface for callbacks to be notified of MediaSource events.
        - + MediaSourceEventListener.EventDispatcher
        Dispatches events to MediaSourceEventListeners.
        - + MediaSourceFactory
        Factory for creating MediaSources from MediaItems.
        - + MediaSourceTestRunner
        A runner for MediaSource tests.
        - + MergingMediaSource
        Merges multiple MediaSources.
        - + MergingMediaSource.IllegalMergeException
        Thrown when a MergingMediaSource cannot merge its sources.
        - + MergingMediaSource.IllegalMergeException.Reason
        The reason the merge failed.
        - + Metadata
        A collection of metadata entries.
        - + Metadata.Entry
        A metadata entry.
        - + MetadataDecoder
        Decodes metadata from binary data.
        - + MetadataDecoderFactory
        A factory for MetadataDecoder instances.
        - + MetadataInputBuffer - + MetadataOutput
        Receives metadata output.
        - + MetadataRenderer
        A renderer for metadata.
        - + MetadataRetriever
        Retrieves the static metadata of MediaItems.
        - + MimeTypes
        Defines common MIME types and helper methods.
        - + MlltFrame
        MPEG location lookup table frame.
        - + MotionPhotoMetadata
        Metadata of a motion photo file.
        - + Mp3Extractor
        Extracts data from the MP3 container format.
        - + Mp3Extractor.Flags
        Flags controlling the behavior of the extractor.
        - + Mp4Extractor
        Extracts data from the MP4 container format.
        - + Mp4Extractor.Flags
        Flags controlling the behavior of the extractor.
        - + Mp4WebvttDecoder
        A SimpleSubtitleDecoder for Webvtt embedded in a Mp4 container file.
        - + MpegAudioReader
        Parses a continuous MPEG Audio byte stream and extracts individual frames.
        - + MpegAudioUtil
        Utility methods for handling MPEG audio streams.
        - + MpegAudioUtil.Header
        Stores the metadata for an MPEG audio frame.
        - + NalUnitUtil
        Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
        - + +NalUnitUtil.H265SpsData + +
        Holds data parsed from a H.265 sequence parameter set NAL unit.
        + + + NalUnitUtil.PpsData
        Holds data parsed from a picture parameter set NAL unit.
        - + NalUnitUtil.SpsData -
        Holds data parsed from a sequence parameter set NAL unit.
        +
        Holds data parsed from a H.264 sequence parameter set NAL unit.
        - + NetworkTypeObserver
        Observer for network type changes.
        - + NetworkTypeObserver.Config
        Configuration for NetworkTypeObserver.
        - + NetworkTypeObserver.Listener
        A listener for network type changes.
        - + NonNullApi
        Annotation to declare all type usages in the annotated instance as Nonnull, unless explicitly marked with a nullable annotation.
        - + NoOpCacheEvictor
        Evictor that doesn't ever evict cache files.
        - + NoSampleRenderer
        A Renderer implementation whose track type is C.TRACK_TYPE_NONE and does not consume data from its SampleStream.
        - + NotificationUtil
        Utility methods for displaying Notifications.
        - + NotificationUtil.Importance
        Notification channel importance levels.
        - + NoUidTimeline
        A timeline which wraps another timeline and overrides all window and period uids to 0.
        - + OfflineLicenseHelper
        Helper class to download, renew and release offline licenses.
        - + OggExtractor
        Extracts data from the Ogg container format.
        - + OkHttpDataSource
        An HttpDataSource that delegates to Square's Call.Factory.
        - + OkHttpDataSource.Factory - + OkHttpDataSourceFactory Deprecated. - + OpusDecoder
        Opus decoder.
        - + OpusDecoderException
        Thrown when an Opus decoder error occurs.
        - + OpusLibrary
        Configures and queries the underlying native library.
        - + OpusUtil
        Utility methods for handling Opus audio streams.
        - -OutputBuffer - -
        Output buffer decoded by a Decoder.
        - - - -OutputBuffer.Owner<S extends OutputBuffer> - -
        Buffer owner.
        - - - + OutputConsumerAdapterV30
        MediaParser.OutputConsumer implementation that redirects output to an ExtractorOutput.
        - + ParsableBitArray
        Wraps a byte array, providing methods that allow it to be read as a bitstream.
        - + ParsableByteArray
        Wraps a byte array, providing a set of methods for parsing data from it.
        - + ParsableNalUnitBitArray
        Wraps a byte array, providing methods that allow it to be read as a NAL unit bitstream.
        - + ParserException
        Thrown when an error occurs parsing media data and metadata.
        - + ParsingLoadable<T>
        A Loader.Loadable for objects that can be parsed from binary data using a ParsingLoadable.Parser.
        - + ParsingLoadable.Parser<T>
        Parses an object from loaded data.
        - + PassthroughSectionPayloadReader
        A SectionPayloadReader that directly outputs the section bytes as sample data.
        - + PercentageRating
        A rating expressed as a percentage.
        - + Period
        Encapsulates media content components over a contiguous period of time.
        - + PesReader
        Parses PES packet data and extracts samples.
        - + PgsDecoder
        A SimpleSubtitleDecoder for PGS subtitles.
        - + PictureFrame
        A picture parsed from a FLAC file.
        - + PlatformScheduler
        A Scheduler that uses JobScheduler.
        - + PlatformScheduler.PlatformSchedulerService
        A JobService that starts the target service if the requirements are met.
        - + PlaybackException
        Thrown when a non locally recoverable playback failure occurs.
        - + PlaybackException.ErrorCode
        Codes that identify causes of player errors.
        - + PlaybackException.FieldNumber
        Identifiers for fields in a Bundle which represents a playback exception.
        - + PlaybackOutput
        Class to capture output from a playback test.
        - + PlaybackParameters
        Parameters that apply to playback, including speed setting.
        - + PlaybackSessionManager
        Manager for active playback sessions.
        - + PlaybackSessionManager.Listener
        A listener for session updates.
        - + PlaybackStats
        Statistics about playbacks.
        - + PlaybackStats.EventTimeAndException
        Stores an exception with the event time at which it occurred.
        - + PlaybackStats.EventTimeAndFormat
        Stores a format with the event time at which it started being used, or null to indicate that no format was used.
        - + PlaybackStats.EventTimeAndPlaybackState
        Stores a playback state with the event time at which it became active.
        - + PlaybackStatsListener
        AnalyticsListener to gather PlaybackStats from the player.
        - + PlaybackStatsListener.Callback
        A listener for PlaybackStats updates.
        - + Player
        A media player interface defining traditional high-level functionality, such as the ability to play, pause, seek and query properties of the currently playing media.
        - + Player.Command
        Commands that can be executed on a Player.
        - + Player.Commands
        A set of commands.
        - + Player.Commands.Builder
        A builder for Player.Commands instances.
        - + Player.DiscontinuityReason
        Reasons for position discontinuities.
        - + Player.Event
        Events that can be reported via Player.Listener.onEvents(Player, Events).
        - + Player.EventListener Deprecated. - + Player.Events
        A set of events.
        - + Player.Listener
        Listener of all changes in the Player.
        - + Player.MediaItemTransitionReason
        Reasons for media item transitions.
        - + Player.PlaybackSuppressionReason
        Reason why playback is suppressed even though Player.getPlayWhenReady() is true.
        - + Player.PlayWhenReadyChangeReason
        Reasons for playWhenReady changes.
        - + Player.PositionInfo
        Position info describing a playback position involved in a discontinuity.
        - + Player.RepeatMode
        Repeat modes for playback.
        - + Player.State
        Playback state.
        - + Player.TimelineChangeReason
        Reasons for timeline changes.
        - + PlayerControlView
        A view for controlling Player instances.
        - + PlayerControlView.ProgressUpdateListener
        Listener to be notified when progress has been updated.
        - + PlayerControlView.VisibilityListener
        Listener to be notified about changes of the visibility of the UI control.
        - + PlayerEmsgHandler
        Handles all emsg messages from all media tracks for the player.
        - + PlayerEmsgHandler.PlayerEmsgCallback
        Callbacks for player emsg events encountered during DASH live stream.
        - + PlayerMessage
        Defines a player message which can be sent with a PlayerMessage.Sender and received by a PlayerMessage.Target.
        - + PlayerMessage.Sender
        A sender for messages.
        - + PlayerMessage.Target
        A target for messages.
        - + PlayerNotificationManager
        Starts, updates and cancels a media style notification reflecting the player state.
        - + PlayerNotificationManager.Builder
        A builder for PlayerNotificationManager instances.
        - + PlayerNotificationManager.CustomActionReceiver
        Defines and handles custom actions.
        - + PlayerNotificationManager.MediaDescriptionAdapter
        An adapter to provide content assets of the media currently playing.
        - + PlayerNotificationManager.NotificationListener
        A listener for changes to the notification.
        - + PlayerNotificationManager.Priority
        Priority of the notification (required for API 25 and lower).
        - + PlayerNotificationManager.Visibility
        Visibility of notification on the lock screen.
        - + PlayerView
        A high level view for Player media playbacks.
        - + PlayerView.ShowBuffering
        Determines when the buffering view is shown.
        - + PositionHolder
        Holds a position in the stream.
        - + PriorityDataSource
        A DataSource that can be used as part of a task registered with a PriorityTaskManager.
        - -PriorityDataSourceFactory + +PriorityDataSource.Factory -
        A DataSource.Factory that produces PriorityDataSource instances.
        + - + +PriorityDataSourceFactory +Deprecated. + + + + PriorityTaskManager
        Allows tasks with associated priorities to control how they proceed relative to one another.
        - + PriorityTaskManager.PriorityTooLowException
        Thrown when task attempts to proceed when another registered task has a higher priority.
        - + PrivateCommand
        Represents a private command as defined in SCTE35, Section 9.3.6.
        - + PrivFrame
        PRIV (Private) ID3 frame.
        - + ProgramInformation
        A parsed program information element.
        - + ProgressHolder
        Holds a progress percentage.
        - + ProgressiveDownloader
        A downloader for progressive media streams.
        - + ProgressiveMediaExtractor
        Extracts the contents of a container file from a progressive media stream.
        - + ProgressiveMediaExtractor.Factory
        Creates ProgressiveMediaExtractor instances.
        - + ProgressiveMediaSource
        Provides one period that loads data from a Uri and extracted using an Extractor.
        - + ProgressiveMediaSource.Factory - + PsExtractor
        Extracts data from the MPEG-2 PS container format.
        - + PsshAtomUtil
        Utility methods for handling PSSH atoms.
        - + RandomizedMp3Decoder
        Generates randomized, but correct amount of data on MP3 audio input.
        - + RandomTrackSelection
        An ExoTrackSelection whose selected track is updated randomly.
        - + RandomTrackSelection.Factory
        Factory for RandomTrackSelection instances.
        - + RangedUri
        Defines a range of data located at a reference uri.
        - + Rating
        A rating for media content.
        - + RawCcExtractor
        Extracts data from the RawCC container format.
        - + RawResourceDataSource
        A DataSource for reading a raw resource inside the APK.
        - + RawResourceDataSource.RawResourceDataSourceException
        Thrown when an IOException is encountered reading from a raw resource.
        - + Renderer
        Renders media read from a SampleStream.
        - + +Renderer.MessageType + +
        Represents a type of message that can be passed to a renderer.
        + + + Renderer.State
        The renderer states.
        - -Renderer.VideoScalingMode -Deprecated. - - - - + Renderer.WakeupListener
        Some renderers can signal when Renderer.render(long, long) should be called.
        - + RendererCapabilities
        Defines the capabilities of a Renderer.
        - + RendererCapabilities.AdaptiveSupport
        Level of renderer support for adaptive format switches.
        - + RendererCapabilities.Capabilities
        Combined renderer capabilities.
        - + RendererCapabilities.FormatSupport Deprecated.
        Use C.FormatSupport instead.
        - + RendererCapabilities.TunnelingSupport
        Level of renderer support for tunneling.
        - + RendererConfiguration
        The configuration of a Renderer.
        - + RenderersFactory -
        Builds Renderer instances for use by a SimpleExoPlayer.
        +
        Builds Renderer instances for use by an ExoPlayer.
        - + RepeatModeActionProvider
        Provides a custom action for toggling repeat modes.
        - + RepeatModeUtil
        Util class for repeat mode handling.
        - + RepeatModeUtil.RepeatToggleModes
        Set of repeat toggle modes.
        - + Representation
        A DASH representation.
        - + Representation.MultiSegmentRepresentation
        A DASH representation consisting of multiple segments.
        - + Representation.SingleSegmentRepresentation
        A DASH representation consisting of a single segment.
        - + Requirements
        Defines a set of device state requirements.
        - + Requirements.RequirementFlags
        Requirement flags.
        - + RequirementsWatcher
        Watches whether the Requirements are met and notifies the RequirementsWatcher.Listener on changes.
        - + RequirementsWatcher.Listener
        Notified when RequirementsWatcher instance first created and on changes whether the Requirements are met.
        - + ResolvingDataSource
        DataSource wrapper allowing just-in-time resolution of DataSpecs.
        - + ResolvingDataSource.Factory - + ResolvingDataSource.Resolver
        Resolves DataSpecs.
        - -ReusableBufferedOutputStream - -
        This is a subclass of BufferedOutputStream with a ReusableBufferedOutputStream.reset(OutputStream) method - that allows an instance to be re-used with another underlying output stream.
        - - - + RobolectricUtil
        Utility methods for Robolectric-based tests.
        - + RtmpDataSource
        A Real-Time Messaging Protocol (RTMP) DataSource.
        - + RtmpDataSource.Factory - + RtmpDataSourceFactory Deprecated. - + RtpAc3Reader
        Parses an AC3 byte stream carried on RTP packets, and extracts AC3 frames.
        - + RtpPacket
        Represents the header and the payload of an RTP packet.
        - + RtpPacket.Builder
        Builder class for an RtpPacket
        - + RtpPayloadFormat
        Represents the payload format used in RTP.
        - + RtpPayloadReader
        Extracts media samples from the payload of received RTP packets.
        - + RtpPayloadReader.Factory
        Factory of RtpPayloadReader instances.
        - + RtpUtils
        Utility methods for RTP.
        - + RtspMediaSource
        An Rtsp MediaSource
        - + RtspMediaSource.Factory
        Factory for RtspMediaSource
        - + RtspMediaSource.RtspPlaybackException
        Thrown when an exception or error is encountered during loading an RTSP stream.
        - + RubySpan
        A styling span for ruby text.
        - + RunnableFutureTask<R,​E extends Exception>
        A RunnableFuture that supports additional uninterruptible operations to query whether execution has started and finished.
        - + SampleQueue
        A queue of media samples.
        - + SampleQueue.UpstreamFormatChangedListener
        A listener for changes to the upstream format.
        - + SampleQueueMappingException
        Thrown when it is not possible to map a TrackGroup to a SampleQueue.
        - + SampleStream
        A stream of media samples (and associated format information).
        - + SampleStream.ReadDataResult - + SampleStream.ReadFlags - + Scheduler
        Schedules a service to be started in the foreground when some Requirements are met.
        - + SectionPayloadReader
        Reads section data.
        - + SectionReader
        Reads section data packets and feeds the whole sections to a given SectionPayloadReader.
        - + SeekMap
        Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream.
        - + SeekMap.SeekPoints
        Contains one or two SeekPoints.
        - + SeekMap.Unseekable
        A SeekMap that does not support seeking.
        - + SeekParameters
        Parameters that apply to seeking.
        - + SeekPoint
        Defines a seek point in a media stream.
        - + SegmentBase
        An approximate representation of a SegmentBase manifest element.
        - + SegmentBase.MultiSegmentBase
        A SegmentBase that consists of multiple segments.
        - + SegmentBase.SegmentList
        A SegmentBase.MultiSegmentBase that uses a SegmentList to define its segments.
        - + SegmentBase.SegmentTemplate
        A SegmentBase.MultiSegmentBase that uses a SegmentTemplate to define its segments.
        - + SegmentBase.SegmentTimelineElement
        Represents a timeline segment from the MPD's SegmentTimeline list.
        - + SegmentBase.SingleSegmentBase
        A SegmentBase that defines a single segment.
        - + SegmentDownloader<M extends FilterableManifest<M>>
        Base class for multi segment stream downloaders.
        - + SegmentDownloader.Segment
        Smallest unit of content to be downloaded.
        - + SeiReader
        Consumes SEI buffers, outputting contained CEA-608/708 messages to a TrackOutput.
        - + SequenceableLoader
        A loader that can proceed in approximate synchronization with other loaders.
        - + SequenceableLoader.Callback<T extends SequenceableLoader>
        A callback to be notified of SequenceableLoader events.
        - + ServerSideInsertedAdsMediaSource
        A MediaSource for server-side inserted ad breaks.
        - + ServerSideInsertedAdsUtil
        A static utility class with methods to work with server-side inserted ads.
        - + ServiceDescriptionElement
        Represents a service description element.
        - + SessionAvailabilityListener
        Listener of changes in the cast session availability.
        - + SessionCallbackBuilder
        Builds a MediaSession.SessionCallback with various collaborators.
        - + SessionCallbackBuilder.AllowedCommandProvider
        Provides allowed commands for MediaController.
        - + SessionCallbackBuilder.CustomCommandProvider
        Callbacks for querying what custom commands are supported, and for handling a custom command when a controller sends it.
        - + SessionCallbackBuilder.DefaultAllowedCommandProvider
        Default implementation of SessionCallbackBuilder.AllowedCommandProvider that behaves as follows: @@ -5847,1288 +5945,1356 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Controller is in the same package as the session.
        - + SessionCallbackBuilder.DisconnectedCallback
        Callback for handling controller disconnection.
        - + SessionCallbackBuilder.MediaIdMediaItemProvider
        A SessionCallbackBuilder.MediaItemProvider that creates media items containing only a media ID.
        - + SessionCallbackBuilder.MediaItemProvider
        Provides the MediaItem.
        - + SessionCallbackBuilder.PostConnectCallback
        Callback for handling extra initialization after the connection.
        - + SessionCallbackBuilder.RatingCallback
        Callback receiving a user rating for a specified media id.
        - + SessionCallbackBuilder.SkipCallback
        Callback receiving skip backward and skip forward.
        - + SessionPlayerConnector
        An implementation of SessionPlayer that wraps a given ExoPlayer Player instance.
        - + ShadowMediaCodecConfig
        A JUnit @Rule to configure Roboelectric's ShadowMediaCodec.
        - + ShuffleOrder
        Shuffled order of indices.
        - + ShuffleOrder.DefaultShuffleOrder
        The default ShuffleOrder implementation for random shuffle order.
        - + ShuffleOrder.UnshuffledShuffleOrder
        A ShuffleOrder implementation which does not shuffle.
        - + SilenceMediaSource
        Media source with a single period consisting of silent raw audio of a given duration.
        - + SilenceMediaSource.Factory
        Factory for SilenceMediaSources.
        - + SilenceSkippingAudioProcessor
        An AudioProcessor that skips silence in the input stream.
        - + SimpleCache
        A Cache implementation that maintains an in-memory representation.
        - -SimpleDecoder<I extends DecoderInputBuffer,​O extends OutputBuffer,​E extends DecoderException> + +SimpleDecoder<I extends DecoderInputBuffer,​O extends DecoderOutputBuffer,​E extends DecoderException>
        Base class for Decoders that use their own decode thread and decode each input buffer immediately into a corresponding output buffer.
        - + +SimpleDecoderOutputBuffer + +
        Buffer for SimpleDecoder output.
        + + + SimpleExoPlayer - -
        An ExoPlayer implementation that uses default Renderer components.
        +Deprecated. +
        Use ExoPlayer instead.
        - + SimpleExoPlayer.Builder - -
        A builder for SimpleExoPlayer instances.
        +Deprecated. +
        Use ExoPlayer.Builder instead.
        - + SimpleMetadataDecoder
        A MetadataDecoder base class that validates input buffers and discards any for which Buffer.isDecodeOnly() is true.
        - -SimpleOutputBuffer - -
        Buffer for SimpleDecoder output.
        - - - + SimpleSubtitleDecoder
        Base class for subtitle parsers that use their own decode thread.
        - + SinglePeriodAdTimeline
        A Timeline for sources that have ads.
        - + SinglePeriodTimeline
        A Timeline consisting of a single period and static window.
        - + SingleSampleMediaChunk
        A BaseMediaChunk for chunks consisting of a single raw sample.
        - + SingleSampleMediaSource
        Loads data at a given Uri as a single sample belonging to a single MediaPeriod.
        - + SingleSampleMediaSource.Factory - -SlidingPercentile + +SlidingPercentile
        Calculate any percentile over a sliding window of weighted values.
        - + SlowMotionData
        Holds information about the segments of slow motion playback within a track.
        - + SlowMotionData.Segment
        Holds information about a single segment of slow motion playback within a track.
        - + SmtaMetadataEntry
        Stores metadata from the Samsung smta box.
        - + SntpClient
        Static utility to retrieve the device time offset using SNTP.
        - + SntpClient.InitializationCallback - + SonicAudioProcessor
        An AudioProcessor that uses the Sonic library to modify audio speed/pitch/sample rate.
        - + SpannedSubject
        A Truth Subject for assertions on Spanned instances containing text styling.
        - + SpannedSubject.AbsoluteSized
        Allows assertions about the absolute size of a span.
        - + SpannedSubject.Aligned
        Allows assertions about the alignment of a span.
        - + SpannedSubject.AndSpanFlags
        Allows additional assertions to be made on the flags of matching spans.
        - + SpannedSubject.Colored
        Allows assertions about the color of a span.
        - + SpannedSubject.EmphasizedText
        Allows assertions about a span's text emphasis mark and its position.
        - + SpannedSubject.RelativeSized
        Allows assertions about the relative size of a span.
        - + SpannedSubject.RubyText
        Allows assertions about a span's ruby text and its position.
        - + SpannedSubject.Typefaced
        Allows assertions about the typeface of a span.
        - + SpannedSubject.WithSpanFlags
        Allows additional assertions to be made on the flags of matching spans.
        - + SpanUtil
        Utility methods for Android span styling.
        - + SphericalGLSurfaceView
        Renders a GL scene in a non-VR Activity that is affected by phone orientation and touch input.
        - + SphericalGLSurfaceView.VideoSurfaceListener
        Listener for the Surface to which video frames should be rendered.
        - + SpliceCommand
        Superclass for SCTE35 splice commands.
        - + SpliceInfoDecoder
        Decodes splice info sections and produces splice commands.
        - + SpliceInsertCommand
        Represents a splice insert command defined in SCTE35, Section 9.3.3.
        - + SpliceInsertCommand.ComponentSplice
        Holds splicing information for specific splice insert command components.
        - + SpliceNullCommand
        Represents a splice null command as defined in SCTE35, Section 9.3.1.
        - + SpliceScheduleCommand
        Represents a splice schedule command as defined in SCTE35, Section 9.3.2.
        - + SpliceScheduleCommand.ComponentSplice
        Holds splicing information for specific splice schedule command components.
        - + SpliceScheduleCommand.Event
        Represents a splice event as contained in a SpliceScheduleCommand.
        - + SsaDecoder
        A SimpleSubtitleDecoder for SSA/ASS.
        - + SsChunkSource
        A ChunkSource for SmoothStreaming.
        - + SsChunkSource.Factory
        Factory for SsChunkSources.
        - + SsDownloader
        A downloader for SmoothStreaming streams.
        - + SsManifest
        Represents a SmoothStreaming manifest.
        - + SsManifest.ProtectionElement
        Represents a protection element containing a single header.
        - + SsManifest.StreamElement
        Represents a StreamIndex element.
        - + SsManifestParser
        Parses SmoothStreaming client manifests.
        - + SsManifestParser.MissingFieldException
        Thrown if a required field is missing.
        - + SsMediaSource
        A SmoothStreaming MediaSource.
        - + SsMediaSource.Factory
        Factory for SsMediaSource.
        - + +StandaloneDatabaseProvider + +
        An SQLiteOpenHelper that provides instances of a standalone database.
        + + + StandaloneMediaClock
        A MediaClock whose position advances with real time based on the playback parameters when started.
        - + StarRating
        A rating expressed as a fractional number of stars.
        - + StartOffsetExtractorOutput
        An extractor output that wraps another extractor output and applies a give start byte offset to seek positions.
        - + StatsDataSource
        DataSource wrapper which keeps track of bytes transferred, redirected uris, and response headers.
        - + StreamKey
        A key for a subset of media that can be separately loaded (a "stream").
        - + StubExoPlayer
        An abstract ExoPlayer implementation that throws UnsupportedOperationException from every method.
        - + +StubPlayer + +
        An abstract Player implementation that throws UnsupportedOperationException from + every method.
        + + + StyledPlayerControlView
        A view for controlling Player instances.
        - + StyledPlayerControlView.OnFullScreenModeChangedListener
        Listener to be invoked to inform the fullscreen mode is changed.
        - + StyledPlayerControlView.ProgressUpdateListener
        Listener to be notified when progress has been updated.
        - + StyledPlayerControlView.VisibilityListener
        Listener to be notified about changes of the visibility of the UI control.
        - + StyledPlayerView
        A high level view for Player media playbacks.
        - + StyledPlayerView.ShowBuffering
        Determines when the buffering view is shown.
        - + SubripDecoder
        A SimpleSubtitleDecoder for SubRip.
        - + Subtitle
        A subtitle consisting of timed Cues.
        - + SubtitleDecoder - + SubtitleDecoderException
        Thrown when an error occurs decoding subtitle data.
        - + SubtitleDecoderFactory
        A factory for SubtitleDecoder instances.
        - + +SubtitleExtractor + +
        Generic extractor for extracting subtitles from various subtitle formats.
        + + + SubtitleInputBuffer - + SubtitleOutputBuffer
        Base class for SubtitleDecoder output buffers.
        - + SubtitleView
        A view for displaying subtitle Cues.
        - + SubtitleView.ViewType
        The type of View to use to display subtitles.
        - + SynchronousMediaCodecAdapter
        A MediaCodecAdapter that operates the underlying MediaCodec in synchronous mode.
        - + SynchronousMediaCodecAdapter.Factory
        A factory for SynchronousMediaCodecAdapter instances.
        - + SystemClock
        The standard implementation of Clock, an instance of which is available via Clock.DEFAULT.
        - + TeeAudioProcessor
        Audio processor that outputs its input unmodified and also outputs its input to a given sink.
        - + TeeAudioProcessor.AudioBufferSink
        A sink for audio buffers handled by the audio processor.
        - + TeeAudioProcessor.WavFileAudioBufferSink
        A sink for audio buffers that writes output audio as .wav files with a given path prefix.
        - + TeeDataSource
        Tees data into a DataSink as the data is read.
        - + TestDownloadManagerListener
        Allows tests to block for, and assert properties of, calls from a DownloadManager to its DownloadManager.Listener.
        - + TestExoPlayerBuilder
        A builder of SimpleExoPlayer instances for testing.
        - + TestPlayerRunHelper -
        Helper methods to block the calling thread until the provided SimpleExoPlayer instance - reaches a particular state.
        +
        Helper methods to block the calling thread until the provided ExoPlayer instance reaches + a particular state.
        - + TestUtil
        Utility methods for tests.
        - + TextAnnotation
        Properties of a text annotation (i.e.
        - + TextAnnotation.Position
        The possible positions of the annotation text relative to the base text.
        - + TextEmphasisSpan
        A styling span for text emphasis marks.
        - + TextEmphasisSpan.MarkFill
        The possible mark fills that can be used.
        - + TextEmphasisSpan.MarkShape
        The possible mark shapes that can be used.
        - + TextInformationFrame
        Text information ID3 frame.
        - + TextOutput
        Receives text output.
        - + TextRenderer
        A renderer for text.
        - + ThumbRating
        A rating expressed as "thumbs up" or "thumbs down".
        - + TimeBar
        Interface for time bar views that can display a playback position, buffered position, duration and ad markers, and that have a listener for scrubbing (seeking) events.
        - + TimeBar.OnScrubListener
        Listener for scrubbing events.
        - + TimedValueQueue<V>
        A utility class to keep a queue of values with timestamps.
        - + Timeline
        A flexible representation of the structure of media.
        - + Timeline.Period
        Holds information about a period in a Timeline.
        - + Timeline.RemotableTimeline
        A concrete class of Timeline to restore a Timeline instance from a Bundle sent by another process via IBinder.
        - + Timeline.Window
        Holds information about a window in a Timeline.
        - + TimelineAsserts
        Assertion methods for Timeline.
        - + TimelineQueueEditor - + TimelineQueueEditor.MediaDescriptionConverter
        Converts a MediaDescriptionCompat to a MediaItem.
        - + TimelineQueueEditor.MediaIdEqualityChecker
        Media description comparator comparing the media IDs.
        - + TimelineQueueEditor.QueueDataAdapter
        Adapter to get MediaDescriptionCompat of items in the queue and to notify the application about changes in the queue to sync the data structure backing the MediaSessionConnector.
        - + TimelineQueueNavigator
        An abstract implementation of the MediaSessionConnector.QueueNavigator that maps the windows of a Player's Timeline to the media session queue.
        - + TimeSignalCommand
        Represents a time signal command as defined in SCTE35, Section 9.3.4.
        - + TimestampAdjuster
        Adjusts and offsets sample timestamps.
        - + TimestampAdjusterProvider
        Provides TimestampAdjuster instances for use during HLS playbacks.
        - + TimeToFirstByteEstimator
        Provides an estimate of the time to first byte of a transfer.
        - + TraceUtil
        Calls through to Trace methods on supported API levels.
        - + Track
        Encapsulates information describing an MP4 track.
        - + Track.Transformation
        The transformation to apply to samples in the track, if any.
        - + TrackEncryptionBox
        Encapsulates information parsed from a track encryption (tenc) box or sample group description (sgpd) box in an MP4 stream.
        - + TrackGroup
        Defines an immutable group of tracks identified by their format identity.
        - + TrackGroupArray
        An immutable array of TrackGroups.
        - + TrackNameProvider
        Converts Formats to user readable track names.
        - + TrackOutput
        Receives track level data extracted by an Extractor.
        - + TrackOutput.CryptoData
        Holds data required to decrypt a sample.
        - + TrackOutput.SampleDataPart
        Defines the part of the sample data to which a call to TrackOutput.sampleData(com.google.android.exoplayer2.upstream.DataReader, int, boolean) corresponds.
        - + TrackSelection
        A track selection consisting of a static subset of selected tracks belonging to a TrackGroup.
        - + +TrackSelection.Type + +
        Represents a type track selection.
        + + + TrackSelectionArray
        An array of TrackSelections.
        - + TrackSelectionDialogBuilder
        Builder for a dialog with a TrackSelectionView.
        - + TrackSelectionDialogBuilder.DialogCallback
        Callback which is invoked when a track selection has been made.
        - + +TrackSelectionOverrides + +
        Forces the selection of the specified tracks in TrackGroups.
        + + + +TrackSelectionOverrides.Builder + + + + + +TrackSelectionOverrides.TrackSelectionOverride + + + + + TrackSelectionParameters
        Constraint parameters for track selection.
        - + TrackSelectionParameters.Builder - + TrackSelectionUtil
        Track selection related utility methods.
        - + TrackSelectionUtil.AdaptiveTrackSelectionFactory
        Functional interface to create a single adaptive track selection.
        - + TrackSelectionView
        A view for making track selections.
        - + TrackSelectionView.TrackSelectionListener
        Listener for changes to the selected tracks.
        - + TrackSelector
        The component of an ExoPlayer responsible for selecting tracks to be consumed by each of the player's Renderers.
        - + TrackSelector.InvalidationListener
        Notified when selections previously made by a TrackSelector are no longer valid.
        - + TrackSelectorResult
        The result of a TrackSelector operation.
        - + +TracksInfo + +
        Immutable information (TracksInfo.TrackGroupInfo) about tracks.
        + + + +TracksInfo.TrackGroupInfo + +
        Information about tracks in a TrackGroup: their C.TrackType, if their format is + supported by the player and if they are selected for playback.
        + + + +TranscodingTransformer + +
        A transcoding transformer to transform media inputs.
        + + + +TranscodingTransformer.Builder + +
        A builder for TranscodingTransformer instances.
        + + + +TranscodingTransformer.Listener + +
        A listener for the transformation events.
        + + + +TranscodingTransformer.ProgressState + +
        Progress state.
        + + + TransferListener
        A listener of data transfer events.
        - + Transformer
        A transformer to transform media inputs.
        - + Transformer.Builder
        A builder for Transformer instances.
        - + Transformer.Listener
        A listener for the transformation events.
        - + Transformer.ProgressState
        Progress state.
        - + +TrueHdSampleRechunker + +
        Rechunks TrueHD sample data into groups of Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT samples.
        + + + TsExtractor
        Extracts data from the MPEG-2 TS container format.
        - + TsExtractor.Mode
        Modes for the extractor.
        - + TsPayloadReader
        Parses TS packet payload data.
        - + TsPayloadReader.DvbSubtitleInfo
        Holds information about a DVB subtitle, as defined in ETSI EN 300 468 V1.11.1 section 6.2.41.
        - + TsPayloadReader.EsInfo
        Holds information associated with a PMT entry.
        - + TsPayloadReader.Factory
        Factory of TsPayloadReader instances.
        - + TsPayloadReader.Flags
        Contextual flags indicating the presence of indicators in the TS packet or PES packet headers.
        - + TsPayloadReader.TrackIdGenerator
        Generates track ids for initializing TsPayloadReaders' TrackOutputs.
        - + TsUtil
        Utilities method for extracting MPEG-TS streams.
        - + TtmlDecoder
        A SimpleSubtitleDecoder for TTML supporting the DFXP presentation profile.
        - + Tx3gDecoder - + UdpDataSource
        A UDP DataSource.
        - + UdpDataSource.UdpDataSourceException
        Thrown when an error is encountered when trying to read from a UdpDataSource.
        - + UnknownNull
        Annotation for specifying unknown nullness.
        - + UnrecognizedInputFormatException
        Thrown if the input format was not recognized.
        - + UnsupportedDrmException
        Thrown when the requested DRM scheme is not supported.
        - + UnsupportedDrmException.Reason
        The reason for the exception.
        - -UnsupportedMediaCrypto - -
        ExoMediaCrypto type that cannot be used to handle any type of protected content.
        - - - + UriUtil
        Utility methods for manipulating URIs.
        - + UrlLinkFrame
        Url link ID3 frame.
        - + UrlTemplate
        A template from which URLs can be built.
        - + UtcTimingElement
        Represents a UTCTiming element.
        - + Util
        Miscellaneous utility methods.
        - + VersionTable -
        Utility methods for accessing versions of ExoPlayer database components.
        +
        Utility methods for accessing versions of media library database components.
        - + VideoDecoderGLSurfaceView -
        GLSurfaceView implementing VideoDecoderOutputBufferRenderer for rendering VideoDecoderOutputBuffers.
        +
        GLSurfaceView implementing VideoDecoderOutputBufferRenderer for rendering VideoDecoderOutputBuffers.
        - -VideoDecoderInputBuffer - -
        Input buffer to a video decoder.
        - - - -VideoDecoderOutputBuffer + +VideoDecoderOutputBuffer
        Video decoder output buffer containing video frame data.
        - + VideoDecoderOutputBufferRenderer - + - + VideoFrameMetadataListener
        A listener for metadata corresponding to video frames being rendered.
        - + VideoFrameReleaseHelper
        Helps a video Renderer release frames to a Surface.
        - -VideoListener -Deprecated. - - - - + VideoRendererEventListener
        Listener of video Renderer events.
        - + VideoRendererEventListener.EventDispatcher
        Dispatches events to a VideoRendererEventListener.
        - + VideoSize
        Represents the video size.
        - + VorbisBitArray
        Wraps a byte array, providing methods that allow it to be read as a Vorbis bitstream.
        - + VorbisComment
        A vorbis comment.
        - + VorbisUtil
        Utility methods for parsing Vorbis streams.
        - + VorbisUtil.CommentHeader
        Vorbis comment header.
        - + VorbisUtil.Mode
        Vorbis setup header modes.
        - + VorbisUtil.VorbisIdHeader
        Vorbis identification header.
        - + VpxDecoder
        Vpx decoder.
        - + VpxDecoderException
        Thrown when a libvpx decoder error occurs.
        - + VpxLibrary
        Configures and queries the underlying native library.
        - + WavExtractor
        Extracts data from WAV byte streams.
        - + WavUtil
        Utilities for handling WAVE files.
        - + WebServerDispatcher
        A Dispatcher for MockWebServer that allows per-path customisation of the static data served.
        - + WebServerDispatcher.Resource
        A resource served by WebServerDispatcher.
        - + WebServerDispatcher.Resource.Builder - + WebvttCssStyle
        Style object of a Css style block in a Webvtt file.
        - + WebvttCssStyle.FontSizeUnit
        Font size unit enum.
        - + WebvttCssStyle.StyleFlags
        Style flag enum.
        - + WebvttCueInfo
        A representation of a WebVTT cue.
        - + WebvttCueParser
        Parser for WebVTT cues.
        - + WebvttDecoder
        A SimpleSubtitleDecoder for WebVTT.
        - + WebvttExtractor
        A special purpose extractor for WebVTT content in HLS.
        - + WebvttParserUtil
        Utility methods for parsing WebVTT data.
        - + WidevineUtil
        Utility methods for Widevine.
        - + WorkManagerScheduler
        A Scheduler that uses WorkManager.
        - + WorkManagerScheduler.SchedulerWorker
        A Worker that starts the target service if the requirements are met.
        - + WritableDownloadIndex
        A writable index of Downloads.
        - + XmlPullParserUtil
        XmlPullParser utility methods.
        diff --git a/docs/doc/reference/allclasses.html b/docs/doc/reference/allclasses.html index 288afe5bcf..5d8782d8f4 100644 --- a/docs/doc/reference/allclasses.html +++ b/docs/doc/reference/allclasses.html @@ -109,6 +109,7 @@
      • AspectRatioFrameLayout.AspectRatioListener
      • AspectRatioFrameLayout.ResizeMode
      • Assertions
      • +
      • AssetContentProvider
      • AssetDataSource
      • AssetDataSource.AssetDataSourceException
      • AtomicFile
      • @@ -117,7 +118,6 @@
      • AudioCapabilities
      • AudioCapabilitiesReceiver
      • AudioCapabilitiesReceiver.Listener
      • -
      • AudioListener
      • AudioProcessor
      • AudioProcessor.AudioFormat
      • AudioProcessor.UnhandledAudioFormatException
      • @@ -158,7 +158,7 @@
      • Buffer
      • Bundleable
      • Bundleable.Creator
      • -
      • BundleableUtils
      • +
      • BundleableUtil
      • BundledChunkExtractor
      • BundledExtractorsAdapter
      • BundledHlsMediaChunkExtractor
      • @@ -171,6 +171,7 @@
      • C.AudioContentType
      • C.AudioFlags
      • C.AudioFocusGain
      • +
      • C.AudioManagerOffloadMode
      • C.AudioUsage
      • C.BufferFlags
      • C.ColorRange
      • @@ -178,6 +179,7 @@
      • C.ColorTransfer
      • C.ContentType
      • C.CryptoMode
      • +
      • C.CryptoType
      • C.DataType
      • C.Encoding
      • C.FormatSupport
      • @@ -186,8 +188,11 @@
      • C.Projection
      • C.RoleFlags
      • C.SelectionFlags
      • +
      • C.SelectionReason
      • C.StereoMode
      • C.StreamType
      • +
      • C.TrackType
      • +
      • C.VideoChangeFrameRateStrategy
      • C.VideoOutputMode
      • C.VideoScalingMode
      • C.WakeMode
      • @@ -199,14 +204,12 @@
      • CacheDataSink
      • CacheDataSink.CacheDataSinkException
      • CacheDataSink.Factory
      • -
      • CacheDataSinkFactory
      • CacheDataSource
      • CacheDataSource.CacheIgnoredReason
      • CacheDataSource.EventListener
      • CacheDataSource.Factory
      • CacheDataSource.Flags
      • -
      • CacheDataSourceFactory
      • -
      • CachedRegionTracker
      • +
      • CachedRegionTracker
      • CacheEvictor
      • CacheKeyFactory
      • CacheSpan
      • @@ -254,7 +257,6 @@
      • ContentDataSource.ContentDataSourceException
      • ContentMetadata
      • ContentMetadataMutations
      • -
      • ControlDispatcher
      • CopyOnWriteMultiset
      • CronetDataSource
      • CronetDataSource.Factory
      • @@ -262,6 +264,8 @@
      • CronetDataSourceFactory
      • CronetEngineWrapper
      • CronetUtil
      • +
      • CryptoConfig
      • +
      • CryptoException
      • CryptoInfo
      • Cue
      • Cue.AnchorType
      • @@ -269,6 +273,8 @@
      • Cue.LineType
      • Cue.TextSizeType
      • Cue.VerticalType
      • +
      • CueDecoder
      • +
      • CueEncoder
      • DashChunkSource
      • DashChunkSource.Factory
      • DashDownloader
      • @@ -296,6 +302,7 @@
      • DataSourceContractTest.TestResource.Builder
      • DataSourceException
      • DataSourceInputStream
      • +
      • DataSourceUtil
      • DataSpec
      • DataSpec.Builder
      • DataSpec.Flags
      • @@ -309,11 +316,12 @@
      • DecoderInputBuffer
      • DecoderInputBuffer.BufferReplacementMode
      • DecoderInputBuffer.InsufficientCapacityException
      • +
      • DecoderOutputBuffer
      • +
      • DecoderOutputBuffer.Owner
      • DecoderReuseEvaluation
      • DecoderReuseEvaluation.DecoderDiscardReasons
      • DecoderReuseEvaluation.DecoderReuseResult
      • DecoderVideoRenderer
      • -
      • DecryptionException
      • DefaultAllocator
      • DefaultAudioSink
      • DefaultAudioSink.AudioProcessorChain
      • @@ -325,13 +333,13 @@
      • DefaultCastOptionsProvider
      • DefaultCompositeSequenceableLoaderFactory
      • DefaultContentMetadata
      • -
      • DefaultControlDispatcher
      • DefaultDashChunkSource
      • DefaultDashChunkSource.Factory
      • DefaultDashChunkSource.RepresentationHolder
      • DefaultDashChunkSource.RepresentationSegmentIterator
      • DefaultDatabaseProvider
      • DefaultDataSource
      • +
      • DefaultDataSource.Factory
      • DefaultDataSourceFactory
      • DefaultDownloaderFactory
      • DefaultDownloadIndex
      • @@ -348,12 +356,12 @@
      • DefaultHlsPlaylistTracker
      • DefaultHttpDataSource
      • DefaultHttpDataSource.Factory
      • -
      • DefaultHttpDataSourceFactory
      • DefaultLivePlaybackSpeedControl
      • DefaultLivePlaybackSpeedControl.Builder
      • DefaultLoadControl
      • DefaultLoadControl.Builder
      • DefaultLoadErrorHandlingPolicy
      • +
      • DefaultMediaCodecAdapterFactory
      • DefaultMediaDescriptionAdapter
      • DefaultMediaItemConverter
      • DefaultMediaItemConverter
      • @@ -379,9 +387,8 @@
      • DefaultTsPayloadReaderFactory
      • DefaultTsPayloadReaderFactory.Flags
      • Descriptor
      • -
      • DeviceInfo
      • -
      • DeviceInfo.PlaybackType
      • -
      • DeviceListener
      • +
      • DeviceInfo
      • +
      • DeviceInfo.PlaybackType
      • DolbyVisionConfig
      • Download
      • Download.FailureReason
      • @@ -448,7 +455,6 @@
      • EventStream
      • ExoDatabaseProvider
      • ExoHostedTest
      • -
      • ExoMediaCrypto
      • ExoMediaDrm
      • ExoMediaDrm.AppManagedProvider
      • ExoMediaDrm.KeyRequest
      • @@ -466,9 +472,9 @@
      • ExoPlayer.AudioOffloadListener
      • ExoPlayer.Builder
      • ExoPlayer.DeviceComponent
      • -
      • ExoPlayer.MetadataComponent
      • ExoPlayer.TextComponent
      • ExoPlayer.VideoComponent
      • +
      • ExoplayerCuesDecoder
      • ExoPlayerLibraryInfo
      • ExoPlayerTestRunner
      • ExoPlayerTestRunner.Builder
      • @@ -499,6 +505,7 @@
      • FakeChunkSource
      • FakeChunkSource.Factory
      • FakeClock
      • +
      • FakeCryptoConfig
      • FakeDataSet
      • FakeDataSet.FakeData
      • FakeDataSet.FakeData.Segment
      • @@ -518,6 +525,8 @@
      • FakeMediaPeriod.TrackDataFactory
      • FakeMediaSource
      • FakeMediaSource.InitialTimeline
      • +
      • FakeMediaSourceFactory
      • +
      • FakeMetadataEntry
      • FakeRenderer
      • FakeSampleStream
      • FakeSampleStream.FakeSampleStreamItem
      • @@ -535,14 +544,13 @@
      • FileDataSource
      • FileDataSource.Factory
      • FileDataSource.FileDataSourceException
      • -
      • FileDataSourceFactory
      • FileTypes
      • FileTypes.Type
      • FilterableManifest
      • FilteringHlsPlaylistParserFactory
      • FilteringManifestParser
      • FixedTrackSelection
      • -
      • FlacConstants
      • +
      • FlacConstants
      • FlacDecoder
      • FlacDecoderException
      • FlacExtractor
      • @@ -569,7 +577,7 @@
      • ForwardingTimeline
      • FragmentedMp4Extractor
      • FragmentedMp4Extractor.Flags
      • -
      • FrameworkMediaCrypto
      • +
      • FrameworkCryptoConfig
      • FrameworkMediaDrm
      • GaplessInfoHolder
      • Gav1Decoder
      • @@ -578,8 +586,10 @@
      • GeobFrame
      • GlUtil
      • GlUtil.Attribute
      • +
      • GlUtil.GlException
      • +
      • GlUtil.Program
      • GlUtil.Uniform
      • -
      • GvrAudioProcessor
      • +
      • GlUtil.UnsupportedEglVersionException
      • H262Reader
      • H263Reader
      • H264Reader
      • @@ -648,7 +658,6 @@
      • IndexSeekMap
      • InitializationChunk
      • InputReaderAdapterV30
      • -
      • IntArrayQueue
      • InternalFrame
      • JpegExtractor
      • KeysExpiredException
      • @@ -716,12 +725,20 @@
      • MediaFormatUtil
      • MediaItem
      • MediaItem.AdsConfiguration
      • +
      • MediaItem.AdsConfiguration.Builder
      • MediaItem.Builder
      • +
      • MediaItem.ClippingConfiguration
      • +
      • MediaItem.ClippingConfiguration.Builder
      • MediaItem.ClippingProperties
      • MediaItem.DrmConfiguration
      • +
      • MediaItem.DrmConfiguration.Builder
      • MediaItem.LiveConfiguration
      • +
      • MediaItem.LiveConfiguration.Builder
      • +
      • MediaItem.LocalConfiguration
      • MediaItem.PlaybackProperties
      • MediaItem.Subtitle
      • +
      • MediaItem.SubtitleConfiguration
      • +
      • MediaItem.SubtitleConfiguration.Builder
      • MediaItemConverter
      • MediaItemConverter
      • MediaLoadData
      • @@ -780,6 +797,7 @@
      • MpegAudioUtil
      • MpegAudioUtil.Header
      • NalUnitUtil
      • +
      • NalUnitUtil.H265SpsData
      • NalUnitUtil.PpsData
      • NalUnitUtil.SpsData
      • NetworkTypeObserver
      • @@ -800,8 +818,6 @@
      • OpusDecoderException
      • OpusLibrary
      • OpusUtil
      • -
      • OutputBuffer
      • -
      • OutputBuffer.Owner
      • OutputConsumerAdapterV30
      • ParsableBitArray
      • ParsableByteArray
      • @@ -865,6 +881,7 @@
      • PlayerView.ShowBuffering
      • PositionHolder
      • PriorityDataSource
      • +
      • PriorityDataSource.Factory
      • PriorityDataSourceFactory
      • PriorityTaskManager
      • PriorityTaskManager.PriorityTooLowException
      • @@ -888,8 +905,8 @@
      • RawResourceDataSource
      • RawResourceDataSource.RawResourceDataSourceException
      • Renderer
      • +
      • Renderer.MessageType
      • Renderer.State
      • -
      • Renderer.VideoScalingMode
      • Renderer.WakeupListener
      • RendererCapabilities
      • RendererCapabilities.AdaptiveSupport
      • @@ -911,7 +928,6 @@
      • ResolvingDataSource
      • ResolvingDataSource.Factory
      • ResolvingDataSource.Resolver
      • -
      • ReusableBufferedOutputStream
      • RobolectricUtil
      • RtmpDataSource
      • RtmpDataSource.Factory
      • @@ -977,17 +993,17 @@
      • SilenceSkippingAudioProcessor
      • SimpleCache
      • SimpleDecoder
      • +
      • SimpleDecoderOutputBuffer
      • SimpleExoPlayer
      • SimpleExoPlayer.Builder
      • SimpleMetadataDecoder
      • -
      • SimpleOutputBuffer
      • SimpleSubtitleDecoder
      • SinglePeriodAdTimeline
      • SinglePeriodTimeline
      • SingleSampleMediaChunk
      • SingleSampleMediaSource
      • SingleSampleMediaSource.Factory
      • -
      • SlidingPercentile
      • +
      • SlidingPercentile
      • SlowMotionData
      • SlowMotionData.Segment
      • SmtaMetadataEntry
      • @@ -1026,12 +1042,14 @@
      • SsManifestParser.MissingFieldException
      • SsMediaSource
      • SsMediaSource.Factory
      • +
      • StandaloneDatabaseProvider
      • StandaloneMediaClock
      • StarRating
      • StartOffsetExtractorOutput
      • StatsDataSource
      • StreamKey
      • StubExoPlayer
      • +
      • StubPlayer
      • StyledPlayerControlView
      • StyledPlayerControlView.OnFullScreenModeChangedListener
      • StyledPlayerControlView.ProgressUpdateListener
      • @@ -1043,6 +1061,7 @@
      • SubtitleDecoder
      • SubtitleDecoderException
      • SubtitleDecoderFactory
      • +
      • SubtitleExtractor
      • SubtitleInputBuffer
      • SubtitleOutputBuffer
      • SubtitleView
      • @@ -1095,9 +1114,13 @@
      • TrackOutput.CryptoData
      • TrackOutput.SampleDataPart
      • TrackSelection
      • +
      • TrackSelection.Type
      • TrackSelectionArray
      • TrackSelectionDialogBuilder
      • TrackSelectionDialogBuilder.DialogCallback
      • +
      • TrackSelectionOverrides
      • +
      • TrackSelectionOverrides.Builder
      • +
      • TrackSelectionOverrides.TrackSelectionOverride
      • TrackSelectionParameters
      • TrackSelectionParameters.Builder
      • TrackSelectionUtil
      • @@ -1107,11 +1130,18 @@
      • TrackSelector
      • TrackSelector.InvalidationListener
      • TrackSelectorResult
      • +
      • TracksInfo
      • +
      • TracksInfo.TrackGroupInfo
      • +
      • TranscodingTransformer
      • +
      • TranscodingTransformer.Builder
      • +
      • TranscodingTransformer.Listener
      • +
      • TranscodingTransformer.ProgressState
      • TransferListener
      • Transformer
      • Transformer.Builder
      • Transformer.Listener
      • Transformer.ProgressState
      • +
      • TrueHdSampleRechunker
      • TsExtractor
      • TsExtractor.Mode
      • TsPayloadReader
      • @@ -1129,7 +1159,6 @@
      • UnrecognizedInputFormatException
      • UnsupportedDrmException
      • UnsupportedDrmException.Reason
      • -
      • UnsupportedMediaCrypto
      • UriUtil
      • UrlLinkFrame
      • UrlTemplate
      • @@ -1137,12 +1166,10 @@
      • Util
      • VersionTable
      • VideoDecoderGLSurfaceView
      • -
      • VideoDecoderInputBuffer
      • -
      • VideoDecoderOutputBuffer
      • +
      • VideoDecoderOutputBuffer
      • VideoDecoderOutputBufferRenderer
      • VideoFrameMetadataListener
      • VideoFrameReleaseHelper
      • -
      • VideoListener
      • VideoRendererEventListener
      • VideoRendererEventListener.EventDispatcher
      • VideoSize
      • diff --git a/docs/doc/reference/allpackages-index.html b/docs/doc/reference/allpackages-index.html index b1bd57364a..aa307180e2 100644 --- a/docs/doc/reference/allpackages-index.html +++ b/docs/doc/reference/allpackages-index.html @@ -124,35 +124,27 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));   -com.google.android.exoplayer2.device -  - - com.google.android.exoplayer2.drm   - + com.google.android.exoplayer2.ext.av1   - + com.google.android.exoplayer2.ext.cast   - + com.google.android.exoplayer2.ext.cronet   - + com.google.android.exoplayer2.ext.ffmpeg   - -com.google.android.exoplayer2.ext.flac -  - -com.google.android.exoplayer2.ext.gvr +com.google.android.exoplayer2.ext.flac   diff --git a/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html index 460a936697..6dfc96dea1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html +++ b/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html @@ -316,8 +316,8 @@ extends T int -getNextWindowIndex​(int windowIndex, - int repeatMode, +getNextWindowIndex​(int windowIndex, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)
        Returns the index of the window after the window at index windowIndex depending on the @@ -343,8 +343,8 @@ extends T int -getPreviousWindowIndex​(int windowIndex, - int repeatMode, +getPreviousWindowIndex​(int windowIndex, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)
        Returns the index of the window before the window at index windowIndex depending on the @@ -380,7 +380,7 @@ extends T

        Methods inherited from class com.google.android.exoplayer2.Timeline

        -equals, getNextPeriodIndex, getPeriod, getPeriodCount, getPeriodPosition, getPeriodPosition, getWindow, getWindowCount, hashCode, isEmpty, isLastPeriod, toBundle, toBundle +equals, getNextPeriodIndex, getPeriod, getPeriodCount, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, getWindowCount, hashCode, isEmpty, isLastPeriod, toBundle, toBundle
      - + - +
    @@ -254,156 +254,219 @@ implements +boolean +canAdvertiseSession() + +
    Returns whether the player can be used to advertise a media session.
    + + + void clearMediaItems()
    Clears the playlist.
    - + protected Player.Commands getAvailableCommands​(Player.Commands permanentAvailableCommands)
    Returns the Player.Commands available in the player.
    - + int getBufferedPercentage() -
    Returns an estimate of the percentage in the current content window or ad up to which data is +
    Returns an estimate of the percentage in the current content or ad up to which data is buffered, or 0 if no estimate is available.
    - -long -getContentDuration() - -
    If Player.isPlayingAd() returns true, returns the duration of the current content - window in milliseconds, or C.TIME_UNSET if the duration is not known.
    - - long -getCurrentLiveOffset() +getContentDuration() -
    Returns the offset of the current playback position from the live edge in milliseconds, or - C.TIME_UNSET if the current window isn't live or the - offset is unknown.
    +
    If Player.isPlayingAd() returns true, returns the duration of the current content in + milliseconds, or C.TIME_UNSET if the duration is not known.
    +long +getCurrentLiveOffset() + +
    Returns the offset of the current playback position from the live edge in milliseconds, or + C.TIME_UNSET if the current MediaItem Player.isCurrentMediaItemLive() isn't + live} or the offset is unknown.
    + + + Object getCurrentManifest()
    Returns the current manifest.
    - + MediaItem getCurrentMediaItem() -
    Returns the media item of the current window in the timeline.
    +
    Returns the currently playing MediaItem.
    - + +int +getCurrentWindowIndex() + +
    Deprecated.
    + + + MediaItem getMediaItemAt​(int index)
    Returns the MediaItem at the given index.
    - + int getMediaItemCount()
    Returns the number of media items in the playlist.
    - + +int +getNextMediaItemIndex() + +
    Returns the index of the MediaItem that will be played if Player.seekToNextMediaItem() is called, which may depend on the current repeat mode and whether + shuffle mode is enabled.
    + + + int getNextWindowIndex() -
    Returns the index of the window that will be played if Player.seekToNextWindow() is called, - which may depend on the current repeat mode and whether shuffle mode is enabled.
    +
    Deprecated.
    - + +int +getPreviousMediaItemIndex() + +
    Returns the index of the MediaItem that will be played if Player.seekToPreviousMediaItem() is called, which may depend on the current repeat mode and whether + shuffle mode is enabled.
    + + + int getPreviousWindowIndex() -
    Returns the index of the window that will be played if Player.seekToPreviousWindow() is - called, which may depend on the current repeat mode and whether shuffle mode is enabled.
    +
    Deprecated.
    - + boolean hasNext()
    Deprecated.
    - + +boolean +hasNextMediaItem() + +
    Returns whether a next MediaItem exists, which may depend on the current repeat mode + and whether shuffle mode is enabled.
    + + + boolean hasNextWindow() -
    Returns whether a next window exists, which may depend on the current repeat mode and whether - shuffle mode is enabled.
    +
    Deprecated.
    - + boolean hasPrevious()
    Deprecated.
    - + boolean -hasPreviousWindow() +hasPreviousMediaItem() -
    Returns whether a previous window exists, which may depend on the current repeat mode and +
    Returns whether a previous media item exists, which may depend on the current repeat mode and whether shuffle mode is enabled.
    - + boolean -isCommandAvailable​(int command) +hasPreviousWindow() + +
    Deprecated.
    + + + +boolean +isCommandAvailable​(@com.google.android.exoplayer2.Player.Command int command)
    Returns whether the provided Player.Command is available.
    - + +boolean +isCurrentMediaItemDynamic() + +
    Returns whether the current MediaItem is dynamic (may change when the Timeline + is updated), or false if the Timeline is empty.
    + + + +boolean +isCurrentMediaItemLive() + +
    Returns whether the current MediaItem is live, or false if the Timeline + is empty.
    + + + +boolean +isCurrentMediaItemSeekable() + +
    Returns whether the current MediaItem is seekable, or false if the Timeline is empty.
    + + + boolean isCurrentWindowDynamic() -
    Returns whether the current window is dynamic, or false if the Timeline is - empty.
    +
    Deprecated.
    - + boolean isCurrentWindowLive() -
    Returns whether the current window is live, or false if the Timeline is empty.
    +
    Deprecated.
    - + boolean isCurrentWindowSeekable() -
    Returns whether the current window is seekable, or false if the Timeline is - empty.
    +
    Deprecated.
    - + boolean isPlaying()
    Returns whether the player is playing, i.e.
    - + void moveMediaItem​(int currentIndex, int newIndex) @@ -411,107 +474,122 @@ implements Moves the media item at the current index to the new index.
    - + void next()
    Deprecated.
    - + void pause()
    Pauses playback.
    - + void play()
    Resumes playback as soon as Player.getPlaybackState() == Player.STATE_READY.
    - + void previous()
    Deprecated.
    - + void removeMediaItem​(int index)
    Removes the media item at the given index of the playlist.
    - + void seekBack() -
    Seeks back in the current window by Player.getSeekBackIncrement() milliseconds.
    +
    Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds.
    - + void seekForward() -
    Seeks forward in the current window by Player.getSeekForwardIncrement() milliseconds.
    +
    Seeks forward in the current MediaItem by Player.getSeekForwardIncrement() + milliseconds.
    - + void seekTo​(long positionMs) -
    Seeks to a position specified in milliseconds in the current window.
    +
    Seeks to a position specified in milliseconds in the current MediaItem.
    - + void seekToDefaultPosition() -
    Seeks to the default position associated with the current window.
    +
    Seeks to the default position associated with the current MediaItem.
    - + void -seekToDefaultPosition​(int windowIndex) +seekToDefaultPosition​(int mediaItemIndex) -
    Seeks to the default position associated with the specified window.
    +
    Seeks to the default position associated with the specified MediaItem.
    - + void seekToNext() -
    Seeks to a later position in the current or next window (if available).
    +
    Seeks to a later position in the current or next MediaItem (if available).
    - + +void +seekToNextMediaItem() + +
    Seeks to the default position of the next MediaItem, which may depend on the current + repeat mode and whether shuffle mode is enabled.
    + + + void seekToNextWindow() -
    Seeks to the default position of the next window, which may depend on the current repeat mode - and whether shuffle mode is enabled.
    +
    Deprecated.
    - + void seekToPrevious() -
    Seeks to an earlier position in the current or previous window (if available).
    +
    Seeks to an earlier position in the current or previous MediaItem (if available).
    - + +void +seekToPreviousMediaItem() + +
    Seeks to the default position of the previous MediaItem, which may depend on the + current repeat mode and whether shuffle mode is enabled.
    + + + void seekToPreviousWindow() -
    Seeks to the default position of the previous window, which may depend on the current repeat - mode and whether shuffle mode is enabled.
    +
    Deprecated.
    - + void setMediaItem​(MediaItem mediaItem) @@ -519,7 +597,7 @@ implements + void setMediaItem​(MediaItem mediaItem, boolean resetPosition) @@ -527,7 +605,7 @@ implements Clears the playlist and adds the specified MediaItem.
    - + void setMediaItem​(MediaItem mediaItem, long startPositionMs) @@ -535,7 +613,7 @@ implements Clears the playlist and adds the specified MediaItem. - + void setMediaItems​(List<MediaItem> mediaItems) @@ -543,20 +621,13 @@ implements + void setPlaybackSpeed​(float speed)
    Changes the rate at which playback occurs.
    - -void -stop() - -
    Stops playback without resetting the player.
    - -
@@ -679,7 +750,7 @@ implements Parameters:
mediaItem - The new MediaItem.
resetPosition - Whether the playback position should be reset to the default position. If - false, playback will start from the position defined by Player.getCurrentWindowIndex() + false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
@@ -808,28 +879,27 @@ implements + @@ -942,13 +1028,13 @@ implements public final void seekTo​(long positionMs) -
Seeks to a position specified in milliseconds in the current window.
+
Seeks to a position specified in milliseconds in the current MediaItem.
Specified by:
seekTo in interface Player
Parameters:
-
positionMs - The seek position in the current window, or C.TIME_UNSET to seek to - the window's default position.
+
positionMs - The seek position in the current MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
@@ -960,7 +1046,7 @@ implements public final void seekBack() -
Seeks back in the current window by Player.getSeekBackIncrement() milliseconds.
+
Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds.
Specified by:
seekBack in interface Player
@@ -975,7 +1061,8 @@ implements public final void seekForward() -
Seeks forward in the current window by Player.getSeekForwardIncrement() milliseconds.
+
Seeks forward in the current MediaItem by Player.getSeekForwardIncrement() + milliseconds.
Specified by:
seekForward in interface Player
@@ -1003,9 +1090,24 @@ public final boolean hasPrevious()
  • hasPreviousWindow

    -
    public final boolean hasPreviousWindow()
    -
    Description copied from interface: Player
    -
    Returns whether a previous window exists, which may depend on the current repeat mode and +
    @Deprecated
    +public final boolean hasPreviousWindow()
    +
    Deprecated.
    +
    +
    Specified by:
    +
    hasPreviousWindow in interface Player
    +
    +
  • +
+ + + +
    +
  • +

    hasPreviousMediaItem

    +
    public final boolean hasPreviousMediaItem()
    +
    Description copied from interface: Player
    +
    Returns whether a previous media item exists, which may depend on the current repeat mode and whether shuffle mode is enabled.

    Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when @@ -1013,7 +1115,7 @@ public final boolean hasPrevious() details.

    Specified by:
    -
    hasPreviousWindow in interface Player
    +
    hasPreviousMediaItem in interface Player
@@ -1038,18 +1140,32 @@ public final void previous()
  • seekToPreviousWindow

    -
    public final void seekToPreviousWindow()
    -
    Description copied from interface: Player
    -
    Seeks to the default position of the previous window, which may depend on the current repeat - mode and whether shuffle mode is enabled. Does nothing if Player.hasPreviousWindow() is - false. +
    @Deprecated
    +public final void seekToPreviousWindow()
    +
    Deprecated.
    +
    +
    Specified by:
    +
    seekToPreviousWindow in interface Player
    +
    +
  • +
+ + + + @@ -1061,18 +1177,20 @@ public final void previous()

seekToPrevious

public final void seekToPrevious()
Description copied from interface: Player
-
Seeks to an earlier position in the current or previous window (if available). More precisely: +
Seeks to an earlier position in the current or previous MediaItem (if available). More + precisely:
Specified by:
@@ -1101,17 +1219,32 @@ public final boolean hasNext()
  • hasNextWindow

    -
    public final boolean hasNextWindow()
    -
    Description copied from interface: Player
    -
    Returns whether a next window exists, which may depend on the current repeat mode and whether - shuffle mode is enabled. +
    @Deprecated
    +public final boolean hasNextWindow()
    +
    Deprecated.
    +
    +
    Specified by:
    +
    hasNextWindow in interface Player
    +
    +
  • +
+ + + + @@ -1136,17 +1269,33 @@ public final void next()
  • seekToNextWindow

    -
    public final void seekToNextWindow()
    -
    Description copied from interface: Player
    -
    Seeks to the default position of the next window, which may depend on the current repeat mode - and whether shuffle mode is enabled. Does nothing if Player.hasNextWindow() is false. +
    @Deprecated
    +public final void seekToNextWindow()
    +
    Deprecated.
    +
    +
    Specified by:
    +
    seekToNextWindow in interface Player
    +
    +
  • +
+ + + + @@ -1158,14 +1307,15 @@ public final void next()

seekToNext

public final void seekToNext()
Description copied from interface: Player
-
Seeks to a later position in the current or next window (if available). More precisely: +
Seeks to a later position in the current or next MediaItem (if available). More + precisely:
  • If the timeline is empty or seeking is not possible, does nothing. -
  • Otherwise, if a next window exists, seeks to the default - position of the next window. -
  • Otherwise, if the current window is live and has not - ended, seeks to the live edge of the current window. +
  • Otherwise, if a next media item exists, seeks to the default + position of the next MediaItem. +
  • Otherwise, if the current MediaItem is live and + has not ended, seeks to the live edge of the current MediaItem.
  • Otherwise, does nothing.
@@ -1195,26 +1345,18 @@ public final void next()
- +
  • -

    stop

    -
    public final void stop()
    -
    Description copied from interface: Player
    -
    Stops playback without resetting the player. Use Player.pause() rather than this method if - the intention is to pause playback. - -

    Calling this method will cause the playback state to transition to Player.STATE_IDLE. The - player instance can still be used, and Player.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.

    +

    getCurrentWindowIndex

    +
    @Deprecated
    +public final int getCurrentWindowIndex()
    +
    Deprecated.
    Specified by:
    -
    stop in interface Player
    +
    getCurrentWindowIndex in interface Player
@@ -1224,17 +1366,33 @@ public final void next() + + + + @@ -1244,18 +1402,33 @@ public final void next() + + + + @@ -1268,13 +1441,12 @@ public final void next()
@Nullable
 public final MediaItem getCurrentMediaItem()
Description copied from interface: Player
-
Returns the media item of the current window in the timeline. May be null if the timeline is - empty.
+
Returns the currently playing MediaItem. May be null if the timeline is empty.
Specified by:
getCurrentMediaItem in interface Player
See Also:
-
Player.Listener.onMediaItemTransition(MediaItem, int)
+
Player.Listener.onMediaItemTransition(MediaItem, int)
@@ -1332,7 +1504,7 @@ public final public final int getBufferedPercentage() -
Returns an estimate of the percentage in the current content window or ad up to which data is +
Returns an estimate of the percentage in the current content or ad up to which data is buffered, or 0 if no estimate is available.
Specified by:
@@ -1346,13 +1518,28 @@ public final 
  • isCurrentWindowDynamic

    -
    public final boolean isCurrentWindowDynamic()
    -
    -
    Returns whether the current window is dynamic, or false if the Timeline is - empty.
    +
    @Deprecated
    +public final boolean isCurrentWindowDynamic()
    +
    Deprecated.
    Specified by:
    isCurrentWindowDynamic in interface Player
    +
    +
  • + + + + + + + + + + + + +
    @@ -89,32 +102,26 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +
    -

    Package com.google.android.exoplayer2.ext.gvr

    + +

    Annotation Type C.AudioManagerOffloadMode

    +
    +
    +
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -356,7 +356,7 @@ implements
  • UNKNOWN

    -
    public static final DeviceInfo UNKNOWN
    +
    public static final DeviceInfo UNKNOWN
    Unknown DeviceInfo.
  • @@ -366,7 +366,7 @@ implements
  • playbackType

    -
    public final @com.google.android.exoplayer2.device.DeviceInfo.PlaybackType int playbackType
    +
    public final @com.google.android.exoplayer2.DeviceInfo.PlaybackType int playbackType
    The type of playback.
  • @@ -396,8 +396,8 @@ implements
  • CREATOR

    -
    public static final Bundleable.Creator<DeviceInfo> CREATOR
    -
    Object that can restore DeviceInfo from a Bundle.
    +
    public static final Bundleable.Creator<DeviceInfo> CREATOR
    +
    Object that can restore DeviceInfo from a Bundle.
  • @@ -410,13 +410,13 @@ implements +
    • DeviceInfo

      -
      public DeviceInfo​(@com.google.android.exoplayer2.device.DeviceInfo.PlaybackType int playbackType,
      +
      public DeviceInfo​(@com.google.android.exoplayer2.DeviceInfo.PlaybackType int playbackType,
                         int minVolume,
                         int maxVolume)
      Creates device information.
      @@ -466,11 +466,11 @@ implements

      toBundle

      public Bundle toBundle()
      -
      Description copied from interface: Bundleable
      +
      Description copied from interface: Bundleable
      Returns a Bundle representing the information stored in this object.
      Specified by:
      -
      toBundle in interface Bundleable
      +
      toBundle in interface Bundleable
    @@ -494,18 +494,18 @@ implements -
  • Overview
  • +
  • Overview
  • Package
  • Tree
  • -
  • Deprecated
  • -
  • Index
  • -
  • Help
  • +
  • Deprecated
  • +
  • Index
  • +
  • Help

  • -
    public static interface ExoPlayer.TextComponent
    -
    The text component of an ExoPlayer.
    +
    @Deprecated
    +public static interface ExoPlayer.TextComponent
    +
    Deprecated. +
    Use Player, as the ExoPlayer.TextComponent methods are defined by that + interface.
    +
    @@ -152,27 +156,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Description -void -addTextOutput​(TextOutput listener) - -
    Deprecated. - -
    - - - List<Cue> getCurrentCues() -
    Returns the current Cues.
    - - - -void -removeTextOutput​(TextOutput listener) - @@ -193,50 +181,17 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    Method Detail

    - - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.VideoComponent.html b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.VideoComponent.html index a0e7a8c2bf..c2f0aecfef 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.VideoComponent.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.VideoComponent.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":38,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":38,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6,"i16":6,"i17":6}; +var data = {"i0":38,"i1":38,"i2":38,"i3":38,"i4":38,"i5":38,"i6":38,"i7":38,"i8":38,"i9":38,"i10":38,"i11":38,"i12":38,"i13":38,"i14":38,"i15":38,"i16":38,"i17":38}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -129,8 +129,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    ExoPlayer

    -
    public static interface ExoPlayer.VideoComponent
    -
    The video component of an ExoPlayer.
    +
    @Deprecated
    +public static interface ExoPlayer.VideoComponent
    +
    Deprecated. +
    Use ExoPlayer, as the ExoPlayer.VideoComponent methods are defined by that + interface.
    +
    @@ -153,137 +157,166 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); void -addVideoListener​(VideoListener listener) +clearCameraMotionListener​(CameraMotionListener listener) void -clearCameraMotionListener​(CameraMotionListener listener) +clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener) -
    Clears the listener which receives camera motion events if it matches the one passed.
    + void -clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener) +clearVideoSurface() -
    Clears the listener which receives video frame metadata events if it matches the one passed.
    +
    Deprecated. + +
    void -clearVideoSurface() +clearVideoSurface​(Surface surface) -
    Clears any Surface, SurfaceHolder, SurfaceView or TextureView - currently set on the player.
    +
    Deprecated. + +
    void -clearVideoSurface​(Surface surface) +clearVideoSurfaceHolder​(SurfaceHolder surfaceHolder) -
    Clears the Surface onto which video is being rendered if it matches the one passed.
    + void -clearVideoSurfaceHolder​(SurfaceHolder surfaceHolder) +clearVideoSurfaceView​(SurfaceView surfaceView) -
    Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed.
    +
    Deprecated. + +
    void -clearVideoSurfaceView​(SurfaceView surfaceView) +clearVideoTextureView​(TextureView textureView) -
    Clears the SurfaceView onto which video is being rendered if it matches the one - passed.
    +
    Deprecated. + +
    -void -clearVideoTextureView​(TextureView textureView) +int +getVideoChangeFrameRateStrategy() -
    Clears the TextureView onto which video is being rendered if it matches the one - passed.
    + int getVideoScalingMode() -
    Returns the C.VideoScalingMode.
    +
    Deprecated. + +
    VideoSize getVideoSize() -
    Gets the size of the video.
    +
    Deprecated. + +
    void -removeVideoListener​(VideoListener listener) +setCameraMotionListener​(CameraMotionListener listener) void -setCameraMotionListener​(CameraMotionListener listener) +setVideoChangeFrameRateStrategy​(int videoChangeFrameRateStrategy) -
    Sets a listener of camera motion events.
    + void setVideoFrameMetadataListener​(VideoFrameMetadataListener listener) -
    Sets a listener to receive video frame metadata events.
    + void setVideoScalingMode​(int videoScalingMode) - +
    Deprecated. + +
    void setVideoSurface​(Surface surface) -
    Sets the Surface onto which video will be rendered.
    +
    Deprecated. + +
    void setVideoSurfaceHolder​(SurfaceHolder surfaceHolder) -
    Sets the SurfaceHolder that holds the Surface onto which video will be - rendered.
    + void setVideoSurfaceView​(SurfaceView surfaceView) -
    Sets the SurfaceView onto which video will be rendered.
    +
    Deprecated. + +
    void setVideoTextureView​(TextureView textureView) -
    Sets the TextureView onto which video will be rendered.
    +
    Deprecated. + +
    @@ -309,13 +342,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); @@ -324,45 +356,40 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); - + - + @@ -371,16 +398,12 @@ void removeVideoListener​(
  • setVideoFrameMetadataListener

    -
    void setVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
    -
    Sets a listener to receive video frame metadata events. - -

    This method is intended to be called by the same component that sets the Surface - onto which video will be rendered. If using ExoPlayer's standard UI components, this method - should not be called directly from application code.

    -
    -
    Parameters:
    -
    listener - The listener.
    -
    +
    @Deprecated
    +void setVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
    +
  • @@ -389,13 +412,12 @@ void removeVideoListener​(
  • clearVideoFrameMetadataListener

    -
    void clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
    -
    Clears the listener which receives video frame metadata events if it matches the one passed. - Else does nothing.
    -
    -
    Parameters:
    -
    listener - The listener to clear.
    -
    +
    @Deprecated
    +void clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
    +
  • @@ -404,12 +426,11 @@ void removeVideoListener​(
  • setCameraMotionListener

    -
    void setCameraMotionListener​(CameraMotionListener listener)
    -
    Sets a listener of camera motion events.
    -
    -
    Parameters:
    -
    listener - The listener.
    -
    +
    @Deprecated
    +void setCameraMotionListener​(CameraMotionListener listener)
    +
  • @@ -418,13 +439,11 @@ void removeVideoListener​(
  • clearCameraMotionListener

    -
    void clearCameraMotionListener​(CameraMotionListener listener)
    -
    Clears the listener which receives camera motion events if it matches the one passed. Else - does nothing.
    -
    -
    Parameters:
    -
    listener - The listener to clear.
    -
    +
    @Deprecated
    +void clearCameraMotionListener​(CameraMotionListener listener)
    +
  • @@ -433,9 +452,11 @@ void removeVideoListener​(
  • clearVideoSurface

    -
    void clearVideoSurface()
    -
    Clears any Surface, SurfaceHolder, SurfaceView or TextureView - currently set on the player.
    +
    @Deprecated
    +void clearVideoSurface()
    +
    Deprecated. + +
  • @@ -444,14 +465,12 @@ void removeVideoListener​(
  • clearVideoSurface

    -
    void clearVideoSurface​(@Nullable
    +
    @Deprecated
    +void clearVideoSurface​(@Nullable
                            Surface surface)
    -
    Clears the Surface onto which video is being rendered if it matches the one passed. - Else does nothing.
    -
    -
    Parameters:
    -
    surface - The surface to clear.
    -
    +
    Deprecated. + +
  • @@ -460,19 +479,12 @@ void removeVideoListener​(
  • setVideoSurface

    -
    void setVideoSurface​(@Nullable
    +
    @Deprecated
    +void setVideoSurface​(@Nullable
                          Surface surface)
    -
    Sets the Surface onto which video will be rendered. The caller is responsible for - tracking the lifecycle of the surface, and must clear the surface by calling - setVideoSurface(null) if the surface is destroyed. - -

    If the surface is held by a SurfaceView, TextureView or SurfaceHolder then it's recommended to use setVideoSurfaceView(SurfaceView), setVideoTextureView(TextureView) or setVideoSurfaceHolder(SurfaceHolder) rather - than this method, since passing the holder allows the player to track the lifecycle of the - surface automatically.

    -
    -
    Parameters:
    -
    surface - The Surface.
    -
    +
    Deprecated. + +
  • @@ -481,17 +493,12 @@ void removeVideoListener​(
  • setVideoSurfaceHolder

    -
    void setVideoSurfaceHolder​(@Nullable
    +
    @Deprecated
    +void setVideoSurfaceHolder​(@Nullable
                                SurfaceHolder surfaceHolder)
    -
    Sets the SurfaceHolder that holds the Surface onto which video will be - rendered. The player will track the lifecycle of the surface automatically. - -

    The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper().

    -
    -
    Parameters:
    -
    surfaceHolder - The surface holder.
    -
    +
  • @@ -500,14 +507,12 @@ void removeVideoListener​(
  • clearVideoSurfaceHolder

    -
    void clearVideoSurfaceHolder​(@Nullable
    +
    @Deprecated
    +void clearVideoSurfaceHolder​(@Nullable
                                  SurfaceHolder surfaceHolder)
    -
    Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed. Else does nothing.
    -
    -
    Parameters:
    -
    surfaceHolder - The surface holder to clear.
    -
    +
  • @@ -516,17 +521,12 @@ void removeVideoListener​(
  • setVideoSurfaceView

    -
    void setVideoSurfaceView​(@Nullable
    +
    @Deprecated
    +void setVideoSurfaceView​(@Nullable
                              SurfaceView surfaceView)
    -
    Sets the SurfaceView onto which video will be rendered. The player will track the - lifecycle of the surface automatically. - -

    The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper().

    -
    -
    Parameters:
    -
    surfaceView - The surface view.
    -
    +
    Deprecated. + +
  • @@ -535,14 +535,12 @@ void removeVideoListener​(
  • clearVideoSurfaceView

    -
    void clearVideoSurfaceView​(@Nullable
    +
    @Deprecated
    +void clearVideoSurfaceView​(@Nullable
                                SurfaceView surfaceView)
    -
    Clears the SurfaceView onto which video is being rendered if it matches the one - passed. Else does nothing.
    -
    -
    Parameters:
    -
    surfaceView - The texture view to clear.
    -
    +
    Deprecated. + +
  • @@ -551,17 +549,12 @@ void removeVideoListener​(
  • setVideoTextureView

    -
    void setVideoTextureView​(@Nullable
    +
    @Deprecated
    +void setVideoTextureView​(@Nullable
                              TextureView textureView)
    -
    Sets the TextureView onto which video will be rendered. The player will track the - lifecycle of the surface automatically. - -

    The thread that calls the TextureView.SurfaceTextureListener methods must be the - thread associated with Player.getApplicationLooper().

    -
    -
    Parameters:
    -
    textureView - The texture view.
    -
    +
    Deprecated. + +
  • @@ -570,14 +563,12 @@ void removeVideoListener​(
  • clearVideoTextureView

    -
    void clearVideoTextureView​(@Nullable
    +
    @Deprecated
    +void clearVideoTextureView​(@Nullable
                                TextureView textureView)
    -
    Clears the TextureView onto which video is being rendered if it matches the one - passed. Else does nothing.
    -
    -
    Parameters:
    -
    textureView - The texture view to clear.
    -
    +
    Deprecated. + +
  • @@ -586,15 +577,11 @@ void removeVideoListener​(
  • getVideoSize

    -
    VideoSize getVideoSize()
    -
    Gets the size of the video. - -

    The width and height of size could be 0 if there is no video or the size has not been - determined yet.

    -
    -
    See Also:
    -
    VideoListener.onVideoSizeChanged(int, int, int, float)
    -
    +
    @Deprecated
    +VideoSize getVideoSize()
    +
    Deprecated. + +
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html index e8a5fbd7ea..88195ef603 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6,"i16":6,"i17":6,"i18":6,"i19":6,"i20":6,"i21":38,"i22":38,"i23":6,"i24":38,"i25":6,"i26":6,"i27":6,"i28":6,"i29":6,"i30":6,"i31":6,"i32":6,"i33":6,"i34":6}; +var data = {"i0":6,"i1":6,"i2":38,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":38,"i15":6,"i16":6,"i17":6,"i18":6,"i19":38,"i20":6,"i21":6,"i22":6,"i23":6,"i24":6,"i25":6,"i26":6,"i27":38,"i28":6,"i29":6,"i30":38,"i31":6,"i32":6,"i33":6,"i34":38,"i35":38,"i36":6,"i37":6,"i38":38,"i39":38,"i40":6,"i41":6,"i42":6,"i43":6,"i44":6,"i45":6,"i46":38,"i47":6,"i48":6,"i49":6,"i50":6,"i51":6,"i52":6,"i53":6,"i54":6,"i55":6,"i56":6,"i57":6,"i58":38,"i59":6,"i60":6,"i61":6,"i62":6}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -131,7 +131,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    public interface ExoPlayer
     extends Player
    -
    An extensible media player that plays MediaSources. Instances can be obtained from SimpleExoPlayer.Builder. +
    An extensible media player that plays MediaSources. Instances can be obtained from ExoPlayer.Builder.

    Player components

    @@ -143,9 +143,9 @@ extends
  • MediaSources that define the media to be played, load the media, - and from which the loaded media can be read. MediaSources are created from MediaItems by the MediaSourceFactory injected into the player Builder, or can be added directly by methods - like setMediaSource(MediaSource). The library provides a DefaultMediaSourceFactory for progressive media files, DASH, SmoothStreaming and HLS, - which also includes functionality for side-loading subtitle files and clipping media. + and from which the loaded media can be read. MediaSources are created from MediaItems by the MediaSourceFactory injected into the player Builder, or can be added directly by methods like setMediaSource(MediaSource). The library provides a DefaultMediaSourceFactory for + progressive media files, DASH, SmoothStreaming and HLS, which also includes functionality + for side-loading subtitle files and clipping media.
  • Renderers that render individual components of the media. The library provides default implementations for common media types (MediaCodecVideoRenderer, MediaCodecAudioRenderer, TextRenderer and MetadataRenderer). A @@ -229,7 +229,10 @@ extends static interface  ExoPlayer.AudioComponent -
    The audio component of an ExoPlayer.
    +
    Deprecated. +
    Use ExoPlayer, as the ExoPlayer.AudioComponent methods are defined by that + interface.
    +
    @@ -243,37 +246,37 @@ extends static class  ExoPlayer.Builder -
    Deprecated. - -
    +
    A builder for ExoPlayer instances.
    static interface  ExoPlayer.DeviceComponent -
    The device component of an ExoPlayer.
    +
    Deprecated. +
    Use Player, as the ExoPlayer.DeviceComponent methods are defined by that + interface.
    +
    static interface  -ExoPlayer.MetadataComponent +ExoPlayer.TextComponent -
    The metadata component of an ExoPlayer.
    +
    Deprecated. +
    Use Player, as the ExoPlayer.TextComponent methods are defined by that + interface.
    +
    static interface  -ExoPlayer.TextComponent - -
    The text component of an ExoPlayer.
    - - - -static interface  ExoPlayer.VideoComponent -
    The video component of an ExoPlayer.
    +
    Deprecated. +
    Use ExoPlayer, as the ExoPlayer.VideoComponent methods are defined by that + interface.
    +
    @@ -303,6 +306,13 @@ extends static long +DEFAULT_DETACH_SURFACE_TIMEOUT_MS + +
    The default timeout for detaching a surface from the player, in milliseconds.
    + + + +static long DEFAULT_RELEASE_TIMEOUT_MS
    The default timeout for calls to Player.release() and setForegroundMode(boolean), in @@ -315,7 +325,7 @@ extends

    Fields inherited from interface com.google.android.exoplayer2.Player

    -COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_CURRENT_MEDIA_ITEM, COMMAND_GET_DEVICE_VOLUME, COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_GET_TEXT, COMMAND_GET_TIMELINE, COMMAND_GET_VOLUME, COMMAND_INVALID, COMMAND_PLAY_PAUSE, COMMAND_PREPARE_STOP, COMMAND_SEEK_BACK, COMMAND_SEEK_FORWARD, COMMAND_SEEK_IN_CURRENT_WINDOW, COMMAND_SEEK_TO_DEFAULT_POSITION, COMMAND_SEEK_TO_NEXT, COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_WINDOW, COMMAND_SET_DEVICE_VOLUME, COMMAND_SET_MEDIA_ITEMS_METADATA, COMMAND_SET_REPEAT_MODE, COMMAND_SET_SHUFFLE_MODE, COMMAND_SET_SPEED_AND_PITCH, COMMAND_SET_VIDEO_SURFACE, COMMAND_SET_VOLUME, DISCONTINUITY_REASON_AUTO_TRANSITION, DISCONTINUITY_REASON_INTERNAL, DISCONTINUITY_REASON_REMOVE, DISCONTINUITY_REASON_SEEK, DISCONTINUITY_REASON_SEEK_ADJUSTMENT, DISCONTINUITY_REASON_SKIP, EVENT_AVAILABLE_COMMANDS_CHANGED, EVENT_IS_LOADING_CHANGED, EVENT_IS_PLAYING_CHANGED, EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, EVENT_MEDIA_ITEM_TRANSITION, EVENT_MEDIA_METADATA_CHANGED, EVENT_PLAY_WHEN_READY_CHANGED, EVENT_PLAYBACK_PARAMETERS_CHANGED, EVENT_PLAYBACK_STATE_CHANGED, EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, EVENT_PLAYER_ERROR, EVENT_PLAYLIST_METADATA_CHANGED, EVENT_POSITION_DISCONTINUITY, EVENT_REPEAT_MODE_CHANGED, EVENT_SEEK_BACK_INCREMENT_CHANGED, EVENT_SEEK_FORWARD_INCREMENT_CHANGED, EVENT_SHUFFLE_MODE_ENABLED_CHANGED, EVENT_STATIC_METADATA_CHANGED, EVENT_TIMELINE_CHANGED, EVENT_TRACKS_CHANGED, MEDIA_ITEM_TRANSITION_REASON_AUTO, MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED, MEDIA_ITEM_TRANSITION_REASON_REPEAT, MEDIA_ITEM_TRANSITION_REASON_SEEK, PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM, PLAY_WHEN_READY_CHANGE_REASON_REMOTE, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, PLAYBACK_SUPPRESSION_REASON_NONE, PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS, REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, STATE_BUFFERING, STATE_ENDED, STATE_IDLE, STATE_READY, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, TIMELINE_CHANGE_REASON_SOURCE_UPDATE
  • +COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_CURRENT_MEDIA_ITEM, COMMAND_GET_DEVICE_VOLUME, COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_GET_TEXT, COMMAND_GET_TIMELINE, COMMAND_GET_TRACK_INFOS, COMMAND_GET_VOLUME, COMMAND_INVALID, COMMAND_PLAY_PAUSE, COMMAND_PREPARE, COMMAND_SEEK_BACK, COMMAND_SEEK_FORWARD, COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_IN_CURRENT_WINDOW, COMMAND_SEEK_TO_DEFAULT_POSITION, COMMAND_SEEK_TO_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_WINDOW, COMMAND_SET_DEVICE_VOLUME, COMMAND_SET_MEDIA_ITEMS_METADATA, COMMAND_SET_REPEAT_MODE, COMMAND_SET_SHUFFLE_MODE, COMMAND_SET_SPEED_AND_PITCH, COMMAND_SET_TRACK_SELECTION_PARAMETERS, COMMAND_SET_VIDEO_SURFACE, COMMAND_SET_VOLUME, COMMAND_STOP, DISCONTINUITY_REASON_AUTO_TRANSITION, DISCONTINUITY_REASON_INTERNAL, DISCONTINUITY_REASON_REMOVE, DISCONTINUITY_REASON_SEEK, DISCONTINUITY_REASON_SEEK_ADJUSTMENT, DISCONTINUITY_REASON_SKIP, EVENT_AVAILABLE_COMMANDS_CHANGED, EVENT_IS_LOADING_CHANGED, EVENT_IS_PLAYING_CHANGED, EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, EVENT_MEDIA_ITEM_TRANSITION, EVENT_MEDIA_METADATA_CHANGED, EVENT_PLAY_WHEN_READY_CHANGED, EVENT_PLAYBACK_PARAMETERS_CHANGED, EVENT_PLAYBACK_STATE_CHANGED, EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, EVENT_PLAYER_ERROR, EVENT_PLAYLIST_METADATA_CHANGED, EVENT_POSITION_DISCONTINUITY, EVENT_REPEAT_MODE_CHANGED, EVENT_SEEK_BACK_INCREMENT_CHANGED, EVENT_SEEK_FORWARD_INCREMENT_CHANGED, EVENT_SHUFFLE_MODE_ENABLED_CHANGED, EVENT_TIMELINE_CHANGED, EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, EVENT_TRACKS_CHANGED, MEDIA_ITEM_TRANSITION_REASON_AUTO, MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED, MEDIA_ITEM_TRANSITION_REASON_REPEAT, MEDIA_ITEM_TRANSITION_REASON_SEEK, PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM, PLAY_WHEN_READY_CHANGE_REASON_REMOTE, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, PLAYBACK_SUPPRESSION_REASON_NONE, PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS, REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, STATE_BUFFERING, STATE_ENDED, STATE_IDLE, STATE_READY, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, TIMELINE_CHANGE_REASON_SOURCE_UPDATE @@ -336,12 +346,28 @@ extends void +addAnalyticsListener​(AnalyticsListener listener) + +
    Adds an AnalyticsListener to receive analytics events.
    + + + +void addAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener)
    Adds a listener to receive audio offload events.
    - + +void +addListener​(Player.EventListener listener) + + + + + void addMediaSource​(int index, MediaSource mediaSource) @@ -349,14 +375,14 @@ extends Adds a media source at the given index of the playlist.
    - + void addMediaSource​(MediaSource mediaSource)
    Adds a media source to the end of the playlist.
    - + void addMediaSources​(int index, List<MediaSource> mediaSources) @@ -364,77 +390,125 @@ extends Adds a list of media sources at the given index of the playlist.
    - + void addMediaSources​(List<MediaSource> mediaSources)
    Adds a list of media sources to the end of the playlist.
    - + +void +clearAuxEffectInfo() + +
    Detaches any previously attached auxiliary audio effect from the underlying audio track.
    + + + +void +clearCameraMotionListener​(CameraMotionListener listener) + +
    Clears the listener which receives camera motion events if it matches the one passed.
    + + + +void +clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener) + +
    Clears the listener which receives video frame metadata events if it matches the one passed.
    + + + PlayerMessage createMessage​(PlayerMessage.Target target)
    Creates a message that can be sent to a PlayerMessage.Target.
    - + boolean experimentalIsSleepingForOffload()
    Returns whether the player has paused its main loop to save power in offload scheduling mode.
    - + void experimentalSetOffloadSchedulingEnabled​(boolean offloadSchedulingEnabled)
    Sets whether audio offload scheduling is enabled.
    - + +AnalyticsCollector +getAnalyticsCollector() + +
    Returns the AnalyticsCollector used for collecting analytics events.
    + + + ExoPlayer.AudioComponent getAudioComponent() -
    Returns the component of this player for audio output, or null if audio is not supported.
    +
    Deprecated. +
    Use ExoPlayer, as the ExoPlayer.AudioComponent methods are defined by that + interface.
    +
    - + +DecoderCounters +getAudioDecoderCounters() + +
    Returns DecoderCounters for audio, or null if no audio is being played.
    + + + +Format +getAudioFormat() + +
    Returns the audio format currently being played, or null if no audio is being played.
    + + + +int +getAudioSessionId() + +
    Returns the audio session identifier, or C.AUDIO_SESSION_ID_UNSET if not set.
    + + + Clock getClock()
    Returns the Clock used for playback.
    - + ExoPlayer.DeviceComponent getDeviceComponent() -
    Returns the component of this player for playback device, or null if it's not supported.
    +
    Deprecated. +
    Use Player, as the ExoPlayer.DeviceComponent methods are defined by that + interface.
    +
    - -ExoPlayer.MetadataComponent -getMetadataComponent() - -
    Returns the component of this player for metadata output, or null if metadata is not supported.
    - - - + boolean getPauseAtEndOfMediaItems()
    Returns whether the player pauses playback at the end of each media item.
    - + Looper getPlaybackLooper()
    Returns the Looper associated with the playback thread.
    - + ExoPlaybackException getPlayerError() @@ -442,49 +516,90 @@ extends ExoPlaybackException. - + int getRendererCount()
    Returns the number of renderers.
    - -int + +@com.google.android.exoplayer2.C.TrackType int getRendererType​(int index)
    Returns the track type that the renderer at a given index handles.
    - + SeekParameters getSeekParameters()
    Returns the currently active SeekParameters of the player.
    - + +boolean +getSkipSilenceEnabled() + +
    Returns whether skipping silences in the audio stream is enabled.
    + + + ExoPlayer.TextComponent getTextComponent() -
    Returns the component of this player for text output, or null if text is not supported.
    +
    Deprecated. +
    Use Player, as the ExoPlayer.TextComponent methods are defined by that + interface.
    +
    - + TrackSelector getTrackSelector()
    Returns the track selector that this player uses, or null if track selection is not supported.
    - + +int +getVideoChangeFrameRateStrategy() + + + + + ExoPlayer.VideoComponent getVideoComponent() -
    Returns the component of this player for video output, or null if video is not supported.
    +
    Deprecated. +
    Use ExoPlayer, as the ExoPlayer.VideoComponent methods are defined by that + interface.
    +
    - + +DecoderCounters +getVideoDecoderCounters() + +
    Returns DecoderCounters for video, or null if no video is being played.
    + + + +Format +getVideoFormat() + +
    Returns the video format currently being played, or null if no video is being played.
    + + + +int +getVideoScalingMode() + +
    Returns the C.VideoScalingMode.
    + + + void prepare​(MediaSource mediaSource) @@ -493,7 +608,7 @@ extends - + void prepare​(MediaSource mediaSource, boolean resetPosition, @@ -504,14 +619,30 @@ extends - + +void +removeAnalyticsListener​(AnalyticsListener listener) + +
    Removes an AnalyticsListener.
    + + + void removeAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener)
    Removes a listener of audio offload events.
    - + +void +removeListener​(Player.EventListener listener) + + + + + void retry() @@ -520,7 +651,36 @@ extends - + +void +setAudioAttributes​(AudioAttributes audioAttributes, + boolean handleAudioFocus) + +
    Sets the attributes for audio playback, used by the underlying audio track.
    + + + +void +setAudioSessionId​(int audioSessionId) + +
    Sets the ID of the audio session to attach to the underlying AudioTrack.
    + + + +void +setAuxEffectInfo​(AuxEffectInfo auxEffectInfo) + +
    Sets information on an auxiliary audio effect to attach to the underlying audio track.
    + + + +void +setCameraMotionListener​(CameraMotionListener listener) + +
    Sets a listener of camera motion events.
    + + + void setForegroundMode​(boolean foregroundMode) @@ -528,7 +688,24 @@ extends - + +void +setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy) + +
    Sets whether the player should pause automatically when audio is rerouted from a headset to + device speakers.
    + + + +void +setHandleWakeLock​(boolean handleWakeLock) + +
    Deprecated. +
    Use setWakeMode(int) instead.
    +
    + + + void setMediaSource​(MediaSource mediaSource) @@ -536,7 +713,7 @@ extends - + void setMediaSource​(MediaSource mediaSource, boolean resetPosition) @@ -544,7 +721,7 @@ extends Clears the playlist and adds the specified MediaSource. - + void setMediaSource​(MediaSource mediaSource, long startPositionMs) @@ -552,7 +729,7 @@ extends Clears the playlist and adds the specified MediaSource. - + void setMediaSources​(List<MediaSource> mediaSources) @@ -560,7 +737,7 @@ extends - + void setMediaSources​(List<MediaSource> mediaSources, boolean resetPosition) @@ -568,43 +745,95 @@ extends Clears the playlist and adds the specified MediaSources. - + void setMediaSources​(List<MediaSource> mediaSources, - int startWindowIndex, + int startMediaItemIndex, long startPositionMs)
    Clears the playlist and adds the specified MediaSources.
    - + void setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems)
    Sets whether to pause playback at the end of each media item.
    - + +void +setPriorityTaskManager​(PriorityTaskManager priorityTaskManager) + +
    Sets a PriorityTaskManager, or null to clear a previously set priority task manager.
    + + + void setSeekParameters​(SeekParameters seekParameters)
    Sets the parameters that control how seek operations are performed.
    - + void setShuffleOrder​(ShuffleOrder shuffleOrder)
    Sets the shuffle order.
    + +void +setSkipSilenceEnabled​(boolean skipSilenceEnabled) + +
    Sets whether skipping silences in the audio stream is enabled.
    + + + +void +setThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread) + +
    Deprecated. +
    Disabling the enforcement can result in hard-to-detect bugs.
    +
    + + + +void +setVideoChangeFrameRateStrategy​(int videoChangeFrameRateStrategy) + +
    Sets a C.VideoChangeFrameRateStrategy that will be used by the player when provided + with a video output Surface.
    + + + +void +setVideoFrameMetadataListener​(VideoFrameMetadataListener listener) + +
    Sets a listener to receive video frame metadata events.
    + + + +void +setVideoScalingMode​(int videoScalingMode) + + + + + +void +setWakeMode​(@com.google.android.exoplayer2.C.WakeMode int wakeMode) + +
    Sets how the player should keep the device awake for playback when the screen is off.
    + + @@ -625,7 +854,7 @@ extends -
      +
      • DEFAULT_RELEASE_TIMEOUT_MS

        static final long DEFAULT_RELEASE_TIMEOUT_MS
        @@ -637,6 +866,20 @@ extends
      + + + +
        +
      • +

        DEFAULT_DETACH_SURFACE_TIMEOUT_MS

        +
        static final long DEFAULT_DETACH_SURFACE_TIMEOUT_MS
        +
        The default timeout for detaching a surface from the player, in milliseconds.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
    @@ -673,8 +916,12 @@ extends

    getAudioComponent

    @Nullable
    +@Deprecated
     ExoPlayer.AudioComponent getAudioComponent()
    -
    Returns the component of this player for audio output, or null if audio is not supported.
    +
    Deprecated. +
    Use ExoPlayer, as the ExoPlayer.AudioComponent methods are defined by that + interface.
    +
    @@ -684,8 +931,12 @@ extends

    getVideoComponent

    @Nullable
    +@Deprecated
     ExoPlayer.VideoComponent getVideoComponent()
    -
    Returns the component of this player for video output, or null if video is not supported.
    +
    Deprecated. +
    Use ExoPlayer, as the ExoPlayer.VideoComponent methods are defined by that + interface.
    +
    @@ -695,19 +946,12 @@ extends

    getTextComponent

    @Nullable
    +@Deprecated
     ExoPlayer.TextComponent getTextComponent()
    -
    Returns the component of this player for text output, or null if text is not supported.
    - - - - - -
      -
    • -

      getMetadataComponent

      -
      @Nullable
      -ExoPlayer.MetadataComponent getMetadataComponent()
      -
      Returns the component of this player for metadata output, or null if metadata is not supported.
      +
      Deprecated. +
      Use Player, as the ExoPlayer.TextComponent methods are defined by that + interface.
      +
    @@ -717,8 +961,51 @@ extends

    getDeviceComponent

    @Nullable
    +@Deprecated
     ExoPlayer.DeviceComponent getDeviceComponent()
    -
    Returns the component of this player for playback device, or null if it's not supported.
    +
    Deprecated. +
    Use Player, as the ExoPlayer.DeviceComponent methods are defined by that + interface.
    +
    + + + + + + + + + + @@ -749,6 +1036,44 @@ extends + + + + + + + +
      +
    • +

      addAnalyticsListener

      +
      void addAnalyticsListener​(AnalyticsListener listener)
      +
      Adds an AnalyticsListener to receive analytics events.
      +
      +
      Parameters:
      +
      listener - The listener to be added.
      +
      +
    • +
    + + + +
      +
    • +

      removeAnalyticsListener

      +
      void removeAnalyticsListener​(AnalyticsListener listener)
      +
      Removes an AnalyticsListener.
      +
      +
      Parameters:
      +
      listener - The listener to be removed.
      +
      +
    • +
    @@ -765,7 +1090,7 @@ extends
  • getRendererType

    -
    int getRendererType​(int index)
    +
    @com.google.android.exoplayer2.C.TrackType int getRendererType​(int index)
  • @@ -879,7 +1204,7 @@ void prepare​(MediaSources.
    resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined - by Player.getCurrentWindowIndex() and Player.getCurrentPosition().
    + by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition(). @@ -890,17 +1215,16 @@ void prepare​(

    setMediaSources

    void setMediaSources​(List<MediaSource> mediaSources,
    -                     int startWindowIndex,
    +                     int startMediaItemIndex,
                          long startPositionMs)
    Clears the playlist and adds the specified MediaSources.
    Parameters:
    mediaSources - The new MediaSources.
    -
    startWindowIndex - The window index to start playback from. If C.INDEX_UNSET is - passed, the current position is not reset.
    -
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given window is used. In any case, if - startWindowIndex is set to C.INDEX_UNSET, this parameter is ignored and the - position is not reset at all.
    +
    startMediaItemIndex - The media item index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
    +
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given media item is used. In any case, + if startMediaItemIndex is set to C.INDEX_UNSET, this parameter is ignored + and the position is not reset at all.
    @@ -948,7 +1272,7 @@ void prepare​(Parameters:
    mediaSource - The new MediaSource.
    resetPosition - Whether the playback position should be reset to the default position. If - false, playback will start from the position defined by Player.getCurrentWindowIndex() + false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
    @@ -1027,6 +1351,231 @@ void prepare​( + + +
      +
    • +

      setAudioAttributes

      +
      void setAudioAttributes​(AudioAttributes audioAttributes,
      +                        boolean handleAudioFocus)
      +
      Sets the attributes for audio playback, used by the underlying audio track. If not set, the + default audio attributes will be used. They are suitable for general media playback. + +

      Setting the audio attributes during playback may introduce a short gap in audio output as + the audio track is recreated. A new audio session id will also be generated. + +

      If tunneling is enabled by the track selector, the specified audio attributes will be + ignored, but they will take effect if audio is later played without tunneling. + +

      If the device is running a build before platform API version 21, audio attributes cannot be + set directly on the underlying audio track. In this case, the usage will be mapped onto an + equivalent stream type using Util.getStreamTypeForAudioUsage(int). + +

      If audio focus should be handled, the AudioAttributes.usage must be C.USAGE_MEDIA or C.USAGE_GAME. Other usages will throw an IllegalArgumentException.

      +
      +
      Parameters:
      +
      audioAttributes - The attributes to use for audio playback.
      +
      handleAudioFocus - True if the player should handle audio focus, false otherwise.
      +
      +
    • +
    + + + +
      +
    • +

      setAudioSessionId

      +
      void setAudioSessionId​(int audioSessionId)
      +
      Sets the ID of the audio session to attach to the underlying AudioTrack. + +

      The audio session ID can be generated using Util.generateAudioSessionIdV21(Context) + for API 21+.

      +
      +
      Parameters:
      +
      audioSessionId - The audio session ID, or C.AUDIO_SESSION_ID_UNSET if it should be + generated by the framework.
      +
      +
    • +
    + + + +
      +
    • +

      getAudioSessionId

      +
      int getAudioSessionId()
      +
      Returns the audio session identifier, or C.AUDIO_SESSION_ID_UNSET if not set.
      +
    • +
    + + + +
      +
    • +

      setAuxEffectInfo

      +
      void setAuxEffectInfo​(AuxEffectInfo auxEffectInfo)
      +
      Sets information on an auxiliary audio effect to attach to the underlying audio track.
      +
    • +
    + + + +
      +
    • +

      clearAuxEffectInfo

      +
      void clearAuxEffectInfo()
      +
      Detaches any previously attached auxiliary audio effect from the underlying audio track.
      +
    • +
    + + + +
      +
    • +

      setSkipSilenceEnabled

      +
      void setSkipSilenceEnabled​(boolean skipSilenceEnabled)
      +
      Sets whether skipping silences in the audio stream is enabled.
      +
      +
      Parameters:
      +
      skipSilenceEnabled - Whether skipping silences in the audio stream is enabled.
      +
      +
    • +
    + + + +
      +
    • +

      getSkipSilenceEnabled

      +
      boolean getSkipSilenceEnabled()
      +
      Returns whether skipping silences in the audio stream is enabled.
      +
    • +
    + + + + + + + + + + + + + + + + + + + +
      +
    • +

      setVideoFrameMetadataListener

      +
      void setVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
      +
      Sets a listener to receive video frame metadata events. + +

      This method is intended to be called by the same component that sets the Surface + onto which video will be rendered. If using ExoPlayer's standard UI components, this method + should not be called directly from application code.

      +
      +
      Parameters:
      +
      listener - The listener.
      +
      +
    • +
    + + + +
      +
    • +

      clearVideoFrameMetadataListener

      +
      void clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
      +
      Clears the listener which receives video frame metadata events if it matches the one passed. + Else does nothing.
      +
      +
      Parameters:
      +
      listener - The listener to clear.
      +
      +
    • +
    + + + +
      +
    • +

      setCameraMotionListener

      +
      void setCameraMotionListener​(CameraMotionListener listener)
      +
      Sets a listener of camera motion events.
      +
      +
      Parameters:
      +
      listener - The listener.
      +
      +
    • +
    + + + +
      +
    • +

      clearCameraMotionListener

      +
      void clearCameraMotionListener​(CameraMotionListener listener)
      +
      Clears the listener which receives camera motion events if it matches the one passed. Else does + nothing.
      +
      +
      Parameters:
      +
      listener - The listener to clear.
      +
      +
    • +
    @@ -1037,8 +1586,8 @@ void prepare​(Creates a message that can be sent to a PlayerMessage.Target. By default, the message will be delivered immediately without blocking on the playback thread. The default PlayerMessage.getType() is 0 and the default PlayerMessage.getPayload() is null. If a position is specified with PlayerMessage.setPosition(long), the message will be - delivered at this position in the current window defined by Player.getCurrentWindowIndex(). - Alternatively, the message can be sent at a specific window using PlayerMessage.setPosition(int, long). + delivered at this position in the current media item defined by Player.getCurrentMediaItemIndex(). Alternatively, the message can be sent at a specific mediaItem + using PlayerMessage.setPosition(int, long). @@ -1112,7 +1661,7 @@ void prepare​(void setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems) +

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    Parameters:
    pauseAtEndOfMediaItems - Whether to pause playback at the end of each media item.
    @@ -1133,6 +1682,143 @@ void prepare​( + + +
      +
    • +

      getAudioFormat

      +
      @Nullable
      +Format getAudioFormat()
      +
      Returns the audio format currently being played, or null if no audio is being played.
      +
    • +
    + + + +
      +
    • +

      getVideoFormat

      +
      @Nullable
      +Format getVideoFormat()
      +
      Returns the video format currently being played, or null if no video is being played.
      +
    • +
    + + + +
      +
    • +

      getAudioDecoderCounters

      +
      @Nullable
      +DecoderCounters getAudioDecoderCounters()
      +
      Returns DecoderCounters for audio, or null if no audio is being played.
      +
    • +
    + + + +
      +
    • +

      getVideoDecoderCounters

      +
      @Nullable
      +DecoderCounters getVideoDecoderCounters()
      +
      Returns DecoderCounters for video, or null if no video is being played.
      +
    • +
    + + + +
      +
    • +

      setHandleAudioBecomingNoisy

      +
      void setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy)
      +
      Sets whether the player should pause automatically when audio is rerouted from a headset to + device speakers. See the audio + becoming noisy documentation for more information.
      +
      +
      Parameters:
      +
      handleAudioBecomingNoisy - Whether the player should pause automatically when audio is + rerouted from a headset to device speakers.
      +
      +
    • +
    + + + +
      +
    • +

      setHandleWakeLock

      +
      @Deprecated
      +void setHandleWakeLock​(boolean handleWakeLock)
      +
      Deprecated. +
      Use setWakeMode(int) instead.
      +
      +
    • +
    + + + +
      +
    • +

      setWakeMode

      +
      void setWakeMode​(@WakeMode
      +                 @com.google.android.exoplayer2.C.WakeMode int wakeMode)
      +
      Sets how the player should keep the device awake for playback when the screen is off. + +

      Enabling this feature requires the Manifest.permission.WAKE_LOCK permission. + It should be used together with a foreground Service for use cases where + playback occurs and the screen is off (e.g. background audio playback). It is not useful when + the screen will be kept on during playback (e.g. foreground video playback). + +

      When enabled, the locks (PowerManager.WakeLock / WifiManager.WifiLock) will be held whenever the player is in the Player.STATE_READY or Player.STATE_BUFFERING states with playWhenReady = true. The locks + held depends on the specified C.WakeMode.

      +
      +
      Parameters:
      +
      wakeMode - The C.WakeMode option to keep the device awake during playback.
      +
      +
    • +
    + + + +
      +
    • +

      setPriorityTaskManager

      +
      void setPriorityTaskManager​(@Nullable
      +                            PriorityTaskManager priorityTaskManager)
      +
      Sets a PriorityTaskManager, or null to clear a previously set priority task manager. + +

      The priority C.PRIORITY_PLAYBACK will be set while the player is loading.

      +
      +
      Parameters:
      +
      priorityTaskManager - The PriorityTaskManager, or null to clear a previously set + priority task manager.
      +
      +
    • +
    + + + +
      +
    • +

      setThrowsWhenUsingWrongThread

      +
      @Deprecated
      +void setThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread)
      +
      Deprecated. +
      Disabling the enforcement can result in hard-to-detect bugs. Do not use this method + except to ease the transition while wrong thread access problems are fixed.
      +
      +
      Sets whether the player should throw an IllegalStateException when methods are called + from a thread other than the one associated with Player.getApplicationLooper(). + +

      The default is true and this method will be removed in the future.

      +
      +
      Parameters:
      +
      throwsWhenUsingWrongThread - Whether to throw when methods are called from a wrong thread.
      +
      +
    • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayerLibraryInfo.html b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayerLibraryInfo.html index b191655a6c..a5f9f4b29b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayerLibraryInfo.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayerLibraryInfo.html @@ -131,7 +131,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    public final class ExoPlayerLibraryInfo
     extends Object
    -
    Information about the ExoPlayer library.
    +
    Information about the media libraries.
    @@ -156,24 +156,7 @@ extends static boolean ASSERTIONS_ENABLED -
    Whether the library was compiled with Assertions - checks enabled.
    - - - -static String -DEFAULT_USER_AGENT - -
    Deprecated. -
    ExoPlayer now uses the user agent of the underlying network stack by default.
    -
    - - - -static boolean -GL_ASSERTIONS_ENABLED - -
    Whether an exception should be thrown in case of an OpenGl error.
    +
    Whether the library was compiled with Assertions checks enabled.
    @@ -187,8 +170,7 @@ extends static boolean TRACE_ENABLED -
    Whether the library was compiled with TraceUtil - trace enabled.
    +
    Whether the library was compiled with TraceUtil trace enabled.
    @@ -209,7 +191,7 @@ extends static String VERSION_SLASHY -
    The version of the library expressed as "ExoPlayerLib/" + VERSION.
    +
    The version of the library expressed as TAG + "/" + VERSION.
    @@ -303,7 +285,7 @@ extends

    VERSION_SLASHY

    public static final String VERSION_SLASHY
    -
    The version of the library expressed as "ExoPlayerLib/" + VERSION.
    +
    The version of the library expressed as TAG + "/" + VERSION.
    See Also:
    Constant Field Values
    @@ -328,20 +310,6 @@ extends - - - -
      -
    • -

      DEFAULT_USER_AGENT

      -
      @Deprecated
      -public static final String DEFAULT_USER_AGENT
      -
      Deprecated. -
      ExoPlayer now uses the user agent of the underlying network stack by default.
      -
      -
      The default user agent for requests made by the library.
      -
    • -
    @@ -349,28 +317,13 @@ public static final 

    ASSERTIONS_ENABLED

    public static final boolean ASSERTIONS_ENABLED
    -
    +
    Whether the library was compiled with Assertions checks enabled.
    See Also:
    Constant Field Values
    - - - -
      -
    • -

      GL_ASSERTIONS_ENABLED

      -
      public static final boolean GL_ASSERTIONS_ENABLED
      -
      Whether an exception should be thrown in case of an OpenGl error.
      -
      -
      See Also:
      -
      Constant Field Values
      -
      -
    • -
    @@ -378,8 +331,7 @@ public static final 

    TRACE_ENABLED

    public static final boolean TRACE_ENABLED
    -
    +
    Whether the library was compiled with TraceUtil trace enabled.
    See Also:
    Constant Field Values
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html index a2b33749a8..4dc4edf5dc 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html @@ -234,32 +234,32 @@ extends Format.Builder +setCryptoType​(@com.google.android.exoplayer2.C.CryptoType int cryptoType) + + + + + +Format.Builder setDrmInitData​(DrmInitData drmInitData) - + Format.Builder setEncoderDelay​(int encoderDelay) - + Format.Builder setEncoderPadding​(int encoderPadding) - -Format.Builder -setExoMediaCryptoType​(Class<? extends ExoMediaCrypto> exoMediaCryptoType) - - - - Format.Builder setFrameRate​(float frameRate) @@ -353,7 +353,7 @@ extends Format.Builder -setRoleFlags​(int roleFlags) +setRoleFlags​(@com.google.android.exoplayer2.C.RoleFlags int roleFlags) @@ -381,7 +381,7 @@ extends Format.Builder -setSelectionFlags​(int selectionFlags) +setSelectionFlags​(@com.google.android.exoplayer2.C.SelectionFlags int selectionFlags) @@ -519,14 +519,14 @@ extends - +
    • setSelectionFlags

      public Format.Builder setSelectionFlags​(@SelectionFlags
      -                                        int selectionFlags)
      + @com.google.android.exoplayer2.C.SelectionFlags int selectionFlags)
      Sets Format.selectionFlags. The default value is 0.
      Parameters:
      @@ -536,14 +536,14 @@ extends
    - +
    • setRoleFlags

      public Format.Builder setRoleFlags​(@RoleFlags
      -                                   int roleFlags)
      + @com.google.android.exoplayer2.C.RoleFlags int roleFlags)
      Sets Format.roleFlags. The default value is 0.
      Parameters:
      @@ -947,18 +947,17 @@ extends
    - +
    • -

      setExoMediaCryptoType

      -
      public Format.Builder setExoMediaCryptoType​(@Nullable
      -                                            Class<? extends ExoMediaCrypto> exoMediaCryptoType)
      -
      Sets Format.exoMediaCryptoType. The default value is null.
      +

      setCryptoType

      +
      public Format.Builder setCryptoType​(@com.google.android.exoplayer2.C.CryptoType int cryptoType)
      +
      Sets Format.cryptoType. The default value is C.CRYPTO_TYPE_NONE.
      Parameters:
      -
      exoMediaCryptoType - The Format.exoMediaCryptoType.
      +
      cryptoType - The C.CryptoType.
      Returns:
      The builder.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Format.html b/docs/doc/reference/com/google/android/exoplayer2/Format.html index 9e78b8eb59..f896e1c926 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Format.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Format.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":42,"i2":42,"i3":10,"i4":42,"i5":42,"i6":42,"i7":42,"i8":42,"i9":42,"i10":42,"i11":42,"i12":41,"i13":41,"i14":41,"i15":41,"i16":41,"i17":41,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":9,"i24":10,"i25":10,"i26":10}; +var data = {"i0":10,"i1":42,"i2":10,"i3":42,"i4":42,"i5":42,"i6":42,"i7":42,"i8":42,"i9":42,"i10":42,"i11":42,"i12":41,"i13":41,"i14":41,"i15":41,"i16":41,"i17":41,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":9,"i24":10,"i25":10}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -130,12 +130,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • All Implemented Interfaces:
      -
      Parcelable
      +
      Bundleable

      public final class Format
       extends Object
      -implements Parcelable
      +implements Bundleable
      Represents a media format.

      When building formats, populate all fields whose values are known and relevant to the type of @@ -236,11 +236,11 @@ implements -

    • +
    • -

      Nested classes/interfaces inherited from interface android.os.Parcelable

      -Parcelable.ClassLoaderCreator<T extends Object>, Parcelable.Creator<T extends Object>
    • +

      Nested classes/interfaces inherited from interface com.google.android.exoplayer2.Bundleable

      +Bundleable.Creator<T extends Bundleable>
    @@ -309,18 +309,27 @@ implements -static Parcelable.Creator<Format> +static Bundleable.Creator<Format> CREATOR -  + +
    Object that can restore Format from a Bundle.
    + +@com.google.android.exoplayer2.C.CryptoType int +cryptoType + +
    The type of crypto that must be used to decode samples associated with this format, or C.CRYPTO_TYPE_NONE if the content is not encrypted.
    + + + DrmInitData drmInitData
    DRM initialization data if the stream is protected, or null otherwise.
    - + int encoderDelay @@ -328,21 +337,13 @@ implements + int encoderPadding
    The number of frames to trim from the end of the decoded audio stream, or 0 if not applicable.
    - -Class<? extends ExoMediaCrypto> -exoMediaCryptoType - -
    The type of ExoMediaCrypto that will be associated with the content this format - describes, or null if the content is not encrypted.
    - - float frameRate @@ -444,7 +445,7 @@ implements -int +@com.google.android.exoplayer2.C.RoleFlags int roleFlags
    Track role flags.
    @@ -473,7 +474,7 @@ implements -int +@com.google.android.exoplayer2.C.SelectionFlags int selectionFlags
    Track selection flags.
    @@ -502,13 +503,6 @@ implements -
  • - - -

    Fields inherited from interface android.os.Parcelable

    -CONTENTS_FILE_DESCRIPTOR, PARCELABLE_WRITE_RETURN_VALUE
  • - @@ -544,6 +538,13 @@ implements Format +copyWithCryptoType​(@com.google.android.exoplayer2.C.CryptoType int cryptoType) + +
    Returns a copy of this format with the specified cryptoType.
    + + + +Format copyWithDrmInitData​(DrmInitData drmInitData)
    Deprecated. @@ -551,13 +552,6 @@ implements -Format -copyWithExoMediaCryptoType​(Class<? extends ExoMediaCrypto> exoMediaCryptoType) - -
    Returns a copy of this format with the specified exoMediaCryptoType.
    - - Format copyWithFrameRate​(float frameRate) @@ -634,7 +628,7 @@ implements static Format -createAudioSampleFormat​(String id, +createAudioSampleFormat​(String id, String sampleMimeType, String codecs, int bitrate, @@ -644,7 +638,7 @@ implements List<byte[]> initializationData, DrmInitData drmInitData, - int selectionFlags, + @com.google.android.exoplayer2.C.SelectionFlags int selectionFlags, String language)
    Deprecated. @@ -654,7 +648,7 @@ implements static Format -createAudioSampleFormat​(String id, +createAudioSampleFormat​(String id, String sampleMimeType, String codecs, int bitrate, @@ -663,7 +657,7 @@ implements List<byte[]> initializationData, DrmInitData drmInitData, - int selectionFlags, + @com.google.android.exoplayer2.C.SelectionFlags int selectionFlags, String language)
    Deprecated. @@ -673,14 +667,14 @@ implements static Format -createContainerFormat​(String id, +createContainerFormat​(String id, String label, String containerMimeType, String sampleMimeType, String codecs, int bitrate, - int selectionFlags, - int roleFlags, + @com.google.android.exoplayer2.C.SelectionFlags int selectionFlags, + @com.google.android.exoplayer2.C.RoleFlags int roleFlags, String language)
    Deprecated. @@ -737,16 +731,11 @@ implements -int -describeContents() -  - - boolean equals​(Object obj)   - + int getPixelCount() @@ -754,12 +743,12 @@ implements NO_VALUE otherwise
    - + int hashCode()   - + boolean initializationDataEquals​(Format other) @@ -767,6 +756,13 @@ implements +Bundle +toBundle() + +
    Returns a Bundle representing the information stored in this object.
    + + static String toLogString​(Format format) @@ -784,12 +780,6 @@ implements withManifestFormatInfo​(Format manifestFormat)   - -void -writeToParcel​(Parcel dest, - int flags) -  - @@ -894,7 +884,7 @@ public final int selectionFlags
  • roleFlags

    @RoleFlags
    -public final int roleFlags
    +public final @com.google.android.exoplayer2.C.RoleFlags int roleFlags
    Track role flags.
  • @@ -1197,16 +1187,16 @@ public final int pcmEncoding
    The Accessibility channel, or NO_VALUE if not known or applicable.
    - +
    • -

      exoMediaCryptoType

      -
      @Nullable
      -public final Class<? extends ExoMediaCrypto> exoMediaCryptoType
      -
      The type of ExoMediaCrypto that will be associated with the content this format - describes, or null if the content is not encrypted. Cannot be null if drmInitData is non-null.
      +

      cryptoType

      +
      public final @com.google.android.exoplayer2.C.CryptoType int cryptoType
      +
      The type of crypto that must be used to decode samples associated with this format, or C.CRYPTO_TYPE_NONE if the content is not encrypted. Cannot be C.CRYPTO_TYPE_NONE if + drmInitData is non-null, but may be C.CRYPTO_TYPE_UNSUPPORTED to indicate that + the samples are encrypted using an unsupported crypto type.
    @@ -1215,7 +1205,8 @@ public final 
  • CREATOR

    -
    public static final Parcelable.Creator<Format> CREATOR
    +
    public static final Bundleable.Creator<Format> CREATOR
    +
    Object that can restore Format from a Bundle.
  • @@ -1284,7 +1275,7 @@ public static  + @@ -217,34 +217,27 @@ implements void -addListener​(Player.EventListener listener) +addListener​(Player.Listener listener)
    Deprecated.
    void -addListener​(Player.Listener listener) - -
    Registers a listener to receive all events from the player.
    - - - -void addMediaItem​(int index, MediaItem mediaItem)
    Adds a media item at the given index of the playlist.
    - + void addMediaItem​(MediaItem mediaItem)
    Adds a media item to the end of the playlist.
    - + void addMediaItems​(int index, List<MediaItem> mediaItems) @@ -252,13 +245,20 @@ implements Adds a list of media items at the given index of the playlist.
    - + void addMediaItems​(List<MediaItem> mediaItems)
    Adds a list of media items to the end of the playlist.
    + +boolean +canAdvertiseSession() + +
    Returns whether the player can be used to advertise a media session.
    + + void clearMediaItems() @@ -314,8 +314,7 @@ implements Looper getApplicationLooper() -
    Returns the Looper associated with the application thread that's used to access the - player and on which player events are received.
    +
    Deprecated.
    @@ -336,7 +335,7 @@ implements int getBufferedPercentage() -
    Returns an estimate of the percentage in the current content window or ad up to which data is +
    Returns an estimate of the percentage in the current content or ad up to which data is buffered, or 0 if no estimate is available.
    @@ -344,8 +343,8 @@ implements long getBufferedPosition() -
    Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
    +
    Returns an estimate of the position in the current content or ad up to which data is buffered, + in milliseconds.
    @@ -353,15 +352,15 @@ implements getContentBufferedPosition()
    If Player.isPlayingAd() returns true, returns an estimate of the content position in - the current content window up to which data is buffered, in milliseconds.
    + the current content up to which data is buffered, in milliseconds.
    long getContentDuration() -
    If Player.isPlayingAd() returns true, returns the duration of the current content - window in milliseconds, or C.TIME_UNSET if the duration is not known.
    +
    If Player.isPlayingAd() returns true, returns the duration of the current content in + milliseconds, or C.TIME_UNSET if the duration is not known.
    @@ -399,8 +398,8 @@ implements getCurrentLiveOffset()
    Returns the offset of the current playback position from the live edge in milliseconds, or - C.TIME_UNSET if the current window isn't live or the - offset is unknown.
    + C.TIME_UNSET if the current MediaItem Player.isCurrentMediaItemLive() isn't + live} or the offset is unknown.
    @@ -414,30 +413,30 @@ implements MediaItem getCurrentMediaItem() -
    Returns the media item of the current window in the timeline.
    +
    Returns the currently playing MediaItem.
    int +getCurrentMediaItemIndex() + +
    Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is + empty.
    + + + +int getCurrentPeriodIndex()
    Returns the index of the period currently being played.
    - + long getCurrentPosition() -
    Returns the playback position in the current content window or ad, in milliseconds, or the - prospective position in milliseconds if the current timeline is - empty.
    - - - -List<Metadata> -getCurrentStaticMetadata() - -
    Deprecated.
    +
    Returns the playback position in the current content or ad, in milliseconds, or the prospective + position in milliseconds if the current timeline is empty.
    @@ -462,56 +461,63 @@ implements -int -getCurrentWindowIndex() +TracksInfo +getCurrentTracksInfo() -
    Returns the index of the current window in the timeline, or the prospective window index if the current timeline is empty.
    +
    Returns the available tracks, as well as the tracks' support, type, and selection status.
    -DeviceInfo +int +getCurrentWindowIndex() + +
    Deprecated.
    + + + +DeviceInfo getDeviceInfo()
    Gets the device information.
    - + int getDeviceVolume()
    Gets the current volume of the device.
    - + long getDuration() -
    Returns the duration of the current content window or ad in milliseconds, or C.TIME_UNSET if the duration is not known.
    - - - -int -getMaxSeekToPreviousPosition() - -
    Returns the maximum position for which Player.seekToPrevious() seeks to the previous window, - in milliseconds.
    +
    Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if + the duration is not known.
    +long +getMaxSeekToPreviousPosition() + +
    Returns the maximum position for which Player.seekToPrevious() seeks to the previous MediaItem, in milliseconds.
    + + + MediaItem getMediaItemAt​(int index)
    Returns the MediaItem at the given index.
    - + int getMediaItemCount()
    Returns the number of media items in the playlist.
    - + MediaMetadata getMediaMetadata() @@ -519,29 +525,36 @@ implements + +int +getNextMediaItemIndex() + +
    Returns the index of the MediaItem that will be played if Player.seekToNextMediaItem() is called, which may depend on the current repeat mode and whether + shuffle mode is enabled.
    + + + int getNextWindowIndex() -
    Returns the index of the window that will be played if Player.seekToNextWindow() is called, - which may depend on the current repeat mode and whether shuffle mode is enabled.
    +
    Deprecated.
    - + PlaybackParameters getPlaybackParameters()
    Returns the currently active playback parameters.
    - + int getPlaybackState()
    Returns the current playback state of the player.
    - + int getPlaybackSuppressionReason() @@ -549,187 +562,236 @@ implements Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
    - + PlaybackException getPlayerError()
    Returns the error that caused playback to fail.
    - + MediaMetadata getPlaylistMetadata()
    Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
    - + boolean getPlayWhenReady()
    Whether playback will proceed when Player.getPlaybackState() == Player.STATE_READY.
    - + +int +getPreviousMediaItemIndex() + +
    Returns the index of the MediaItem that will be played if Player.seekToPreviousMediaItem() is called, which may depend on the current repeat mode and whether + shuffle mode is enabled.
    + + + int getPreviousWindowIndex() -
    Returns the index of the window that will be played if Player.seekToPreviousWindow() is - called, which may depend on the current repeat mode and whether shuffle mode is enabled.
    +
    Deprecated.
    - + int getRepeatMode()
    Returns the current Player.RepeatMode used for playback.
    - + long getSeekBackIncrement()
    Returns the Player.seekBack() increment.
    - + long getSeekForwardIncrement()
    Returns the Player.seekForward() increment.
    - + boolean getShuffleModeEnabled() -
    Returns whether shuffling of windows is enabled.
    +
    Returns whether shuffling of media items is enabled.
    - + long getTotalBufferedDuration()
    Returns an estimate of the total buffered duration from the current position, in milliseconds.
    - + +TrackSelectionParameters +getTrackSelectionParameters() + +
    Returns the parameters constraining the track selection.
    + + + VideoSize getVideoSize()
    Gets the size of the video.
    - + float getVolume()
    Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    - + Player getWrappedPlayer()
    Returns the Player to which operations are forwarded.
    - + boolean hasNext()
    Deprecated.
    - + +boolean +hasNextMediaItem() + +
    Returns whether a next MediaItem exists, which may depend on the current repeat mode + and whether shuffle mode is enabled.
    + + + boolean hasNextWindow() -
    Returns whether a next window exists, which may depend on the current repeat mode and whether - shuffle mode is enabled.
    +
    Deprecated.
    - + boolean hasPrevious()
    Deprecated.
    - + boolean -hasPreviousWindow() +hasPreviousMediaItem() -
    Returns whether a previous window exists, which may depend on the current repeat mode and +
    Returns whether a previous media item exists, which may depend on the current repeat mode and whether shuffle mode is enabled.
    - + +boolean +hasPreviousWindow() + +
    Deprecated.
    + + + void increaseDeviceVolume()
    Increases the volume of the device.
    - + boolean -isCommandAvailable​(int command) +isCommandAvailable​(@com.google.android.exoplayer2.Player.Command int command)
    Returns whether the provided Player.Command is available.
    - + +boolean +isCurrentMediaItemDynamic() + +
    Returns whether the current MediaItem is dynamic (may change when the Timeline + is updated), or false if the Timeline is empty.
    + + + +boolean +isCurrentMediaItemLive() + +
    Returns whether the current MediaItem is live, or false if the Timeline + is empty.
    + + + +boolean +isCurrentMediaItemSeekable() + +
    Returns whether the current MediaItem is seekable, or false if the Timeline is empty.
    + + + boolean isCurrentWindowDynamic() -
    Returns whether the current window is dynamic, or false if the Timeline is - empty.
    +
    Deprecated.
    - + boolean isCurrentWindowLive() -
    Returns whether the current window is live, or false if the Timeline is empty.
    +
    Deprecated.
    - + boolean isCurrentWindowSeekable() -
    Returns whether the current window is seekable, or false if the Timeline is - empty.
    +
    Deprecated.
    - + boolean isDeviceMuted()
    Gets whether the device is muted or not.
    - + boolean isLoading()
    Whether the player is currently loading the source.
    - + boolean isPlaying()
    Returns whether the player is playing, i.e.
    - + boolean isPlayingAd()
    Returns whether the player is currently playing an ad.
    - + void moveMediaItem​(int currentIndex, int newIndex) @@ -737,7 +799,7 @@ implements Moves the media item at the current index to the new index.
    - + void moveMediaItems​(int fromIndex, int toIndex, @@ -746,70 +808,63 @@ implements Moves the media item range to the new index. - + void next()
    Deprecated.
    - + void pause()
    Pauses playback.
    - + void play()
    Resumes playback as soon as Player.getPlaybackState() == Player.STATE_READY.
    - + void prepare()
    Prepares the player.
    - + void previous()
    Deprecated.
    - + void release()
    Releases the player.
    - -void -removeListener​(Player.EventListener listener) - -
    Deprecated.
    - - - + void removeListener​(Player.Listener listener)
    Unregister a listener registered through Player.addListener(Listener).
    - + void removeMediaItem​(int index)
    Removes the media item at the given index of the playlist.
    - + void removeMediaItems​(int fromIndex, int toIndex) @@ -817,94 +872,109 @@ implements Removes a range of media items from the playlist. - + void seekBack() -
    Seeks back in the current window by Player.getSeekBackIncrement() milliseconds.
    - - - -void -seekForward() - -
    Seeks forward in the current window by Player.getSeekForwardIncrement() milliseconds.
    - - - -void -seekTo​(int windowIndex, - long positionMs) - -
    Seeks to a position specified in milliseconds in the specified window.
    - - - -void -seekTo​(long positionMs) - -
    Seeks to a position specified in milliseconds in the current window.
    - - - -void -seekToDefaultPosition() - -
    Seeks to the default position associated with the current window.
    - - - -void -seekToDefaultPosition​(int windowIndex) - -
    Seeks to the default position associated with the specified window.
    - - - -void -seekToNext() - -
    Seeks to a later position in the current or next window (if available).
    - - - -void -seekToNextWindow() - -
    Seeks to the default position of the next window, which may depend on the current repeat mode - and whether shuffle mode is enabled.
    - - - -void -seekToPrevious() - -
    Seeks to an earlier position in the current or previous window (if available).
    +
    Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds.
    void -seekToPreviousWindow() +seekForward() -
    Seeks to the default position of the previous window, which may depend on the current repeat - mode and whether shuffle mode is enabled.
    +
    Seeks forward in the current MediaItem by Player.getSeekForwardIncrement() + milliseconds.
    void +seekTo​(int mediaItemIndex, + long positionMs) + +
    Seeks to a position specified in milliseconds in the specified MediaItem.
    + + + +void +seekTo​(long positionMs) + +
    Seeks to a position specified in milliseconds in the current MediaItem.
    + + + +void +seekToDefaultPosition() + +
    Seeks to the default position associated with the current MediaItem.
    + + + +void +seekToDefaultPosition​(int mediaItemIndex) + +
    Seeks to the default position associated with the specified MediaItem.
    + + + +void +seekToNext() + +
    Seeks to a later position in the current or next MediaItem (if available).
    + + + +void +seekToNextMediaItem() + +
    Seeks to the default position of the next MediaItem, which may depend on the current + repeat mode and whether shuffle mode is enabled.
    + + + +void +seekToNextWindow() + +
    Deprecated.
    + + + +void +seekToPrevious() + +
    Seeks to an earlier position in the current or previous MediaItem (if available).
    + + + +void +seekToPreviousMediaItem() + +
    Seeks to the default position of the previous MediaItem, which may depend on the + current repeat mode and whether shuffle mode is enabled.
    + + + +void +seekToPreviousWindow() + +
    Deprecated.
    + + + +void setDeviceMuted​(boolean muted)
    Sets the mute state of the device.
    - + void setDeviceVolume​(int volume)
    Sets the volume of the device.
    - + void setMediaItem​(MediaItem mediaItem) @@ -912,7 +982,7 @@ implements + void setMediaItem​(MediaItem mediaItem, boolean resetPosition) @@ -920,7 +990,7 @@ implements Clears the playlist and adds the specified MediaItem. - + void setMediaItem​(MediaItem mediaItem, long startPositionMs) @@ -928,7 +998,7 @@ implements Clears the playlist and adds the specified MediaItem. - + void setMediaItems​(List<MediaItem> mediaItems) @@ -936,7 +1006,7 @@ implements + void setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition) @@ -944,65 +1014,72 @@ implements Clears the playlist and adds the specified MediaItems. - + void setMediaItems​(List<MediaItem> mediaItems, - int startWindowIndex, + int startIndex, long startPositionMs)
    Clears the playlist and adds the specified MediaItems.
    - + void setPlaybackParameters​(PlaybackParameters playbackParameters)
    Attempts to set the playback parameters.
    - + void setPlaybackSpeed​(float speed)
    Changes the rate at which playback occurs.
    - + void setPlaylistMetadata​(MediaMetadata mediaMetadata)
    Sets the playlist MediaMetadata.
    - + void setPlayWhenReady​(boolean playWhenReady)
    Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY.
    - + void -setRepeatMode​(int repeatMode) +setRepeatMode​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
    Sets the Player.RepeatMode to be used for playback.
    - + void setShuffleModeEnabled​(boolean shuffleModeEnabled) -
    Sets whether shuffling of windows is enabled.
    +
    Sets whether shuffling of media items is enabled.
    - + +void +setTrackSelectionParameters​(TrackSelectionParameters parameters) + +
    Sets the parameters constraining the track selection.
    + + + void setVideoSurface​(Surface surface)
    Sets the Surface onto which video will be rendered.
    - + void setVideoSurfaceHolder​(SurfaceHolder surfaceHolder) @@ -1010,35 +1087,36 @@ implements + void setVideoSurfaceView​(SurfaceView surfaceView)
    Sets the SurfaceView onto which video will be rendered.
    - + void setVideoTextureView​(TextureView textureView)
    Sets the TextureView onto which video will be rendered.
    - + void -setVolume​(float audioVolume) +setVolume​(float volume) -
    Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    +
    Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal + unchanged), inclusive.
    - + void stop()
    Stops playback without resetting the player.
    - + void stop​(boolean reset) @@ -1095,7 +1173,9 @@ implements
  • getApplicationLooper

    -
    public Looper getApplicationLooper()
    +
    @Deprecated
    +public Looper getApplicationLooper()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the Looper associated with the application thread that's used to access the player and on which player events are received.
    @@ -1105,42 +1185,19 @@ implements - - -
      -
    • -

      addListener

      -
      @Deprecated
      -public void addListener​(Player.EventListener listener)
      -
      Deprecated.
      -
      Description copied from interface: Player
      -
      Registers a listener to receive events from the player. - -

      The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

      -
      -
      Specified by:
      -
      addListener in interface Player
      -
      Parameters:
      -
      listener - The listener to register.
      -
      -
    • -
    • addListener

      -
      public void addListener​(Player.Listener listener)
      +
      @Deprecated
      +public void addListener​(Player.Listener listener)
      +
      Deprecated.
      Description copied from interface: Player
      Registers a listener to receive all events from the player. -

      The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

      +

      The listener's methods will be called on the thread associated with Player.getApplicationLooper().

      Specified by:
      addListener in interface Player
      @@ -1149,26 +1206,6 @@ public void addListener​(
    - - - - @@ -1222,7 +1259,7 @@ public void removeListener​(mediaItems - The new MediaItems.
    resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined - by Player.getCurrentWindowIndex() and Player.getCurrentPosition().
    + by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
  • @@ -1233,7 +1270,7 @@ public void removeListener​(

    setMediaItems

    public void setMediaItems​(List<MediaItem> mediaItems,
    -                          int startWindowIndex,
    +                          int startIndex,
                               long startPositionMs)
    Description copied from interface: Player
    Clears the playlist and adds the specified MediaItems.
    @@ -1242,11 +1279,11 @@ public void removeListener​(setMediaItems in interface Player
    Parameters:
    mediaItems - The new MediaItems.
    -
    startWindowIndex - The window index to start playback from. If C.INDEX_UNSET is - passed, the current position is not reset.
    -
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given window is used. In any case, if - startWindowIndex is set to C.INDEX_UNSET, this parameter is ignored and the - position is not reset at all.
    +
    startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET + is passed, the current position is not reset.
    +
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In + any case, if startIndex is set to C.INDEX_UNSET, this parameter is ignored + and the position is not reset at all.
    @@ -1303,7 +1340,7 @@ public void removeListener​(Parameters:
    mediaItem - The new MediaItem.
    resetPosition - Whether the playback position should be reset to the default position. If - false, playback will start from the position defined by Player.getCurrentWindowIndex() + false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
    @@ -1477,28 +1514,27 @@ public void removeListener​( - + + + + +
      +
    • +

      canAdvertiseSession

      +
      public boolean canAdvertiseSession()
      +
      Description copied from interface: Player
      +
      Returns whether the player can be used to advertise a media session.
      +
      +
      Specified by:
      +
      canAdvertiseSession in interface Player
      +
      +
    • +
    @@ -1521,12 +1572,11 @@ public void removeListener​(The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands change. -

    Executing a command that is not available (for example, calling Player.seekToNextWindow() - if Player.COMMAND_SEEK_TO_NEXT_WINDOW is unavailable) will neither throw an exception nor - generate a Player.getPlayerError() player error}. +

    Executing a command that is not available (for example, calling Player.seekToNextMediaItem() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will + neither throw an exception nor generate a Player.getPlayerError() player error}. -

    Player.COMMAND_SEEK_TO_PREVIOUS_WINDOW and Player.COMMAND_SEEK_TO_NEXT_WINDOW are - unavailable if there is no such MediaItem. +

    Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM + are unavailable if there is no such MediaItem.

    Specified by:
    getAvailableCommands in interface Player
    @@ -1567,7 +1617,7 @@ public void removeListener​(Returns:
    The current playback state.
    See Also:
    -
    Player.Listener.onPlaybackStateChanged(int)
    +
    Player.Listener.onPlaybackStateChanged(int)
    @@ -1587,7 +1637,7 @@ public void removeListener​(Returns:
    The current playback suppression reason.
    See Also:
    -
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
    +
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
    @@ -1708,23 +1758,23 @@ public Returns:
    Whether playback will proceed when ready.
    See Also:
    -
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    +
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    - + @@ -1757,7 +1807,7 @@ public public void setShuffleModeEnabled​(boolean shuffleModeEnabled) -
    Sets whether shuffling of windows is enabled.
    +
    Sets whether shuffling of media items is enabled.
    Specified by:
    setShuffleModeEnabled in interface Player
    @@ -1774,7 +1824,7 @@ public public boolean getShuffleModeEnabled() -
    Returns whether shuffling of windows is enabled.
    +
    Returns whether shuffling of media items is enabled.
    Specified by:
    getShuffleModeEnabled in interface Player
    @@ -1810,9 +1860,9 @@ public public void seekToDefaultPosition() -
    Seeks to the default position associated with the current window. The position can depend on - the type of media being played. For live streams it will typically be the live edge of the - window. For other streams it will typically be the start of the window.
    +
    Seeks to the default position associated with the current MediaItem. The position can + depend on the type of media being played. For live streams it will typically be the live edge. + For other streams it will typically be the start.
    Specified by:
    seekToDefaultPosition in interface Player
    @@ -1825,17 +1875,17 @@ public 
  • seekToDefaultPosition

    -
    public void seekToDefaultPosition​(int windowIndex)
    +
    public void seekToDefaultPosition​(int mediaItemIndex)
    -
    Seeks to the default position associated with the specified window. The position can depend on - the type of media being played. For live streams it will typically be the live edge of the - window. For other streams it will typically be the start of the window.
    +
    Seeks to the default position associated with the specified MediaItem. The position can + depend on the type of media being played. For live streams it will typically be the live edge. + For other streams it will typically be the start.
    Specified by:
    seekToDefaultPosition in interface Player
    Parameters:
    -
    windowIndex - The index of the window whose associated default position should be seeked - to.
    +
    mediaItemIndex - The index of the MediaItem whose associated default position + should be seeked to.
  • @@ -1847,13 +1897,13 @@ public public void seekTo​(long positionMs) -
    Seeks to a position specified in milliseconds in the current window.
    +
    Seeks to a position specified in milliseconds in the current MediaItem.
    Specified by:
    seekTo in interface Player
    Parameters:
    -
    positionMs - The seek position in the current window, or C.TIME_UNSET to seek to - the window's default position.
    +
    positionMs - The seek position in the current MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
    @@ -1863,17 +1913,17 @@ public 
  • seekTo

    -
    public void seekTo​(int windowIndex,
    +
    public void seekTo​(int mediaItemIndex,
                        long positionMs)
    -
    Seeks to a position specified in milliseconds in the specified window.
    +
    Seeks to a position specified in milliseconds in the specified MediaItem.
    Specified by:
    seekTo in interface Player
    Parameters:
    -
    windowIndex - The index of the window.
    -
    positionMs - The seek position in the specified window, or C.TIME_UNSET to seek to - the window's default position.
    +
    mediaItemIndex - The index of the MediaItem.
    +
    positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
  • @@ -1904,7 +1954,7 @@ public public void seekBack() -
    Seeks back in the current window by Player.getSeekBackIncrement() milliseconds.
    +
    Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds.
    Specified by:
    seekBack in interface Player
    @@ -1938,7 +1988,8 @@ public public void seekForward() -
    Seeks forward in the current window by Player.getSeekForwardIncrement() milliseconds.
    +
    Seeks forward in the current MediaItem by Player.getSeekForwardIncrement() + milliseconds.
    Specified by:
    seekForward in interface Player
    @@ -1966,9 +2017,24 @@ public boolean hasPrevious()
    • hasPreviousWindow

      -
      public boolean hasPreviousWindow()
      -
      Description copied from interface: Player
      -
      Returns whether a previous window exists, which may depend on the current repeat mode and +
      @Deprecated
      +public boolean hasPreviousWindow()
      +
      Deprecated.
      +
      +
      Specified by:
      +
      hasPreviousWindow in interface Player
      +
      +
    • +
    + + + +
      +
    • +

      hasPreviousMediaItem

      +
      public boolean hasPreviousMediaItem()
      +
      Description copied from interface: Player
      +
      Returns whether a previous media item exists, which may depend on the current repeat mode and whether shuffle mode is enabled.

      Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when @@ -1976,7 +2042,7 @@ public boolean hasPrevious() details.

      Specified by:
      -
      hasPreviousWindow in interface Player
      +
      hasPreviousMediaItem in interface Player
    @@ -2001,18 +2067,32 @@ public void previous()
    • seekToPreviousWindow

      -
      public void seekToPreviousWindow()
      -
      Description copied from interface: Player
      -
      Seeks to the default position of the previous window, which may depend on the current repeat - mode and whether shuffle mode is enabled. Does nothing if Player.hasPreviousWindow() is - false. +
      @Deprecated
      +public void seekToPreviousWindow()
      +
      Deprecated.
      +
      +
      Specified by:
      +
      seekToPreviousWindow in interface Player
      +
      +
    • +
    + + + + @@ -2024,18 +2104,20 @@ public void previous()

    seekToPrevious

    public void seekToPrevious()
    Description copied from interface: Player
    -
    Seeks to an earlier position in the current or previous window (if available). More precisely: +
    Seeks to an earlier position in the current or previous MediaItem (if available). More + precisely:
    Specified by:
    @@ -2049,17 +2131,16 @@ public void previous() @@ -2084,17 +2165,32 @@ public boolean hasNext()
    • hasNextWindow

      -
      public boolean hasNextWindow()
      -
      Description copied from interface: Player
      -
      Returns whether a next window exists, which may depend on the current repeat mode and whether - shuffle mode is enabled. +
      @Deprecated
      +public boolean hasNextWindow()
      +
      Deprecated.
      +
      +
      Specified by:
      +
      hasNextWindow in interface Player
      +
      +
    • +
    + + + + @@ -2119,17 +2215,33 @@ public void next()
    • seekToNextWindow

      -
      public void seekToNextWindow()
      -
      Description copied from interface: Player
      -
      Seeks to the default position of the next window, which may depend on the current repeat mode - and whether shuffle mode is enabled. Does nothing if Player.hasNextWindow() is false. +
      @Deprecated
      +public void seekToNextWindow()
      +
      Deprecated.
      +
      +
      Specified by:
      +
      seekToNextWindow in interface Player
      +
      +
    • +
    + + + + @@ -2141,14 +2253,15 @@ public void next()

    seekToNext

    public void seekToNext()
    Description copied from interface: Player
    -
    Seeks to a later position in the current or next window (if available). More precisely: +
    Seeks to a later position in the current or next MediaItem (if available). More + precisely:
    • If the timeline is empty or seeking is not possible, does nothing. -
    • Otherwise, if a next window exists, seeks to the default - position of the next window. -
    • Otherwise, if the current window is live and has not - ended, seeks to the live edge of the current window. +
    • Otherwise, if a next media item exists, seeks to the default + position of the next MediaItem. +
    • Otherwise, if the current MediaItem is live and + has not ended, seeks to the live edge of the current MediaItem.
    • Otherwise, does nothing.
    @@ -2283,7 +2396,7 @@ public void stop​(boolean reset)
    Specified by:
    getCurrentTrackGroups in interface Player
    See Also:
    -
    Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    +
    Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    @@ -2304,22 +2417,70 @@ public void stop​(boolean reset)
    Specified by:
    getCurrentTrackSelections in interface Player
    See Also:
    -
    Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    +
    Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    - + + + + + + + + +
      +
    • +

      setTrackSelectionParameters

      +
      public void setTrackSelectionParameters​(TrackSelectionParameters parameters)
      +
      Description copied from interface: Player
      +
      Sets the parameters constraining the track selection. + +

      Unsupported parameters will be silently ignored. + +

      Use Player.getTrackSelectionParameters() to retrieve the current parameters. For example, + the following snippet restricts video to SD whilst keep other track selection parameters + unchanged: + +

      
      + player.setTrackSelectionParameters(
      +   player.getTrackSelectionParameters()
      +         .buildUpon()
      +         .setMaxVideoSizeSd()
      +         .build())
      + 
      +
      +
      Specified by:
      +
      setTrackSelectionParameters in interface Player
    @@ -2336,7 +2497,8 @@ public MediaMetadata is a combination of the MediaItem.mediaMetadata and the static and dynamic metadata from the track selections' - formats and MetadataOutput.onMetadata(Metadata).
    + formats
    and Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, it will be prioritised above the same field coming from static or + dynamic metadata.
    Specified by:
    getMediaMetadata in interface Player
    @@ -2402,7 +2564,7 @@ public Specified by:
    getCurrentTimeline in interface Player
    See Also:
    -
    Player.Listener.onTimelineChanged(Timeline, int)
    +
    Player.Listener.onTimelineChanged(Timeline, int)
    @@ -2427,32 +2589,64 @@ public 
  • getCurrentWindowIndex

    -
    public int getCurrentWindowIndex()
    -
    -
    Returns the index of the current window in the timeline, or the prospective window index if the current timeline is empty.
    +
    @Deprecated
    +public int getCurrentWindowIndex()
    +
    Deprecated.
    Specified by:
    getCurrentWindowIndex in interface Player
  • + + + + + + + + @@ -2462,18 +2656,33 @@ public 
  • getPreviousWindowIndex

    -
    public int getPreviousWindowIndex()
    -
    -
    Returns the index of the window that will be played if Player.seekToPreviousWindow() is - called, which may depend on the current repeat mode and whether shuffle mode is enabled. - Returns C.INDEX_UNSET if Player.hasPreviousWindow() is false. +
    @Deprecated
    +public int getPreviousWindowIndex()
    +
    Deprecated.
    +
    +
    Specified by:
    +
    getPreviousWindowIndex in interface Player
    +
    +
  • + + + + + @@ -2486,13 +2695,12 @@ public @Nullable public MediaItem getCurrentMediaItem()
    Description copied from interface: Player
    -
    Returns the media item of the current window in the timeline. May be null if the timeline is - empty.
    +
    Returns the currently playing MediaItem. May be null if the timeline is empty.
    Specified by:
    getCurrentMediaItem in interface Player
    See Also:
    -
    Player.Listener.onMediaItemTransition(MediaItem, int)
    +
    Player.Listener.onMediaItemTransition(MediaItem, int)
    @@ -2534,7 +2742,8 @@ public public long getDuration() -
    Returns the duration of the current content window or ad in milliseconds, or C.TIME_UNSET if the duration is not known.
    +
    Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if + the duration is not known.
    Specified by:
    getDuration in interface Player
    @@ -2549,9 +2758,8 @@ public public long getCurrentPosition() -
    Returns the playback position in the current content window or ad, in milliseconds, or the - prospective position in milliseconds if the current timeline is - empty.
    +
    Returns the playback position in the current content or ad, in milliseconds, or the prospective + position in milliseconds if the current timeline is empty.
    Specified by:
    getCurrentPosition in interface Player
    @@ -2566,8 +2774,8 @@ public public long getBufferedPosition() -
    Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
    +
    Returns an estimate of the position in the current content or ad up to which data is buffered, + in milliseconds.
    Specified by:
    getBufferedPosition in interface Player
    @@ -2582,7 +2790,7 @@ public public int getBufferedPercentage() -
    Returns an estimate of the percentage in the current content window or ad up to which data is +
    Returns an estimate of the percentage in the current content or ad up to which data is buffered, or 0 if no estimate is available.
    Specified by:
    @@ -2599,7 +2807,7 @@ public public long getTotalBufferedDuration()
    Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and windows.
    + This includes pre-buffered data for subsequent ads and media items.
    Specified by:
    getTotalBufferedDuration in interface Player
    @@ -2612,13 +2820,28 @@ public 
  • isCurrentWindowDynamic

    -
    public boolean isCurrentWindowDynamic()
    -
    -
    Returns whether the current window is dynamic, or false if the Timeline is - empty.
    +
    @Deprecated
    +public boolean isCurrentWindowDynamic()
    +
    Deprecated.
    Specified by:
    isCurrentWindowDynamic in interface Player
    +
    +
  • + + + + + + + + + + + + + @@ -3052,7 +3306,7 @@ public 
  • getDeviceInfo

    -
    public DeviceInfo getDeviceInfo()
    +
    public DeviceInfo getDeviceInfo()
    Description copied from interface: Player
    Gets the device information.
    @@ -3071,12 +3325,12 @@ public Description copied from interface: Player
    Gets the current volume of the device. -

    For devices with local playback, the volume returned +

    For devices with local playback, the volume returned by this method varies according to the current stream type. The stream type is determined by AudioAttributes.usage which can be converted to stream type with - Util.getStreamTypeForAudioUsage(int). + Util.getStreamTypeForAudioUsage(int). -

    For devices with remote playback, the volume of the +

    For devices with remote playback, the volume of the remote device is returned.

    Specified by:
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.AdsConfiguration.Builder.html similarity index 60% rename from docs/doc/reference/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.html rename to docs/doc/reference/com/google/android/exoplayer2/MediaItem.AdsConfiguration.Builder.html index 13079461d9..d71987cbca 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.AdsConfiguration.Builder.html @@ -2,36 +2,36 @@ -CacheDataSinkFactory (ExoPlayer library) +MediaItem.AdsConfiguration.Builder (ExoPlayer library) - - - - - + + + + + - - + +
  • @@ -255,6 +291,16 @@ public final  + + + @@ -333,7 +379,7 @@ public final 
  • Summary: 
  • -
  • Nested | 
  • +
  • Nested | 
  • Field | 
  • Constr | 
  • Method
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Builder.html index 7a637f2164..2cc26876fd 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Builder.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; +var data = {"i0":10,"i1":10,"i2":42,"i3":42,"i4":42,"i5":42,"i6":10,"i7":42,"i8":42,"i9":42,"i10":42,"i11":10,"i12":10,"i13":42,"i14":42,"i15":42,"i16":42,"i17":42,"i18":42,"i19":42,"i20":42,"i21":42,"i22":42,"i23":10,"i24":42,"i25":42,"i26":42,"i27":42,"i28":42,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":42,"i35":10,"i36":10,"i37":10}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -173,7 +173,7 @@ extends

    Method Summary

    - + @@ -188,203 +188,273 @@ extends - + - + - + - + - + - + - + - + + + + + + + + + + + - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + + + + + + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - +
    Deprecated.
    All Methods Instance Methods Concrete Methods All Methods Instance Methods Concrete Methods Deprecated Methods 
    Modifier and Type Method
    MediaItem.BuildersetAdTagUri​(Uri adTagUri)setAdsConfiguration​(MediaItem.AdsConfiguration adsConfiguration) -
    Sets the optional ad tag Uri.
    +
    Sets the optional MediaItem.AdsConfiguration.
    MediaItem.BuildersetAdTagUri​(Uri adTagUri, - Object adsId)setAdTagUri​(Uri adTagUri) -
    Sets the optional ad tag Uri and ads identifier.
    +
    Deprecated. +
    Use setAdsConfiguration(AdsConfiguration) and pass the adTagUri + to Builder(Uri) instead.
    +
    MediaItem.BuildersetAdTagUri​(String adTagUri)setAdTagUri​(Uri adTagUri, + Object adsId) -
    Sets the optional ad tag Uri.
    +
    Deprecated. + +
    MediaItem.BuildersetClipEndPositionMs​(long endPositionMs)setAdTagUri​(String adTagUri) -
    Sets the optional end position in milliseconds which must be a value larger than or equal to - zero, or C.TIME_END_OF_SOURCE to end when playback reaches the end of media (Default: - C.TIME_END_OF_SOURCE).
    +
    Deprecated. +
    Use setAdsConfiguration(AdsConfiguration), parse the adTagUri + with Uri.parse(String) and pass the result to Builder(Uri) instead.
    +
    MediaItem.BuildersetClipRelativeToDefaultPosition​(boolean relativeToDefaultPosition)setClipEndPositionMs​(long endPositionMs) -
    Sets whether the start position and the end position are relative to the default position in - the window (Default: false).
    +
    MediaItem.BuildersetClipRelativeToLiveWindow​(boolean relativeToLiveWindow)setClippingConfiguration​(MediaItem.ClippingConfiguration clippingConfiguration) -
    Sets whether the start/end positions should move with the live window for live streams.
    +
    MediaItem.BuildersetClipStartPositionMs​(long startPositionMs)setClipRelativeToDefaultPosition​(boolean relativeToDefaultPosition) -
    Sets the optional start position in milliseconds which must be a value larger than or equal - to zero (Default: 0).
    +
    MediaItem.BuildersetClipStartsAtKeyFrame​(boolean startsAtKeyFrame)setClipRelativeToLiveWindow​(boolean relativeToLiveWindow) -
    Sets whether the start point is guaranteed to be a key frame.
    +
    MediaItem.BuildersetClipStartPositionMs​(long startPositionMs) + +
    MediaItem.BuildersetClipStartsAtKeyFrame​(boolean startsAtKeyFrame) + +
    MediaItem.Builder setCustomCacheKey​(String customCacheKey)
    Sets the optional custom cache key (only used for progressive streams).
    MediaItem.BuildersetDrmForceDefaultLicenseUri​(boolean forceDefaultLicenseUri) -
    Sets whether to force use the default DRM license server URI even if the media specifies its - own DRM license server URI.
    -
    MediaItem.BuildersetDrmKeySetId​(byte[] keySetId) -
    Sets the key set ID of the offline license.
    -
    MediaItem.BuildersetDrmLicenseRequestHeaders​(Map<String,​String> licenseRequestHeaders)setDrmConfiguration​(MediaItem.DrmConfiguration drmConfiguration) -
    Sets the optional request headers attached to the DRM license request.
    +
    Sets the optional DRM configuration.
    MediaItem.BuildersetDrmLicenseUri​(Uri licenseUri)setDrmForceDefaultLicenseUri​(boolean forceDefaultLicenseUri) -
    Sets the optional default DRM license server URI.
    +
    MediaItem.BuildersetDrmLicenseUri​(String licenseUri)setDrmKeySetId​(byte[] keySetId) -
    Sets the optional default DRM license server URI.
    +
    MediaItem.BuildersetDrmMultiSession​(boolean multiSession)setDrmLicenseRequestHeaders​(Map<String,​String> licenseRequestHeaders) -
    Sets whether the DRM configuration is multi session enabled.
    +
    MediaItem.BuildersetDrmPlayClearContentWithoutKey​(boolean playClearContentWithoutKey)setDrmLicenseUri​(Uri licenseUri) -
    Sets whether clear samples within protected content should be played when keys for the - encrypted part of the content have yet to be loaded.
    +
    MediaItem.BuildersetDrmSessionForClearPeriods​(boolean sessionForClearPeriods)setDrmLicenseUri​(String licenseUri) -
    Sets whether a DRM session should be used for clear tracks of type C.TRACK_TYPE_VIDEO - and C.TRACK_TYPE_AUDIO.
    +
    MediaItem.BuildersetDrmSessionForClearTypes​(List<Integer> sessionForClearTypes)setDrmMultiSession​(boolean multiSession) -
    Sets a list of C.TRACK_TYPE_* constants for which to use a DRM session even - when the tracks are in the clear.
    +
    MediaItem.BuildersetDrmUuid​(UUID uuid)setDrmPlayClearContentWithoutKey​(boolean playClearContentWithoutKey) -
    Sets the UUID of the protection scheme.
    +
    MediaItem.BuildersetLiveMaxOffsetMs​(long liveMaxOffsetMs)setDrmSessionForClearPeriods​(boolean sessionForClearPeriods) -
    Sets the optional maximum offset from the live edge for live streams, in milliseconds.
    +
    MediaItem.BuildersetLiveMaxPlaybackSpeed​(float maxPlaybackSpeed)setDrmSessionForClearTypes​(List<@TrackType Integer> sessionForClearTypes) -
    Sets the optional maximum playback speed for live stream speed adjustment.
    +
    MediaItem.BuildersetLiveMinOffsetMs​(long liveMinOffsetMs)setDrmUuid​(UUID uuid) -
    Sets the optional minimum offset from the live edge for live streams, in milliseconds.
    +
    Deprecated. +
    Use setDrmConfiguration(DrmConfiguration) and pass the uuid to + Builder(UUID) instead.
    +
    MediaItem.BuildersetLiveMinPlaybackSpeed​(float minPlaybackSpeed)setLiveConfiguration​(MediaItem.LiveConfiguration liveConfiguration) -
    Sets the optional minimum playback speed for live stream speed adjustment.
    +
    MediaItem.BuildersetLiveTargetOffsetMs​(long liveTargetOffsetMs)setLiveMaxOffsetMs​(long liveMaxOffsetMs) -
    Sets the optional target offset from the live edge for live streams, in milliseconds.
    +
    MediaItem.BuildersetLiveMaxPlaybackSpeed​(float maxPlaybackSpeed) + +
    MediaItem.BuildersetLiveMinOffsetMs​(long liveMinOffsetMs) + +
    MediaItem.BuildersetLiveMinPlaybackSpeed​(float minPlaybackSpeed) + +
    MediaItem.BuildersetLiveTargetOffsetMs​(long liveTargetOffsetMs) + +
    MediaItem.Builder setMediaId​(String mediaId)
    Sets the optional media ID which identifies the media item.
    MediaItem.Builder setMediaMetadata​(MediaMetadata mediaMetadata)
    Sets the media metadata.
    MediaItem.Builder setMimeType​(String mimeType)
    Sets the optional MIME type.
    MediaItem.Builder setStreamKeys​(List<StreamKey> streamKeys) @@ -392,28 +462,37 @@ extends
    MediaItem.BuildersetSubtitles​(List<MediaItem.Subtitle> subtitles)setSubtitleConfigurations​(List<MediaItem.SubtitleConfiguration> subtitleConfigurations)
    Sets the optional subtitles.
    MediaItem.BuildersetSubtitles​(List<MediaItem.Subtitle> subtitles) +
    Deprecated. + +
    +
    MediaItem.Builder setTag​(Object tag)
    Sets the optional tag for custom attributes.
    MediaItem.Builder setUri​(Uri uri)
    Sets the optional URI.
    MediaItem.Builder setUri​(String uri) @@ -486,8 +565,8 @@ extends String uri)
    Sets the optional URI. -

    If uri is null or unset then no MediaItem.PlaybackProperties object is created - during build() and no other Builder methods that would populate MediaItem.playbackProperties should be called.

    +

    If uri is null or unset then no MediaItem.LocalConfiguration object is created + during build() and no other Builder methods that would populate MediaItem.localConfiguration should be called. @@ -500,8 +579,8 @@ extends Uri uri)

    Sets the optional URI. -

    If uri is null or unset then no MediaItem.PlaybackProperties object is created - during build() and no other Builder methods that would populate MediaItem.playbackProperties should be called.

    +

    If uri is null or unset then no MediaItem.LocalConfiguration object is created + during build() and no other Builder methods that would populate MediaItem.localConfiguration should be called. @@ -523,15 +602,28 @@ extends + + + +

    @@ -540,10 +632,11 @@ extends
  • setClipEndPositionMs

    -
    public MediaItem.Builder setClipEndPositionMs​(long endPositionMs)
    -
    Sets the optional end position in milliseconds which must be a value larger than or equal to - zero, or C.TIME_END_OF_SOURCE to end when playback reaches the end of media (Default: - C.TIME_END_OF_SOURCE).
    +
    @Deprecated
    +public MediaItem.Builder setClipEndPositionMs​(long endPositionMs)
    +
  • @@ -552,10 +645,11 @@ extends
  • setClipRelativeToLiveWindow

    -
    public MediaItem.Builder setClipRelativeToLiveWindow​(boolean relativeToLiveWindow)
    -
    Sets whether the start/end positions should move with the live window for live streams. If - false, live streams end when playback reaches the end position in live window seen - when the media is first loaded (Default: false).
    +
    @Deprecated
    +public MediaItem.Builder setClipRelativeToLiveWindow​(boolean relativeToLiveWindow)
    +
  • @@ -564,9 +658,11 @@ extends
  • setClipRelativeToDefaultPosition

    -
    public MediaItem.Builder setClipRelativeToDefaultPosition​(boolean relativeToDefaultPosition)
    -
    Sets whether the start position and the end position are relative to the default position in - the window (Default: false).
    +
    @Deprecated
    +public MediaItem.Builder setClipRelativeToDefaultPosition​(boolean relativeToDefaultPosition)
    +
  • @@ -575,9 +671,22 @@ extends
  • setClipStartsAtKeyFrame

    -
    public MediaItem.Builder setClipStartsAtKeyFrame​(boolean startsAtKeyFrame)
    -
    Sets whether the start point is guaranteed to be a key frame. If false, the playback - transition into the clip may not be seamless (Default: false).
    +
    @Deprecated
    +public MediaItem.Builder setClipStartsAtKeyFrame​(boolean startsAtKeyFrame)
    + +
  • + + + + + @@ -586,12 +695,12 @@ extends
  • setDrmLicenseUri

    -
    public MediaItem.Builder setDrmLicenseUri​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setDrmLicenseUri​(@Nullable
                                               Uri licenseUri)
    -
    Sets the optional default DRM license server URI. If this URI is set, the MediaItem.DrmConfiguration.uuid needs to be specified as well. - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
  • @@ -600,12 +709,12 @@ extends
  • setDrmLicenseUri

    -
    public MediaItem.Builder setDrmLicenseUri​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setDrmLicenseUri​(@Nullable
                                               String licenseUri)
    -
    Sets the optional default DRM license server URI. If this URI is set, the MediaItem.DrmConfiguration.uuid needs to be specified as well. - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
  • @@ -614,14 +723,13 @@ extends
  • setDrmLicenseRequestHeaders

    -
    public MediaItem.Builder setDrmLicenseRequestHeaders​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setDrmLicenseRequestHeaders​(@Nullable
                                                          Map<String,​String> licenseRequestHeaders)
    -
    Sets the optional request headers attached to the DRM license request. - -

    null or an empty Map can be used for a reset. - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
  • @@ -630,14 +738,13 @@ extends
  • setDrmUuid

    -
    public MediaItem.Builder setDrmUuid​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setDrmUuid​(@Nullable
                                         UUID uuid)
    -
    Sets the UUID of the protection scheme. - -

    If uuid is null or unset then no MediaItem.DrmConfiguration object is created during - build() and no other Builder methods that would populate MediaItem.PlaybackProperties.drmConfiguration should be called. - -

    This method should only be called if setUri(java.lang.String) is passed a non-null value.

    +
    Deprecated. +
    Use setDrmConfiguration(DrmConfiguration) and pass the uuid to + Builder(UUID) instead.
    +
  • @@ -646,11 +753,11 @@ extends
  • setDrmMultiSession

    -
    public MediaItem.Builder setDrmMultiSession​(boolean multiSession)
    -
    Sets whether the DRM configuration is multi session enabled. - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
    @Deprecated
    +public MediaItem.Builder setDrmMultiSession​(boolean multiSession)
    +
  • @@ -659,12 +766,11 @@ extends
  • setDrmForceDefaultLicenseUri

    -
    public MediaItem.Builder setDrmForceDefaultLicenseUri​(boolean forceDefaultLicenseUri)
    -
    Sets whether to force use the default DRM license server URI even if the media specifies its - own DRM license server URI. - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
    @Deprecated
    +public MediaItem.Builder setDrmForceDefaultLicenseUri​(boolean forceDefaultLicenseUri)
    +
  • @@ -673,12 +779,11 @@ extends
  • setDrmPlayClearContentWithoutKey

    -
    public MediaItem.Builder setDrmPlayClearContentWithoutKey​(boolean playClearContentWithoutKey)
    -
    Sets whether clear samples within protected content should be played when keys for the - encrypted part of the content have yet to be loaded. - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
    @Deprecated
    +public MediaItem.Builder setDrmPlayClearContentWithoutKey​(boolean playClearContentWithoutKey)
    +
  • @@ -687,14 +792,11 @@ extends
  • setDrmSessionForClearPeriods

    -
    public MediaItem.Builder setDrmSessionForClearPeriods​(boolean sessionForClearPeriods)
    -
    Sets whether a DRM session should be used for clear tracks of type C.TRACK_TYPE_VIDEO - and C.TRACK_TYPE_AUDIO. - -

    This method overrides what has been set by previously calling setDrmSessionForClearTypes(List). - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
    @Deprecated
    +public MediaItem.Builder setDrmSessionForClearPeriods​(boolean sessionForClearPeriods)
    +
  • @@ -703,19 +805,13 @@ extends
  • setDrmSessionForClearTypes

    -
    public MediaItem.Builder setDrmSessionForClearTypes​(@Nullable
    -                                                    List<Integer> sessionForClearTypes)
    -
    Sets a list of C.TRACK_TYPE_* constants for which to use a DRM session even - when the tracks are in the clear. - -

    For the common case of using a DRM session for C.TRACK_TYPE_VIDEO and C.TRACK_TYPE_AUDIO the setDrmSessionForClearPeriods(boolean) can be used. - -

    This method overrides what has been set by previously calling setDrmSessionForClearPeriods(boolean). - -

    null or an empty List can be used for a reset. - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
    @Deprecated
    +public MediaItem.Builder setDrmSessionForClearTypes​(@Nullable
    +                                                    List<@TrackType Integer> sessionForClearTypes)
    +
  • @@ -724,16 +820,12 @@ extends
  • setDrmKeySetId

    -
    public MediaItem.Builder setDrmKeySetId​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setDrmKeySetId​(@Nullable
                                             byte[] keySetId)
    -
    Sets the key set ID of the offline license. - -

    The key set ID identifies an offline license. The ID is required to query, renew or - release an existing offline license (see DefaultDrmSessionManager#setMode(int - mode,byte[] offlineLicenseKeySetId)). - -

    This method should only be called if both setUri(java.lang.String) and setDrmUuid(UUID) - are passed non-null values.

    +
  • @@ -750,7 +842,7 @@ extends null or an empty List can be used for a reset.

    If setUri(java.lang.String) is passed a non-null uri, the stream keys are used to create a - MediaItem.PlaybackProperties object. Otherwise they will be ignored. + MediaItem.LocalConfiguration object. Otherwise they will be ignored. @@ -772,11 +864,36 @@ extends

  • setSubtitles

    -
    public MediaItem.Builder setSubtitles​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setSubtitles​(@Nullable
                                           List<MediaItem.Subtitle> subtitles)
    +
    Deprecated. +
    Use setSubtitleConfigurations(List) instead. Note that setSubtitleConfigurations(List) doesn't accept null, use an empty list to clear the + contents.
    +
    +
  • + + + + + + + + + @@ -808,19 +919,13 @@ extends
  • setAdTagUri

    -
    public MediaItem.Builder setAdTagUri​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setAdTagUri​(@Nullable
                                          Uri adTagUri)
    -
    Sets the optional ad tag Uri. - -

    Media items in the playlist with the same ad tag URI, media ID and ads loader will share - the same ad playback state. To resume ad playback when recreating the playlist on returning - from the background, pass media items with the same ad tag URIs and media IDs to the player. - -

    This method should only be called if setUri(java.lang.String) is passed a non-null value.

    -
    -
    Parameters:
    -
    adTagUri - The ad tag URI to load.
    -
    +
    Deprecated. +
    Use setAdsConfiguration(AdsConfiguration) and pass the adTagUri + to Builder(Uri) instead.
    +
  • @@ -829,25 +934,25 @@ extends
  • setAdTagUri

    -
    public MediaItem.Builder setAdTagUri​(@Nullable
    +
    @Deprecated
    +public MediaItem.Builder setAdTagUri​(@Nullable
                                          Uri adTagUri,
                                          @Nullable
                                          Object adsId)
    -
    Sets the optional ad tag Uri and ads identifier. - -

    Media items in the playlist that have the same ads identifier and ads loader share the - same ad playback state. To resume ad playback when recreating the playlist on returning from - the background, pass the same ads IDs to the player. - -

    This method should only be called if setUri(java.lang.String) is passed a non-null value.

    -
    -
    Parameters:
    -
    adTagUri - The ad tag URI to load.
    -
    adsId - An opaque identifier for ad playback state associated with this item. Ad loading - and playback state is shared among all media items that have the same ads ID (by equality) and ads loader, so it is important to pass the same - identifiers when constructing playlist items each time the player returns to the - foreground.
    -
    +
    Deprecated. + +
    +
  • + + + + + @@ -856,15 +961,11 @@ extends
  • setLiveTargetOffsetMs

    -
    public MediaItem.Builder setLiveTargetOffsetMs​(long liveTargetOffsetMs)
    -
    Sets the optional target offset from the live edge for live streams, in milliseconds. - -

    See Player#getCurrentLiveOffset().

    -
    -
    Parameters:
    -
    liveTargetOffsetMs - The target offset, in milliseconds, or C.TIME_UNSET to use - the media-defined default.
    -
    +
    @Deprecated
    +public MediaItem.Builder setLiveTargetOffsetMs​(long liveTargetOffsetMs)
    +
  • @@ -873,15 +974,11 @@ extends
  • setLiveMinOffsetMs

    -
    public MediaItem.Builder setLiveMinOffsetMs​(long liveMinOffsetMs)
    -
    Sets the optional minimum offset from the live edge for live streams, in milliseconds. - -

    See Player#getCurrentLiveOffset().

    -
    -
    Parameters:
    -
    liveMinOffsetMs - The minimum allowed offset, in milliseconds, or C.TIME_UNSET - to use the media-defined default.
    -
    +
    @Deprecated
    +public MediaItem.Builder setLiveMinOffsetMs​(long liveMinOffsetMs)
    +
  • @@ -890,15 +987,11 @@ extends
  • setLiveMaxOffsetMs

    -
    public MediaItem.Builder setLiveMaxOffsetMs​(long liveMaxOffsetMs)
    -
    Sets the optional maximum offset from the live edge for live streams, in milliseconds. - -

    See Player#getCurrentLiveOffset().

    -
    -
    Parameters:
    -
    liveMaxOffsetMs - The maximum allowed offset, in milliseconds, or C.TIME_UNSET - to use the media-defined default.
    -
    +
    @Deprecated
    +public MediaItem.Builder setLiveMaxOffsetMs​(long liveMaxOffsetMs)
    +
  • @@ -907,15 +1000,11 @@ extends
  • setLiveMinPlaybackSpeed

    -
    public MediaItem.Builder setLiveMinPlaybackSpeed​(float minPlaybackSpeed)
    -
    Sets the optional minimum playback speed for live stream speed adjustment. - -

    This value is ignored for other stream types.

    -
    -
    Parameters:
    -
    minPlaybackSpeed - The minimum factor by which playback can be sped up for live streams, - or C.RATE_UNSET to use the media-defined default.
    -
    +
    @Deprecated
    +public MediaItem.Builder setLiveMinPlaybackSpeed​(float minPlaybackSpeed)
    +
  • @@ -924,15 +1013,11 @@ extends
  • setLiveMaxPlaybackSpeed

    -
    public MediaItem.Builder setLiveMaxPlaybackSpeed​(float maxPlaybackSpeed)
    -
    Sets the optional maximum playback speed for live stream speed adjustment. - -

    This value is ignored for other stream types.

    -
    -
    Parameters:
    -
    maxPlaybackSpeed - The maximum factor by which playback can be sped up for live streams, - or C.RATE_UNSET to use the media-defined default.
    -
    +
    @Deprecated
    +public MediaItem.Builder setLiveMaxPlaybackSpeed​(float maxPlaybackSpeed)
    +
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html new file mode 100644 index 0000000000..09e7bc2dc1 --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html @@ -0,0 +1,434 @@ + + + + +MediaItem.ClippingConfiguration.Builder (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class MediaItem.ClippingConfiguration.Builder

    +
    +
    +
      +
    • java.lang.Object
    • +
    • +
        +
      • com.google.android.exoplayer2.MediaItem.ClippingConfiguration.Builder
      • +
      +
    • +
    +
    + +
    +
    + +
    +
    +
      +
    • + +
      +
        +
      • + + +

        Constructor Detail

        + + + +
          +
        • +

          Builder

          +
          public Builder()
          +
          Constructs an instance.
          +
        • +
        +
      • +
      +
      + +
      +
        +
      • + + +

        Method Detail

        + + + +
          +
        • +

          setStartPositionMs

          +
          public MediaItem.ClippingConfiguration.Builder setStartPositionMs​(@IntRange(from=0L)
          +                                                                  long startPositionMs)
          +
          Sets the optional start position in milliseconds which must be a value larger than or equal + to zero (Default: 0).
          +
        • +
        + + + + + + + +
          +
        • +

          setRelativeToLiveWindow

          +
          public MediaItem.ClippingConfiguration.Builder setRelativeToLiveWindow​(boolean relativeToLiveWindow)
          +
          Sets whether the start/end positions should move with the live window for live streams. If + false, live streams end when playback reaches the end position in live window seen + when the media is first loaded (Default: false).
          +
        • +
        + + + +
          +
        • +

          setRelativeToDefaultPosition

          +
          public MediaItem.ClippingConfiguration.Builder setRelativeToDefaultPosition​(boolean relativeToDefaultPosition)
          +
          Sets whether the start position and the end position are relative to the default position + in the window (Default: false).
          +
        • +
        + + + +
          +
        • +

          setStartsAtKeyFrame

          +
          public MediaItem.ClippingConfiguration.Builder setStartsAtKeyFrame​(boolean startsAtKeyFrame)
          +
          Sets whether the start point is guaranteed to be a key frame. If false, the + playback transition into the clip may not be seamless (Default: false).
          +
        • +
        + + + + + + + + +
      • +
      +
      +
    • +
    +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.html new file mode 100644 index 0000000000..b58cccb864 --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.html @@ -0,0 +1,521 @@ + + + + +MediaItem.ClippingConfiguration (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class MediaItem.ClippingConfiguration

    +
    +
    +
      +
    • java.lang.Object
    • +
    • +
        +
      • com.google.android.exoplayer2.MediaItem.ClippingConfiguration
      • +
      +
    • +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingProperties.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingProperties.html index 4fe4df1dcc..b619bd05a7 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingProperties.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingProperties.html @@ -25,12 +25,6 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; -var altColor = "altColor"; -var rowColor = "rowColor"; -var tableTab = "tableTab"; -var activeTableTab = "activeTableTab"; var pathtoroot = "../../../../"; var useModuleDirectories = false; loadScripts(document, 'script'); @@ -95,7 +89,7 @@ loadScripts(document, 'script');
  • Detail: 
  • Field | 
  • Constr | 
  • -
  • Method
  • +
  • Method
  • @@ -121,10 +115,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • java.lang.Object
  • +
  • +
    @@ -155,6 +156,13 @@ implements +
  • + + +

    Nested classes/interfaces inherited from class com.google.android.exoplayer2.MediaItem.ClippingConfiguration

    +MediaItem.ClippingConfiguration.Builder
  • + +
    static Bundleable.Creator<MediaItem.ClippingProperties>CREATORstatic MediaItem.ClippingPropertiesUNSET -
    Object that can restore MediaItem.ClippingProperties from a Bundle.
    -
    longendPositionMs -
    The end position in milliseconds.
    -
    booleanrelativeToDefaultPosition -
    Whether startPositionMs and endPositionMs are relative to the default - position.
    -
    booleanrelativeToLiveWindow -
    Whether the clipping of active media periods moves with a live window.
    -
    longstartPositionMs -
    The start position in milliseconds.
    -
    booleanstartsAtKeyFrame -
    Sets whether the start point is guaranteed to be a key frame.
    -
    + @@ -232,31 +211,13 @@ implements -All Methods Instance Methods Concrete Methods  - -Modifier and Type -Method -Description - - -boolean -equals​(Object obj) -  - - -int -hashCode() -  - - -Bundle -toBundle() - -
    Returns a Bundle representing the information stored in this object.
    - - - +
    • @@ -280,118 +241,14 @@ implements - - -
        -
      • -

        startPositionMs

        -
        public final long startPositionMs
        -
        The start position in milliseconds. This is a value larger than or equal to zero.
        -
      • -
      - - - -
        -
      • -

        endPositionMs

        -
        public final long endPositionMs
        -
        The end position in milliseconds. This is a value larger than or equal to zero or C.TIME_END_OF_SOURCE to play to the end of the stream.
        -
      • -
      - - - -
        -
      • -

        relativeToLiveWindow

        -
        public final boolean relativeToLiveWindow
        -
        Whether the clipping of active media periods moves with a live window. If false, - playback ends when it reaches endPositionMs.
        -
      • -
      - - - -
        -
      • -

        relativeToDefaultPosition

        -
        public final boolean relativeToDefaultPosition
        -
        Whether startPositionMs and endPositionMs are relative to the default - position.
        -
      • -
      - - - -
        -
      • -

        startsAtKeyFrame

        -
        public final boolean startsAtKeyFrame
        -
        Sets whether the start point is guaranteed to be a key frame.
        -
      • -
      - + -
    • -
    - - -
    - diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.Builder.html new file mode 100644 index 0000000000..b8bc49adea --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.Builder.html @@ -0,0 +1,503 @@ + + + + +MediaItem.DrmConfiguration.Builder (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class MediaItem.DrmConfiguration.Builder

    +
    +
    +
      +
    • java.lang.Object
    • +
    • +
        +
      • com.google.android.exoplayer2.MediaItem.DrmConfiguration.Builder
      • +
      +
    • +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.html index 962e8326a7..b92180a011 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.DrmConfiguration.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -86,7 +86,7 @@ loadScripts(document, 'script');
    @@ -121,10 +115,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • java.lang.Object
  • +
  • +
    • @@ -133,9 +132,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      MediaItem

    -
    public static final class MediaItem.PlaybackProperties
    -extends Object
    -
    Properties for local playback.
    +
    @Deprecated
    +public static final class MediaItem.PlaybackProperties
    +extends MediaItem.LocalConfiguration
    +
    Deprecated. + +
    @@ -149,70 +151,13 @@ extends

    Field Summary

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Fields 
    Modifier and TypeFieldDescription
    MediaItem.AdsConfigurationadsConfiguration -
    Optional ads configuration.
    -
    StringcustomCacheKey -
    Optional custom cache key (only used for progressive streams).
    -
    MediaItem.DrmConfigurationdrmConfiguration -
    Optional MediaItem.DrmConfiguration for the media.
    -
    StringmimeType -
    The optional MIME type of the item, or null if unspecified.
    -
    List<StreamKey>streamKeys -
    Optional stream keys by which the manifest is filtered.
    -
    List<MediaItem.Subtitle>subtitles -
    Optional subtitles to be sideloaded.
    -
    Objecttag -
    Optional tag for custom attributes.
    -
    Uriuri -
    The Uri.
    -
    + @@ -223,24 +168,13 @@ extends

    Method Summary

    - - - - - - - - - - - - - - - - - -
    All Methods Instance Methods Concrete Methods 
    Modifier and TypeMethodDescription
    booleanequals​(Object obj) 
    inthashCode() 
    + -
    -
      -
    • - -
      -
        -
      • - - -

        Field Detail

        - - - -
          -
        • -

          uri

          -
          public final Uri uri
          -
          The Uri.
          -
        • -
        - - - -
          -
        • -

          mimeType

          -
          @Nullable
          -public final String mimeType
          -
          The optional MIME type of the item, or null if unspecified. - -

          The MIME type can be used to disambiguate media items that have a URI which does not allow - to infer the actual media type.

          -
        • -
        - - - - - - - - - - - -
          -
        • -

          streamKeys

          -
          public final List<StreamKey> streamKeys
          -
          Optional stream keys by which the manifest is filtered.
          -
        • -
        - - - -
          -
        • -

          customCacheKey

          -
          @Nullable
          -public final String customCacheKey
          -
          Optional custom cache key (only used for progressive streams).
          -
        • -
        - - - - - - - -
          -
        • -

          tag

          -
          @Nullable
          -public final Object tag
          -
          Optional tag for custom attributes. The tag for the media source which will be published in - the com.google.android.exoplayer2.Timeline of the source as - com.google.android.exoplayer2.Timeline.Window#tag.
          -
        • -
        -
      • -
      -
      - -
      -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          equals

          -
          public boolean equals​(@Nullable
          -                      Object obj)
          -
          -
          Overrides:
          -
          equals in class Object
          -
          -
        • -
        - - - -
          -
        • -

          hashCode

          -
          public int hashCode()
          -
          -
          Overrides:
          -
          hashCode in class Object
          -
          -
        • -
        -
      • -
      -
      -
    • -
    -
    @@ -449,9 +240,9 @@ public final 
  • Detail: 
  • -
  • Field | 
  • +
  • Field | 
  • Constr | 
  • -
  • Method
  • +
  • Method
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Subtitle.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Subtitle.html index 6f9c93e6b6..22d9d9bfcf 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Subtitle.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.Subtitle.html @@ -25,12 +25,6 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; -var altColor = "altColor"; -var rowColor = "rowColor"; -var tableTab = "tableTab"; -var activeTableTab = "activeTableTab"; var pathtoroot = "../../../../"; var useModuleDirectories = false; loadScripts(document, 'script'); @@ -86,16 +80,16 @@ loadScripts(document, 'script');
    @@ -121,10 +115,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • java.lang.Object
  • +
  • +
    • @@ -133,15 +132,35 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      MediaItem

    -
    public static final class MediaItem.Subtitle
    -extends Object
    -
    Properties for a text track.
    +
    @Deprecated
    +public static final class MediaItem.Subtitle
    +extends MediaItem.SubtitleConfiguration
    +
    Deprecated. + +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.Builder.html new file mode 100644 index 0000000000..b3385dc9c7 --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.Builder.html @@ -0,0 +1,423 @@ + + + + +MediaItem.SubtitleConfiguration.Builder (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class MediaItem.SubtitleConfiguration.Builder

    +
    +
    +
      +
    • java.lang.Object
    • +
    • +
        +
      • com.google.android.exoplayer2.MediaItem.SubtitleConfiguration.Builder
      • +
      +
    • +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.html new file mode 100644 index 0000000000..646cfb4fc5 --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.SubtitleConfiguration.html @@ -0,0 +1,471 @@ + + + + +MediaItem.SubtitleConfiguration (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class MediaItem.SubtitleConfiguration

    +
    +
    +
      +
    • java.lang.Object
    • +
    • +
        +
      • com.google.android.exoplayer2.MediaItem.SubtitleConfiguration
      • +
      +
    • +
    +
    +
      +
    • +
      +
      Direct Known Subclasses:
      +
      MediaItem.Subtitle
      +
      +
      +
      Enclosing class:
      +
      MediaItem
      +
      +
      +
      public static class MediaItem.SubtitleConfiguration
      +extends Object
      +
      Properties for a text track.
      +
    • +
    +
    +
    + +
    +
    +
      +
    • + +
      +
        +
      • + + +

        Field Detail

        + + + +
          +
        • +

          uri

          +
          public final Uri uri
          +
          The Uri to the subtitle file.
          +
        • +
        + + + +
          +
        • +

          mimeType

          +
          @Nullable
          +public final String mimeType
          +
          The optional MIME type of the subtitle file, or null if unspecified.
          +
        • +
        + + + +
          +
        • +

          language

          +
          @Nullable
          +public final String language
          +
          The language.
          +
        • +
        + + + +
          +
        • +

          selectionFlags

          +
          @SelectionFlags
          +public final @com.google.android.exoplayer2.C.SelectionFlags int selectionFlags
          +
          The selection flags.
          +
        • +
        + + + +
          +
        • +

          roleFlags

          +
          @RoleFlags
          +public final @com.google.android.exoplayer2.C.RoleFlags int roleFlags
          +
          The role flags.
          +
        • +
        + + + +
          +
        • +

          label

          +
          @Nullable
          +public final String label
          +
          The label.
          +
        • +
        +
      • +
      +
      + +
      + +
      +
    • +
    +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.html index 9e476679f9..6286401732 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.html @@ -173,36 +173,63 @@ implements static class  -MediaItem.ClippingProperties +MediaItem.ClippingConfiguration
    Optionally clips the media item to a custom start and end position.
    static class  +MediaItem.ClippingProperties + +
    Deprecated. + +
    + + + +static class  MediaItem.DrmConfiguration
    DRM configuration for a media item.
    - + static class  MediaItem.LiveConfiguration
    Live playback configuration.
    + +static class  +MediaItem.LocalConfiguration + +
    Properties for local playback.
    + + static class  MediaItem.PlaybackProperties -
    Properties for local playback.
    +
    Deprecated. + +
    static class  MediaItem.Subtitle +
    Deprecated. + +
    + + + +static class  +MediaItem.SubtitleConfiguration +
    Properties for a text track.
    @@ -232,40 +259,56 @@ implements Description -MediaItem.ClippingProperties -clippingProperties +MediaItem.ClippingConfiguration +clippingConfiguration
    The clipping properties.
    +MediaItem.ClippingProperties +clippingProperties + +
    Deprecated. + +
    + + + static Bundleable.Creator<MediaItem> CREATOR
    Object that can restore MediaItem from a Bundle.
    - + static String DEFAULT_MEDIA_ID
    The default media ID that is used if the media ID is not explicitly set by MediaItem.Builder.setMediaId(String).
    - + static MediaItem EMPTY
    Empty MediaItem.
    - + MediaItem.LiveConfiguration liveConfiguration
    The live playback configuration.
    + +MediaItem.LocalConfiguration +localConfiguration + +
    Optional configuration for local playback.
    + + String mediaId @@ -284,7 +327,9 @@ implements MediaItem.PlaybackProperties playbackProperties -
    Optional playback properties.
    +
    Deprecated. +
    Use localConfiguration instead.
    +
    @@ -401,15 +446,30 @@ implements Identifies the media item. + + + +
      +
    • +

      localConfiguration

      +
      @Nullable
      +public final MediaItem.LocalConfiguration localConfiguration
      +
      Optional configuration for local playback. May be null if shared over process + boundaries.
      +
    • +
    @@ -432,14 +492,27 @@ public final The media metadata. + + + + @@ -451,7 +524,7 @@ public final Bundleable.Creator<MediaItem> CREATOR
    Object that can restore MediaItem from a Bundle. -

    The playbackProperties of a restored instance will always be null.

    +

    The localConfiguration of a restored instance will always be null. @@ -542,7 +615,7 @@ public final public Bundle toBundle()

    Returns a Bundle representing the information stored in this object. -

    It omits the playbackProperties field. The playbackProperties of an +

    It omits the localConfiguration field. The localConfiguration of an instance restored by CREATOR will always be null.

    Specified by:
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html index cceffedcbf..764cb28e27 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":42,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":42}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":42,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":42}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -186,8 +186,8 @@ extends MediaMetadata.Builder -maybeSetArtworkData​(byte[] artworkData, - int artworkDataType) +maybeSetArtworkData​(byte[] artworkData, + @com.google.android.exoplayer2.MediaMetadata.PictureType int artworkDataType)
    Sets the artwork data as a compressed byte array in the event that the associated MediaMetadata.PictureType is MediaMetadata.PICTURE_TYPE_FRONT_COVER, the existing MediaMetadata.PictureType is not MediaMetadata.PICTURE_TYPE_FRONT_COVER, or the current artworkData is not set.
    @@ -195,239 +195,246 @@ extends MediaMetadata.Builder +populate​(MediaMetadata mediaMetadata) + +
    Populates all the fields from mediaMetadata, provided they are non-null.
    + + + +MediaMetadata.Builder populateFromMetadata​(Metadata metadata)
    Sets all fields supported by the entries within the Metadata.
    - + MediaMetadata.Builder populateFromMetadata​(List<Metadata> metadataList)
    Sets all fields supported by the entries within the list of Metadata.
    - + MediaMetadata.Builder setAlbumArtist​(CharSequence albumArtist)
    Sets the album artist.
    - + MediaMetadata.Builder setAlbumTitle​(CharSequence albumTitle)
    Sets the album title.
    - + MediaMetadata.Builder setArtist​(CharSequence artist)
    Sets the artist.
    - + MediaMetadata.Builder setArtworkData​(byte[] artworkData) - + MediaMetadata.Builder setArtworkData​(byte[] artworkData, - Integer artworkDataType) + @PictureType Integer artworkDataType)
    Sets the artwork data as a compressed byte array with an associated artworkDataType.
    - + MediaMetadata.Builder setArtworkUri​(Uri artworkUri)
    Sets the artwork Uri.
    - + MediaMetadata.Builder setCompilation​(CharSequence compilation)
    Sets the compilation.
    - + MediaMetadata.Builder setComposer​(CharSequence composer)
    Sets the composer.
    - + MediaMetadata.Builder setConductor​(CharSequence conductor)
    Sets the conductor.
    - + MediaMetadata.Builder setDescription​(CharSequence description)
    Sets the description.
    - + MediaMetadata.Builder setDiscNumber​(Integer discNumber)
    Sets the disc number.
    - + MediaMetadata.Builder setDisplayTitle​(CharSequence displayTitle)
    Sets the display title.
    - + MediaMetadata.Builder setExtras​(Bundle extras)
    Sets the extras Bundle.
    - + MediaMetadata.Builder -setFolderType​(Integer folderType) +setFolderType​(@FolderType Integer folderType) - + MediaMetadata.Builder setGenre​(CharSequence genre)
    Sets the genre.
    - + MediaMetadata.Builder setIsPlayable​(Boolean isPlayable)
    Sets whether the media is playable.
    - + MediaMetadata.Builder setMediaUri​(Uri mediaUri)
    Sets the media Uri.
    - + MediaMetadata.Builder setOverallRating​(Rating overallRating)
    Sets the overall Rating.
    - + MediaMetadata.Builder setRecordingDay​(Integer recordingDay)
    Sets the day of the recording date.
    - + MediaMetadata.Builder setRecordingMonth​(Integer recordingMonth)
    Sets the month of the recording date.
    - + MediaMetadata.Builder setRecordingYear​(Integer recordingYear)
    Sets the year of the recording date.
    - + MediaMetadata.Builder setReleaseDay​(Integer releaseDay)
    Sets the day of the release date.
    - + MediaMetadata.Builder setReleaseMonth​(Integer releaseMonth)
    Sets the month of the release date.
    - + MediaMetadata.Builder setReleaseYear​(Integer releaseYear)
    Sets the year of the release date.
    - + MediaMetadata.Builder setSubtitle​(CharSequence subtitle)
    Sets the subtitle.
    - + MediaMetadata.Builder setTitle​(CharSequence title)
    Sets the title.
    - + MediaMetadata.Builder setTotalDiscCount​(Integer totalDiscCount)
    Sets the total number of discs.
    - + MediaMetadata.Builder setTotalTrackCount​(Integer totalTrackCount)
    Sets the total number of tracks.
    - + MediaMetadata.Builder setTrackNumber​(Integer trackNumber)
    Sets the track number.
    - + MediaMetadata.Builder setUserRating​(Rating userRating)
    Sets the user Rating.
    - + MediaMetadata.Builder setWriter​(CharSequence writer)
    Sets the writer.
    - + MediaMetadata.Builder setYear​(Integer year) @@ -601,7 +608,7 @@ extends MediaMetadata.Builder setArtworkData​(@Nullable byte[] artworkData) @@ -614,11 +621,11 @@ public public MediaMetadata.Builder setArtworkData​(@Nullable byte[] artworkData, @Nullable @PictureType - Integer artworkDataType) + @PictureType Integer artworkDataType)
    Sets the artwork data as a compressed byte array with an associated artworkDataType.
    - + @@ -894,6 +901,17 @@ public Metadata.Entry objects within any of the Metadata relate to the same MediaMetadata field, then the last one will be used. + + + +
      +
    • +

      populate

      +
      public MediaMetadata.Builder populate​(@Nullable
      +                                      MediaMetadata mediaMetadata)
      +
      Populates all the fields from mediaMetadata, provided they are non-null.
      +
    • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html index 5cf377cb11..c555f2c606 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    @Documented
     @Retention(SOURCE)
    +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
     public static @interface MediaMetadata.FolderType
    The folder type of the media item. diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html index 8aa35d1a6e..06dab22929 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    @Documented
     @Retention(SOURCE)
    +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
     public static @interface MediaMetadata.PictureType
    The picture type of the artwork. diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html index a8ce3ba8e0..446b711fa7 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html @@ -232,7 +232,7 @@ implements -Integer +@PictureType Integer artworkDataType
    Optional MediaMetadata.PictureType of the artwork data.
    @@ -365,7 +365,7 @@ implements -Integer +@FolderType Integer folderType @@ -1196,7 +1196,7 @@ public final byte[] artworkData

    artworkDataType

    @Nullable
     @PictureType
    -public final Integer artworkDataType
    +public final @PictureType Integer artworkDataType
    Optional MediaMetadata.PictureType of the artwork data.
    @@ -1241,7 +1241,7 @@ public final @FolderType -public final Integer folderType +public final @FolderType Integer folderType diff --git a/docs/doc/reference/com/google/android/exoplayer2/NoSampleRenderer.html b/docs/doc/reference/com/google/android/exoplayer2/NoSampleRenderer.html index 36998fa580..038a05ad8e 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/NoSampleRenderer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/NoSampleRenderer.html @@ -156,7 +156,7 @@ implements Renderer -Renderer.State, Renderer.VideoScalingMode, Renderer.WakeupListener +Renderer.MessageType, Renderer.State, Renderer.WakeupListener
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html index ca5c0339c3..80bb720c04 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html @@ -181,14 +181,14 @@ extends Player.Commands.Builder -add​(int command) +add​(@com.google.android.exoplayer2.Player.Command int command) Player.Commands.Builder -addAll​(int... commands) +addAll​(@com.google.android.exoplayer2.Player.Command int... commands)
    Adds commands.
    @@ -209,7 +209,7 @@ extends Player.Commands.Builder -addIf​(int command, +addIf​(@com.google.android.exoplayer2.Player.Command int command, boolean condition)
    Adds a Player.Command if the provided condition is true.
    @@ -224,21 +224,21 @@ extends Player.Commands.Builder -remove​(int command) +remove​(@com.google.android.exoplayer2.Player.Command int command)
    Removes a Player.Command.
    Player.Commands.Builder -removeAll​(int... commands) +removeAll​(@com.google.android.exoplayer2.Player.Command int... commands)
    Removes commands.
    Player.Commands.Builder -removeIf​(int command, +removeIf​(@com.google.android.exoplayer2.Player.Command int command, boolean condition)
    Removes a Player.Command if the provided condition is true.
    @@ -288,14 +288,14 @@ extends

    Method Detail

    - + - +
    • addIf

      public Player.Commands.Builder addIf​(@Command
      -                                     int command,
      +                                     @com.google.android.exoplayer2.Player.Command int command,
                                            boolean condition)
      Adds a Player.Command if the provided condition is true. Does nothing otherwise.
      @@ -328,14 +328,14 @@ extends
    - + - + - +
    • removeIf

      public Player.Commands.Builder removeIf​(@Command
      -                                        int command,
      +                                        @com.google.android.exoplayer2.Player.Command int command,
                                               boolean condition)
      Removes a Player.Command if the provided condition is true. Does nothing otherwise.
      @@ -421,14 +421,14 @@ extends
    - +
    • removeAll

      public Player.Commands.Builder removeAll​(@Command
      -                                         int... commands)
      + @com.google.android.exoplayer2.Player.Command int... commands)
      Removes commands.
      Parameters:
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html index 7b79b004ec..545fd32c74 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html @@ -236,7 +236,7 @@ implements boolean -contains​(int command) +contains​(@com.google.android.exoplayer2.Player.Command int command)
      Returns whether the set of commands contains the specified Player.Command.
      @@ -247,7 +247,7 @@ implements   -int +@com.google.android.exoplayer2.Player.Command int get​(int index)
      Returns the Player.Command at the given index.
      @@ -336,14 +336,14 @@ implements Returns a Player.Commands.Builder initialized with the values of this instance.
    - +
    • contains

      public boolean contains​(@Command
      -                        int command)
      + @com.google.android.exoplayer2.Player.Command int command)
      Returns whether the set of commands contains the specified Player.Command.
    @@ -364,7 +364,7 @@ implements

    get

    @Command
    -public int get​(int index)
    +public @com.google.android.exoplayer2.Player.Command int get​(int index)
    Returns the Player.Command at the given index.
    Parameters:
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.DiscontinuityReason.html b/docs/doc/reference/com/google/android/exoplayer2/Player.DiscontinuityReason.html index 0fa6ed4600..76879e9080 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.DiscontinuityReason.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.DiscontinuityReason.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    @Documented
     @Retention(SOURCE)
    +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
     public static @interface Player.DiscontinuityReason
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.Event.html b/docs/doc/reference/com/google/android/exoplayer2/Player.Event.html index 51e7f50293..5407eca322 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.Event.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.Event.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    @Documented
     @Retention(SOURCE)
    +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
     public static @interface Player.Event
    Events that can be reported via Player.Listener.onEvents(Player, Events). diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.EventListener.html b/docs/doc/reference/com/google/android/exoplayer2/Player.EventListener.html index 8aadfb108f..f6beaf7e46 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.EventListener.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.EventListener.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":50,"i1":50,"i2":50,"i3":50,"i4":50,"i5":50,"i6":50,"i7":50,"i8":50,"i9":50,"i10":50,"i11":50,"i12":50,"i13":50,"i14":50,"i15":50,"i16":50,"i17":50,"i18":50,"i19":50,"i20":50,"i21":50,"i22":50,"i23":50,"i24":50,"i25":50}; +var data = {"i0":50,"i1":50,"i2":50,"i3":50,"i4":50,"i5":50,"i6":50,"i7":50,"i8":50,"i9":50,"i10":50,"i11":50,"i12":50,"i13":50,"i14":50,"i15":50,"i16":50,"i17":50,"i18":50,"i19":50,"i20":50,"i21":50,"i22":50,"i23":50,"i24":50,"i25":50,"i26":50}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],16:["t5","Default Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -126,7 +126,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    All Known Implementing Classes:
    -
    AnalyticsCollector, DebugTextViewHelper, ExoPlayerTestRunner, ImaAdsLoader
    +
    AnalyticsCollector, DebugTextViewHelper, ExoPlayerTestRunner, ImaAdsLoader, SubtitleView
    Enclosing interface:
    @@ -169,7 +169,7 @@ public static interface Player.EventListener< onAvailableCommandsChanged​(Player.Commands availableCommands)
    Deprecated.
    -
    Called when the value returned from Player.isCommandAvailable(int) changes for at least one +
    Called when the value returned from Player.isCommandAvailable(int) changes for at least one Player.Command.
    @@ -209,7 +209,7 @@ public static interface Player.EventListener< default void -onMaxSeekToPreviousPositionChanged​(int maxSeekToPreviousPositionMs) +onMaxSeekToPreviousPositionChanged​(long maxSeekToPreviousPositionMs)
    Deprecated.
    Called when the value of Player.getMaxSeekToPreviousPosition() changes.
    @@ -217,8 +217,8 @@ public static interface Player.EventListener< default void -onMediaItemTransition​(MediaItem mediaItem, - int reason) +onMediaItemTransition​(MediaItem mediaItem, + @com.google.android.exoplayer2.Player.MediaItemTransitionReason int reason)
    Deprecated.
    Called when playback transitions to a media item or starts repeating a media item according @@ -243,7 +243,7 @@ public static interface Player.EventListener< default void -onPlaybackStateChanged​(int playbackState) +onPlaybackStateChanged​(@com.google.android.exoplayer2.Player.State int playbackState)
    Deprecated.
    Called when the value returned from Player.getPlaybackState() changes.
    @@ -251,7 +251,7 @@ public static interface Player.EventListener< default void -onPlaybackSuppressionReasonChanged​(int playbackSuppressionReason) +onPlaybackSuppressionReasonChanged​(@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int playbackSuppressionReason)
    Deprecated.
    Called when the value returned from Player.getPlaybackSuppressionReason() changes.
    @@ -275,11 +275,11 @@ public static interface Player.EventListener< default void -onPlayerStateChanged​(boolean playWhenReady, - int playbackState) +onPlayerStateChanged​(boolean playWhenReady, + @com.google.android.exoplayer2.Player.State int playbackState) @@ -293,8 +293,8 @@ public static interface Player.EventListener< default void -onPlayWhenReadyChanged​(boolean playWhenReady, - int reason) +onPlayWhenReadyChanged​(boolean playWhenReady, + @com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason int reason)
    Deprecated.
    Called when the value returned from Player.getPlayWhenReady() changes.
    @@ -302,18 +302,18 @@ public static interface Player.EventListener< default void -onPositionDiscontinuity​(int reason) +onPositionDiscontinuity​(@com.google.android.exoplayer2.Player.DiscontinuityReason int reason) default void -onPositionDiscontinuity​(Player.PositionInfo oldPosition, +onPositionDiscontinuity​(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, - int reason) + @com.google.android.exoplayer2.Player.DiscontinuityReason int reason)
    Deprecated.
    Called when a position discontinuity occurs.
    @@ -321,7 +321,7 @@ public static interface Player.EventListener< default void -onRepeatModeChanged​(int repeatMode) +onRepeatModeChanged​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
    Deprecated.
    Called when the value of Player.getRepeatMode() changes.
    @@ -362,29 +362,35 @@ public static interface Player.EventListener< default void -onStaticMetadataChanged​(List<Metadata> metadataList) - -
    Deprecated. -
    Use Player.getMediaMetadata() and onMediaMetadataChanged(MediaMetadata) for access to structured metadata, or access the - raw static metadata directly from the track - selections' formats.
    -
    - - - -default void -onTimelineChanged​(Timeline timeline, - int reason) +onTimelineChanged​(Timeline timeline, + @com.google.android.exoplayer2.Player.TimelineChangeReason int reason)
    Deprecated.
    Called when the timeline has been refreshed.
    - + default void onTracksChanged​(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) +
    Deprecated. + +
    + + + +default void +onTrackSelectionParametersChanged​(TrackSelectionParameters parameters) + +
    Deprecated.
    +
    Called when the value returned from Player.getTrackSelectionParameters() changes.
    + + + +default void +onTracksInfoChanged​(TracksInfo tracksInfo) +
    Deprecated.
    Called when the available or selected tracks change.
    @@ -406,7 +412,7 @@ public static interface Player.EventListener<

    Method Detail

    - +
      @@ -414,12 +420,14 @@ public static interface Player.EventListener<

      onTimelineChanged

      default void onTimelineChanged​(Timeline timeline,
                                      @TimelineChangeReason
      -                               int reason)
      + @com.google.android.exoplayer2.Player.TimelineChangeReason int reason)
      Deprecated.
      Called when the timeline has been refreshed. -

      Note that the current window or period index may change as a result of a timeline change. - If playback can't continue smoothly because of this timeline change, a separate onPositionDiscontinuity(PositionInfo, PositionInfo, int) callback will be triggered. +

      Note that the current MediaItem or playback position may change as a result of a + timeline change. If playback can't continue smoothly because of this timeline change, a + separate onPositionDiscontinuity(PositionInfo, PositionInfo, int) callback will be + triggered.

      onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -430,7 +438,7 @@ public static interface Player.EventListener<
    - +
      @@ -439,7 +447,7 @@ public static interface Player.EventListener<
      default void onMediaItemTransition​(@Nullable
                                          MediaItem mediaItem,
                                          @MediaItemTransitionReason
      -                                   int reason)
      + @com.google.android.exoplayer2.Player.MediaItemTransitionReason int reason)
      Deprecated.
      Called when playback transitions to a media item or starts repeating a media item according to the current repeat mode. @@ -462,9 +470,12 @@ public static interface Player.EventListener<
    - + @@ -506,7 +520,11 @@ default void onStaticMetadataChanged​(MediaMetadata is a combination of the MediaItem.mediaMetadata and the static and dynamic metadata from the track - selections' formats and MetadataOutput.onMetadata(Metadata). + selections' formats and Player.Listener.onMetadata(Metadata). If a field is populated in + the MediaItem.mediaMetadata, it will be prioritised above the same field coming from + static or dynamic metadata. + +

    This method may be called multiple times in quick succession.

    onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration. @@ -566,7 +584,7 @@ default void onLoadingChanged​(boolean isLoading)

    onAvailableCommandsChanged

    default void onAvailableCommandsChanged​(Player.Commands availableCommands)
    Deprecated.
    -
    Called when the value returned from Player.isCommandAvailable(int) changes for at least one +
    Called when the value returned from Player.isCommandAvailable(int) changes for at least one Player.Command.

    onEvents(Player, Events) will also be called to report this event along with @@ -577,7 +595,25 @@ default void onLoadingChanged​(boolean isLoading)

    - + + + + + - +
    • onPlaybackStateChanged

      default void onPlaybackStateChanged​(@State
      -                                    int playbackState)
      + @com.google.android.exoplayer2.Player.State int playbackState)
      Deprecated.
      Called when the value returned from Player.getPlaybackState() changes. @@ -611,7 +647,7 @@ default void onPlayerStateChanged​(boolean playWhenReady,
    - +
      @@ -619,7 +655,7 @@ default void onPlayerStateChanged​(boolean playWhenReady,

      onPlayWhenReadyChanged

      default void onPlayWhenReadyChanged​(boolean playWhenReady,
                                           @PlayWhenReadyChangeReason
      -                                    int reason)
      + @com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason int reason)
      Deprecated.
      Called when the value returned from Player.getPlayWhenReady() changes. @@ -632,14 +668,14 @@ default void onPlayerStateChanged​(boolean playWhenReady,
    - +
    • onPlaybackSuppressionReasonChanged

      default void onPlaybackSuppressionReasonChanged​(@PlaybackSuppressionReason
      -                                                int playbackSuppressionReason)
      + @com.google.android.exoplayer2.Player.PlaybackSuppressionReason int playbackSuppressionReason)
      Deprecated.
      Called when the value returned from Player.getPlaybackSuppressionReason() changes. @@ -669,14 +705,14 @@ default void onPlayerStateChanged​(boolean playWhenReady,
    - +
    • onRepeatModeChanged

      default void onRepeatModeChanged​(@RepeatMode
      -                                 int repeatMode)
      + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
      Deprecated.
      Called when the value of Player.getRepeatMode() changes. @@ -702,7 +738,7 @@ default void onPlayerStateChanged​(boolean playWhenReady, other events that happen in the same Looper message queue iteration.
      Parameters:
      -
      shuffleModeEnabled - Whether shuffling of windows is enabled.
      +
      shuffleModeEnabled - Whether shuffling of media items is enabled.
    @@ -750,7 +786,7 @@ default void onPlayerStateChanged​(boolean playWhenReady,
    - + - + @@ -419,7 +411,7 @@ extends + - + - +
    • -

      onTracksChanged

      -
      default void onTracksChanged​(TrackGroupArray trackGroups,
      -                             TrackSelectionArray trackSelections)
      -
      Description copied from interface: Player.EventListener
      +

      onTracksInfoChanged

      +
      default void onTracksInfoChanged​(TracksInfo tracksInfo)
      +
      Description copied from interface: Player.EventListener
      Called when the available or selected tracks change.

      Player.EventListener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      Specified by:
      -
      onTracksChanged in interface Player.EventListener
      +
      onTracksInfoChanged in interface Player.EventListener
      Parameters:
      -
      trackGroups - The available tracks. Never null, but may be of length zero.
      -
      trackSelections - The selected tracks. Never null, but may contain null elements. A - concrete implementation may include null elements if it has a fixed number of renderer - components, wishes to report a TrackSelection for each of them, and has one or more - renderer components that is not assigned any selected tracks.
      +
      tracksInfo - The available tracks information. Never null, but may be of length zero.
    @@ -526,7 +515,7 @@ extends default void onAvailableCommandsChanged​(Player.Commands availableCommands)
    Description copied from interface: Player.EventListener
    -
    Called when the value returned from Player.isCommandAvailable(int) changes for at least one +
    Called when the value returned from Player.isCommandAvailable(int) changes for at least one Player.Command.

    Player.EventListener.onEvents(Player, Events) will also be called to report this event along with @@ -539,28 +528,28 @@ extends +

    - + - +
    @@ -713,7 +702,7 @@ extends + - + @@ -896,12 +868,7 @@ extends default void onDeviceVolumeChanged​(int volume, boolean muted) -
    Called when the device volume or mute state changes.
    -
    -
    Specified by:
    -
    onDeviceVolumeChanged in interface DeviceListener
    -
    @@ -924,15 +891,15 @@ extends Player.EventListener.onPlaybackStateChanged(int) and Player.EventListener.onPlayWhenReadyChanged(boolean, + both Player.EventListener.onPlaybackStateChanged(int) and Player.EventListener.onPlayWhenReadyChanged(boolean, int)).
  • They need access to the Player object to trigger further events (e.g. to call - Player.seekTo(long) after a Player.EventListener.onMediaItemTransition(MediaItem, int)). + Player.seekTo(long) after a Player.EventListener.onMediaItemTransition(MediaItem, int)).
  • They intend to use multiple state values together or in combination with Player - getter methods. For example using Player.getCurrentWindowIndex() with the - timeline provided in Player.EventListener.onTimelineChanged(Timeline, int) is only safe from + getter methods. For example using Player.getCurrentMediaItemIndex() with the + timeline provided in Player.EventListener.onTimelineChanged(Timeline, int) is only safe from within this method. -
  • They are interested in events that logically happened together (e.g Player.EventListener.onPlaybackStateChanged(int) to Player.STATE_BUFFERING because of Player.EventListener.onMediaItemTransition(MediaItem, int)). +
  • They are interested in events that logically happened together (e.g Player.EventListener.onPlaybackStateChanged(int) to Player.STATE_BUFFERING because of Player.EventListener.onMediaItemTransition(MediaItem, int)).
    Specified by:
    @@ -952,11 +919,8 @@ extends

    onVideoSizeChanged

    default void onVideoSizeChanged​(VideoSize videoSize)
    -
    Description copied from interface: VideoListener
    Called each time there's a change in the size of the video being rendered.
    -
    Specified by:
    -
    onVideoSizeChanged in interface VideoListener
    Parameters:
    videoSize - The new size of the video.
    @@ -970,15 +934,12 @@ extends default void onSurfaceSizeChanged​(int width, int height) -
    Called each time there's a change in the size of the surface onto which the video is being rendered.
    -
    Specified by:
    -
    onSurfaceSizeChanged in interface VideoListener
    Parameters:
    -
    width - The surface width in pixels. May be C.LENGTH_UNSET if unknown, or 0 if the - video is not rendered onto a surface.
    +
    width - The surface width in pixels. May be C.LENGTH_UNSET if unknown, or 0 if + the video is not rendered onto a surface.
    height - The surface height in pixels. May be C.LENGTH_UNSET if unknown, or 0 if the video is not rendered onto a surface.
    @@ -991,13 +952,8 @@ extends

    onRenderedFirstFrame

    default void onRenderedFirstFrame()
    -
    Called when a frame is rendered for the first time since setting the surface, or since the renderer was reset, or since the stream being rendered was changed.
    -
    -
    Specified by:
    -
    onRenderedFirstFrame in interface VideoListener
    -
  • @@ -1007,14 +963,11 @@ extends

    onCues

    default void onCues​(List<Cue> cues)
    -
    Description copied from interface: TextOutput
    Called when there is a change in the Cues.

    cues is in ascending order of priority. If any of the cue boxes overlap when displayed, the Cue nearer the end of the list should be shown on top.

    -
    Specified by:
    -
    onCues in interface TextOutput
    Parameters:
    cues - The Cues. May be empty.
    @@ -1027,11 +980,8 @@ extends

    onMetadata

    default void onMetadata​(Metadata metadata)
    -
    Description copied from interface: MetadataOutput
    -
    Called when there is metadata associated with current playback time.
    +
    Called when there is metadata associated with the current playback time.
    -
    Specified by:
    -
    onMetadata in interface MetadataOutput
    Parameters:
    metadata - The metadata.
    @@ -1049,7 +999,11 @@ extends MediaMetadata is a combination of the MediaItem.mediaMetadata and the static and dynamic metadata from the track - selections' formats and MetadataOutput.onMetadata(Metadata). + selections' formats
    and onMetadata(Metadata). If a field is populated in + the MediaItem.mediaMetadata, it will be prioritised above the same field coming from + static or dynamic metadata. + +

    This method may be called multiple times in quick succession.

    Player.EventListener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration. diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.MediaItemTransitionReason.html b/docs/doc/reference/com/google/android/exoplayer2/Player.MediaItemTransitionReason.html index ca6b2f59b9..9aeb9e8ff3 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.MediaItemTransitionReason.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.MediaItemTransitionReason.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));


    @Documented
     @Retention(SOURCE)
    +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
     public static @interface Player.MediaItemTransitionReason
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.PlayWhenReadyChangeReason.html b/docs/doc/reference/com/google/android/exoplayer2/Player.PlayWhenReadyChangeReason.html index 5727e123a3..c859add3c2 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.PlayWhenReadyChangeReason.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.PlayWhenReadyChangeReason.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    @Documented
     @Retention(SOURCE)
    +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
     public static @interface Player.PlayWhenReadyChangeReason
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.PlaybackSuppressionReason.html b/docs/doc/reference/com/google/android/exoplayer2/Player.PlaybackSuppressionReason.html index adb9d25a81..e9449f426b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.PlaybackSuppressionReason.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.PlaybackSuppressionReason.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    @Documented
     @Retention(SOURCE)
    +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
     public static @interface Player.PlaybackSuppressionReason
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html b/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html index 56aad779c6..14ab0b9589 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html @@ -207,6 +207,20 @@ implements +MediaItem +mediaItem + +
    The media item, or null if the timeline is empty.
    + + + +int +mediaItemIndex + +
    The media item index.
    + + + int periodIndex @@ -217,7 +231,7 @@ implements Object periodUid -
    The UID of the period, or null, if the timeline is empty.
    +
    The UID of the period, or null if the timeline is empty.
    @@ -231,14 +245,16 @@ implements int windowIndex -
    The window index.
    +
    Deprecated. +
    Use mediaItemIndex instead.
    +
    Object windowUid -
    The UID of the window, or null, if the timeline is empty.
    +
    The UID of the window, or null if the timeline is empty.
    @@ -259,8 +275,9 @@ implements Description -PositionInfo​(Object windowUid, - int windowIndex, +PositionInfo​(Object windowUid, + int mediaItemIndex, + MediaItem mediaItem, Object periodUid, int periodIndex, long positionMs, @@ -271,6 +288,22 @@ implements Creates an instance. + +PositionInfo​(Object windowUid, + int mediaItemIndex, + Object periodUid, + int periodIndex, + long positionMs, + long contentPositionMs, + int adGroupIndex, + int adIndexInAdGroup) + + + + @@ -338,7 +371,7 @@ implements Object windowUid -
    The UID of the window, or null, if the timeline is empty.
    +
    The UID of the window, or null if the timeline is empty.
    @@ -347,8 +380,32 @@ public final 
  • windowIndex

    -
    public final int windowIndex
    -
    The window index.
    +
    @Deprecated
    +public final int windowIndex
    +
    Deprecated. +
    Use mediaItemIndex instead.
    +
    +
  • + + + + +
      +
    • +

      mediaItemIndex

      +
      public final int mediaItemIndex
      +
      The media item index.
      +
    • +
    + + + +
      +
    • +

      mediaItem

      +
      @Nullable
      +public final MediaItem mediaItem
      +
      The media item, or null if the timeline is empty.
    @@ -359,7 +416,7 @@ public final Object periodUid -
    The UID of the period, or null, if the timeline is empty.
    +
    The UID of the period, or null if the timeline is empty.
    @@ -437,12 +494,37 @@ public final  + + + +
    • PositionInfo

      public PositionInfo​(@Nullable
                           Object windowUid,
      -                    int windowIndex,
      +                    int mediaItemIndex,
      +                    @Nullable
      +                    MediaItem mediaItem,
                           @Nullable
                           Object periodUid,
                           int periodIndex,
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.RepeatMode.html b/docs/doc/reference/com/google/android/exoplayer2/Player.RepeatMode.html
      index 6c7fa3a240..f2c8abd799 100644
      --- a/docs/doc/reference/com/google/android/exoplayer2/Player.RepeatMode.html
      +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.RepeatMode.html
      @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      @Documented
       @Retention(SOURCE)
      +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
       public static @interface Player.RepeatMode
    • diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.State.html b/docs/doc/reference/com/google/android/exoplayer2/Player.State.html index f4758744ae..2e49a77eda 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.State.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.State.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      @Documented
       @Retention(SOURCE)
      +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
       public static @interface Player.State
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.TimelineChangeReason.html b/docs/doc/reference/com/google/android/exoplayer2/Player.TimelineChangeReason.html index 34514269e3..9972d82602 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.TimelineChangeReason.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.TimelineChangeReason.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      @Documented
       @Retention(SOURCE)
      +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
       public static @interface Player.TimelineChangeReason
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.html b/docs/doc/reference/com/google/android/exoplayer2/Player.html index 06a5f53fd3..a2e407a755 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":38,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6,"i16":6,"i17":6,"i18":6,"i19":6,"i20":6,"i21":6,"i22":6,"i23":6,"i24":6,"i25":6,"i26":6,"i27":6,"i28":6,"i29":38,"i30":6,"i31":6,"i32":6,"i33":6,"i34":6,"i35":6,"i36":6,"i37":6,"i38":6,"i39":6,"i40":6,"i41":6,"i42":6,"i43":6,"i44":6,"i45":6,"i46":6,"i47":6,"i48":6,"i49":6,"i50":6,"i51":6,"i52":6,"i53":6,"i54":6,"i55":6,"i56":38,"i57":6,"i58":38,"i59":6,"i60":6,"i61":6,"i62":6,"i63":6,"i64":6,"i65":6,"i66":6,"i67":6,"i68":6,"i69":6,"i70":6,"i71":38,"i72":6,"i73":6,"i74":6,"i75":38,"i76":6,"i77":38,"i78":6,"i79":6,"i80":6,"i81":6,"i82":6,"i83":6,"i84":6,"i85":6,"i86":6,"i87":6,"i88":6,"i89":6,"i90":6,"i91":6,"i92":6,"i93":6,"i94":6,"i95":6,"i96":6,"i97":6,"i98":6,"i99":6,"i100":6,"i101":6,"i102":6,"i103":6,"i104":6,"i105":6,"i106":6,"i107":6,"i108":6,"i109":6,"i110":6,"i111":38}; +var data = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6,"i16":6,"i17":6,"i18":6,"i19":6,"i20":6,"i21":6,"i22":6,"i23":6,"i24":6,"i25":6,"i26":6,"i27":6,"i28":6,"i29":6,"i30":6,"i31":38,"i32":38,"i33":6,"i34":38,"i35":6,"i36":6,"i37":6,"i38":6,"i39":6,"i40":6,"i41":6,"i42":6,"i43":38,"i44":6,"i45":6,"i46":6,"i47":6,"i48":6,"i49":6,"i50":6,"i51":38,"i52":6,"i53":6,"i54":6,"i55":6,"i56":6,"i57":6,"i58":6,"i59":6,"i60":38,"i61":6,"i62":38,"i63":38,"i64":6,"i65":38,"i66":6,"i67":6,"i68":6,"i69":6,"i70":6,"i71":38,"i72":38,"i73":38,"i74":6,"i75":6,"i76":6,"i77":6,"i78":6,"i79":6,"i80":38,"i81":6,"i82":6,"i83":6,"i84":38,"i85":6,"i86":6,"i87":6,"i88":6,"i89":6,"i90":6,"i91":6,"i92":6,"i93":6,"i94":6,"i95":6,"i96":6,"i97":38,"i98":6,"i99":6,"i100":38,"i101":6,"i102":6,"i103":6,"i104":6,"i105":6,"i106":6,"i107":6,"i108":6,"i109":6,"i110":6,"i111":6,"i112":6,"i113":6,"i114":6,"i115":6,"i116":6,"i117":6,"i118":6,"i119":6,"i120":6,"i121":6,"i122":38}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -126,7 +126,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    All Known Implementing Classes:
    -
    BasePlayer, CastPlayer, ForwardingPlayer, SimpleExoPlayer, StubExoPlayer
    +
    BasePlayer, CastPlayer, ForwardingPlayer, SimpleExoPlayer, StubExoPlayer, StubPlayer

    public interface Player
    @@ -143,10 +143,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); @@ -311,7 +309,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_GET_CURRENT_MEDIA_ITEM -
    Command to get the MediaItem of the current window.
    +
    Command to get the currently playing MediaItem.
    @@ -344,130 +342,180 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int +COMMAND_GET_TRACK_INFOS + +
    Command to get track infos.
    + + + +static int COMMAND_GET_VOLUME
    Command to get the player volume.
    - + static int COMMAND_INVALID
    Represents an invalid Player.Command.
    - + static int COMMAND_PLAY_PAUSE
    Command to start, pause or resume playback.
    + +static int +COMMAND_PREPARE + +
    Command to prepare the player.
    + + static int -COMMAND_PREPARE_STOP +COMMAND_SEEK_BACK -
    Command to prepare the player, stop playback or release the player.
    +
    Command to seek back by a fixed increment into the current MediaItem.
    static int -COMMAND_SEEK_BACK +COMMAND_SEEK_FORWARD -
    Command to seek back by a fixed increment into the current window.
    +
    Command to seek forward by a fixed increment into the current MediaItem.
    static int -COMMAND_SEEK_FORWARD +COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM -
    Command to seek forward by a fixed increment into the current window.
    +
    Command to seek anywhere into the current MediaItem.
    static int COMMAND_SEEK_IN_CURRENT_WINDOW -
    Command to seek anywhere into the current window.
    +
    Deprecated. + +
    static int COMMAND_SEEK_TO_DEFAULT_POSITION -
    Command to seek to the default position of the current window.
    +
    Command to seek to the default position of the current MediaItem.
    static int +COMMAND_SEEK_TO_MEDIA_ITEM + +
    Command to seek anywhere in any MediaItem.
    + + + +static int COMMAND_SEEK_TO_NEXT -
    Command to seek to a later position in the current or next window.
    +
    Command to seek to a later position in the current or next MediaItem.
    + + + +static int +COMMAND_SEEK_TO_NEXT_MEDIA_ITEM + +
    Command to seek to the default position of the next MediaItem.
    static int COMMAND_SEEK_TO_NEXT_WINDOW -
    Command to seek to the default position of the next window.
    +
    Deprecated. + +
    static int COMMAND_SEEK_TO_PREVIOUS -
    Command to seek to an earlier position in the current or previous window.
    +
    Command to seek to an earlier position in the current or previous MediaItem.
    static int -COMMAND_SEEK_TO_PREVIOUS_WINDOW +COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -
    Command to seek to the default position of the previous window.
    +
    Command to seek to the default position of the previous MediaItem.
    static int -COMMAND_SEEK_TO_WINDOW +COMMAND_SEEK_TO_PREVIOUS_WINDOW -
    Command to seek anywhere in any window.
    +
    Deprecated. + +
    static int +COMMAND_SEEK_TO_WINDOW + +
    Deprecated. + +
    + + + +static int COMMAND_SET_DEVICE_VOLUME
    Command to set the device volume and mute it.
    - + static int COMMAND_SET_MEDIA_ITEMS_METADATA
    Command to set the MediaItems metadata.
    - + static int COMMAND_SET_REPEAT_MODE
    Command to set the repeat mode.
    - + static int COMMAND_SET_SHUFFLE_MODE
    Command to enable shuffling.
    - + static int COMMAND_SET_SPEED_AND_PITCH
    Command to set the playback speed and pitch.
    + +static int +COMMAND_SET_TRACK_SELECTION_PARAMETERS + +
    Command to set the player's track selection parameters.
    + + static int COMMAND_SET_VIDEO_SURFACE @@ -484,33 +532,40 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int +COMMAND_STOP + +
    Command to stop playback or release the player.
    + + + +static int DISCONTINUITY_REASON_AUTO_TRANSITION
    Automatic playback transition from one period in the timeline to the next.
    - + static int DISCONTINUITY_REASON_INTERNAL
    Discontinuity introduced internally (e.g.
    - + static int DISCONTINUITY_REASON_REMOVE
    Discontinuity caused by the removal of the current period from the Timeline.
    - + static int DISCONTINUITY_REASON_SEEK
    Seek within the current period or to another period.
    - + static int DISCONTINUITY_REASON_SEEK_ADJUSTMENT @@ -518,141 +573,132 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); permitted to be inexact. - + static int DISCONTINUITY_REASON_SKIP
    Discontinuity introduced by a skipped period (for instance a skipped ad).
    - + static int EVENT_AVAILABLE_COMMANDS_CHANGED -
    isCommandAvailable(int) changed for at least one Player.Command.
    +
    isCommandAvailable(int) changed for at least one Player.Command.
    - + static int EVENT_IS_LOADING_CHANGED
    isLoading() ()} changed.
    - + static int EVENT_IS_PLAYING_CHANGED
    isPlaying() changed.
    - + static int EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED - + static int EVENT_MEDIA_ITEM_TRANSITION
    getCurrentMediaItem() changed or the player started repeating the current item.
    - + static int EVENT_MEDIA_METADATA_CHANGED - + static int EVENT_PLAY_WHEN_READY_CHANGED - + static int EVENT_PLAYBACK_PARAMETERS_CHANGED - + static int EVENT_PLAYBACK_STATE_CHANGED - + static int EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED - + static int EVENT_PLAYER_ERROR - + static int EVENT_PLAYLIST_METADATA_CHANGED - + static int EVENT_POSITION_DISCONTINUITY
    A position discontinuity occurred.
    - + static int EVENT_REPEAT_MODE_CHANGED - + static int EVENT_SEEK_BACK_INCREMENT_CHANGED - + static int EVENT_SEEK_FORWARD_INCREMENT_CHANGED - + static int EVENT_SHUFFLE_MODE_ENABLED_CHANGED - -static int -EVENT_STATIC_METADATA_CHANGED - -
    Deprecated. -
    Use EVENT_MEDIA_METADATA_CHANGED for structured metadata changes.
    -
    - - static int EVENT_TIMELINE_CHANGED @@ -662,110 +708,117 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int -EVENT_TRACKS_CHANGED +EVENT_TRACK_SELECTION_PARAMETERS_CHANGED - + static int +EVENT_TRACKS_CHANGED + + + + + +static int MEDIA_ITEM_TRANSITION_REASON_AUTO
    Playback has automatically transitioned to the next media item.
    - + static int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED
    The current media item has changed because of a change in the playlist.
    - + static int MEDIA_ITEM_TRANSITION_REASON_REPEAT
    The media item has been repeated.
    - + static int MEDIA_ITEM_TRANSITION_REASON_SEEK
    A seek to another media item has occurred.
    - + static int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY
    Playback has been paused to avoid becoming noisy.
    - + static int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
    Playback has been paused because of a loss of audio focus.
    - + static int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM
    Playback has been paused at the end of a media item.
    - + static int PLAY_WHEN_READY_CHANGE_REASON_REMOTE
    Playback has been started or paused because of a remote change.
    - + static int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST
    Playback has been started or paused by a call to setPlayWhenReady(boolean).
    - + static int PLAYBACK_SUPPRESSION_REASON_NONE
    Playback is not suppressed.
    - + static int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
    Playback is suppressed due to transient audio focus loss.
    - + static int REPEAT_MODE_ALL
    Repeats the entire timeline infinitely.
    - + static int REPEAT_MODE_OFF
    Normal playback without repetition.
    - + static int REPEAT_MODE_ONE -
    Repeats the currently playing window infinitely during ongoing playback.
    +
    Repeats the currently playing MediaItem infinitely during ongoing playback.
    - + static int STATE_BUFFERING @@ -773,35 +826,35 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); so. - + static int STATE_ENDED
    The player has finished playing the media.
    - + static int STATE_IDLE
    The player is idle, and must be prepared before it will play the media.
    - + static int STATE_READY
    The player is able to immediately play from its current position.
    - + static int TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED
    Timeline changed as a result of a change of the playlist items or the order of the items.
    - + static int TIMELINE_CHANGE_REASON_SOURCE_UPDATE @@ -828,21 +881,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); void -addListener​(Player.EventListener listener) - -
    Deprecated. - -
    - - - -void addListener​(Player.Listener listener)
    Registers a listener to receive all events from the player.
    - + void addMediaItem​(int index, MediaItem mediaItem) @@ -850,14 +894,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Adds a media item at the given index of the playlist.
    - + void addMediaItem​(MediaItem mediaItem)
    Adds a media item to the end of the playlist.
    - + void addMediaItems​(int index, List<MediaItem> mediaItems) @@ -865,13 +909,20 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Adds a list of media items at the given index of the playlist.
    - + void addMediaItems​(List<MediaItem> mediaItems)
    Adds a list of media items to the end of the playlist.
    + +boolean +canAdvertiseSession() + +
    Returns whether the player can be used to advertise a media session.
    + + void clearMediaItems() @@ -949,7 +1000,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); int getBufferedPercentage() -
    Returns an estimate of the percentage in the current content window or ad up to which data is +
    Returns an estimate of the percentage in the current content or ad up to which data is buffered, or 0 if no estimate is available.
    @@ -957,8 +1008,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); long getBufferedPosition() -
    Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
    +
    Returns an estimate of the position in the current content or ad up to which data is buffered, + in milliseconds.
    @@ -966,15 +1017,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); getContentBufferedPosition()
    If isPlayingAd() returns true, returns an estimate of the content position in - the current content window up to which data is buffered, in milliseconds.
    + the current content up to which data is buffered, in milliseconds.
    long getContentDuration() -
    If isPlayingAd() returns true, returns the duration of the current content - window in milliseconds, or C.TIME_UNSET if the duration is not known.
    +
    If isPlayingAd() returns true, returns the duration of the current content in + milliseconds, or C.TIME_UNSET if the duration is not known.
    @@ -1012,8 +1063,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); getCurrentLiveOffset()
    Returns the offset of the current playback position from the live edge in milliseconds, or - C.TIME_UNSET if the current window isn't live or the - offset is unknown.
    + C.TIME_UNSET if the current MediaItem isCurrentMediaItemLive() isn't + live} or the offset is unknown. @@ -1027,34 +1078,30 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); MediaItem getCurrentMediaItem() -
    Returns the media item of the current window in the timeline.
    +
    Returns the currently playing MediaItem.
    int +getCurrentMediaItemIndex() + +
    Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is + empty.
    + + + +int getCurrentPeriodIndex()
    Returns the index of the period currently being played.
    - + long getCurrentPosition() -
    Returns the playback position in the current content window or ad, in milliseconds, or the - prospective position in milliseconds if the current timeline is - empty.
    - - - -List<Metadata> -getCurrentStaticMetadata() - -
    Deprecated. -
    Use getMediaMetadata() and Player.Listener.onMediaMetadataChanged(MediaMetadata) for access to structured metadata, or - access the raw static metadata directly from the track - selections' formats.
    -
    +
    Returns the playback position in the current content or ad, in milliseconds, or the prospective + position in milliseconds if the current timeline is empty.
    @@ -1068,67 +1115,80 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); TrackGroupArray getCurrentTrackGroups() -
    Returns the available track groups.
    +
    Deprecated. + +
    TrackSelectionArray getCurrentTrackSelections() -
    Returns the current track selections.
    +
    Deprecated. + +
    -int -getCurrentWindowIndex() +TracksInfo +getCurrentTracksInfo() -
    Returns the index of the current window in the timeline, or the prospective window index if the current timeline is empty.
    +
    Returns the available tracks, as well as the tracks' support, type, and selection status.
    -DeviceInfo +int +getCurrentWindowIndex() + +
    Deprecated. + +
    + + + +DeviceInfo getDeviceInfo()
    Gets the device information.
    - + int getDeviceVolume()
    Gets the current volume of the device.
    - + long getDuration() -
    Returns the duration of the current content window or ad in milliseconds, or C.TIME_UNSET if the duration is not known.
    - - - -int -getMaxSeekToPreviousPosition() - -
    Returns the maximum position for which seekToPrevious() seeks to the previous window, - in milliseconds.
    +
    Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if + the duration is not known.
    +long +getMaxSeekToPreviousPosition() + +
    Returns the maximum position for which seekToPrevious() seeks to the previous MediaItem, in milliseconds.
    + + + MediaItem getMediaItemAt​(int index)
    Returns the MediaItem at the given index.
    - + int getMediaItemCount()
    Returns the number of media items in the playlist.
    - + MediaMetadata getMediaMetadata() @@ -1136,214 +1196,284 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); supported. - + +int +getNextMediaItemIndex() + +
    Returns the index of the MediaItem that will be played if seekToNextMediaItem() is called, which may depend on the current repeat mode and whether + shuffle mode is enabled.
    + + + int getNextWindowIndex() -
    Returns the index of the window that will be played if seekToNextWindow() is called, - which may depend on the current repeat mode and whether shuffle mode is enabled.
    +
    Deprecated. + +
    - + PlaybackParameters getPlaybackParameters()
    Returns the currently active playback parameters.
    - -int + +@com.google.android.exoplayer2.Player.State int getPlaybackState()
    Returns the current playback state of the player.
    - -int + +@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int getPlaybackSuppressionReason()
    Returns the reason why playback is suppressed even though getPlayWhenReady() is true, or PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
    - + PlaybackException getPlayerError()
    Returns the error that caused playback to fail.
    - + MediaMetadata getPlaylistMetadata()
    Returns the playlist MediaMetadata, as set by setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
    - + boolean getPlayWhenReady()
    Whether playback will proceed when getPlaybackState() == STATE_READY.
    - + +int +getPreviousMediaItemIndex() + +
    Returns the index of the MediaItem that will be played if seekToPreviousMediaItem() is called, which may depend on the current repeat mode and whether + shuffle mode is enabled.
    + + + int getPreviousWindowIndex() -
    Returns the index of the window that will be played if seekToPreviousWindow() is - called, which may depend on the current repeat mode and whether shuffle mode is enabled.
    +
    Deprecated. + +
    - -int + +@com.google.android.exoplayer2.Player.RepeatMode int getRepeatMode()
    Returns the current Player.RepeatMode used for playback.
    - + long getSeekBackIncrement()
    Returns the seekBack() increment.
    - + long getSeekForwardIncrement()
    Returns the seekForward() increment.
    - + boolean getShuffleModeEnabled() -
    Returns whether shuffling of windows is enabled.
    +
    Returns whether shuffling of media items is enabled.
    - + long getTotalBufferedDuration()
    Returns an estimate of the total buffered duration from the current position, in milliseconds.
    - + +TrackSelectionParameters +getTrackSelectionParameters() + +
    Returns the parameters constraining the track selection.
    + + + VideoSize getVideoSize()
    Gets the size of the video.
    - + float getVolume()
    Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    - + boolean hasNext()
    Deprecated. -
    Use hasNextWindow() instead.
    +
    Use hasNextMediaItem() instead.
    - + +boolean +hasNextMediaItem() + +
    Returns whether a next MediaItem exists, which may depend on the current repeat mode + and whether shuffle mode is enabled.
    + + + boolean hasNextWindow() -
    Returns whether a next window exists, which may depend on the current repeat mode and whether - shuffle mode is enabled.
    +
    Deprecated. +
    Use hasNextMediaItem() instead.
    +
    - + boolean hasPrevious()
    Deprecated. -
    Use hasPreviousWindow() instead.
    +
    - + boolean -hasPreviousWindow() +hasPreviousMediaItem() -
    Returns whether a previous window exists, which may depend on the current repeat mode and +
    Returns whether a previous media item exists, which may depend on the current repeat mode and whether shuffle mode is enabled.
    - + +boolean +hasPreviousWindow() + +
    Deprecated. + +
    + + + void increaseDeviceVolume()
    Increases the volume of the device.
    - + boolean -isCommandAvailable​(int command) +isCommandAvailable​(@com.google.android.exoplayer2.Player.Command int command)
    Returns whether the provided Player.Command is available.
    - + +boolean +isCurrentMediaItemDynamic() + +
    Returns whether the current MediaItem is dynamic (may change when the Timeline + is updated), or false if the Timeline is empty.
    + + + +boolean +isCurrentMediaItemLive() + +
    Returns whether the current MediaItem is live, or false if the Timeline + is empty.
    + + + +boolean +isCurrentMediaItemSeekable() + +
    Returns whether the current MediaItem is seekable, or false if the Timeline is empty.
    + + + boolean isCurrentWindowDynamic() -
    Returns whether the current window is dynamic, or false if the Timeline is - empty.
    +
    Deprecated. + +
    - + boolean isCurrentWindowLive() -
    Returns whether the current window is live, or false if the Timeline is empty.
    +
    Deprecated. + +
    - + boolean isCurrentWindowSeekable() -
    Returns whether the current window is seekable, or false if the Timeline is - empty.
    +
    Deprecated. + +
    - + boolean isDeviceMuted()
    Gets whether the device is muted or not.
    - + boolean isLoading()
    Whether the player is currently loading the source.
    - + boolean isPlaying()
    Returns whether the player is playing, i.e.
    - + boolean isPlayingAd()
    Returns whether the player is currently playing an ad.
    - + void moveMediaItem​(int currentIndex, int newIndex) @@ -1351,7 +1481,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Moves the media item at the current index to the new index.
    - + void moveMediaItems​(int fromIndex, int toIndex, @@ -1360,76 +1490,67 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Moves the media item range to the new index.
    - + void next()
    Deprecated. -
    Use seekToNextWindow() instead.
    +
    - + void pause()
    Pauses playback.
    - + void play()
    Resumes playback as soon as getPlaybackState() == STATE_READY.
    - + void prepare()
    Prepares the player.
    - + void previous()
    Deprecated. - +
    - + void release()
    Releases the player.
    - -void -removeListener​(Player.EventListener listener) - -
    Deprecated. - -
    - - - + void removeListener​(Player.Listener listener)
    Unregister a listener registered through addListener(Listener).
    - + void removeMediaItem​(int index)
    Removes the media item at the given index of the playlist.
    - + void removeMediaItems​(int fromIndex, int toIndex) @@ -1437,94 +1558,113 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Removes a range of media items from the playlist.
    - + void seekBack() -
    Seeks back in the current window by getSeekBackIncrement() milliseconds.
    - - - -void -seekForward() - -
    Seeks forward in the current window by getSeekForwardIncrement() milliseconds.
    - - - -void -seekTo​(int windowIndex, - long positionMs) - -
    Seeks to a position specified in milliseconds in the specified window.
    - - - -void -seekTo​(long positionMs) - -
    Seeks to a position specified in milliseconds in the current window.
    - - - -void -seekToDefaultPosition() - -
    Seeks to the default position associated with the current window.
    - - - -void -seekToDefaultPosition​(int windowIndex) - -
    Seeks to the default position associated with the specified window.
    - - - -void -seekToNext() - -
    Seeks to a later position in the current or next window (if available).
    - - - -void -seekToNextWindow() - -
    Seeks to the default position of the next window, which may depend on the current repeat mode - and whether shuffle mode is enabled.
    - - - -void -seekToPrevious() - -
    Seeks to an earlier position in the current or previous window (if available).
    +
    Seeks back in the current MediaItem by getSeekBackIncrement() milliseconds.
    void -seekToPreviousWindow() +seekForward() -
    Seeks to the default position of the previous window, which may depend on the current repeat - mode and whether shuffle mode is enabled.
    +
    Seeks forward in the current MediaItem by getSeekForwardIncrement() + milliseconds.
    void +seekTo​(int mediaItemIndex, + long positionMs) + +
    Seeks to a position specified in milliseconds in the specified MediaItem.
    + + + +void +seekTo​(long positionMs) + +
    Seeks to a position specified in milliseconds in the current MediaItem.
    + + + +void +seekToDefaultPosition() + +
    Seeks to the default position associated with the current MediaItem.
    + + + +void +seekToDefaultPosition​(int mediaItemIndex) + +
    Seeks to the default position associated with the specified MediaItem.
    + + + +void +seekToNext() + +
    Seeks to a later position in the current or next MediaItem (if available).
    + + + +void +seekToNextMediaItem() + +
    Seeks to the default position of the next MediaItem, which may depend on the current + repeat mode and whether shuffle mode is enabled.
    + + + +void +seekToNextWindow() + +
    Deprecated. + +
    + + + +void +seekToPrevious() + +
    Seeks to an earlier position in the current or previous MediaItem (if available).
    + + + +void +seekToPreviousMediaItem() + +
    Seeks to the default position of the previous MediaItem, which may depend on the + current repeat mode and whether shuffle mode is enabled.
    + + + +void +seekToPreviousWindow() + +
    Deprecated. + +
    + + + +void setDeviceMuted​(boolean muted)
    Sets the mute state of the device.
    - + void setDeviceVolume​(int volume)
    Sets the volume of the device.
    - + void setMediaItem​(MediaItem mediaItem) @@ -1532,7 +1672,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); default position.
    - + void setMediaItem​(MediaItem mediaItem, boolean resetPosition) @@ -1540,7 +1680,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Clears the playlist and adds the specified MediaItem.
    - + void setMediaItem​(MediaItem mediaItem, long startPositionMs) @@ -1548,7 +1688,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Clears the playlist and adds the specified MediaItem.
    - + void setMediaItems​(List<MediaItem> mediaItems) @@ -1556,7 +1696,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); the default position. - + void setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition) @@ -1564,65 +1704,72 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Clears the playlist and adds the specified MediaItems.
    - + void setMediaItems​(List<MediaItem> mediaItems, - int startWindowIndex, + int startIndex, long startPositionMs)
    Clears the playlist and adds the specified MediaItems.
    - + void setPlaybackParameters​(PlaybackParameters playbackParameters)
    Attempts to set the playback parameters.
    - + void setPlaybackSpeed​(float speed)
    Changes the rate at which playback occurs.
    - + void setPlaylistMetadata​(MediaMetadata mediaMetadata)
    Sets the playlist MediaMetadata.
    - + void setPlayWhenReady​(boolean playWhenReady)
    Sets whether playback should proceed when getPlaybackState() == STATE_READY.
    - + void -setRepeatMode​(int repeatMode) +setRepeatMode​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
    Sets the Player.RepeatMode to be used for playback.
    - + void setShuffleModeEnabled​(boolean shuffleModeEnabled) -
    Sets whether shuffling of windows is enabled.
    +
    Sets whether shuffling of media items is enabled.
    - + +void +setTrackSelectionParameters​(TrackSelectionParameters parameters) + +
    Sets the parameters constraining the track selection.
    + + + void setVideoSurface​(Surface surface)
    Sets the Surface onto which video will be rendered.
    - + void setVideoSurfaceHolder​(SurfaceHolder surfaceHolder) @@ -1630,35 +1777,36 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); rendered. - + void setVideoSurfaceView​(SurfaceView surfaceView)
    Sets the SurfaceView onto which video will be rendered.
    - + void setVideoTextureView​(TextureView textureView)
    Sets the TextureView onto which video will be rendered.
    - + void -setVolume​(float audioVolume) +setVolume​(float volume) -
    Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    +
    Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal + unchanged), inclusive.
    - + void stop()
    Stops playback without resetting the player.
    - + void stop​(boolean reset) @@ -1850,7 +1998,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    REPEAT_MODE_OFF

    static final int REPEAT_MODE_OFF
    Normal playback without repetition. "Previous" and "Next" actions move to the previous and next - windows respectively, and do nothing when there is no previous or next window to move to.
    + MediaItem respectively, and do nothing when there is no previous or next MediaItem to move to.
    See Also:
    Constant Field Values
    @@ -1864,9 +2012,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • REPEAT_MODE_ONE

    static final int REPEAT_MODE_ONE
    -
    Repeats the currently playing window infinitely during ongoing playback. "Previous" and "Next" - actions behave as they do in REPEAT_MODE_OFF, moving to the previous and next windows - respectively, and doing nothing when there is no previous or next window to move to.
    +
    Repeats the currently playing MediaItem infinitely during ongoing playback. "Previous" + and "Next" actions behave as they do in REPEAT_MODE_OFF, moving to the previous and + next MediaItem respectively, and doing nothing when there is no previous or next MediaItem to move to.
    See Also:
    Constant Field Values
    @@ -1882,8 +2030,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    static final int REPEAT_MODE_ALL
    Repeats the entire timeline infinitely. "Previous" and "Next" actions behave as they do in REPEAT_MODE_OFF, but with looping at the ends so that "Previous" when playing the - first window will move to the last window, and "Next" when playing the last window will move to - the first window.
    + first MediaItem will move to the last MediaItem, and "Next" when playing the + last MediaItem will move to the first MediaItem.
    See Also:
    Constant Field Values
    @@ -2108,30 +2256,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • EVENT_TRACKS_CHANGED

    static final int EVENT_TRACKS_CHANGED
    - +
    See Also:
    Constant Field Values
  • - - - - @@ -2251,7 +2382,7 @@ static final int EVENT_STATIC_METADATA_CHANGED
  • EVENT_POSITION_DISCONTINUITY

    static final int EVENT_POSITION_DISCONTINUITY
    -
    A position discontinuity occurred. See Player.Listener.onPositionDiscontinuity(PositionInfo, +
    See Also:
    @@ -2280,7 +2411,7 @@ static final int EVENT_STATIC_METADATA_CHANGED
  • EVENT_AVAILABLE_COMMANDS_CHANGED

    static final int EVENT_AVAILABLE_COMMANDS_CHANGED
    -
    isCommandAvailable(int) changed for at least one Player.Command.
    +
    isCommandAvailable(int) changed for at least one Player.Command.
    See Also:
    Constant Field Values
    @@ -2357,6 +2488,20 @@ static final int EVENT_STATIC_METADATA_CHANGED
  • + + + + @@ -2371,17 +2516,31 @@ static final int EVENT_STATIC_METADATA_CHANGED
  • - +
    • -

      COMMAND_PREPARE_STOP

      -
      static final int COMMAND_PREPARE_STOP
      -
      Command to prepare the player, stop playback or release the player.
      +

      COMMAND_PREPARE

      +
      static final int COMMAND_PREPARE
      +
      Command to prepare the player.
      See Also:
      -
      Constant Field Values
      +
      Constant Field Values
      +
      +
    • +
    + + + +
      +
    • +

      COMMAND_STOP

      +
      static final int COMMAND_STOP
      +
      Command to stop playback or release the player.
      +
      +
      See Also:
      +
      Constant Field Values
    @@ -2392,35 +2551,69 @@ static final int EVENT_STATIC_METADATA_CHANGED
  • COMMAND_SEEK_TO_DEFAULT_POSITION

    static final int COMMAND_SEEK_TO_DEFAULT_POSITION
    -
    Command to seek to the default position of the current window.
    +
    Command to seek to the default position of the current MediaItem.
    See Also:
    Constant Field Values
  • + + + +
      +
    • +

      COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM

      +
      static final int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM
      +
      Command to seek anywhere into the current MediaItem.
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    + + + +
      +
    • +

      COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM

      +
      static final int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM
      +
      Command to seek to the default position of the previous MediaItem.
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    • COMMAND_SEEK_TO_PREVIOUS_WINDOW

      -
      static final int COMMAND_SEEK_TO_PREVIOUS_WINDOW
      -
      Command to seek to the default position of the previous window.
      +
      @Deprecated
      +static final int COMMAND_SEEK_TO_PREVIOUS_WINDOW
      +
      Deprecated. + +
      See Also:
      Constant Field Values
      @@ -2434,21 +2627,38 @@ static final int EVENT_STATIC_METADATA_CHANGED
    • COMMAND_SEEK_TO_PREVIOUS

      static final int COMMAND_SEEK_TO_PREVIOUS
      -
      Command to seek to an earlier position in the current or previous window.
      +
      Command to seek to an earlier position in the current or previous MediaItem.
      See Also:
      Constant Field Values
    + + + +
      +
    • +

      COMMAND_SEEK_TO_NEXT_MEDIA_ITEM

      +
      static final int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
      +
      Command to seek to the default position of the next MediaItem.
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    • COMMAND_SEEK_TO_NEXT_WINDOW

      -
      static final int COMMAND_SEEK_TO_NEXT_WINDOW
      -
      Command to seek to the default position of the next window.
      +
      @Deprecated
      +static final int COMMAND_SEEK_TO_NEXT_WINDOW
      +
      Deprecated. + +
      See Also:
      Constant Field Values
      @@ -2462,21 +2672,38 @@ static final int EVENT_STATIC_METADATA_CHANGED
    • COMMAND_SEEK_TO_NEXT

      static final int COMMAND_SEEK_TO_NEXT
      -
      Command to seek to a later position in the current or next window.
      +
      Command to seek to a later position in the current or next MediaItem.
      See Also:
      Constant Field Values
    + + + +
      +
    • +

      COMMAND_SEEK_TO_MEDIA_ITEM

      +
      static final int COMMAND_SEEK_TO_MEDIA_ITEM
      +
      Command to seek anywhere in any MediaItem.
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    • COMMAND_SEEK_TO_WINDOW

      -
      static final int COMMAND_SEEK_TO_WINDOW
      -
      Command to seek anywhere in any window.
      +
      @Deprecated
      +static final int COMMAND_SEEK_TO_WINDOW
      +
      Deprecated. + +
      See Also:
      Constant Field Values
      @@ -2490,7 +2717,7 @@ static final int EVENT_STATIC_METADATA_CHANGED
    • COMMAND_SEEK_BACK

      static final int COMMAND_SEEK_BACK
      -
      Command to seek back by a fixed increment into the current window.
      +
      Command to seek back by a fixed increment into the current MediaItem.
      See Also:
      Constant Field Values
      @@ -2504,7 +2731,7 @@ static final int EVENT_STATIC_METADATA_CHANGED
    • COMMAND_SEEK_FORWARD

      static final int COMMAND_SEEK_FORWARD
      -
      Command to seek forward by a fixed increment into the current window.
      +
      Command to seek forward by a fixed increment into the current MediaItem.
      See Also:
      Constant Field Values
      @@ -2560,7 +2787,7 @@ static final int EVENT_STATIC_METADATA_CHANGED
    • COMMAND_GET_CURRENT_MEDIA_ITEM

      static final int COMMAND_GET_CURRENT_MEDIA_ITEM
      -
      Command to get the MediaItem of the current window.
      +
      Command to get the currently playing MediaItem.
      See Also:
      Constant Field Values
      @@ -2735,6 +2962,34 @@ static final int EVENT_STATIC_METADATA_CHANGED
    + + + +
      +
    • +

      COMMAND_SET_TRACK_SELECTION_PARAMETERS

      +
      static final int COMMAND_SET_TRACK_SELECTION_PARAMETERS
      +
      Command to set the player's track selection parameters.
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    + + + +
      +
    • +

      COMMAND_GET_TRACK_INFOS

      +
      static final int COMMAND_GET_TRACK_INFOS
      +
      Command to get track infos.
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    @@ -2770,28 +3025,6 @@ static final int EVENT_STATIC_METADATA_CHANGED player and on which player events are received.
  • - - - -
      -
    • -

      addListener

      -
      @Deprecated
      -void addListener​(Player.EventListener listener)
      -
      Deprecated. - -
      -
      Registers a listener to receive events from the player. - -

      The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

      -
      -
      Parameters:
      -
      listener - The listener to register.
      -
      -
    • -
    @@ -2801,34 +3034,13 @@ void addListener​(void addListener​(Player.Listener listener)
    Registers a listener to receive all events from the player. -

    The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

    +

    The listener's methods will be called on the thread associated with getApplicationLooper().

    Parameters:
    listener - The listener to register.
    - - - - @@ -2873,7 +3085,7 @@ void removeListener​(MediaItems.
    resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined - by getCurrentWindowIndex() and getCurrentPosition().
    + by getCurrentMediaItemIndex() and getCurrentPosition().
    @@ -2884,19 +3096,19 @@ void removeListener​(

    setMediaItems

    void setMediaItems​(List<MediaItem> mediaItems,
    -                   int startWindowIndex,
    +                   int startIndex,
                        long startPositionMs)
    Clears the playlist and adds the specified MediaItems.
    Parameters:
    mediaItems - The new MediaItems.
    -
    startWindowIndex - The window index to start playback from. If C.INDEX_UNSET is - passed, the current position is not reset.
    -
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given window is used. In any case, if - startWindowIndex is set to C.INDEX_UNSET, this parameter is ignored and the - position is not reset at all.
    +
    startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET + is passed, the current position is not reset.
    +
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In + any case, if startIndex is set to C.INDEX_UNSET, this parameter is ignored + and the position is not reset at all.
    Throws:
    -
    IllegalSeekPositionException - If the provided startWindowIndex is not within the +
    IllegalSeekPositionException - If the provided startIndex is not within the bounds of the list of media items.
    @@ -2945,7 +3157,7 @@ void removeListener​(Parameters:
    mediaItem - The new MediaItem.
    resetPosition - Whether the playback position should be reset to the default position. If - false, playback will start from the position defined by getCurrentWindowIndex() + false, playback will start from the position defined by getCurrentMediaItemIndex() and getCurrentPosition().
    @@ -3090,24 +3302,23 @@ void removeListener​(Clears the playlist. - + @@ -3178,14 +3398,14 @@ int getPlaybackState()
  • getPlaybackSuppressionReason

    @PlaybackSuppressionReason
    -int getPlaybackSuppressionReason()
    +@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int getPlaybackSuppressionReason()
    Returns the reason why playback is suppressed even though getPlayWhenReady() is true, or PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
    Returns:
    The current playback suppression reason.
    See Also:
    -
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
    +
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
  • @@ -3284,18 +3504,18 @@ int getPlaybackSuppressionReason()
    Returns:
    Whether playback will proceed when ready.
    See Also:
    -
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    +
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    - + @@ -3327,7 +3547,7 @@ int getRepeatMode()
  • setShuffleModeEnabled

    void setShuffleModeEnabled​(boolean shuffleModeEnabled)
    -
    Sets whether shuffling of windows is enabled.
    +
    Sets whether shuffling of media items is enabled.
    Parameters:
    shuffleModeEnabled - Whether shuffling is enabled.
    @@ -3341,7 +3561,7 @@ int getRepeatMode()
  • getShuffleModeEnabled

    boolean getShuffleModeEnabled()
    -
    Returns whether shuffling of windows is enabled.
    +
    Returns whether shuffling of media items is enabled.
    See Also:
    Player.Listener.onShuffleModeEnabledChanged(boolean)
    @@ -3371,9 +3591,9 @@ int getRepeatMode()
  • seekToDefaultPosition

    void seekToDefaultPosition()
    -
    Seeks to the default position associated with the current window. The position can depend on - the type of media being played. For live streams it will typically be the live edge of the - window. For other streams it will typically be the start of the window.
    +
    Seeks to the default position associated with the current MediaItem. The position can + depend on the type of media being played. For live streams it will typically be the live edge. + For other streams it will typically be the start.
  • @@ -3382,17 +3602,17 @@ int getRepeatMode() @@ -3403,11 +3623,11 @@ int getRepeatMode()
  • seekTo

    void seekTo​(long positionMs)
    -
    Seeks to a position specified in milliseconds in the current window.
    +
    Seeks to a position specified in milliseconds in the current MediaItem.
    Parameters:
    -
    positionMs - The seek position in the current window, or C.TIME_UNSET to seek to - the window's default position.
    +
    positionMs - The seek position in the current MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
  • @@ -3417,17 +3637,17 @@ int getRepeatMode()
    • seekTo

      -
      void seekTo​(int windowIndex,
      +
      void seekTo​(int mediaItemIndex,
                   long positionMs)
      -
      Seeks to a position specified in milliseconds in the specified window.
      +
      Seeks to a position specified in milliseconds in the specified MediaItem.
      Parameters:
      -
      windowIndex - The index of the window.
      -
      positionMs - The seek position in the specified window, or C.TIME_UNSET to seek to - the window's default position.
      +
      mediaItemIndex - The index of the MediaItem.
      +
      positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
      Throws:
      IllegalSeekPositionException - If the player has a non-empty timeline and the provided - windowIndex is not within the bounds of the current timeline.
      + mediaItemIndex is not within the bounds of the current timeline.
    @@ -3454,7 +3674,7 @@ int getRepeatMode()
  • seekBack

    void seekBack()
    -
    Seeks back in the current window by getSeekBackIncrement() milliseconds.
    +
    Seeks back in the current MediaItem by getSeekBackIncrement() milliseconds.
  • @@ -3480,7 +3700,8 @@ int getRepeatMode()
  • seekForward

    void seekForward()
    -
    +
    Seeks forward in the current MediaItem by getSeekForwardIncrement() + milliseconds.
  • @@ -3492,7 +3713,7 @@ int getRepeatMode()
    @Deprecated
     boolean hasPrevious()
    Deprecated. -
    Use hasPreviousWindow() instead.
    +
  • @@ -3502,8 +3723,21 @@ boolean hasPrevious()
    • hasPreviousWindow

      -
      boolean hasPreviousWindow()
      -
      Returns whether a previous window exists, which may depend on the current repeat mode and +
      @Deprecated
      +boolean hasPreviousWindow()
      +
      Deprecated. + +
      +
    • +
    + + + +
      +
    • +

      hasPreviousMediaItem

      +
      boolean hasPreviousMediaItem()
      +
      Returns whether a previous media item exists, which may depend on the current repeat mode and whether shuffle mode is enabled.

      Note: When the repeat mode is REPEAT_MODE_ONE, this method behaves the same as when @@ -3520,7 +3754,7 @@ boolean hasPrevious()

      @Deprecated
       void previous()
      Deprecated. - +
    @@ -3530,10 +3764,22 @@ void previous()
    • seekToPreviousWindow

      -
      void seekToPreviousWindow()
      -
      Seeks to the default position of the previous window, which may depend on the current repeat - mode and whether shuffle mode is enabled. Does nothing if hasPreviousWindow() is - false. +
      @Deprecated
      +void seekToPreviousWindow()
      +
      Deprecated. + +
      +
    • +
    + + + + @@ -3588,7 +3835,7 @@ void previous()
    @Deprecated
     boolean hasNext()
    Deprecated. -
    Use hasNextWindow() instead.
    +
    Use hasNextMediaItem() instead.
  • @@ -3598,9 +3845,22 @@ boolean hasNext()
    • hasNextWindow

      -
      boolean hasNextWindow()
      -
      Returns whether a next window exists, which may depend on the current repeat mode and whether - shuffle mode is enabled. +
      @Deprecated
      +boolean hasNextWindow()
      +
      Deprecated. +
      Use hasNextMediaItem() instead.
      +
      +
    • +
    + + + + @@ -3626,9 +3886,23 @@ void next()
    • seekToNextWindow

      -
      void seekToNextWindow()
      -
      Seeks to the default position of the next window, which may depend on the current repeat mode - and whether shuffle mode is enabled. Does nothing if hasNextWindow() is false. +
      @Deprecated
      +void seekToNextWindow()
      +
      Deprecated. + +
      +
    • +
    + + + + @@ -3871,8 +4193,22 @@ void stop​(boolean reset) + + + +
      +
    • +

      getCurrentMediaItemIndex

      +
      int getCurrentMediaItemIndex()
      +
      Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is + empty.
    @@ -3881,9 +4217,23 @@ void stop​(boolean reset) + + + + @@ -3959,9 +4322,8 @@ void stop​(boolean reset)
  • getCurrentPosition

    long getCurrentPosition()
    -
    +
    Returns the playback position in the current content or ad, in milliseconds, or the prospective + position in milliseconds if the current timeline is empty.
  • @@ -3971,8 +4333,8 @@ void stop​(boolean reset)
  • getBufferedPosition

    long getBufferedPosition()
    -
    Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
    +
    Returns an estimate of the position in the current content or ad up to which data is buffered, + in milliseconds.
  • @@ -3981,8 +4343,10 @@ void stop​(boolean reset)
    • getBufferedPercentage

      -
      int getBufferedPercentage()
      -
      Returns an estimate of the percentage in the current content window or ad up to which data is +
      @IntRange(from=0L,
      +          to=100L)
      +int getBufferedPercentage()
      +
      Returns an estimate of the percentage in the current content or ad up to which data is buffered, or 0 if no estimate is available.
    @@ -3994,7 +4358,7 @@ void stop​(boolean reset)

    getTotalBufferedDuration

    long getTotalBufferedDuration()
    Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and windows.
    + This includes pre-buffered data for subsequent ads and
    media items. @@ -4003,9 +4367,22 @@ void stop​(boolean reset) + + + + @@ -155,7 +155,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); TextOutput textRendererOutput, MetadataOutput metadataRendererOutput) -
    Builds the Renderer instances for a SimpleExoPlayer.
    +
    Builds the Renderer instances for an ExoPlayer.
    @@ -186,7 +186,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput, MetadataOutput metadataRendererOutput) -
    Builds the Renderer instances for a SimpleExoPlayer.
    +
    Builds the Renderer instances for an ExoPlayer.
    Parameters:
    eventHandler - A handler to use when invoking event listeners and outputs.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html index cb8e403feb..3b18bc8635 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; +var data = {"i0":42,"i1":42,"i2":42,"i3":42,"i4":42,"i5":42,"i6":42,"i7":42,"i8":42,"i9":42,"i10":42,"i11":42,"i12":42,"i13":42,"i14":42,"i15":42,"i16":42,"i17":42,"i18":42,"i19":42,"i20":42,"i21":42,"i22":42,"i23":42}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -133,11 +133,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    SimpleExoPlayer

    -
    public static final class SimpleExoPlayer.Builder
    +
    @Deprecated
    +public static final class SimpleExoPlayer.Builder
     extends Object
    -
    A builder for SimpleExoPlayer instances. - -

    See Builder(Context) for the list of default values.

    +
    Deprecated. +
    Use ExoPlayer.Builder instead.
    +
    @@ -160,21 +161,27 @@ extends Builder​(Context context) -
    Creates a builder.
    +
    Deprecated. +
    Use Builder(Context) instead.
    +
    Builder​(Context context, ExtractorsFactory extractorsFactory) -
    Creates a builder with a custom ExtractorsFactory.
    + Builder​(Context context, RenderersFactory renderersFactory) -
    Creates a builder with a custom RenderersFactory.
    +
    Deprecated. + +
    @@ -182,7 +189,10 @@ extends RenderersFactory renderersFactory, ExtractorsFactory extractorsFactory)
    -
    Creates a builder with a custom RenderersFactory and ExtractorsFactory.
    + @@ -194,7 +204,11 @@ extends BandwidthMeter bandwidthMeter, AnalyticsCollector analyticsCollector) -
    Creates a builder with the specified custom components.
    + @@ -209,7 +223,7 @@ extends

    Method Summary

    - + @@ -219,21 +233,28 @@ extends SimpleExoPlayer @@ -241,144 +262,191 @@ extends setAudioAttributes​(AudioAttributes audioAttributes, boolean handleAudioFocus) - + - + + + + + +
    All Methods Instance Methods Concrete Methods All Methods Instance Methods Concrete Methods Deprecated Methods 
    Modifier and Type Method build() -
    Builds a SimpleExoPlayer instance.
    +
    Deprecated. + +
    SimpleExoPlayer.Builder experimentalSetForegroundModeTimeoutMs​(long timeoutMs) -
    Set a limit on the time a call to SimpleExoPlayer.setForegroundMode(boolean) can spend.
    +
    SimpleExoPlayer.Builder setAnalyticsCollector​(AnalyticsCollector analyticsCollector) -
    Sets the AnalyticsCollector that will collect and forward all player events.
    +
    -
    Sets AudioAttributes that will be used by the player and whether to handle audio - focus.
    +
    SimpleExoPlayer.Builder setBandwidthMeter​(BandwidthMeter bandwidthMeter) -
    Sets the BandwidthMeter that will be used by the player.
    +
    SimpleExoPlayer.Builder setClock​(Clock clock) -
    Sets the Clock that will be used by the player.
    +
    Deprecated. + +
    SimpleExoPlayer.Builder setDetachSurfaceTimeoutMs​(long detachSurfaceTimeoutMs) -
    Sets a timeout for detaching a surface from the player.
    +
    SimpleExoPlayer.Builder setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy) -
    Sets whether the player should pause automatically when audio is rerouted from a headset to - device speakers.
    +
    SimpleExoPlayer.Builder setLivePlaybackSpeedControl​(LivePlaybackSpeedControl livePlaybackSpeedControl) -
    Sets the LivePlaybackSpeedControl that will control the playback speed when playing - live streams, in order to maintain a steady target offset from the live stream edge.
    +
    SimpleExoPlayer.Builder setLoadControl​(LoadControl loadControl) -
    Sets the LoadControl that will be used by the player.
    +
    SimpleExoPlayer.Builder setLooper​(Looper looper) -
    Sets the Looper that must be used for all calls to the player and that is used to - call listeners on.
    +
    Deprecated. + +
    SimpleExoPlayer.Builder setMediaSourceFactory​(MediaSourceFactory mediaSourceFactory) -
    Sets the MediaSourceFactory that will be used by the player.
    +
    SimpleExoPlayer.Builder setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems) -
    Sets whether to pause playback at the end of each media item.
    +
    SimpleExoPlayer.Builder setPriorityTaskManager​(PriorityTaskManager priorityTaskManager) -
    Sets an PriorityTaskManager that will be used by the player.
    +
    SimpleExoPlayer.Builder setReleaseTimeoutMs​(long releaseTimeoutMs) - +
    SimpleExoPlayer.Builder setSeekBackIncrementMs​(long seekBackIncrementMs) -
    Sets the BasePlayer.seekBack() increment.
    +
    SimpleExoPlayer.Builder setSeekForwardIncrementMs​(long seekForwardIncrementMs) -
    Sets the BasePlayer.seekForward() increment.
    +
    SimpleExoPlayer.Builder setSeekParameters​(SeekParameters seekParameters) -
    Sets the parameters that control how seek operations are performed.
    +
    SimpleExoPlayer.Builder setSkipSilenceEnabled​(boolean skipSilenceEnabled) -
    Sets whether silences silences in the audio stream is enabled.
    +
    SimpleExoPlayer.Builder setTrackSelector​(TrackSelector trackSelector) -
    Sets the TrackSelector that will be used by the player.
    +
    SimpleExoPlayer.Builder setUseLazyPreparation​(boolean useLazyPreparation) -
    Sets whether media sources should be initialized lazily.
    +
    SimpleExoPlayer.BuildersetVideoScalingMode​(int videoScalingMode)setVideoChangeFrameRateStrategy​(int videoChangeFrameRateStrategy) -
    Sets the C.VideoScalingMode that will be used by the player.
    +
    SimpleExoPlayer.BuildersetWakeMode​(int wakeMode)setVideoScalingMode​(int videoScalingMode) -
    Sets the C.WakeMode that will be used by the player.
    +
    Deprecated. + +
    +
    SimpleExoPlayer.BuildersetWakeMode​(@com.google.android.exoplayer2.C.WakeMode int wakeMode) +
    Deprecated. + +
    @@ -411,43 +479,11 @@ extends
  • Builder

    -
    public Builder​(Context context)
    -
    Creates a builder. - -

    Use Builder(Context, RenderersFactory), Builder(Context, - RenderersFactory) or Builder(Context, RenderersFactory, ExtractorsFactory) instead, - if you intend to provide a custom RenderersFactory or a custom ExtractorsFactory. This is to ensure that ProGuard or R8 can remove ExoPlayer's DefaultRenderersFactory and DefaultExtractorsFactory from the APK. - -

    The builder uses the following default values: - -

    -
    -
    Parameters:
    -
    context - A Context.
    -
    +
    @Deprecated
    +public Builder​(Context context)
    +
    Deprecated. +
    Use Builder(Context) instead.
    +
  • @@ -456,17 +492,12 @@ extends
  • Builder

    -
    public Builder​(Context context,
    +
    @Deprecated
    +public Builder​(Context context,
                    RenderersFactory renderersFactory)
    -
    Creates a builder with a custom RenderersFactory. - -

    See Builder(Context) for a list of default values.

    -
    -
    Parameters:
    -
    context - A Context.
    -
    renderersFactory - A factory for creating Renderers to be used by the - player.
    -
    +
    Deprecated. + +
  • @@ -475,17 +506,12 @@ extends
  • Builder

    -
    public Builder​(Context context,
    +
    @Deprecated
    +public Builder​(Context context,
                    ExtractorsFactory extractorsFactory)
    -
    Creates a builder with a custom ExtractorsFactory. - -

    See Builder(Context) for a list of default values.

    -
    -
    Parameters:
    -
    context - A Context.
    -
    extractorsFactory - An ExtractorsFactory used to extract progressive media from - its container.
    -
    +
  • @@ -494,20 +520,14 @@ extends
  • Builder

    -
    public Builder​(Context context,
    +
    @Deprecated
    +public Builder​(Context context,
                    RenderersFactory renderersFactory,
                    ExtractorsFactory extractorsFactory)
    -
    Creates a builder with a custom RenderersFactory and ExtractorsFactory. - -

    See Builder(Context) for a list of default values.

    -
    -
    Parameters:
    -
    context - A Context.
    -
    renderersFactory - A factory for creating Renderers to be used by the - player.
    -
    extractorsFactory - An ExtractorsFactory used to extract progressive media from - its container.
    -
    +
  • @@ -516,28 +536,19 @@ extends
  • Builder

    -
    public Builder​(Context context,
    +
    @Deprecated
    +public Builder​(Context context,
                    RenderersFactory renderersFactory,
                    TrackSelector trackSelector,
                    MediaSourceFactory mediaSourceFactory,
                    LoadControl loadControl,
                    BandwidthMeter bandwidthMeter,
                    AnalyticsCollector analyticsCollector)
    -
    Creates a builder with the specified custom components. - -

    Note that this constructor is only useful to try and ensure that ExoPlayer's default - components can be removed by ProGuard or R8.

    -
    -
    Parameters:
    -
    context - A Context.
    -
    renderersFactory - A factory for creating Renderers to be used by the - player.
    -
    trackSelector - A TrackSelector.
    -
    mediaSourceFactory - A MediaSourceFactory.
    -
    loadControl - A LoadControl.
    -
    bandwidthMeter - A BandwidthMeter.
    -
    analyticsCollector - An AnalyticsCollector.
    -
    +
  • @@ -556,15 +567,12 @@ extends
  • experimentalSetForegroundModeTimeoutMs

    -
    public SimpleExoPlayer.Builder experimentalSetForegroundModeTimeoutMs​(long timeoutMs)
    -
    Set a limit on the time a call to SimpleExoPlayer.setForegroundMode(boolean) can spend. If a call to SimpleExoPlayer.setForegroundMode(boolean) takes more than timeoutMs milliseconds to complete, the player - will raise an error via Player.Listener.onPlayerError(com.google.android.exoplayer2.PlaybackException). - -

    This method is experimental, and will be renamed or removed in a future release.

    -
    -
    Parameters:
    -
    timeoutMs - The time limit in milliseconds.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder experimentalSetForegroundModeTimeoutMs​(long timeoutMs)
    +
  • @@ -573,16 +581,11 @@ extends
  • setTrackSelector

    -
    public SimpleExoPlayer.Builder setTrackSelector​(TrackSelector trackSelector)
    -
    Sets the TrackSelector that will be used by the player.
    -
    -
    Parameters:
    -
    trackSelector - A TrackSelector.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setTrackSelector​(TrackSelector trackSelector)
    +
  • @@ -591,16 +594,11 @@ extends
  • setMediaSourceFactory

    -
    public SimpleExoPlayer.Builder setMediaSourceFactory​(MediaSourceFactory mediaSourceFactory)
    -
    Sets the MediaSourceFactory that will be used by the player.
    -
    -
    Parameters:
    -
    mediaSourceFactory - A MediaSourceFactory.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setMediaSourceFactory​(MediaSourceFactory mediaSourceFactory)
    +
  • @@ -609,16 +607,11 @@ extends
  • setLoadControl

    -
    public SimpleExoPlayer.Builder setLoadControl​(LoadControl loadControl)
    -
    Sets the LoadControl that will be used by the player.
    -
    -
    Parameters:
    -
    loadControl - A LoadControl.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setLoadControl​(LoadControl loadControl)
    +
  • @@ -627,16 +620,11 @@ extends
  • setBandwidthMeter

    -
    public SimpleExoPlayer.Builder setBandwidthMeter​(BandwidthMeter bandwidthMeter)
    -
    Sets the BandwidthMeter that will be used by the player.
    -
    -
    Parameters:
    -
    bandwidthMeter - A BandwidthMeter.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setBandwidthMeter​(BandwidthMeter bandwidthMeter)
    +
  • @@ -645,17 +633,11 @@ extends
  • setLooper

    -
    public SimpleExoPlayer.Builder setLooper​(Looper looper)
    -
    Sets the Looper that must be used for all calls to the player and that is used to - call listeners on.
    -
    -
    Parameters:
    -
    looper - A Looper.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setLooper​(Looper looper)
    +
    Deprecated. + +
  • @@ -664,16 +646,11 @@ extends
  • setAnalyticsCollector

    -
    public SimpleExoPlayer.Builder setAnalyticsCollector​(AnalyticsCollector analyticsCollector)
    -
    Sets the AnalyticsCollector that will collect and forward all player events.
    -
    -
    Parameters:
    -
    analyticsCollector - An AnalyticsCollector.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setAnalyticsCollector​(AnalyticsCollector analyticsCollector)
    +
  • @@ -682,19 +659,13 @@ extends
  • setPriorityTaskManager

    -
    public SimpleExoPlayer.Builder setPriorityTaskManager​(@Nullable
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setPriorityTaskManager​(@Nullable
                                                           PriorityTaskManager priorityTaskManager)
    -
    Sets an PriorityTaskManager that will be used by the player. - -

    The priority C.PRIORITY_PLAYBACK will be set while the player is loading.

    -
    -
    Parameters:
    -
    priorityTaskManager - A PriorityTaskManager, or null to not use one.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
  • @@ -703,48 +674,27 @@ extends
  • setAudioAttributes

    -
    public SimpleExoPlayer.Builder setAudioAttributes​(AudioAttributes audioAttributes,
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setAudioAttributes​(AudioAttributes audioAttributes,
                                                       boolean handleAudioFocus)
    -
    Sets AudioAttributes that will be used by the player and whether to handle audio - focus. - -

    If audio focus should be handled, the AudioAttributes.usage must be C.USAGE_MEDIA or C.USAGE_GAME. Other usages will throw an IllegalArgumentException.

    -
    -
    Parameters:
    -
    audioAttributes - AudioAttributes.
    -
    handleAudioFocus - Whether the player should handle audio focus.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
  • - + @@ -753,19 +703,11 @@ extends
  • setHandleAudioBecomingNoisy

    -
    public SimpleExoPlayer.Builder setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy)
    -
    Sets whether the player should pause automatically when audio is rerouted from a headset to - device speakers. See the audio - becoming noisy documentation for more information.
    -
    -
    Parameters:
    -
    handleAudioBecomingNoisy - Whether the player should pause automatically when audio is - rerouted from a headset to device speakers.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy)
    +
  • @@ -774,16 +716,11 @@ extends
  • setSkipSilenceEnabled

    -
    public SimpleExoPlayer.Builder setSkipSilenceEnabled​(boolean skipSilenceEnabled)
    -
    Sets whether silences silences in the audio stream is enabled.
    -
    -
    Parameters:
    -
    skipSilenceEnabled - Whether skipping silences is enabled.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setSkipSilenceEnabled​(boolean skipSilenceEnabled)
    +
  • @@ -792,19 +729,26 @@ extends
  • setVideoScalingMode

    -
    public SimpleExoPlayer.Builder setVideoScalingMode​(@VideoScalingMode
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setVideoScalingMode​(@VideoScalingMode
                                                        int videoScalingMode)
    -
    Sets the C.VideoScalingMode that will be used by the player. - -

    Note that the scaling mode only applies if a MediaCodec-based video Renderer is enabled and if the output surface is owned by a SurfaceView.

    -
    -
    Parameters:
    -
    videoScalingMode - A C.VideoScalingMode.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    Deprecated. + +
    +
  • + + + + + @@ -813,20 +757,11 @@ extends
  • setUseLazyPreparation

    -
    public SimpleExoPlayer.Builder setUseLazyPreparation​(boolean useLazyPreparation)
    -
    Sets whether media sources should be initialized lazily. - -

    If false, all initial preparation steps (e.g., manifest loads) happen immediately. If - true, these initial preparations are triggered only when the player starts buffering the - media.

    -
    -
    Parameters:
    -
    useLazyPreparation - Whether to use lazy preparation.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setUseLazyPreparation​(boolean useLazyPreparation)
    +
  • @@ -835,16 +770,11 @@ extends
  • setSeekParameters

    -
    public SimpleExoPlayer.Builder setSeekParameters​(SeekParameters seekParameters)
    -
    Sets the parameters that control how seek operations are performed.
    -
    -
    Parameters:
    -
    seekParameters - The SeekParameters.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setSeekParameters​(SeekParameters seekParameters)
    +
  • @@ -853,18 +783,12 @@ extends
  • setSeekBackIncrementMs

    -
    public SimpleExoPlayer.Builder setSeekBackIncrementMs​(@IntRange(from=1L)
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setSeekBackIncrementMs​(@IntRange(from=1L)
                                                           long seekBackIncrementMs)
    -
    Sets the BasePlayer.seekBack() increment.
    -
    -
    Parameters:
    -
    seekBackIncrementMs - The seek back increment, in milliseconds.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalArgumentException - If seekBackIncrementMs is non-positive.
    -
    IllegalStateException - If build() has already been called.
    -
    +
  • @@ -873,18 +797,12 @@ extends
  • setSeekForwardIncrementMs

    -
    public SimpleExoPlayer.Builder setSeekForwardIncrementMs​(@IntRange(from=1L)
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setSeekForwardIncrementMs​(@IntRange(from=1L)
                                                              long seekForwardIncrementMs)
    -
    Sets the BasePlayer.seekForward() increment.
    -
    -
    Parameters:
    -
    seekForwardIncrementMs - The seek forward increment, in milliseconds.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalArgumentException - If seekForwardIncrementMs is non-positive.
    -
    IllegalStateException - If build() has already been called.
    -
    +
  • @@ -893,19 +811,11 @@ extends
  • setReleaseTimeoutMs

    -
    public SimpleExoPlayer.Builder setReleaseTimeoutMs​(long releaseTimeoutMs)
    - -
    -
    Parameters:
    -
    releaseTimeoutMs - The release timeout, in milliseconds.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setReleaseTimeoutMs​(long releaseTimeoutMs)
    +
  • @@ -914,19 +824,11 @@ extends
  • setDetachSurfaceTimeoutMs

    -
    public SimpleExoPlayer.Builder setDetachSurfaceTimeoutMs​(long detachSurfaceTimeoutMs)
    -
    Sets a timeout for detaching a surface from the player. - -

    If detaching a surface or replacing a surface takes more than - detachSurfaceTimeoutMs to complete, the player will report an error via Player.Listener.onPlayerError(com.google.android.exoplayer2.PlaybackException).

    -
    -
    Parameters:
    -
    detachSurfaceTimeoutMs - The timeout for detaching a surface, in milliseconds.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setDetachSurfaceTimeoutMs​(long detachSurfaceTimeoutMs)
    +
  • @@ -935,18 +837,11 @@ extends
  • setPauseAtEndOfMediaItems

    -
    public SimpleExoPlayer.Builder setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems)
    -
    Sets whether to pause playback at the end of each media item. - -

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    -
    -
    Parameters:
    -
    pauseAtEndOfMediaItems - Whether to pause playback at the end of each media item.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems)
    +
  • @@ -955,17 +850,11 @@ extends
  • setLivePlaybackSpeedControl

    -
    public SimpleExoPlayer.Builder setLivePlaybackSpeedControl​(LivePlaybackSpeedControl livePlaybackSpeedControl)
    -
    Sets the LivePlaybackSpeedControl that will control the playback speed when playing - live streams, in order to maintain a steady target offset from the live stream edge.
    -
    -
    Parameters:
    -
    livePlaybackSpeedControl - The LivePlaybackSpeedControl.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setLivePlaybackSpeedControl​(LivePlaybackSpeedControl livePlaybackSpeedControl)
    +
  • @@ -974,17 +863,11 @@ extends
  • setClock

    -
    public SimpleExoPlayer.Builder setClock​(Clock clock)
    -
    Sets the Clock that will be used by the player. Should only be set for testing - purposes.
    -
    -
    Parameters:
    -
    clock - A Clock.
    -
    Returns:
    -
    This builder.
    -
    Throws:
    -
    IllegalStateException - If build() has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer.Builder setClock​(Clock clock)
    +
    Deprecated. + +
  • @@ -993,12 +876,11 @@ extends
  • build

    -
    public SimpleExoPlayer build()
    -
    Builds a SimpleExoPlayer instance.
    -
    -
    Throws:
    -
    IllegalStateException - If this method has already been called.
    -
    +
    @Deprecated
    +public SimpleExoPlayer build()
    +
    Deprecated. + +
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html index 3471b07a47..f9d5724fe1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":42,"i2":10,"i3":42,"i4":42,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":42,"i12":42,"i13":42,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":42,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":10,"i50":10,"i51":10,"i52":10,"i53":10,"i54":10,"i55":10,"i56":10,"i57":10,"i58":10,"i59":10,"i60":10,"i61":10,"i62":10,"i63":10,"i64":10,"i65":10,"i66":10,"i67":10,"i68":10,"i69":10,"i70":10,"i71":10,"i72":10,"i73":10,"i74":10,"i75":10,"i76":10,"i77":10,"i78":10,"i79":10,"i80":10,"i81":10,"i82":10,"i83":10,"i84":10,"i85":10,"i86":42,"i87":42,"i88":10,"i89":10,"i90":42,"i91":10,"i92":42,"i93":42,"i94":10,"i95":10,"i96":42,"i97":42,"i98":42,"i99":42,"i100":10,"i101":10,"i102":10,"i103":10,"i104":10,"i105":10,"i106":10,"i107":10,"i108":10,"i109":42,"i110":10,"i111":10,"i112":10,"i113":10,"i114":10,"i115":10,"i116":10,"i117":10,"i118":10,"i119":10,"i120":10,"i121":10,"i122":10,"i123":10,"i124":10,"i125":10,"i126":10,"i127":10,"i128":42,"i129":10,"i130":10,"i131":10,"i132":10,"i133":10,"i134":10,"i135":10,"i136":10,"i137":42}; +var data = {"i0":42,"i1":42,"i2":42,"i3":42,"i4":42,"i5":42,"i6":42,"i7":42,"i8":42,"i9":42,"i10":42,"i11":42,"i12":42,"i13":42,"i14":42,"i15":42,"i16":42,"i17":42,"i18":42,"i19":42,"i20":42,"i21":42,"i22":42,"i23":42,"i24":42,"i25":42,"i26":42,"i27":42,"i28":42,"i29":42,"i30":42,"i31":42,"i32":42,"i33":42,"i34":42,"i35":42,"i36":42,"i37":42,"i38":42,"i39":42,"i40":42,"i41":42,"i42":42,"i43":42,"i44":42,"i45":42,"i46":42,"i47":42,"i48":42,"i49":42,"i50":42,"i51":42,"i52":42,"i53":42,"i54":42,"i55":42,"i56":42,"i57":42,"i58":42,"i59":42,"i60":42,"i61":42,"i62":42,"i63":42,"i64":42,"i65":42,"i66":42,"i67":42,"i68":42,"i69":42,"i70":42,"i71":42,"i72":42,"i73":42,"i74":42,"i75":42,"i76":42,"i77":42,"i78":42,"i79":42,"i80":42,"i81":42,"i82":42,"i83":42,"i84":42,"i85":42,"i86":42,"i87":42,"i88":42,"i89":42,"i90":42,"i91":42,"i92":42,"i93":42,"i94":42,"i95":42,"i96":42,"i97":42,"i98":42,"i99":42,"i100":42,"i101":42,"i102":42,"i103":42,"i104":42,"i105":42,"i106":42,"i107":42,"i108":42,"i109":42,"i110":42,"i111":42,"i112":42,"i113":42,"i114":42,"i115":42,"i116":42,"i117":42,"i118":42,"i119":42,"i120":42,"i121":42,"i122":42,"i123":42,"i124":42,"i125":42,"i126":42,"i127":42,"i128":42,"i129":42,"i130":42,"i131":42}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -135,14 +135,16 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Implemented Interfaces:
    -
    ExoPlayer, ExoPlayer.AudioComponent, ExoPlayer.DeviceComponent, ExoPlayer.MetadataComponent, ExoPlayer.TextComponent, ExoPlayer.VideoComponent, Player
    +
    ExoPlayer, ExoPlayer.AudioComponent, ExoPlayer.DeviceComponent, ExoPlayer.TextComponent, ExoPlayer.VideoComponent, Player

    -
    public class SimpleExoPlayer
    +
    @Deprecated
    +public class SimpleExoPlayer
     extends BasePlayer
    -implements ExoPlayer, ExoPlayer.AudioComponent, ExoPlayer.VideoComponent, ExoPlayer.TextComponent, ExoPlayer.MetadataComponent, ExoPlayer.DeviceComponent
    -
    An ExoPlayer implementation that uses default Renderer components. Instances can - be obtained from SimpleExoPlayer.Builder.
    +implements ExoPlayer, ExoPlayer.AudioComponent, ExoPlayer.VideoComponent, ExoPlayer.TextComponent, ExoPlayer.DeviceComponent
    +
    Deprecated. +
    Use ExoPlayer instead.
    +
  • @@ -167,7 +169,9 @@ implements static class  SimpleExoPlayer.Builder -
    A builder for SimpleExoPlayer instances.
    +
    Deprecated. +
    Use ExoPlayer.Builder instead.
    +
    @@ -176,7 +180,7 @@ implements ExoPlayer -ExoPlayer.AudioComponent, ExoPlayer.AudioOffloadListener, ExoPlayer.DeviceComponent, ExoPlayer.MetadataComponent, ExoPlayer.TextComponent, ExoPlayer.VideoComponent +ExoPlayer.AudioComponent, ExoPlayer.AudioOffloadListener, ExoPlayer.DeviceComponent, ExoPlayer.TextComponent, ExoPlayer.VideoComponent @@ -267,14 +266,16 @@ implements Looper applicationLooper)
    Deprecated. - +
    protected SimpleExoPlayer​(SimpleExoPlayer.Builder builder) -  + +
    Deprecated.
    +  @@ -298,623 +299,672 @@ implements void addAnalyticsListener​(AnalyticsListener listener) +
    Deprecated.
    Adds an AnalyticsListener to receive analytics events.
    void -addAudioListener​(AudioListener listener) - -
    Deprecated.
    - - - -void addAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener) +
    Deprecated.
    Adds a listener to receive audio offload events.
    - -void -addDeviceListener​(DeviceListener listener) - -
    Deprecated.
    - - - + void addListener​(Player.EventListener listener)
    Deprecated.
    - + void addListener​(Player.Listener listener) +
    Deprecated.
    Registers a listener to receive all events from the player.
    - + void addMediaItems​(int index, List<MediaItem> mediaItems) +
    Deprecated.
    Adds a list of media items at the given index of the playlist.
    - + void addMediaSource​(int index, MediaSource mediaSource) +
    Deprecated.
    Adds a media source at the given index of the playlist.
    - + void addMediaSource​(MediaSource mediaSource) +
    Deprecated.
    Adds a media source to the end of the playlist.
    - + void addMediaSources​(int index, List<MediaSource> mediaSources) +
    Deprecated.
    Adds a list of media sources at the given index of the playlist.
    + +void +addMediaSources​(List<MediaSource> mediaSources) + +
    Deprecated.
    +
    Adds a list of media sources to the end of the playlist.
    + + + +void +clearAuxEffectInfo() + +
    Deprecated.
    +
    Detaches any previously attached auxiliary audio effect from the underlying audio track.
    + + void -addMediaSources​(List<MediaSource> mediaSources) +clearCameraMotionListener​(CameraMotionListener listener) -
    Adds a list of media sources to the end of the playlist.
    +
    Deprecated.
    +
    Clears the listener which receives camera motion events if it matches the one passed.
    void -addMetadataOutput​(MetadataOutput output) +clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
    Deprecated.
    +
    Clears the listener which receives video frame metadata events if it matches the one passed.
    void -addTextOutput​(TextOutput listener) - -
    Deprecated.
    - - - -void -addVideoListener​(VideoListener listener) - -
    Deprecated.
    - - - -void -clearAuxEffectInfo() - -
    Detaches any previously attached auxiliary audio effect from the underlying audio track.
    - - - -void -clearCameraMotionListener​(CameraMotionListener listener) - -
    Clears the listener which receives camera motion events if it matches the one passed.
    - - - -void -clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener) - -
    Clears the listener which receives video frame metadata events if it matches the one passed.
    - - - -void clearVideoSurface() +
    Deprecated.
    Clears any Surface, SurfaceHolder, SurfaceView or TextureView currently set on the player.
    - + void clearVideoSurface​(Surface surface) +
    Deprecated.
    Clears the Surface onto which video is being rendered if it matches the one passed.
    - + void clearVideoSurfaceHolder​(SurfaceHolder surfaceHolder) +
    Deprecated.
    Clears the SurfaceHolder that holds the Surface onto which video is being rendered if it matches the one passed.
    - + void clearVideoSurfaceView​(SurfaceView surfaceView) +
    Deprecated.
    Clears the SurfaceView onto which video is being rendered if it matches the one passed.
    - + void clearVideoTextureView​(TextureView textureView) +
    Deprecated.
    Clears the TextureView onto which video is being rendered if it matches the one passed.
    - + PlayerMessage createMessage​(PlayerMessage.Target target) +
    Deprecated.
    Creates a message that can be sent to a PlayerMessage.Target.
    - + void decreaseDeviceVolume() +
    Deprecated.
    Decreases the volume of the device.
    - + boolean experimentalIsSleepingForOffload() +
    Deprecated.
    Returns whether the player has paused its main loop to save power in offload scheduling mode.
    - + void experimentalSetOffloadSchedulingEnabled​(boolean offloadSchedulingEnabled) +
    Deprecated.
    Sets whether audio offload scheduling is enabled.
    - + AnalyticsCollector getAnalyticsCollector() +
    Deprecated.
    Returns the AnalyticsCollector used for collecting analytics events.
    - + Looper getApplicationLooper() +
    Deprecated.
    Returns the Looper associated with the application thread that's used to access the player and on which player events are received.
    - + AudioAttributes getAudioAttributes() +
    Deprecated.
    Returns the attributes for audio playback.
    - + ExoPlayer.AudioComponent getAudioComponent() -
    Returns the component of this player for audio output, or null if audio is not supported.
    - +
    Deprecated.
    +  - + DecoderCounters getAudioDecoderCounters() +
    Deprecated.
    Returns DecoderCounters for audio, or null if no audio is being played.
    - + Format getAudioFormat() +
    Deprecated.
    Returns the audio format currently being played, or null if no audio is being played.
    - + int getAudioSessionId() +
    Deprecated.
    Returns the audio session identifier, or C.AUDIO_SESSION_ID_UNSET if not set.
    - + Player.Commands getAvailableCommands() +
    Deprecated.
    Returns the player's currently available Player.Commands.
    - + long getBufferedPosition() -
    Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
    +
    Deprecated.
    +
    Returns an estimate of the position in the current content or ad up to which data is buffered, + in milliseconds.
    - + Clock getClock() +
    Deprecated.
    Returns the Clock used for playback.
    - + long getContentBufferedPosition() +
    Deprecated.
    If Player.isPlayingAd() returns true, returns an estimate of the content position in - the current content window up to which data is buffered, in milliseconds.
    + the current content up to which data is buffered, in milliseconds. - + long getContentPosition() +
    Deprecated.
    If Player.isPlayingAd() returns true, returns the content position that will be played once all ads in the ad group have finished playing, in milliseconds.
    - + int getCurrentAdGroupIndex() +
    Deprecated.
    If Player.isPlayingAd() returns true, returns the index of the ad group in the period currently being played.
    - + int getCurrentAdIndexInAdGroup() +
    Deprecated.
    If Player.isPlayingAd() returns true, returns the index of the ad in its ad group.
    - + List<Cue> getCurrentCues() +
    Deprecated.
    Returns the current Cues.
    - + int -getCurrentPeriodIndex() +getCurrentMediaItemIndex() -
    Returns the index of the period currently being played.
    - - - -long -getCurrentPosition() - -
    Returns the playback position in the current content window or ad, in milliseconds, or the - prospective position in milliseconds if the current timeline is +
    Deprecated.
    +
    Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is empty.
    - -List<Metadata> -getCurrentStaticMetadata() + +int +getCurrentPeriodIndex()
    Deprecated.
    +
    Returns the index of the period currently being played.
    - + +long +getCurrentPosition() + +
    Deprecated.
    +
    Returns the playback position in the current content or ad, in milliseconds, or the prospective + position in milliseconds if the current timeline is empty.
    + + + Timeline getCurrentTimeline() +
    Deprecated.
    Returns the current Timeline.
    - + TrackGroupArray getCurrentTrackGroups() +
    Deprecated.
    Returns the available track groups.
    - + TrackSelectionArray getCurrentTrackSelections() +
    Deprecated.
    Returns the current track selections.
    - -int -getCurrentWindowIndex() + +TracksInfo +getCurrentTracksInfo() -
    Returns the index of the current window in the timeline, or the prospective window index if the current timeline is empty.
    +
    Deprecated.
    +
    Returns the available tracks, as well as the tracks' support, type, and selection status.
    - + ExoPlayer.DeviceComponent getDeviceComponent() -
    Returns the component of this player for playback device, or null if it's not supported.
    - +
    Deprecated.
    +  - -DeviceInfo + +DeviceInfo getDeviceInfo() +
    Deprecated.
    Gets the device information.
    - + int getDeviceVolume() +
    Deprecated.
    Gets the current volume of the device.
    - + long getDuration() -
    Returns the duration of the current content window or ad in milliseconds, or C.TIME_UNSET if the duration is not known.
    +
    Deprecated.
    +
    Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if + the duration is not known.
    - -int + +long getMaxSeekToPreviousPosition() -
    Returns the maximum position for which Player.seekToPrevious() seeks to the previous window, - in milliseconds.
    +
    Deprecated.
    +
    Returns the maximum position for which Player.seekToPrevious() seeks to the previous MediaItem, in milliseconds.
    - + MediaMetadata getMediaMetadata() +
    Deprecated.
    Returns the current combined MediaMetadata, or MediaMetadata.EMPTY if not supported.
    - -ExoPlayer.MetadataComponent -getMetadataComponent() - -
    Returns the component of this player for metadata output, or null if metadata is not supported.
    - - - + boolean getPauseAtEndOfMediaItems() +
    Deprecated.
    Returns whether the player pauses playback at the end of each media item.
    - + Looper getPlaybackLooper() +
    Deprecated.
    Returns the Looper associated with the playback thread.
    - + PlaybackParameters getPlaybackParameters() +
    Deprecated.
    Returns the currently active playback parameters.
    - -int + +@com.google.android.exoplayer2.Player.State int getPlaybackState() +
    Deprecated.
    Returns the current playback state of the player.
    - -int + +@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int getPlaybackSuppressionReason() +
    Deprecated.
    Returns the reason why playback is suppressed even though Player.getPlayWhenReady() is true, or Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
    - + ExoPlaybackException getPlayerError() +
    Deprecated.
    Equivalent to Player.getPlayerError(), except the exception is guaranteed to be an ExoPlaybackException.
    - + MediaMetadata getPlaylistMetadata() +
    Deprecated.
    Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
    - + boolean getPlayWhenReady() +
    Deprecated.
    Whether playback will proceed when Player.getPlaybackState() == Player.STATE_READY.
    - + int getRendererCount() +
    Deprecated.
    Returns the number of renderers.
    - -int + +@com.google.android.exoplayer2.C.TrackType int getRendererType​(int index) +
    Deprecated.
    Returns the track type that the renderer at a given index handles.
    - -int + +@com.google.android.exoplayer2.Player.RepeatMode int getRepeatMode() +
    Deprecated.
    Returns the current Player.RepeatMode used for playback.
    - + long getSeekBackIncrement() +
    Deprecated.
    Returns the Player.seekBack() increment.
    - + long getSeekForwardIncrement() +
    Deprecated.
    Returns the Player.seekForward() increment.
    - + SeekParameters getSeekParameters() +
    Deprecated.
    Returns the currently active SeekParameters of the player.
    - + boolean getShuffleModeEnabled() -
    Returns whether shuffling of windows is enabled.
    +
    Deprecated.
    +
    Returns whether shuffling of media items is enabled.
    - + boolean getSkipSilenceEnabled() +
    Deprecated.
    Returns whether skipping silences in the audio stream is enabled.
    - + ExoPlayer.TextComponent getTextComponent() -
    Returns the component of this player for text output, or null if text is not supported.
    - +
    Deprecated.
    +  - + long getTotalBufferedDuration() +
    Deprecated.
    Returns an estimate of the total buffered duration from the current position, in milliseconds.
    - + +TrackSelectionParameters +getTrackSelectionParameters() + +
    Deprecated.
    +
    Returns the parameters constraining the track selection.
    + + + TrackSelector getTrackSelector() +
    Deprecated.
    Returns the track selector that this player uses, or null if track selection is not supported.
    - + +int +getVideoChangeFrameRateStrategy() + +
    Deprecated.
    + + + + ExoPlayer.VideoComponent getVideoComponent() -
    Returns the component of this player for video output, or null if video is not supported.
    - +
    Deprecated.
    +  - + DecoderCounters getVideoDecoderCounters() +
    Deprecated.
    Returns DecoderCounters for video, or null if no video is being played.
    - + Format getVideoFormat() +
    Deprecated.
    Returns the video format currently being played, or null if no video is being played.
    - + int getVideoScalingMode() +
    Deprecated.
    Returns the C.VideoScalingMode.
    - + VideoSize getVideoSize() +
    Deprecated.
    Gets the size of the video.
    - + float getVolume() +
    Deprecated.
    Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    - + void increaseDeviceVolume() +
    Deprecated.
    Increases the volume of the device.
    - + boolean isDeviceMuted() +
    Deprecated.
    Gets whether the device is muted or not.
    - + boolean isLoading() +
    Deprecated.
    Whether the player is currently loading the source.
    - + boolean isPlayingAd() +
    Deprecated.
    Returns whether the player is currently playing an ad.
    - + void moveMediaItems​(int fromIndex, int toIndex, int newIndex) +
    Deprecated.
    Moves the media item range to the new index.
    - + void prepare() +
    Deprecated.
    Prepares the player.
    - + void prepare​(MediaSource mediaSource) @@ -923,7 +973,7 @@ implements + void prepare​(MediaSource mediaSource, boolean resetPosition, @@ -935,85 +985,55 @@ implements + void release() +
    Deprecated.
    Releases the player.
    - + void removeAnalyticsListener​(AnalyticsListener listener) +
    Deprecated.
    Removes an AnalyticsListener.
    - -void -removeAudioListener​(AudioListener listener) - -
    Deprecated.
    - - - + void removeAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener) +
    Deprecated.
    Removes a listener of audio offload events.
    - -void -removeDeviceListener​(DeviceListener listener) - -
    Deprecated.
    - - - + void removeListener​(Player.EventListener listener)
    Deprecated.
    - + void removeListener​(Player.Listener listener) +
    Deprecated.
    Unregister a listener registered through Player.addListener(Listener).
    - + void removeMediaItems​(int fromIndex, int toIndex) +
    Deprecated.
    Removes a range of media items from the playlist.
    - -void -removeMetadataOutput​(MetadataOutput output) - -
    Deprecated.
    - - - -void -removeTextOutput​(TextOutput listener) - -
    Deprecated.
    - - - -void -removeVideoListener​(VideoListener listener) - -
    Deprecated.
    - - - + void retry() @@ -1022,285 +1042,342 @@ implements + void -seekTo​(int windowIndex, +seekTo​(int mediaItemIndex, long positionMs) -
    Seeks to a position specified in milliseconds in the specified window.
    +
    Deprecated.
    +
    Seeks to a position specified in milliseconds in the specified MediaItem.
    - + void setAudioAttributes​(AudioAttributes audioAttributes, boolean handleAudioFocus) +
    Deprecated.
    Sets the attributes for audio playback, used by the underlying audio track.
    - + void setAudioSessionId​(int audioSessionId) +
    Deprecated.
    Sets the ID of the audio session to attach to the underlying AudioTrack.
    - + void setAuxEffectInfo​(AuxEffectInfo auxEffectInfo) +
    Deprecated.
    Sets information on an auxiliary audio effect to attach to the underlying audio track.
    - + void setCameraMotionListener​(CameraMotionListener listener) +
    Deprecated.
    Sets a listener of camera motion events.
    - + void setDeviceMuted​(boolean muted) +
    Deprecated.
    Sets the mute state of the device.
    - + void setDeviceVolume​(int volume) +
    Deprecated.
    Sets the volume of the device.
    - + void setForegroundMode​(boolean foregroundMode) +
    Deprecated.
    Sets whether the player is allowed to keep holding limited resources such as video decoders, even when in the idle state.
    - + void setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy) +
    Deprecated.
    Sets whether the player should pause automatically when audio is rerouted from a headset to device speakers.
    - + void setHandleWakeLock​(boolean handleWakeLock) -
    Deprecated. -
    Use setWakeMode(int) instead.
    -
    +
    Deprecated.
    - + void setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition) +
    Deprecated.
    Clears the playlist and adds the specified MediaItems.
    - + void setMediaItems​(List<MediaItem> mediaItems, - int startWindowIndex, + int startIndex, long startPositionMs) +
    Deprecated.
    Clears the playlist and adds the specified MediaItems.
    - + void setMediaSource​(MediaSource mediaSource) +
    Deprecated.
    Clears the playlist, adds the specified MediaSource and resets the position to the default position.
    - + void setMediaSource​(MediaSource mediaSource, boolean resetPosition) +
    Deprecated.
    Clears the playlist and adds the specified MediaSource.
    - + void setMediaSource​(MediaSource mediaSource, long startPositionMs) +
    Deprecated.
    Clears the playlist and adds the specified MediaSource.
    - + void setMediaSources​(List<MediaSource> mediaSources) +
    Deprecated.
    Clears the playlist, adds the specified MediaSources and resets the position to the default position.
    - + void setMediaSources​(List<MediaSource> mediaSources, boolean resetPosition) +
    Deprecated.
    Clears the playlist and adds the specified MediaSources.
    + +void +setMediaSources​(List<MediaSource> mediaSources, + int startMediaItemIndex, + long startPositionMs) + +
    Deprecated.
    +
    Clears the playlist and adds the specified MediaSources.
    + + + +void +setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems) + +
    Deprecated.
    +
    Sets whether to pause playback at the end of each media item.
    + + + +void +setPlaybackParameters​(PlaybackParameters playbackParameters) + +
    Deprecated.
    +
    Attempts to set the playback parameters.
    + + + +void +setPlaylistMetadata​(MediaMetadata mediaMetadata) + +
    Deprecated.
    +
    Sets the playlist MediaMetadata.
    + + + +void +setPlayWhenReady​(boolean playWhenReady) + +
    Deprecated.
    +
    Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY.
    + + + +void +setPriorityTaskManager​(PriorityTaskManager priorityTaskManager) + +
    Deprecated.
    +
    Sets a PriorityTaskManager, or null to clear a previously set priority task manager.
    + + + +void +setRepeatMode​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode) + +
    Deprecated.
    +
    Sets the Player.RepeatMode to be used for playback.
    + + + +void +setSeekParameters​(SeekParameters seekParameters) + +
    Deprecated.
    +
    Sets the parameters that control how seek operations are performed.
    + + + +void +setShuffleModeEnabled​(boolean shuffleModeEnabled) + +
    Deprecated.
    +
    Sets whether shuffling of media items is enabled.
    + + void -setMediaSources​(List<MediaSource> mediaSources, - int startWindowIndex, - long startPositionMs) +setShuffleOrder​(ShuffleOrder shuffleOrder) -
    Clears the playlist and adds the specified MediaSources.
    +
    Deprecated.
    +
    Sets the shuffle order.
    void -setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems) +setSkipSilenceEnabled​(boolean skipSilenceEnabled) -
    Sets whether to pause playback at the end of each media item.
    +
    Deprecated.
    +
    Sets whether skipping silences in the audio stream is enabled.
    void -setPlaybackParameters​(PlaybackParameters playbackParameters) +setThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread) -
    Attempts to set the playback parameters.
    +
    Deprecated.
    void -setPlaylistMetadata​(MediaMetadata mediaMetadata) +setTrackSelectionParameters​(TrackSelectionParameters parameters) -
    Sets the playlist MediaMetadata.
    +
    Deprecated.
    +
    Sets the parameters constraining the track selection.
    void -setPlayWhenReady​(boolean playWhenReady) +setVideoChangeFrameRateStrategy​(int videoChangeFrameRateStrategy) -
    Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY.
    +
    Deprecated.
    +
    Sets a C.VideoChangeFrameRateStrategy that will be used by the player when provided + with a video output Surface.
    void -setPriorityTaskManager​(PriorityTaskManager priorityTaskManager) +setVideoFrameMetadataListener​(VideoFrameMetadataListener listener) -
    Sets a PriorityTaskManager, or null to clear a previously set priority task manager.
    +
    Deprecated.
    +
    Sets a listener to receive video frame metadata events.
    void -setRepeatMode​(int repeatMode) +setVideoScalingMode​(int videoScalingMode) -
    Sets the Player.RepeatMode to be used for playback.
    +
    Deprecated.
    + void -setSeekParameters​(SeekParameters seekParameters) +setVideoSurface​(Surface surface) -
    Sets the parameters that control how seek operations are performed.
    +
    Deprecated.
    +
    Sets the Surface onto which video will be rendered.
    void -setShuffleModeEnabled​(boolean shuffleModeEnabled) - -
    Sets whether shuffling of windows is enabled.
    - - - -void -setShuffleOrder​(ShuffleOrder shuffleOrder) - -
    Sets the shuffle order.
    - - - -void -setSkipSilenceEnabled​(boolean skipSilenceEnabled) - -
    Sets whether skipping silences in the audio stream is enabled.
    - - - -void -setThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread) - -
    Deprecated. -
    Disabling the enforcement can result in hard-to-detect bugs.
    -
    - - - -void -setVideoFrameMetadataListener​(VideoFrameMetadataListener listener) - -
    Sets a listener to receive video frame metadata events.
    - - - -void -setVideoScalingMode​(int videoScalingMode) - -
    Sets the video scaling mode.
    - - - -void -setVideoSurface​(Surface surface) - -
    Sets the Surface onto which video will be rendered.
    - - - -void setVideoSurfaceHolder​(SurfaceHolder surfaceHolder) +
    Deprecated.
    Sets the SurfaceHolder that holds the Surface onto which video will be rendered.
    - + void setVideoSurfaceView​(SurfaceView surfaceView) +
    Deprecated.
    Sets the SurfaceView onto which video will be rendered.
    - + void setVideoTextureView​(TextureView textureView) +
    Deprecated.
    Sets the TextureView onto which video will be rendered.
    - + void -setVolume​(float audioVolume) +setVolume​(float volume) -
    Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    +
    Deprecated.
    +
    Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal + unchanged), inclusive.
    - + void -setWakeMode​(int wakeMode) +setWakeMode​(@com.google.android.exoplayer2.C.WakeMode int wakeMode) +
    Deprecated.
    Sets how the player should keep the device awake for playback when the screen is off.
    - + +void +stop() + +
    Deprecated.
    +
    Stops playback without resetting the player.
    + + + void stop​(boolean reset) @@ -1313,7 +1390,7 @@ implements BasePlayer -addMediaItem, addMediaItem, addMediaItems, clearMediaItems, getAvailableCommands, getBufferedPercentage, getContentDuration, getCurrentLiveOffset, getCurrentManifest, getCurrentMediaItem, getMediaItemAt, getMediaItemCount, getNextWindowIndex, getPreviousWindowIndex, hasNext, hasNextWindow, hasPrevious, hasPreviousWindow, isCommandAvailable, isCurrentWindowDynamic, isCurrentWindowLive, isCurrentWindowSeekable, isPlaying, moveMediaItem, next, pause, play, previous, removeMediaItem, seekBack, seekForward, seekTo, seekToDefaultPosition, seekToDefaultPosition, seekToNext, seekToNextWindow, seekToPrevious, seekToPreviousWindow, setMediaItem, setMediaItem, setMediaItem, setMediaItems, setPlaybackSpeed, stop +addMediaItem, addMediaItem, addMediaItems, canAdvertiseSession, clearMediaItems, getAvailableCommands, getBufferedPercentage, getContentDuration, getCurrentLiveOffset, getCurrentManifest, getCurrentMediaItem, getCurrentWindowIndex, getMediaItemAt, getMediaItemCount, getNextMediaItemIndex, getNextWindowIndex, getPreviousMediaItemIndex, getPreviousWindowIndex, hasNext, hasNextMediaItem, hasNextWindow, hasPrevious, hasPreviousMediaItem, hasPreviousWindow, isCommandAvailable, isCurrentMediaItemDynamic, isCurrentMediaItemLive, isCurrentMediaItemSeekable, isCurrentWindowDynamic, isCurrentWindowLive, isCurrentWindowSeekable, isPlaying, moveMediaItem, next, pause, play, previous, removeMediaItem, seekBack, seekForward, seekTo, seekToDefaultPosition, seekToDefaultPosition, seekToNext, seekToNextMediaItem, seekToNextWindow, seekToPrevious, seekToPreviousMediaItem, seekToPreviousWindow, setMediaItem, setMediaItem, setMediaItem, setMediaItems, setPlaybackSpeed @@ -1345,20 +1422,6 @@ implements - - -
      -
    • -

      DEFAULT_DETACH_SURFACE_TIMEOUT_MS

      -
      public static final long DEFAULT_DETACH_SURFACE_TIMEOUT_MS
      -
      The default timeout for detaching a surface from the player, in milliseconds.
      -
      -
      See Also:
      -
      Constant Field Values
      -
      -
    • -
    @@ -1366,6 +1429,7 @@ implements

    renderers

    protected final Renderer[] renderers
    +
    Deprecated.
    @@ -1396,7 +1460,7 @@ protected SimpleExoPlayer​(Clock clock, Looper applicationLooper)
    Deprecated. - +
    @@ -1407,6 +1471,7 @@ protected SimpleExoPlayer​(

    SimpleExoPlayer

    protected SimpleExoPlayer​(SimpleExoPlayer.Builder builder)
    +
    Deprecated.
    Parameters:
    builder - The SimpleExoPlayer.Builder to obtain all construction parameters.
    @@ -1430,6 +1495,7 @@ protected SimpleExoPlayer​(

    experimentalSetOffloadSchedulingEnabled

    public void experimentalSetOffloadSchedulingEnabled​(boolean offloadSchedulingEnabled)
    +
    Deprecated.
    Sets whether audio offload scheduling is enabled. If enabled, ExoPlayer's main loop will run as rarely as possible when playing an audio stream using audio offload. @@ -1476,6 +1542,7 @@ protected SimpleExoPlayer​(

    experimentalIsSleepingForOffload

    public boolean experimentalIsSleepingForOffload()
    +
    Deprecated.
    Returns whether the player has paused its main loop to save power in offload scheduling mode.
    @@ -1495,8 +1562,7 @@ protected SimpleExoPlayer​(@Nullable public ExoPlayer.AudioComponent getAudioComponent() -
    Description copied from interface: ExoPlayer
    -
    Returns the component of this player for audio output, or null if audio is not supported.
    +
    Deprecated.
    Specified by:
    getAudioComponent in interface ExoPlayer
    @@ -1511,8 +1577,7 @@ public @Nullable public ExoPlayer.VideoComponent getVideoComponent() -
    Description copied from interface: ExoPlayer
    -
    Returns the component of this player for video output, or null if video is not supported.
    +
    Deprecated.
    Specified by:
    getVideoComponent in interface ExoPlayer
    @@ -1527,30 +1592,13 @@ public @Nullable public ExoPlayer.TextComponent getTextComponent() -
    Description copied from interface: ExoPlayer
    -
    Returns the component of this player for text output, or null if text is not supported.
    +
    Deprecated.
    Specified by:
    getTextComponent in interface ExoPlayer
    - - - - @@ -1559,8 +1607,7 @@ public @Nullable public ExoPlayer.DeviceComponent getDeviceComponent() -
    Description copied from interface: ExoPlayer
    -
    Returns the component of this player for playback device, or null if it's not supported.
    +
    Deprecated.
    Specified by:
    getDeviceComponent in interface ExoPlayer
    @@ -1575,12 +1622,16 @@ public public void setVideoScalingMode​(@VideoScalingMode int videoScalingMode) -
    Sets the video scaling mode. +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    +
    Sets the C.VideoScalingMode. -

    Note that the scaling mode only applies if a MediaCodec-based video Renderer - is enabled and if the output surface is owned by a SurfaceView.

    +

    The scaling mode only applies if a MediaCodec-based video Renderer is + enabled and if the output surface is owned by a SurfaceView.

    Specified by:
    +
    setVideoScalingMode in interface ExoPlayer
    +
    Specified by:
    setVideoScalingMode in interface ExoPlayer.VideoComponent
    Parameters:
    videoScalingMode - The C.VideoScalingMode.
    @@ -1595,14 +1646,64 @@ public @VideoScalingMode public int getVideoScalingMode() -
    Description copied from interface: ExoPlayer.VideoComponent
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns the C.VideoScalingMode.
    Specified by:
    +
    getVideoScalingMode in interface ExoPlayer
    +
    Specified by:
    getVideoScalingMode in interface ExoPlayer.VideoComponent
    + + + + + + + + @@ -1610,6 +1711,7 @@ public int getVideoScalingMode()
  • getVideoSize

    public VideoSize getVideoSize()
    +
    Deprecated.
    Description copied from interface: Player
    Gets the size of the video. @@ -1632,6 +1734,7 @@ public int getVideoScalingMode()
  • clearVideoSurface

    public void clearVideoSurface()
    +
    Deprecated.
    Description copied from interface: Player
    Clears any Surface, SurfaceHolder, SurfaceView or TextureView currently set on the player.
    @@ -1651,6 +1754,7 @@ public int getVideoScalingMode()

    clearVideoSurface

    public void clearVideoSurface​(@Nullable
                                   Surface surface)
    +
    Deprecated.
    Description copied from interface: Player
    Clears the Surface onto which video is being rendered if it matches the one passed. Else does nothing.
    @@ -1672,6 +1776,7 @@ public int getVideoScalingMode()

    setVideoSurface

    public void setVideoSurface​(@Nullable
                                 Surface surface)
    +
    Deprecated.
    Description copied from interface: Player
    Sets the Surface onto which video will be rendered. The caller is responsible for tracking the lifecycle of the surface, and must clear the surface by calling @@ -1698,6 +1803,7 @@ public int getVideoScalingMode()

    setVideoSurfaceHolder

    public void setVideoSurfaceHolder​(@Nullable
                                       SurfaceHolder surfaceHolder)
    +
    Deprecated.
    Description copied from interface: Player
    Sets the SurfaceHolder that holds the Surface onto which video will be rendered. The player will track the lifecycle of the surface automatically. @@ -1722,6 +1828,7 @@ public int getVideoScalingMode()

    clearVideoSurfaceHolder

    public void clearVideoSurfaceHolder​(@Nullable
                                         SurfaceHolder surfaceHolder)
    +
    Deprecated.
    Description copied from interface: Player
    Clears the SurfaceHolder that holds the Surface onto which video is being rendered if it matches the one passed. Else does nothing.
    @@ -1743,6 +1850,7 @@ public int getVideoScalingMode()

    setVideoSurfaceView

    public void setVideoSurfaceView​(@Nullable
                                     SurfaceView surfaceView)
    +
    Deprecated.
    Description copied from interface: Player
    Sets the SurfaceView onto which video will be rendered. The player will track the lifecycle of the surface automatically. @@ -1767,6 +1875,7 @@ public int getVideoScalingMode()

    clearVideoSurfaceView

    public void clearVideoSurfaceView​(@Nullable
                                       SurfaceView surfaceView)
    +
    Deprecated.
    Description copied from interface: Player
    Clears the SurfaceView onto which video is being rendered if it matches the one passed. Else does nothing.
    @@ -1788,6 +1897,7 @@ public int getVideoScalingMode()

    setVideoTextureView

    public void setVideoTextureView​(@Nullable
                                     TextureView textureView)
    +
    Deprecated.
    Description copied from interface: Player
    Sets the TextureView onto which video will be rendered. The player will track the lifecycle of the surface automatically. @@ -1812,6 +1922,7 @@ public int getVideoScalingMode()

    clearVideoTextureView

    public void clearVideoTextureView​(@Nullable
                                       TextureView textureView)
    +
    Deprecated.
    Description copied from interface: Player
    Clears the TextureView onto which video is being rendered if it matches the one passed. Else does nothing.
    @@ -1832,6 +1943,7 @@ public int getVideoScalingMode()
  • addAudioOffloadListener

    public void addAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Adds a listener to receive audio offload events.
    @@ -1849,6 +1961,7 @@ public int getVideoScalingMode()
  • removeAudioOffloadListener

    public void removeAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Removes a listener of audio offload events.
    @@ -1859,44 +1972,6 @@ public int getVideoScalingMode()
  • - - - - - - - - @@ -1905,7 +1980,8 @@ public void removeAudioListener​(public void setAudioAttributes​(AudioAttributes audioAttributes, boolean handleAudioFocus) -
    Description copied from interface: ExoPlayer.AudioComponent
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Sets the attributes for audio playback, used by the underlying audio track. If not set, the default audio attributes will be used. They are suitable for general media playback. @@ -1915,13 +1991,15 @@ public void removeAudioListener​(Util.getStreamTypeForAudioUsage(int). +

    If the device is running a build before platform API version 21, audio attributes cannot be + set directly on the underlying audio track. In this case, the usage will be mapped onto an + equivalent stream type using Util.getStreamTypeForAudioUsage(int).

    If audio focus should be handled, the AudioAttributes.usage must be C.USAGE_MEDIA or C.USAGE_GAME. Other usages will throw an IllegalArgumentException.

    Specified by:
    +
    setAudioAttributes in interface ExoPlayer
    +
    Specified by:
    setAudioAttributes in interface ExoPlayer.AudioComponent
    Parameters:
    audioAttributes - The attributes to use for audio playback.
    @@ -1936,6 +2014,7 @@ public void removeAudioListener​(

    getAudioAttributes

    public AudioAttributes getAudioAttributes()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the attributes for audio playback.
    @@ -1953,17 +2032,20 @@ public void removeAudioListener​(

    setAudioSessionId

    public void setAudioSessionId​(int audioSessionId)
    -
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Sets the ID of the audio session to attach to the underlying AudioTrack. -

    The audio session ID can be generated using C.generateAudioSessionIdV21(Context) +

    The audio session ID can be generated using Util.generateAudioSessionIdV21(Context) for API 21+.

    Specified by:
    +
    setAudioSessionId in interface ExoPlayer
    +
    Specified by:
    setAudioSessionId in interface ExoPlayer.AudioComponent
    Parameters:
    -
    audioSessionId - The audio session ID, or C.AUDIO_SESSION_ID_UNSET if it should - be generated by the framework.
    +
    audioSessionId - The audio session ID, or C.AUDIO_SESSION_ID_UNSET if it should be + generated by the framework.
  • @@ -1974,10 +2056,13 @@ public void removeAudioListener​(

    getAudioSessionId

    public int getAudioSessionId()
    -
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns the audio session identifier, or C.AUDIO_SESSION_ID_UNSET if not set.
    Specified by:
    +
    getAudioSessionId in interface ExoPlayer
    +
    Specified by:
    getAudioSessionId in interface ExoPlayer.AudioComponent
    @@ -1989,10 +2074,13 @@ public void removeAudioListener​(

    setAuxEffectInfo

    public void setAuxEffectInfo​(AuxEffectInfo auxEffectInfo)
    -
    Description copied from interface: ExoPlayer.AudioComponent
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Sets information on an auxiliary audio effect to attach to the underlying audio track.
    Specified by:
    +
    setAuxEffectInfo in interface ExoPlayer
    +
    Specified by:
    setAuxEffectInfo in interface ExoPlayer.AudioComponent
    @@ -2004,10 +2092,13 @@ public void removeAudioListener​(

    clearAuxEffectInfo

    public void clearAuxEffectInfo()
    -
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Detaches any previously attached auxiliary audio effect from the underlying audio track.
    Specified by:
    +
    clearAuxEffectInfo in interface ExoPlayer
    +
    Specified by:
    clearAuxEffectInfo in interface ExoPlayer.AudioComponent
    @@ -2018,16 +2109,18 @@ public void removeAudioListener​(
  • setVolume

    -
    public void setVolume​(float audioVolume)
    +
    public void setVolume​(float volume)
    +
    Deprecated.
    -
    Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    +
    Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal + unchanged), inclusive.
    Specified by:
    setVolume in interface ExoPlayer.AudioComponent
    Specified by:
    setVolume in interface Player
    Parameters:
    -
    audioVolume - Linear output gain to apply to all audio channels.
    +
    volume - Linear output gain to apply to all audio channels.
  • @@ -2038,6 +2131,7 @@ public void removeAudioListener​(

    getVolume

    public float getVolume()
    +
    Deprecated.
    Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    @@ -2057,10 +2151,13 @@ public void removeAudioListener​(

    getSkipSilenceEnabled

    public boolean getSkipSilenceEnabled()
    -
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns whether skipping silences in the audio stream is enabled.
    Specified by:
    +
    getSkipSilenceEnabled in interface ExoPlayer
    +
    Specified by:
    getSkipSilenceEnabled in interface ExoPlayer.AudioComponent
    @@ -2072,10 +2169,13 @@ public void removeAudioListener​(

    setSkipSilenceEnabled

    public void setSkipSilenceEnabled​(boolean skipSilenceEnabled)
    -
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Sets whether skipping silences in the audio stream is enabled.
    Specified by:
    +
    setSkipSilenceEnabled in interface ExoPlayer
    +
    Specified by:
    setSkipSilenceEnabled in interface ExoPlayer.AudioComponent
    Parameters:
    skipSilenceEnabled - Whether skipping silences in the audio stream is enabled.
    @@ -2089,7 +2189,13 @@ public void removeAudioListener​(

    getAnalyticsCollector

    public AnalyticsCollector getAnalyticsCollector()
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns the AnalyticsCollector used for collecting analytics events.
    +
    +
    Specified by:
    +
    getAnalyticsCollector in interface ExoPlayer
    +
    @@ -2099,8 +2205,12 @@ public void removeAudioListener​(

    addAnalyticsListener

    public void addAnalyticsListener​(AnalyticsListener listener)
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Adds an AnalyticsListener to receive analytics events.
    +
    Specified by:
    +
    addAnalyticsListener in interface ExoPlayer
    Parameters:
    listener - The listener to be added.
    @@ -2113,8 +2223,12 @@ public void removeAudioListener​(

    removeAnalyticsListener

    public void removeAnalyticsListener​(AnalyticsListener listener)
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Removes an AnalyticsListener.
    +
    Specified by:
    +
    removeAnalyticsListener in interface ExoPlayer
    Parameters:
    listener - The listener to be removed.
    @@ -2127,10 +2241,14 @@ public void removeAudioListener​(

    setHandleAudioBecomingNoisy

    public void setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy)
    +
    Deprecated.
    +
    Sets whether the player should pause automatically when audio is rerouted from a headset to device speakers. See the audio becoming noisy documentation for more information.
    +
    Specified by:
    +
    setHandleAudioBecomingNoisy in interface ExoPlayer
    Parameters:
    handleAudioBecomingNoisy - Whether the player should pause automatically when audio is rerouted from a headset to device speakers.
    @@ -2145,10 +2263,14 @@ public void removeAudioListener​(public void setPriorityTaskManager​(@Nullable PriorityTaskManager priorityTaskManager) +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Sets a PriorityTaskManager, or null to clear a previously set priority task manager.

    The priority C.PRIORITY_PLAYBACK will be set while the player is loading.

    +
    Specified by:
    +
    setPriorityTaskManager in interface ExoPlayer
    Parameters:
    priorityTaskManager - The PriorityTaskManager, or null to clear a previously set priority task manager.
    @@ -2163,7 +2285,13 @@ public void removeAudioListener​(@Nullable public Format getVideoFormat() +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns the video format currently being played, or null if no video is being played.
    +
    +
    Specified by:
    +
    getVideoFormat in interface ExoPlayer
    +
    @@ -2174,7 +2302,13 @@ public getAudioFormat
    @Nullable
     public Format getAudioFormat()
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns the audio format currently being played, or null if no audio is being played.
    +
    +
    Specified by:
    +
    getAudioFormat in interface ExoPlayer
    +
    @@ -2185,7 +2319,13 @@ public getVideoDecoderCounters
    @Nullable
     public DecoderCounters getVideoDecoderCounters()
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns DecoderCounters for video, or null if no video is being played.
    +
    +
    Specified by:
    +
    getVideoDecoderCounters in interface ExoPlayer
    +
    @@ -2196,44 +2336,12 @@ public @Nullable public DecoderCounters getAudioDecoderCounters() +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Returns DecoderCounters for audio, or null if no audio is being played.
    - - - - - - - - - - @@ -2244,7 +2352,8 @@ public void removeVideoListener​(

    setVideoFrameMetadataListener

    public void setVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
    -
    Description copied from interface: ExoPlayer.VideoComponent
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Sets a listener to receive video frame metadata events.

    This method is intended to be called by the same component that sets the Surface @@ -2252,6 +2361,8 @@ public void removeVideoListener​(Specified by: +

    setVideoFrameMetadataListener in interface ExoPlayer
    +
    Specified by:
    setVideoFrameMetadataListener in interface ExoPlayer.VideoComponent
    Parameters:
    listener - The listener.
    @@ -2265,11 +2376,14 @@ public void removeVideoListener​(

    clearVideoFrameMetadataListener

    public void clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
    -
    Description copied from interface: ExoPlayer.VideoComponent
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Clears the listener which receives video frame metadata events if it matches the one passed. Else does nothing.
    Specified by:
    +
    clearVideoFrameMetadataListener in interface ExoPlayer
    +
    Specified by:
    clearVideoFrameMetadataListener in interface ExoPlayer.VideoComponent
    Parameters:
    listener - The listener to clear.
    @@ -2283,10 +2397,13 @@ public void removeVideoListener​(

    setCameraMotionListener

    public void setCameraMotionListener​(CameraMotionListener listener)
    -
    Description copied from interface: ExoPlayer.VideoComponent
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    Sets a listener of camera motion events.
    Specified by:
    +
    setCameraMotionListener in interface ExoPlayer
    +
    Specified by:
    setCameraMotionListener in interface ExoPlayer.VideoComponent
    Parameters:
    listener - The listener.
    @@ -2300,55 +2417,20 @@ public void removeVideoListener​(

    clearCameraMotionListener

    public void clearCameraMotionListener​(CameraMotionListener listener)
    -
    Description copied from interface: ExoPlayer.VideoComponent
    -
    Clears the listener which receives camera motion events if it matches the one passed. Else - does nothing.
    +
    Deprecated.
    +
    Description copied from interface: ExoPlayer
    +
    Clears the listener which receives camera motion events if it matches the one passed. Else does + nothing.
    Specified by:
    +
    clearCameraMotionListener in interface ExoPlayer
    +
    Specified by:
    clearCameraMotionListener in interface ExoPlayer.VideoComponent
    Parameters:
    listener - The listener to clear.
    - - - - - - - - @@ -2356,6 +2438,7 @@ public void removeTextOutput​(

    getCurrentCues

    public List<Cue> getCurrentCues()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the current Cues. This list may be empty.
    @@ -2366,44 +2449,6 @@ public void removeTextOutput​( - - - - - - - - @@ -2411,6 +2456,7 @@ public void removeMetadataOutput​(

    getPlaybackLooper

    public Looper getPlaybackLooper()
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Returns the Looper associated with the playback thread.
    @@ -2426,6 +2472,7 @@ public void removeMetadataOutput​(

    getApplicationLooper

    public Looper getApplicationLooper()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the Looper associated with the application thread that's used to access the player and on which player events are received.
    @@ -2442,6 +2489,7 @@ public void removeMetadataOutput​(

    getClock

    public Clock getClock()
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Returns the Clock used for playback.
    @@ -2457,12 +2505,11 @@ public void removeMetadataOutput​(

    addListener

    public void addListener​(Player.Listener listener)
    +
    Deprecated.
    Description copied from interface: Player
    Registers a listener to receive all events from the player. -

    The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

    +

    The listener's methods will be called on the thread associated with Player.getApplicationLooper().

    Specified by:
    addListener in interface Player
    @@ -2480,15 +2527,13 @@ public void removeMetadataOutput​(@Deprecated public void addListener​(Player.EventListener listener)
    Deprecated.
    -
    Description copied from interface: Player
    +
    Description copied from interface: ExoPlayer
    Registers a listener to receive events from the player. -

    The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

    +

    The listener's methods will be called on the thread associated with Player.getApplicationLooper().

    Specified by:
    -
    addListener in interface Player
    +
    addListener in interface ExoPlayer
    Parameters:
    listener - The listener to register.
    @@ -2501,6 +2546,7 @@ public void addListener​(

    removeListener

    public void removeListener​(Player.Listener listener)
    +
    Deprecated.
    Description copied from interface: Player
    Unregister a listener registered through Player.addListener(Listener). The listener will no longer receive events.
    @@ -2521,12 +2567,12 @@ public void addListener​(@Deprecated public void removeListener​(Player.EventListener listener)
    Deprecated.
    -
    Description copied from interface: Player
    -
    Unregister a listener registered through Player.addListener(EventListener). The listener will +
    Description copied from interface: ExoPlayer
    +
    Unregister a listener registered through ExoPlayer.addListener(EventListener). The listener will no longer receive events from the player.
    Specified by:
    -
    removeListener in interface Player
    +
    removeListener in interface ExoPlayer
    Parameters:
    listener - The listener to unregister.
    @@ -2539,7 +2585,8 @@ public void removeListener​(

    getPlaybackState

    @State
    -public int getPlaybackState()
    +public @com.google.android.exoplayer2.Player.State int getPlaybackState() +
    Deprecated.
    Description copied from interface: Player
    Returns the current playback state of the player.
    @@ -2548,7 +2595,7 @@ public int getPlaybackState()
    Returns:
    The current playback state.
    See Also:
    -
    Player.Listener.onPlaybackStateChanged(int)
    +
    Player.Listener.onPlaybackStateChanged(int)
    @@ -2559,7 +2606,8 @@ public int getPlaybackState()
  • getPlaybackSuppressionReason

    @PlaybackSuppressionReason
    -public int getPlaybackSuppressionReason()
    +public @com.google.android.exoplayer2.Player.PlaybackSuppressionReason int getPlaybackSuppressionReason() +
    Deprecated.
    Description copied from interface: Player
    Returns the reason why playback is suppressed even though Player.getPlayWhenReady() is true, or Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
    @@ -2569,7 +2617,7 @@ public int getPlaybackSuppressionReason()
    Returns:
    The current playback suppression reason.
    See Also:
    -
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
    +
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
  • @@ -2581,6 +2629,7 @@ public int getPlaybackSuppressionReason()

    getPlayerError

    @Nullable
     public ExoPlaybackException getPlayerError()
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Equivalent to Player.getPlayerError(), except the exception is guaranteed to be an ExoPlaybackException.
    @@ -2620,18 +2669,18 @@ public void retry()
  • getAvailableCommands

    public Player.Commands getAvailableCommands()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the player's currently available Player.Commands.

    The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands change. -

    Executing a command that is not available (for example, calling Player.seekToNextWindow() - if Player.COMMAND_SEEK_TO_NEXT_WINDOW is unavailable) will neither throw an exception nor - generate a Player.getPlayerError() player error}. +

    Executing a command that is not available (for example, calling Player.seekToNextMediaItem() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will + neither throw an exception nor generate a Player.getPlayerError() player error}. -

    Player.COMMAND_SEEK_TO_PREVIOUS_WINDOW and Player.COMMAND_SEEK_TO_NEXT_WINDOW are - unavailable if there is no such MediaItem.

    +

    Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM + are unavailable if there is no such MediaItem.

    Specified by:
    getAvailableCommands in interface Player
    @@ -2649,6 +2698,7 @@ public void retry()
  • prepare

    public void prepare()
    +
    Deprecated.
    Description copied from interface: Player
    Prepares the player.
    @@ -2702,6 +2752,7 @@ public void prepare​(public void setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition) +
    Deprecated.
    Description copied from interface: Player
    Clears the playlist and adds the specified MediaItems.
    @@ -2711,7 +2762,7 @@ public void prepare​(MediaItems.
    resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined - by Player.getCurrentWindowIndex() and Player.getCurrentPosition().
    + by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
  • @@ -2722,8 +2773,9 @@ public void prepare​(

    setMediaItems

    public void setMediaItems​(List<MediaItem> mediaItems,
    -                          int startWindowIndex,
    +                          int startIndex,
                               long startPositionMs)
    +
    Deprecated.
    Description copied from interface: Player
    Clears the playlist and adds the specified MediaItems.
    @@ -2731,11 +2783,11 @@ public void prepare​(setMediaItems in interface Player
    Parameters:
    mediaItems - The new MediaItems.
    -
    startWindowIndex - The window index to start playback from. If C.INDEX_UNSET is - passed, the current position is not reset.
    -
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given window is used. In any case, if - startWindowIndex is set to C.INDEX_UNSET, this parameter is ignored and the - position is not reset at all.
    +
    startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET + is passed, the current position is not reset.
    +
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In + any case, if startIndex is set to C.INDEX_UNSET, this parameter is ignored + and the position is not reset at all.
  • @@ -2746,6 +2798,7 @@ public void prepare​(

    setMediaSources

    public void setMediaSources​(List<MediaSource> mediaSources)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Clears the playlist, adds the specified MediaSources and resets the position to the default position.
    @@ -2765,6 +2818,7 @@ public void prepare​(public void setMediaSources​(List<MediaSource> mediaSources, boolean resetPosition) +
    Deprecated.
    Description copied from interface: ExoPlayer
    Clears the playlist and adds the specified MediaSources.
    @@ -2774,7 +2828,7 @@ public void prepare​(MediaSources.
    resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined - by Player.getCurrentWindowIndex() and Player.getCurrentPosition().
    + by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
    @@ -2785,8 +2839,9 @@ public void prepare​(

    setMediaSources

    public void setMediaSources​(List<MediaSource> mediaSources,
    -                            int startWindowIndex,
    +                            int startMediaItemIndex,
                                 long startPositionMs)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Clears the playlist and adds the specified MediaSources.
    @@ -2794,11 +2849,10 @@ public void prepare​(setMediaSources in interface ExoPlayer
    Parameters:
    mediaSources - The new MediaSources.
    -
    startWindowIndex - The window index to start playback from. If C.INDEX_UNSET is - passed, the current position is not reset.
    -
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given window is used. In any case, if - startWindowIndex is set to C.INDEX_UNSET, this parameter is ignored and the - position is not reset at all.
    +
    startMediaItemIndex - The media item index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
    +
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given media item is used. In any case, + if startMediaItemIndex is set to C.INDEX_UNSET, this parameter is ignored + and the position is not reset at all.
    @@ -2809,6 +2863,7 @@ public void prepare​(

    setMediaSource

    public void setMediaSource​(MediaSource mediaSource)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Clears the playlist, adds the specified MediaSource and resets the position to the default position.
    @@ -2828,6 +2883,7 @@ public void prepare​(public void setMediaSource​(MediaSource mediaSource, boolean resetPosition) +
    Deprecated.
    Description copied from interface: ExoPlayer
    Clears the playlist and adds the specified MediaSource.
    @@ -2836,7 +2892,7 @@ public void prepare​(Parameters:
    mediaSource - The new MediaSource.
    resetPosition - Whether the playback position should be reset to the default position. If - false, playback will start from the position defined by Player.getCurrentWindowIndex() + false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
    @@ -2849,6 +2905,7 @@ public void prepare​(public void setMediaSource​(MediaSource mediaSource, long startPositionMs) +
    Deprecated.
    Description copied from interface: ExoPlayer
    Clears the playlist and adds the specified MediaSource.
    @@ -2868,6 +2925,7 @@ public void prepare​(public void addMediaItems​(int index, List<MediaItem> mediaItems) +
    Deprecated.
    Description copied from interface: Player
    Adds a list of media items at the given index of the playlist.
    @@ -2887,6 +2945,7 @@ public void prepare​(

    addMediaSource

    public void addMediaSource​(MediaSource mediaSource)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Adds a media source to the end of the playlist.
    @@ -2905,6 +2964,7 @@ public void prepare​(public void addMediaSource​(int index, MediaSource mediaSource) +
    Deprecated.
    Description copied from interface: ExoPlayer
    Adds a media source at the given index of the playlist.
    @@ -2923,6 +2983,7 @@ public void prepare​(

    addMediaSources

    public void addMediaSources​(List<MediaSource> mediaSources)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Adds a list of media sources to the end of the playlist.
    @@ -2941,6 +3002,7 @@ public void prepare​(public void addMediaSources​(int index, List<MediaSource> mediaSources) +
    Deprecated.
    Description copied from interface: ExoPlayer
    Adds a list of media sources at the given index of the playlist.
    @@ -2961,6 +3023,7 @@ public void prepare​(public void moveMediaItems​(int fromIndex, int toIndex, int newIndex) +
    Deprecated.
    Moves the media item range to the new index.
    @@ -2983,6 +3046,7 @@ public void prepare​(public void removeMediaItems​(int fromIndex, int toIndex) +
    Deprecated.
    Removes a range of media items from the playlist.
    @@ -3002,6 +3066,7 @@ public void prepare​(

    setShuffleOrder

    public void setShuffleOrder​(ShuffleOrder shuffleOrder)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Sets the shuffle order.
    @@ -3019,6 +3084,7 @@ public void prepare​(

    setPlayWhenReady

    public void setPlayWhenReady​(boolean playWhenReady)
    +
    Deprecated.
    Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY. @@ -3038,6 +3104,7 @@ public void prepare​(

    getPlayWhenReady

    public boolean getPlayWhenReady()
    +
    Deprecated.
    Whether playback will proceed when Player.getPlaybackState() == Player.STATE_READY.
    @@ -3046,7 +3113,7 @@ public void prepare​(Returns:
    Whether playback will proceed when ready.
    See Also:
    -
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    +
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    @@ -3057,10 +3124,11 @@ public void prepare​(

    setPauseAtEndOfMediaItems

    public void setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems)
    +
    Deprecated.
    Sets whether to pause playback at the end of each media item. -

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    +

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    Specified by:
    setPauseAtEndOfMediaItems in interface ExoPlayer
    @@ -3076,6 +3144,7 @@ public void prepare​(

    getPauseAtEndOfMediaItems

    public boolean getPauseAtEndOfMediaItems()
    +
    Deprecated.
    Returns whether the player pauses playback at the end of each media item.
    @@ -3093,7 +3162,8 @@ public void prepare​(

    getRepeatMode

    @RepeatMode
    -public int getRepeatMode()
    +public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatMode() +
    Deprecated.
    Description copied from interface: Player
    Returns the current Player.RepeatMode used for playback.
    @@ -3102,23 +3172,24 @@ public int getRepeatMode()
    Returns:
    The current repeat mode.
    See Also:
    -
    Player.Listener.onRepeatModeChanged(int)
    +
    Player.Listener.onRepeatModeChanged(int)
    - +
    • setRepeatMode

      public void setRepeatMode​(@RepeatMode
      -                          int repeatMode)
      -
      Description copied from interface: Player
      + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode) +
      Deprecated.
      +
      Description copied from interface: Player
      Sets the Player.RepeatMode to be used for playback.
      Specified by:
      -
      setRepeatMode in interface Player
      +
      setRepeatMode in interface Player
      Parameters:
      repeatMode - The repeat mode.
      @@ -3131,8 +3202,9 @@ public int getRepeatMode()
    • setShuffleModeEnabled

      public void setShuffleModeEnabled​(boolean shuffleModeEnabled)
      +
      Deprecated.
      Description copied from interface: Player
      -
      Sets whether shuffling of windows is enabled.
      +
      Sets whether shuffling of media items is enabled.
      Specified by:
      setShuffleModeEnabled in interface Player
      @@ -3148,8 +3220,9 @@ public int getRepeatMode()
    • getShuffleModeEnabled

      public boolean getShuffleModeEnabled()
      +
      Deprecated.
      Description copied from interface: Player
      -
      Returns whether shuffling of windows is enabled.
      +
      Returns whether shuffling of media items is enabled.
      Specified by:
      getShuffleModeEnabled in interface Player
      @@ -3165,6 +3238,7 @@ public int getRepeatMode()
    • isLoading

      public boolean isLoading()
      +
      Deprecated.
      Description copied from interface: Player
      Whether the player is currently loading the source.
      @@ -3183,17 +3257,18 @@ public int getRepeatMode()
      • seekTo

        -
        public void seekTo​(int windowIndex,
        +
        public void seekTo​(int mediaItemIndex,
                            long positionMs)
        +
        Deprecated.
        Description copied from interface: Player
        -
        Seeks to a position specified in milliseconds in the specified window.
        +
        Seeks to a position specified in milliseconds in the specified MediaItem.
        Specified by:
        seekTo in interface Player
        Parameters:
        -
        windowIndex - The index of the window.
        -
        positionMs - The seek position in the specified window, or C.TIME_UNSET to seek to - the window's default position.
        +
        mediaItemIndex - The index of the MediaItem.
        +
        positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
      @@ -3204,6 +3279,7 @@ public int getRepeatMode()
    • getSeekBackIncrement

      public long getSeekBackIncrement()
      +
      Deprecated.
      Description copied from interface: Player
      Returns the Player.seekBack() increment.
      @@ -3223,6 +3299,7 @@ public int getRepeatMode()
    • getSeekForwardIncrement

      public long getSeekForwardIncrement()
      +
      Deprecated.
      Description copied from interface: Player
      Returns the Player.seekForward() increment.
      @@ -3241,17 +3318,17 @@ public int getRepeatMode() @@ -3262,6 +3339,7 @@ public int getRepeatMode()
    • setPlaybackParameters

      public void setPlaybackParameters​(PlaybackParameters playbackParameters)
      +
      Deprecated.
      Description copied from interface: Player
      Attempts to set the playback parameters. Passing PlaybackParameters.DEFAULT resets the player to the default, which means there is no speed or pitch adjustment. @@ -3283,6 +3361,7 @@ public int getRepeatMode()
    • getPlaybackParameters

      public PlaybackParameters getPlaybackParameters()
      +
      Deprecated.
      Description copied from interface: Player
      Returns the currently active playback parameters.
      @@ -3301,6 +3380,7 @@ public int getRepeatMode()

      setSeekParameters

      public void setSeekParameters​(@Nullable
                                     SeekParameters seekParameters)
      +
      Deprecated.
      Description copied from interface: ExoPlayer
      Sets the parameters that control how seek operations are performed.
      @@ -3318,6 +3398,7 @@ public int getRepeatMode()
    • getSeekParameters

      public SeekParameters getSeekParameters()
      +
      Deprecated.
      Description copied from interface: ExoPlayer
      Returns the currently active SeekParameters of the player.
      @@ -3333,6 +3414,7 @@ public int getRepeatMode()
    • setForegroundMode

      public void setForegroundMode​(boolean foregroundMode)
      +
      Deprecated.
      Description copied from interface: ExoPlayer
      Sets whether the player is allowed to keep holding limited resources such as video decoders, even when in the idle state. By doing so, the player may be able to reduce latency when @@ -3366,6 +3448,30 @@ public int getRepeatMode()
    + + + +
      +
    • +

      stop

      +
      public void stop()
      +
      Deprecated.
      +
      Description copied from interface: Player
      +
      Stops playback without resetting the player. Use Player.pause() rather than this method if + the intention is to pause playback. + +

      Calling this method will cause the playback state to transition to Player.STATE_IDLE. The + player instance can still be used, and Player.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.

      +
      +
      Specified by:
      +
      stop in interface Player
      +
      +
    • +
    @@ -3388,6 +3494,7 @@ public void stop​(boolean reset)
  • release

    public void release()
    +
    Deprecated.
    Description copied from interface: Player
    Releases the player. This method must be called when the player is no longer required. The player must not be used after calling this method.
    @@ -3404,12 +3511,13 @@ public void stop​(boolean reset)
  • createMessage

    public PlayerMessage createMessage​(PlayerMessage.Target target)
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Creates a message that can be sent to a PlayerMessage.Target. By default, the message will be delivered immediately without blocking on the playback thread. The default PlayerMessage.getType() is 0 and the default PlayerMessage.getPayload() is null. If a position is specified with PlayerMessage.setPosition(long), the message will be - delivered at this position in the current window defined by Player.getCurrentWindowIndex(). - Alternatively, the message can be sent at a specific window using PlayerMessage.setPosition(int, long).
    + delivered at this position in the current media item defined by Player.getCurrentMediaItemIndex(). Alternatively, the message can be sent at a specific mediaItem + using PlayerMessage.setPosition(int, long).
    Specified by:
    createMessage in interface ExoPlayer
    @@ -3423,6 +3531,7 @@ public void stop​(boolean reset)
  • getRendererCount

    public int getRendererCount()
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Returns the number of renderers.
    @@ -3437,7 +3546,8 @@ public void stop​(boolean reset)
    • getRendererType

      -
      public int getRendererType​(int index)
      +
      public @com.google.android.exoplayer2.C.TrackType int getRendererType​(int index)
      +
      Deprecated.
      Description copied from interface: ExoPlayer
      Returns the track type that the renderer at a given index handles. @@ -3449,7 +3559,7 @@ public void stop​(boolean reset)
      Parameters:
      index - The index of the renderer.
      Returns:
      -
      One of the TRACK_TYPE_* constants defined in C.
      +
      The track type that the renderer handles.
  • @@ -3461,6 +3571,7 @@ public void stop​(boolean reset)

    getTrackSelector

    @Nullable
     public TrackSelector getTrackSelector()
    +
    Deprecated.
    Description copied from interface: ExoPlayer
    Returns the track selector that this player uses, or null if track selection is not supported.
    @@ -3476,13 +3587,14 @@ public 

    getCurrentTrackGroups

    public TrackGroupArray getCurrentTrackGroups()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the available track groups.
    Specified by:
    getCurrentTrackGroups in interface Player
    See Also:
    -
    Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    +
    Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
  • @@ -3493,6 +3605,7 @@ public 

    getCurrentTrackSelections

    public TrackSelectionArray getCurrentTrackSelections()
    +
    Deprecated.
    Description copied from interface: Player
    - + + + + + + + + +
      +
    • +

      setTrackSelectionParameters

      +
      public void setTrackSelectionParameters​(TrackSelectionParameters parameters)
      +
      Deprecated.
      +
      Description copied from interface: Player
      +
      Sets the parameters constraining the track selection. + +

      Unsupported parameters will be silently ignored. + +

      Use Player.getTrackSelectionParameters() to retrieve the current parameters. For example, + the following snippet restricts video to SD whilst keep other track selection parameters + unchanged: + +

      
      + player.setTrackSelectionParameters(
      +   player.getTrackSelectionParameters()
      +         .buildUpon()
      +         .setMaxVideoSizeSd()
      +         .build())
      + 
      +
      +
      Specified by:
      +
      setTrackSelectionParameters in interface Player
    @@ -3529,13 +3693,15 @@ public 

    getMediaMetadata

    public MediaMetadata getMediaMetadata()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the current combined MediaMetadata, or MediaMetadata.EMPTY if not supported.

    This MediaMetadata is a combination of the MediaItem.mediaMetadata and the static and dynamic metadata from the track selections' - formats and MetadataOutput.onMetadata(Metadata).

    + formats and Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, it will be prioritised above the same field coming from static or + dynamic metadata.
    Specified by:
    getMediaMetadata in interface Player
    @@ -3549,6 +3715,7 @@ public 

    getPlaylistMetadata

    public MediaMetadata getPlaylistMetadata()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
    @@ -3564,6 +3731,7 @@ public 

    setPlaylistMetadata

    public void setPlaylistMetadata​(MediaMetadata mediaMetadata)
    +
    Deprecated.
    Description copied from interface: Player
    Sets the playlist MediaMetadata.
    @@ -3579,13 +3747,14 @@ public 

    getCurrentTimeline

    public Timeline getCurrentTimeline()
    +
    Deprecated.
    Description copied from interface: Player
    Returns the current Timeline. Never null, but may be empty.
    Specified by:
    getCurrentTimeline in interface Player
    See Also:
    -
    Player.Listener.onTimelineChanged(Timeline, int)
    +
    Player.Listener.onTimelineChanged(Timeline, int)
    @@ -3596,6 +3765,7 @@ public 

    getCurrentPeriodIndex

    public int getCurrentPeriodIndex()
    +
    Deprecated.
    Returns the index of the period currently being played.
    @@ -3604,18 +3774,20 @@ public  + @@ -3626,8 +3798,10 @@ public 

    getDuration

    public long getDuration()
    +
    Deprecated.
    -
    Returns the duration of the current content window or ad in milliseconds, or C.TIME_UNSET if the duration is not known.
    +
    Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if + the duration is not known.
    Specified by:
    getDuration in interface Player
    @@ -3641,10 +3815,10 @@ public 

    getCurrentPosition

    public long getCurrentPosition()
    +
    Deprecated.
    -
    Returns the playback position in the current content window or ad, in milliseconds, or the - prospective position in milliseconds if the current timeline is - empty.
    +
    Returns the playback position in the current content or ad, in milliseconds, or the prospective + position in milliseconds if the current timeline is empty.
    Specified by:
    getCurrentPosition in interface Player
    @@ -3658,9 +3832,10 @@ public 

    getBufferedPosition

    public long getBufferedPosition()
    +
    Deprecated.
    -
    Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
    +
    Returns an estimate of the position in the current content or ad up to which data is buffered, + in milliseconds.
    Specified by:
    getBufferedPosition in interface Player
    @@ -3674,9 +3849,10 @@ public 

    getTotalBufferedDuration

    public long getTotalBufferedDuration()
    +
    Deprecated.
    Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and windows.
    + This includes pre-buffered data for subsequent ads and media items.
    Specified by:
    getTotalBufferedDuration in interface Player
    @@ -3690,6 +3866,7 @@ public 

    isPlayingAd

    public boolean isPlayingAd()
    +
    Deprecated.
    Returns whether the player is currently playing an ad.
    @@ -3705,6 +3882,7 @@ public 

    getCurrentAdGroupIndex

    public int getCurrentAdGroupIndex()
    +
    Deprecated.
    If Player.isPlayingAd() returns true, returns the index of the ad group in the period currently being played. Returns C.INDEX_UNSET otherwise.
    @@ -3721,6 +3899,7 @@ public 

    getCurrentAdIndexInAdGroup

    public int getCurrentAdIndexInAdGroup()
    +
    Deprecated.
    If Player.isPlayingAd() returns true, returns the index of the ad in its ad group. Returns C.INDEX_UNSET otherwise.
    @@ -3737,6 +3916,7 @@ public 

    getContentPosition

    public long getContentPosition()
    +
    Deprecated.
    If Player.isPlayingAd() returns true, returns the content position that will be played once all ads in the ad group have finished playing, in milliseconds. If there is no ad @@ -3754,10 +3934,11 @@ public 

    getContentBufferedPosition

    public long getContentBufferedPosition()
    +
    Deprecated.
    If Player.isPlayingAd() returns true, returns an estimate of the content position in - the current content window up to which data is buffered, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by Player.getBufferedPosition().
    + the current content up to which data is buffered, in milliseconds. If there is no ad playing, + the returned position is the same as that returned by Player.getBufferedPosition().
    Specified by:
    getContentBufferedPosition in interface Player
    @@ -3772,31 +3953,23 @@ public @Deprecated public void setHandleWakeLock​(boolean handleWakeLock) -
    Deprecated. -
    Use setWakeMode(int) instead.
    -
    -
    Sets whether the player should use a PowerManager.WakeLock to ensure the - device stays awake for playback, even when the screen is off. - -

    Enabling this feature requires the Manifest.permission.WAKE_LOCK permission. - It should be used together with a foreground Service for use cases where - playback can occur when the screen is off (e.g. background audio playback). It is not useful if - the screen will always be on during playback (e.g. foreground video playback).

    +
    Deprecated.
    -
    Parameters:
    -
    handleWakeLock - Whether the player should use a PowerManager.WakeLock - to ensure the device stays awake for playback, even when the screen is off.
    +
    Specified by:
    +
    setHandleWakeLock in interface ExoPlayer
    - + - - - - - - - -
    - + - + @@ -627,7 +654,7 @@ implements Returns the number of windows in the timeline. - +
    @@ -1939,7 +1927,7 @@ public final void onStaticMetadataChanged​( + - - - - @@ -1495,7 +1467,7 @@ static final int EVENT_STATIC_METADATA_CHANGED
  • EVENT_POSITION_DISCONTINUITY

    static final int EVENT_POSITION_DISCONTINUITY
    - +
    See Also:
    Constant Field Values
    @@ -2157,7 +2129,7 @@ static final int EVENT_STATIC_METADATA_CHANGED

    Method Detail

    - + - + @@ -340,7 +340,7 @@ public final int usage
  • allowedCapturePolicy

    @AudioAllowedCapturePolicy
    -public final int allowedCapturePolicy
    +public final @com.google.android.exoplayer2.C.AudioAllowedCapturePolicy int allowedCapturePolicy
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/audio/AudioListener.html b/docs/doc/reference/com/google/android/exoplayer2/audio/AudioListener.html deleted file mode 100644 index 718a4fc3d8..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/audio/AudioListener.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - -AudioListener (ExoPlayer library) - - - - - - - - - - - - - -
    - -
    - -
    -
    - -

    Interface AudioListener

    -
    -
    -
    - -
    -
    - -
    -
    -
      -
    • - -
      -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          onAudioSessionIdChanged

          -
          default void onAudioSessionIdChanged​(int audioSessionId)
          -
          Deprecated.
          -
          Called when the audio session ID changes.
          -
          -
          Parameters:
          -
          audioSessionId - The audio session ID.
          -
          -
        • -
        - - - -
          -
        • -

          onAudioAttributesChanged

          -
          default void onAudioAttributesChanged​(AudioAttributes audioAttributes)
          -
          Deprecated.
          -
          Called when the audio attributes change.
          -
          -
          Parameters:
          -
          audioAttributes - The audio attributes.
          -
          -
        • -
        - - - -
          -
        • -

          onVolumeChanged

          -
          default void onVolumeChanged​(float volume)
          -
          Deprecated.
          -
          Called when the volume changes.
          -
          -
          Parameters:
          -
          volume - The new volume, with 0 being silence and 1 being unity gain.
          -
          -
        • -
        - - - -
          -
        • -

          onSkipSilenceEnabledChanged

          -
          default void onSkipSilenceEnabledChanged​(boolean skipSilenceEnabled)
          -
          Deprecated.
          -
          Called when skipping silences is enabled or disabled in the audio stream.
          -
          -
          Parameters:
          -
          skipSilenceEnabled - Whether skipping silences in the audio stream is enabled.
          -
          -
        • -
        -
      • -
      -
      -
    • -
    -
    -
    -
    - -
    - -
    - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/audio/AudioProcessor.html b/docs/doc/reference/com/google/android/exoplayer2/audio/AudioProcessor.html index eecefafcb3..2297eca1dd 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/audio/AudioProcessor.html +++ b/docs/doc/reference/com/google/android/exoplayer2/audio/AudioProcessor.html @@ -122,7 +122,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Known Implementing Classes:
    -
    BaseAudioProcessor, GvrAudioProcessor, SilenceSkippingAudioProcessor, SonicAudioProcessor, TeeAudioProcessor
    +
    BaseAudioProcessor, SilenceSkippingAudioProcessor, SonicAudioProcessor, TeeAudioProcessor

    public interface AudioProcessor
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/audio/DecoderAudioRenderer.html b/docs/doc/reference/com/google/android/exoplayer2/audio/DecoderAudioRenderer.html index 88fb36940c..1ada9e6afa 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/audio/DecoderAudioRenderer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/audio/DecoderAudioRenderer.html @@ -114,7 +114,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    -

    Class DecoderAudioRenderer<T extends Decoder<DecoderInputBuffer,​? extends SimpleOutputBuffer,​? extends DecoderException>>

    +

    Class DecoderAudioRenderer<T extends Decoder<DecoderInputBuffer,​? extends SimpleDecoderOutputBuffer,​? extends DecoderException>>


  • -
    public abstract class DecoderAudioRenderer<T extends Decoder<DecoderInputBuffer,​? extends SimpleOutputBuffer,​? extends DecoderException>>
    +
    public abstract class DecoderAudioRenderer<T extends Decoder<DecoderInputBuffer,​? extends SimpleDecoderOutputBuffer,​? extends DecoderException>>
     extends BaseRenderer
     implements MediaClock
    Decodes and renders audio using a Decoder. @@ -154,8 +154,8 @@ implements Renderer.MSG_SET_VOLUME to set the volume. The message payload should be a Float with 0 being silence and 1 being unity gain.
  • Message with type Renderer.MSG_SET_AUDIO_ATTRIBUTES to set the audio attributes. The - message payload should be an AudioAttributes - instance that will configure the underlying audio track. + message payload should be an AudioAttributes instance that will configure the + underlying audio track.
  • Message with type Renderer.MSG_SET_AUX_EFFECT_INFO to set the auxiliary effect. The message payload should be an AuxEffectInfo instance that will configure the underlying audio track. @@ -183,7 +183,7 @@ implements Renderer -Renderer.State, Renderer.VideoScalingMode, Renderer.WakeupListener
  • +Renderer.MessageType, Renderer.State, Renderer.WakeupListener
  • - +

    -
    public final class ExoDatabaseProvider
    -extends SQLiteOpenHelper
    -implements DatabaseProvider
    -
    An SQLiteOpenHelper that provides instances of a standalone ExoPlayer database. - -

    Suitable for use by applications that do not already have their own database, or that would - prefer to keep ExoPlayer tables isolated in their own database. Other applications should prefer - to use DefaultDatabaseProvider with their own SQLiteOpenHelper.

    +
    @Deprecated
    +public final class ExoDatabaseProvider
    +extends StandaloneDatabaseProvider
    +
    Deprecated. + +
    @@ -159,21 +156,13 @@ implements -Fields  - -Modifier and Type -Field -Description - - -static String -DATABASE_NAME - -
    The file name used for the standalone ExoPlayer database.
    - - - +
    • @@ -200,8 +189,8 @@ implements ExoDatabaseProvider​(Context context) -
      Provides instances of the database located by passing DATABASE_NAME to Context.getDatabasePath(String).
      - +
      Deprecated.
    • @@ -214,33 +203,13 @@ implements -All Methods Instance Methods Concrete Methods  - -Modifier and Type -Method -Description - - -void -onCreate​(SQLiteDatabase db) -  - - -void -onDowngrade​(SQLiteDatabase db, - int oldVersion, - int newVersion) -  - - -void -onUpgrade​(SQLiteDatabase db, - int oldVersion, - int newVersion) -  - - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/database/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/database/package-summary.html index b97fa9d159..a21d902eeb 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/database/package-summary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/database/package-summary.html @@ -106,7 +106,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); DatabaseProvider -
      Provides SQLiteDatabase instances to ExoPlayer components, which may read and write +
      Provides SQLiteDatabase instances to media library components, which may read and write tables prefixed with DatabaseProvider.TABLE_PREFIX.
      @@ -129,14 +129,20 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); ExoDatabaseProvider - -
      An SQLiteOpenHelper that provides instances of a standalone ExoPlayer database.
      +Deprecated. + +StandaloneDatabaseProvider + +
      An SQLiteOpenHelper that provides instances of a standalone database.
      + + + VersionTable -
      Utility methods for accessing versions of ExoPlayer database components.
      +
      Utility methods for accessing versions of media library database components.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/database/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/database/package-tree.html index f1e892e04c..4e0149de18 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/database/package-tree.html +++ b/docs/doc/reference/com/google/android/exoplayer2/database/package-tree.html @@ -106,7 +106,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • com.google.android.exoplayer2.database.DefaultDatabaseProvider (implements com.google.android.exoplayer2.database.DatabaseProvider)
    • android.database.sqlite.SQLiteOpenHelper (implements java.lang.AutoCloseable)
    • java.lang.Throwable (implements java.io.Serializable) diff --git a/docs/doc/reference/com/google/android/exoplayer2/decoder/Buffer.html b/docs/doc/reference/com/google/android/exoplayer2/decoder/Buffer.html index 0c467e7bf2..9c9d6fa5c1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/decoder/Buffer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/decoder/Buffer.html @@ -130,7 +130,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • Direct Known Subclasses:
      -
      DecoderInputBuffer, OutputBuffer
      +
      DecoderInputBuffer, DecoderOutputBuffer

      public abstract class Buffer
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/drm/ExoMediaCrypto.html b/docs/doc/reference/com/google/android/exoplayer2/decoder/CryptoConfig.html
      similarity index 88%
      rename from docs/doc/reference/com/google/android/exoplayer2/drm/ExoMediaCrypto.html
      rename to docs/doc/reference/com/google/android/exoplayer2/decoder/CryptoConfig.html
      index 9e822c4669..618770cff3 100644
      --- a/docs/doc/reference/com/google/android/exoplayer2/drm/ExoMediaCrypto.html
      +++ b/docs/doc/reference/com/google/android/exoplayer2/decoder/CryptoConfig.html
      @@ -2,7 +2,7 @@
       
       
       
      -ExoMediaCrypto (ExoPlayer library)
      +CryptoConfig (ExoPlayer library)
       
       
       
      @@ -19,7 +19,7 @@
       
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - -
      - -
      -
      - -

      Class GvrAudioProcessor

      -
      -
      -
        -
      • java.lang.Object
      • -
      • -
          -
        • com.google.android.exoplayer2.ext.gvr.GvrAudioProcessor
        • -
        -
      • -
      -
      -
        -
      • -
        -
        All Implemented Interfaces:
        -
        AudioProcessor
        -
        -
        -
        @Deprecated
        -public class GvrAudioProcessor
        -extends Object
        -implements AudioProcessor
        -
        Deprecated. -
        If you still need this component, please contact us by filing an issue on our issue tracker.
        -
        -
        An AudioProcessor that uses GvrAudioSurround to provide binaural rendering of - surround sound and ambisonic soundfields.
        -
      • -
      -
      -
      - -
      -
      -
        -
      • - -
        -
          -
        • - - -

          Constructor Detail

          - - - -
            -
          • -

            GvrAudioProcessor

            -
            public GvrAudioProcessor()
            -
            Deprecated.
            -
            Creates a new GVR audio processor.
            -
          • -
          -
        • -
        -
        - -
        -
          -
        • - - -

          Method Detail

          - - - -
            -
          • -

            updateOrientation

            -
            public void updateOrientation​(float w,
            -                              float x,
            -                              float y,
            -                              float z)
            -
            Deprecated.
            -
            Updates the listener head orientation. May be called on any thread. See - GvrAudioSurround.updateNativeOrientation.
            -
            -
            Parameters:
            -
            w - The w component of the quaternion.
            -
            x - The x component of the quaternion.
            -
            y - The y component of the quaternion.
            -
            z - The z component of the quaternion.
            -
            -
          • -
          - - - - - - - -
            -
          • -

            isActive

            -
            public boolean isActive()
            -
            Deprecated.
            -
            Description copied from interface: AudioProcessor
            -
            Returns whether the processor is configured and will process input buffers.
            -
            -
            Specified by:
            -
            isActive in interface AudioProcessor
            -
            -
          • -
          - - - -
            -
          • -

            queueInput

            -
            public void queueInput​(ByteBuffer inputBuffer)
            -
            Deprecated.
            -
            Description copied from interface: AudioProcessor
            -
            Queues audio data between the position and limit of the input buffer for processing. - buffer must be a direct byte buffer with native byte order. Its contents are treated as - read-only. Its position will be advanced by the number of bytes consumed (which may be zero). - The caller retains ownership of the provided buffer. Calling this method invalidates any - previous buffer returned by AudioProcessor.getOutput().
            -
            -
            Specified by:
            -
            queueInput in interface AudioProcessor
            -
            Parameters:
            -
            inputBuffer - The input buffer to process.
            -
            -
          • -
          - - - - - - - -
            -
          • -

            getOutput

            -
            public ByteBuffer getOutput()
            -
            Deprecated.
            -
            Description copied from interface: AudioProcessor
            -
            Returns a buffer containing processed output data between its position and limit. The buffer - will always be a direct byte buffer with native byte order. Calling this method invalidates any - previously returned buffer. The buffer will be empty if no output is available.
            -
            -
            Specified by:
            -
            getOutput in interface AudioProcessor
            -
            Returns:
            -
            A buffer containing processed output data between its position and limit.
            -
            -
          • -
          - - - - - - - -
            -
          • -

            flush

            -
            public void flush()
            -
            Deprecated.
            -
            Description copied from interface: AudioProcessor
            -
            Clears any buffered data and pending output. If the audio processor is active, also prepares - the audio processor to receive a new stream of input in the last configured (pending) format.
            -
            -
            Specified by:
            -
            flush in interface AudioProcessor
            -
            -
          • -
          - - - -
            -
          • -

            reset

            -
            public void reset()
            -
            Deprecated.
            -
            Description copied from interface: AudioProcessor
            -
            Resets the processor to its unconfigured state, releasing any resources.
            -
            -
            Specified by:
            -
            reset in interface AudioProcessor
            -
            -
          • -
          -
        • -
        -
        -
      • -
      -
      -
      -
      - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/gvr/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/ext/gvr/package-tree.html deleted file mode 100644 index 3f23cccf8c..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/gvr/package-tree.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.gvr Class Hierarchy (ExoPlayer library) - - - - - - - - - - - - - -
      - -
      -
      -
      -

      Hierarchy For Package com.google.android.exoplayer2.ext.gvr

      -Package Hierarchies: - -
      -
      -
      -

      Class Hierarchy

      - -
      -
      -
      - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.Builder.html index 4991086e65..6350170170 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.Builder.html @@ -570,7 +570,8 @@ extends
    • setVastLoadTimeoutMs

      -
      public ImaAdsLoader.Builder setVastLoadTimeoutMs​(int vastLoadTimeoutMs)
      +
      public ImaAdsLoader.Builder setVastLoadTimeoutMs​(@IntRange(from=1L)
      +                                                 int vastLoadTimeoutMs)
      Sets the VAST load timeout, in milliseconds.
      Parameters:
      @@ -588,7 +589,8 @@ extends
    • setMediaLoadTimeoutMs

      -
      public ImaAdsLoader.Builder setMediaLoadTimeoutMs​(int mediaLoadTimeoutMs)
      +
      public ImaAdsLoader.Builder setMediaLoadTimeoutMs​(@IntRange(from=1L)
      +                                                  int mediaLoadTimeoutMs)
      Sets the ad media load timeout, in milliseconds.
      Parameters:
      @@ -606,7 +608,8 @@ extends
    • setMaxMediaBitrate

      -
      public ImaAdsLoader.Builder setMaxMediaBitrate​(int bitrate)
      +
      public ImaAdsLoader.Builder setMaxMediaBitrate​(@IntRange(from=1L)
      +                                               int bitrate)
      Sets the media maximum recommended bitrate for ads, in bps.
      Parameters:
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html index 3e436a4b9e..96e20ce38e 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html @@ -130,7 +130,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • All Implemented Interfaces:
      -
      AudioListener, DeviceListener, MetadataOutput, Player.EventListener, Player.Listener, AdsLoader, TextOutput, VideoListener
      +
      Player.EventListener, Player.Listener, AdsLoader

      public final class ImaAdsLoader
      @@ -248,16 +248,16 @@ implements 
       void
      -onPositionDiscontinuity​(Player.PositionInfo oldPosition,
      +onPositionDiscontinuity​(Player.PositionInfo oldPosition,
                              Player.PositionInfo newPosition,
      -                       int reason)
      +                       @com.google.android.exoplayer2.Player.DiscontinuityReason int reason)
       
       
      Called when a position discontinuity occurs.
      void -onRepeatModeChanged​(int repeatMode) +onRepeatModeChanged​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
      Called when the value of Player.getRepeatMode() changes.
      @@ -271,8 +271,8 @@ implements void -onTimelineChanged​(Timeline timeline, - int reason) +onTimelineChanged​(Timeline timeline, + @com.google.android.exoplayer2.Player.TimelineChangeReason int reason)
      Called when the timeline has been refreshed.
      @@ -346,21 +346,14 @@ implements Player.EventListener -onLoadingChanged, onMaxSeekToPreviousPositionChanged, onPlayerStateChanged, onPositionDiscontinuity, onSeekProcessed, onStaticMetadataChanged
    • +onLoadingChanged, onMaxSeekToPreviousPositionChanged, onPlayerStateChanged, onPositionDiscontinuity, onSeekProcessed, onTracksChanged, onTrackSelectionParametersChanged
    - @@ -611,7 +604,7 @@ public com.google.ads.interactivemedia.v3.api.AdDisplayContainer getAd
    - + - + - +
    @@ -341,22 +332,6 @@ implements - - -
      -
    • -

      setControlDispatcher

      -
      @Deprecated
      -public void setControlDispatcher​(@Nullable
      -                                 ControlDispatcher controlDispatcher)
      -
      Deprecated. -
      Use a ForwardingPlayer and pass it to the constructor instead. You can also - customize some operations when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      -
      -
    • -
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html b/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html index fa266e8b50..a5cdbfe305 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":42,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -226,7 +226,7 @@ extends androidx.media2.common.SessionPlayer

    Method Summary

    - + @@ -359,54 +359,45 @@ extends androidx.media2.common.SessionPlayer - - - - - - + - + - + - + - + - + - + - + @@ -483,21 +474,6 @@ extends androidx.media2.common.SessionPlayer

    Method Detail

    - - - -
      -
    • -

      setControlDispatcher

      -
      @Deprecated
      -public void setControlDispatcher​(ControlDispatcher controlDispatcher)
      -
      Deprecated. -
      Use a ForwardingPlayer and pass it to the constructor instead. You can also - customize some operations when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      -
      -
    • -
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CaptionCallback.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CaptionCallback.html index 0ff56a6951..51355279c2 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CaptionCallback.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CaptionCallback.html @@ -173,7 +173,7 @@ extends MediaSessionConnector.CommandReceiver -onCommand +onCommand diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CommandReceiver.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CommandReceiver.html index b23a996932..1a33f40bc2 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CommandReceiver.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CommandReceiver.html @@ -157,8 +157,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); - @@ -183,15 +182,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    Method Detail

    - +
    • onCommand

      boolean onCommand​(Player player,
      -                  @Deprecated
      -                  ControlDispatcher controlDispatcher,
                         String command,
                         @Nullable
                         Bundle extras,
      @@ -202,10 +199,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      Parameters:
      player - The player connected to the media session.
      -
      controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      command - The command name.
      extras - Optional parameters for the command, may be null.
      cb - A result receiver to which a result may be sent by the command, may be null.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CustomActionProvider.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CustomActionProvider.html index 548598963a..73b017ab5f 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CustomActionProvider.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.CustomActionProvider.html @@ -163,8 +163,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    - - + @@ -195,32 +195,29 @@ extends - - + + - + - + - - + @@ -291,7 +291,7 @@ extends - - - + - + - - + - - - - - - - + + + + + @@ -226,21 +225,22 @@ extends

    Method Detail

    - +
    • setLibraries

      -
      public static void setLibraries​(Class<? extends ExoMediaCrypto> exoMediaCryptoType,
      +
      public static void setLibraries​(@com.google.android.exoplayer2.C.CryptoType int cryptoType,
                                       String... libraries)
      Override the names of the Vpx native libraries. If an application wishes to call this method, it must do so before calling any other method defined by this class, and before instantiating a LibvpxVideoRenderer instance.
      Parameters:
      -
      exoMediaCryptoType - The ExoMediaCrypto type required for decoding protected - content.
      +
      cryptoType - The C.CryptoType for which the decoder library supports decrypting + protected content, or C.CRYPTO_TYPE_UNSUPPORTED if the library does not support + decryption.
      libraries - The names of the Vpx native libraries.
    • @@ -288,15 +288,14 @@ public static Returns true if the underlying libvpx library supports high bit depth.
    - +
    • -

      matchesExpectedExoMediaCryptoType

      -
      public static boolean matchesExpectedExoMediaCryptoType​(Class<? extends ExoMediaCrypto> exoMediaCryptoType)
      -
      Returns whether the given ExoMediaCrypto type matches the one required for decoding - protected content.
      +

      supportsCryptoType

      +
      public static boolean supportsCryptoType​(@com.google.android.exoplayer2.C.CryptoType int cryptoType)
      +
      Returns whether the library supports the given C.CryptoType.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.SchedulerWorker.html b/docs/doc/reference/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.SchedulerWorker.html index 19fe7b10c4..a8562a193b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.SchedulerWorker.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.SchedulerWorker.html @@ -223,7 +223,7 @@ extends androidx.work.Worker

    Methods inherited from class androidx.work.ListenableWorker

    -getApplicationContext, getBackgroundExecutor, getId, getInputData, getNetwork, getRunAttemptCount, getTags, getTaskExecutor, getTriggeredContentAuthorities, getTriggeredContentUris, getWorkerFactory, isRunInForeground, isStopped, isUsed, onStopped, setForegroundAsync, setProgressAsync, setUsed, stop +getApplicationContext, getBackgroundExecutor, getForegroundInfoAsync, getId, getInputData, getNetwork, getRunAttemptCount, getTags, getTaskExecutor, getTriggeredContentAuthorities, getTriggeredContentUris, getWorkerFactory, isRunInForeground, isStopped, isUsed, onStopped, setForegroundAsync, setProgressAsync, setRunInForeground, setUsed, stop + + +
    All Methods Instance Methods Concrete Methods Deprecated Methods All Methods Instance Methods Concrete Methods 
    Modifier and Type Method  
    voidsetControlDispatcher​(ControlDispatcher controlDispatcher) -
    Deprecated. -
    Use a ForwardingPlayer and pass it to the constructor instead.
    -
    -
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> setMediaItem​(androidx.media2.common.MediaItem item)
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> setPlaybackSpeed​(float playbackSpeed)  
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> setPlaylist​(List<androidx.media2.common.MediaItem> playlist, androidx.media2.common.MediaMetadata metadata)
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> setRepeatMode​(int repeatMode)  
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> setShuffleMode​(int shuffleMode)  
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> skipToNextPlaylistItem()  
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> skipToPlaylistItem​(int index)  
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> skipToPreviousPlaylistItem()  
    ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult> updatePlaylistMetadata​(androidx.media2.common.MediaMetadata metadata)  
    booleanonCommand​(Player player, - ControlDispatcher controlDispatcher, +onCommand​(Player player, String command, Bundle extras, ResultReceiver cb)
    voidonCustomAction​(Player player, - ControlDispatcher controlDispatcher, +onCustomAction​(Player player, String action, Bundle extras) @@ -188,15 +187,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    Method Detail

    - +
    • onCustomAction

      void onCustomAction​(Player player,
      -                    @Deprecated
      -                    ControlDispatcher controlDispatcher,
                           String action,
                           @Nullable
                           Bundle extras)
      @@ -204,10 +201,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Parameters:
      player - The player connected to the media session.
      -
      controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      action - The name of the action which was sent by a media controller.
      extras - Optional extras sent by a media controller, may be null.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.MediaButtonEventHandler.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.MediaButtonEventHandler.html index 55d05bbd05..83fb0e44cd 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.MediaButtonEventHandler.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.MediaButtonEventHandler.html @@ -149,8 +149,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    booleanonMediaButtonEvent​(Player player, - ControlDispatcher controlDispatcher, +onMediaButtonEvent​(Player player, Intent mediaButtonEvent)
    See MediaSessionCompat.Callback.onMediaButtonEvent(Intent).
    @@ -173,24 +172,18 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    Method Detail

    - +
    • onMediaButtonEvent

      boolean onMediaButtonEvent​(Player player,
      -                           @Deprecated
      -                           ControlDispatcher controlDispatcher,
                                  Intent mediaButtonEvent)
      See MediaSessionCompat.Callback.onMediaButtonEvent(Intent).
      Parameters:
      player - The Player.
      -
      controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      mediaButtonEvent - The Intent.
      Returns:
      True if the event was handled, false otherwise.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.PlaybackPreparer.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.PlaybackPreparer.html index d05eda7543..7c5a3db83c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.PlaybackPreparer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.PlaybackPreparer.html @@ -222,7 +222,7 @@ extends MediaSessionConnector.CommandReceiver -onCommand
    • +onCommand
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueEditor.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueEditor.html index 3f1f0f90c5..b0f80a763b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueEditor.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueEditor.html @@ -189,7 +189,7 @@ extends MediaSessionConnector.CommandReceiver -onCommand +onCommand diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueNavigator.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueNavigator.html index 907cfdb50d..dcb49e58f5 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueNavigator.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.QueueNavigator.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"]}; +var data = {"i0":6,"i1":6,"i2":18,"i3":6,"i4":6,"i5":6,"i6":6}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],16:["t5","Default Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -174,7 +174,7 @@ extends -
    All Methods Instance Methods Abstract Methods All Methods Instance Methods Abstract Methods Default Methods 
    Modifier and Type MethodvoidonCurrentWindowIndexChanged​(Player player)default voidonCurrentMediaItemIndexChanged​(Player player) -
    Called when the current window index changed.
    +
    Called when the current media item index changed.
    voidonSkipToNext​(Player player, - ControlDispatcher controlDispatcher)onSkipToNext​(Player player)
    See MediaSessionCompat.Callback.onSkipToNext().
    voidonSkipToPrevious​(Player player, - ControlDispatcher controlDispatcher)onSkipToPrevious​(Player player)
    See MediaSessionCompat.Callback.onSkipToPrevious().
    voidonSkipToQueueItem​(Player player, - ControlDispatcher controlDispatcher, +onSkipToQueueItem​(Player player, long id)
    See MediaSessionCompat.Callback.onSkipToQueueItem(long).
    @@ -239,7 +236,7 @@ extends MediaSessionConnector.CommandReceiver -onCommand +onCommand @@ -311,14 +308,14 @@ extends +
    • -

      onCurrentWindowIndexChanged

      -
      void onCurrentWindowIndexChanged​(Player player)
      -
      Called when the current window index changed.
      +

      onCurrentMediaItemIndexChanged

      +
      default void onCurrentMediaItemIndexChanged​(Player player)
      +
      Called when the current media item index changed.
      Parameters:
      player - The player connected to the media session.
      @@ -345,64 +342,46 @@ extends +
      • onSkipToPrevious

        -
        void onSkipToPrevious​(Player player,
        -                      @Deprecated
        -                      ControlDispatcher controlDispatcher)
        +
        void onSkipToPrevious​(Player player)
        See MediaSessionCompat.Callback.onSkipToPrevious().
        Parameters:
        player - The player connected to the media session.
        -
        controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      - +
      • onSkipToQueueItem

        void onSkipToQueueItem​(Player player,
        -                       @Deprecated
        -                       ControlDispatcher controlDispatcher,
                                long id)
        See MediaSessionCompat.Callback.onSkipToQueueItem(long).
        Parameters:
        player - The player connected to the media session.
        -
        controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      - +
      • onSkipToNext

        -
        void onSkipToNext​(Player player,
        -                  @Deprecated
        -                  ControlDispatcher controlDispatcher)
        +
        void onSkipToNext​(Player player)
        See MediaSessionCompat.Callback.onSkipToNext().
        Parameters:
        player - The player connected to the media session.
        -
        controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.RatingCallback.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.RatingCallback.html index c565055f2e..190613ff38 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.RatingCallback.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.RatingCallback.html @@ -175,7 +175,7 @@ extends MediaSessionConnector.CommandReceiver -onCommand
    • +onCommand
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.html index 2fbe4eb63f..295eed1c1b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":42,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -338,7 +338,7 @@ extends

    Method Summary

    - + @@ -381,28 +381,19 @@ extends - - - - - - + - + @@ -410,7 +401,7 @@ extends Sets a custom error on the session. - + - + - + - + - + - + - + - + - + - + - + - + - + - - @@ -480,32 +479,26 @@ implements +
    • onCommand

      public boolean onCommand​(Player player,
      -                         @Deprecated
      -                         ControlDispatcher controlDispatcher,
                                String command,
                                @Nullable
                                Bundle extras,
                                @Nullable
                                ResultReceiver cb)
      -
      Description copied from interface: MediaSessionConnector.CommandReceiver
      +
      Description copied from interface: MediaSessionConnector.CommandReceiver
      See MediaSessionCompat.Callback.onCommand(String, Bundle, ResultReceiver). The receiver may handle the command, but is not required to do so.
      Specified by:
      -
      onCommand in interface MediaSessionConnector.CommandReceiver
      +
      onCommand in interface MediaSessionConnector.CommandReceiver
      Parameters:
      player - The player connected to the media session.
      -
      controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      command - The command name.
      extras - Optional parameters for the command, may be null.
      cb - A result receiver to which a result may be sent by the command, may be null.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.html index 35b6029880..a4c81304b2 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.html @@ -242,8 +242,7 @@ implements
    - @@ -253,31 +252,28 @@ implements - + - + - + - + @@ -192,43 +192,34 @@ implements - - - - - - + - + - + - + - + @@ -293,7 +293,7 @@ extends BaseRenderer -createRendererException, createRendererException, disable, enable, getCapabilities, getConfiguration, getFormatHolder, getIndex, getLastResetPositionUs, getReadingPositionUs, getState, getStream, getStreamFormats, getTrackType, hasReadStreamToEnd, isCurrentStreamFinal, isSourceReady, maybeThrowStreamError, onReset, onStreamChanged, readSource, replaceStream, reset, resetPosition, setCurrentStreamFinal, setIndex, skipSource, start, stop, supportsMixedMimeTypeAdaptation +createRendererException, createRendererException, disable, enable, getCapabilities, getConfiguration, getFormatHolder, getIndex, getLastResetPositionUs, getReadingPositionUs, getState, getStream, getStreamFormats, getTrackType, hasReadStreamToEnd, isCurrentStreamFinal, isSourceReady, maybeThrowStreamError, onReset, onStreamChanged, readSource, replaceStream, reset, resetPosition, setCurrentStreamFinal, setIndex, skipSource, start, stop, supportsMixedMimeTypeAdaptation - + - - - - - - - + - + - + + + + +
    All Methods Instance Methods Concrete Methods Deprecated Methods All Methods Instance Methods Concrete Methods 
    Modifier and Type Method
    voidsetControlDispatcher​(ControlDispatcher controlDispatcher) -
    Deprecated. -
    Use a ForwardingPlayer and pass it to setPlayer(Player) instead.
    -
    -
    void setCustomActionProviders​(MediaSessionConnector.CustomActionProvider... customActionProviders)
    Sets custom action providers.
    void setCustomErrorMessage​(CharSequence message)
    Sets a custom error on the session.
    void setCustomErrorMessage​(CharSequence message, int code)
    void setCustomErrorMessage​(CharSequence message, int code, @@ -419,7 +410,7 @@ extends Sets a custom error on the session.
    void setDispatchUnsupportedActionsEnabled​(boolean dispatchUnsupportedActionsEnabled) @@ -427,35 +418,35 @@ extends
    void setEnabledPlaybackActions​(long enabledPlaybackActions)
    Sets the enabled playback actions.
    void setErrorMessageProvider​(ErrorMessageProvider<? super PlaybackException> errorMessageProvider)
    Sets the optional ErrorMessageProvider.
    void setMediaButtonEventHandler​(MediaSessionConnector.MediaButtonEventHandler mediaButtonEventHandler)
    void setMediaMetadataProvider​(MediaSessionConnector.MediaMetadataProvider mediaMetadataProvider)
    Sets a provider of metadata to be published to the media session.
    void setMetadataDeduplicationEnabled​(boolean metadataDeduplicationEnabled) @@ -463,28 +454,28 @@ extends MediaSessionCompat.setMetadata(MediaMetadataCompat).
    void setPlaybackPreparer​(MediaSessionConnector.PlaybackPreparer playbackPreparer)
    void setPlayer​(Player player)
    Sets the player to be connected to the media session.
    void setQueueEditor​(MediaSessionConnector.QueueEditor queueEditor)
    Sets the MediaSessionConnector.QueueEditor to handle queue edits sent by the media controller.
    void setQueueNavigator​(MediaSessionConnector.QueueNavigator queueNavigator) @@ -492,14 +483,14 @@ extends ACTION_SKIP_TO_PREVIOUS and ACTION_SKIP_TO_QUEUE_ITEM.
    void setRatingCallback​(MediaSessionConnector.RatingCallback ratingCallback)
    Sets the MediaSessionConnector.RatingCallback to handle user ratings.
    void unregisterCustomCommandReceiver​(MediaSessionConnector.CommandReceiver commandReceiver) @@ -648,21 +639,6 @@ extends - - - -
      -
    • -

      setControlDispatcher

      -
      @Deprecated
      -public void setControlDispatcher​(ControlDispatcher controlDispatcher)
      -
      Deprecated. -
      Use a ForwardingPlayer and pass it to setPlayer(Player) instead. - You can also customize some operations when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      -
      -
    • -
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.html index 32ee306748..4f7834235b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.html @@ -223,8 +223,7 @@ implements
    voidonCustomAction​(Player player, - ControlDispatcher controlDispatcher, +onCustomAction​(Player player, String action, Bundle extras) @@ -323,29 +322,23 @@ public static final int DEFAULT_REPEAT_TOGGLE_MODES

    Method Detail

    - +
    • onCustomAction

      public void onCustomAction​(Player player,
      -                           @Deprecated
      -                           ControlDispatcher controlDispatcher,
                                  String action,
                                  @Nullable
                                  Bundle extras)
      -
      Description copied from interface: MediaSessionConnector.CustomActionProvider
      +
      Description copied from interface: MediaSessionConnector.CustomActionProvider
      Called when a custom action provided by this provider is sent to the media session.
      Specified by:
      -
      onCustomAction in interface MediaSessionConnector.CustomActionProvider
      +
      onCustomAction in interface MediaSessionConnector.CustomActionProvider
      Parameters:
      player - The player connected to the media session.
      -
      controlDispatcher - This parameter is deprecated. Use player instead. Operations - can be customized by passing a ForwardingPlayer to MediaSessionConnector.setPlayer(Player), or - when configuring the player (for example by using - SimpleExoPlayer.Builder#setSeekBackIncrementMs(long)).
      action - The name of the action which was sent by a media controller.
      extras - Optional extras sent by a media controller, may be null.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.html b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.html index d7e91f838e..09fa3b99a6 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.html @@ -288,8 +288,7 @@ implements
    booleanonCommand​(Player player, - ControlDispatcher controlDispatcher, +onCommand​(Player player, String command, Bundle extras, ResultReceiver cb) booleanonCommand​(Player player, - ControlDispatcher controlDispatcher, +onCommand​(Player player, String command, Bundle extras, ResultReceiver cb)voidonCurrentWindowIndexChanged​(Player player)onCurrentMediaItemIndexChanged​(Player player) -
    Called when the current window index changed.
    +
    Called when the current media item index changed.
    voidonSkipToNext​(Player player, - ControlDispatcher controlDispatcher)onSkipToNext​(Player player)
    See MediaSessionCompat.Callback.onSkipToNext().
    voidonSkipToPrevious​(Player player, - ControlDispatcher controlDispatcher)onSkipToPrevious​(Player player)
    See MediaSessionCompat.Callback.onSkipToPrevious().
    voidonSkipToQueueItem​(Player player, - ControlDispatcher controlDispatcher, +onSkipToQueueItem​(Player player, long id)
    See MediaSessionCompat.Callback.onSkipToQueueItem(long).
    @@ -442,18 +438,18 @@ implements +
    All Methods Instance Methods Concrete Methods Deprecated Methods All Methods Instance Methods Concrete Methods 
    Modifier and Type MethodHttpDataSource.RequestPropertiesgetDefaultRequestProperties() -
    Deprecated. - -
    -
    OkHttpDataSource.Factory setCacheControl​(okhttp3.CacheControl cacheControl)
    Sets the CacheControl that will be used.
    OkHttpDataSource.Factory setContentTypePredicate​(Predicate<String> contentTypePredicate)
    Sets a content type Predicate.
    OkHttpDataSource.Factory setDefaultRequestProperties​(Map<String,​String> defaultRequestProperties)
    Sets the default request headers for HttpDataSource instances created by the factory.
    OkHttpDataSource.Factory setTransferListener​(TransferListener transferListener)
    Sets the TransferListener that will be used.
    OkHttpDataSource.Factory setUserAgent​(String userAgent) @@ -284,23 +275,6 @@ implements - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.html b/docs/doc/reference/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.html index ce15a16cd4..829907dceb 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.html @@ -238,7 +238,7 @@ extends HttpDataSource.BaseFactory -createDataSource, getDefaultRequestProperties, setDefaultRequestProperties +createDataSource, setDefaultRequestProperties protected OpusDecodercreateDecoder​(Format format, - ExoMediaCrypto mediaCrypto)createDecoder​(Format format, + CryptoConfig cryptoConfig)
    Creates a decoder for the given format.
    OpusDecoder​(int numInputBuffers, +OpusDecoder​(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, List<byte[]> initializationData, - ExoMediaCrypto exoMediaCrypto, + CryptoConfig cryptoConfig, boolean outputFloat)
    Creates an Opus decoder.
    @@ -225,7 +225,7 @@ extends -
    protected SimpleOutputBufferprotected SimpleDecoderOutputBuffer createOutputBuffer()
    Creates a new output buffer.
    @@ -240,8 +240,8 @@ extends
    protected OpusDecoderExceptiondecode​(DecoderInputBuffer inputBuffer, - SimpleOutputBuffer outputBuffer, +decode​(DecoderInputBuffer inputBuffer, + SimpleDecoderOutputBuffer outputBuffer, boolean reset)
    Decodes the inputBuffer and stores any decoded output in outputBuffer.
    @@ -320,7 +320,7 @@ extends + @@ -397,12 +397,12 @@ extends
  • createOutputBuffer

    -
    protected SimpleOutputBuffer createOutputBuffer()
    +
    protected SimpleDecoderOutputBuffer createOutputBuffer()
    Description copied from class: SimpleDecoder
    Creates a new output buffer.
    Specified by:
    -
    createOutputBuffer in class SimpleDecoder<DecoderInputBuffer,​SimpleOutputBuffer,​OpusDecoderException>
    +
    createOutputBuffer in class SimpleDecoder<DecoderInputBuffer,​SimpleDecoderOutputBuffer,​OpusDecoderException>
  • @@ -417,7 +417,7 @@ extends Creates an exception to propagate for an unexpected decode error.
    Specified by:
    -
    createUnexpectedDecodeException in class SimpleDecoder<DecoderInputBuffer,​SimpleOutputBuffer,​OpusDecoderException>
    +
    createUnexpectedDecodeException in class SimpleDecoder<DecoderInputBuffer,​SimpleDecoderOutputBuffer,​OpusDecoderException>
    Parameters:
    error - The unexpected decode error.
    Returns:
    @@ -425,7 +425,7 @@ extends + diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/opus/OpusLibrary.html b/docs/doc/reference/com/google/android/exoplayer2/ext/opus/OpusLibrary.html index df28b57af1..1444d4d081 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/opus/OpusLibrary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/opus/OpusLibrary.html @@ -167,31 +167,30 @@ extends
    static booleanmatchesExpectedExoMediaCryptoType​(Class<? extends ExoMediaCrypto> exoMediaCryptoType) -
    Returns whether the given ExoMediaCrypto type matches the one required for decoding - protected content.
    -
    static String opusGetVersion()  
    static boolean opusIsSecureDecodeSupported()  
    static voidsetLibraries​(Class<? extends ExoMediaCrypto> exoMediaCryptoType, +setLibraries​(@com.google.android.exoplayer2.C.CryptoType int cryptoType, String... libraries)
    Override the names of the Opus native libraries.
    static booleansupportsCryptoType​(@com.google.android.exoplayer2.C.CryptoType int cryptoType) +
    Returns whether the library supports the given C.CryptoType.
    +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.html b/docs/doc/reference/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.html index 0ed5d79010..3acae48ed6 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.html @@ -164,7 +164,7 @@ extends Renderer -Renderer.State, Renderer.VideoScalingMode, Renderer.WakeupListener +Renderer.MessageType, Renderer.State, Renderer.WakeupListener
    protected VpxDecodercreateDecoder​(Format format, - ExoMediaCrypto mediaCrypto)createDecoder​(Format format, + CryptoConfig cryptoConfig)
    Creates a decoder for the given format.
    protected voidrenderOutputBufferToSurface​(VideoDecoderOutputBuffer outputBuffer, +renderOutputBufferToSurface​(VideoDecoderOutputBuffer outputBuffer, Surface surface)
    Renders the specified output buffer to the passed surface.
    @@ -317,14 +317,14 @@ extends DecoderVideoRenderer -dropOutputBuffer, flushDecoder, handleMessage, isEnded, isReady, maybeDropBuffersToKeyframe, onDisabled, onEnabled, onInputFormatChanged, onPositionReset, onProcessedOutputBuffer, onQueueInputBuffer, onStarted, onStopped, onStreamChanged, releaseDecoder, render, renderOutputBuffer, setOutput, shouldDropBuffersToKeyframe, shouldDropOutputBuffer, shouldForceRenderOutputBuffer, skipOutputBuffer, updateDroppedBufferCounters +dropOutputBuffer, flushDecoder, handleMessage, isEnded, isReady, maybeDropBuffersToKeyframe, onDisabled, onEnabled, onInputFormatChanged, onPositionReset, onProcessedOutputBuffer, onQueueInputBuffer, onStarted, onStopped, onStreamChanged, releaseDecoder, render, renderOutputBuffer, setOutput, shouldDropBuffersToKeyframe, shouldDropOutputBuffer, shouldForceRenderOutputBuffer, skipOutputBuffer, updateDroppedBufferCounters - + - + @@ -161,10 +161,10 @@ extends Description
    VpxDecoder​(int numInputBuffers, +VpxDecoder​(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, - ExoMediaCrypto exoMediaCrypto, + CryptoConfig cryptoConfig, int threads)
    Creates a VP9 decoder.
    @@ -189,14 +189,14 @@ extends Description
    protected VideoDecoderInputBufferprotected DecoderInputBuffer createInputBuffer()
    Creates a new input buffer.
    protected VideoDecoderOutputBufferprotected VideoDecoderOutputBuffer createOutputBuffer()
    Creates a new output buffer.
    @@ -211,8 +211,8 @@ extends
    protected VpxDecoderExceptiondecode​(VideoDecoderInputBuffer inputBuffer, - VideoDecoderOutputBuffer outputBuffer, +decode​(DecoderInputBuffer inputBuffer, + VideoDecoderOutputBuffer outputBuffer, boolean reset)
    Decodes the inputBuffer and stores any decoded output in outputBuffer.
    @@ -234,14 +234,14 @@ extends
    protected voidreleaseOutputBuffer​(VideoDecoderOutputBuffer buffer)releaseOutputBuffer​(VideoDecoderOutputBuffer buffer)
    Releases an output buffer back to the decoder.
    voidrenderToSurface​(VideoDecoderOutputBuffer outputBuffer, +renderToSurface​(VideoDecoderOutputBuffer outputBuffer, Surface surface)
    Renders the outputBuffer to the surface.
    @@ -285,7 +285,7 @@ extends + @@ -358,27 +358,27 @@ extends
  • createOutputBuffer

    -
    protected VideoDecoderOutputBuffer createOutputBuffer()
    +
    protected VideoDecoderOutputBuffer createOutputBuffer()
    Description copied from class: SimpleDecoder
    Creates a new output buffer.
    Specified by:
    -
    createOutputBuffer in class SimpleDecoder<VideoDecoderInputBuffer,​VideoDecoderOutputBuffer,​VpxDecoderException>
    +
    createOutputBuffer in class SimpleDecoder<DecoderInputBuffer,​VideoDecoderOutputBuffer,​VpxDecoderException>
  • - +
    static booleanmatchesExpectedExoMediaCryptoType​(Class<? extends ExoMediaCrypto> exoMediaCryptoType) -
    Returns whether the given ExoMediaCrypto type matches the one required for decoding - protected content.
    -
    static voidsetLibraries​(Class<? extends ExoMediaCrypto> exoMediaCryptoType, +setLibraries​(@com.google.android.exoplayer2.C.CryptoType int cryptoType, String... libraries)
    Override the names of the Vpx native libraries.
    static booleansupportsCryptoType​(@com.google.android.exoplayer2.C.CryptoType int cryptoType) +
    Returns whether the library supports the given C.CryptoType.
    +
    static boolean vpxIsSecureDecodeSupported()
    ConstantBitrateSeekMap​(long inputLength, + long firstFrameBytePosition, + int bitrate, + int frameSize, + boolean allowSeeksIfLengthUnknown) +
    Creates an instance.
    @@ -257,14 +267,14 @@ implements - diff --git a/docs/doc/reference/com/google/android/exoplayer2/extractor/amr/AmrExtractor.html b/docs/doc/reference/com/google/android/exoplayer2/extractor/amr/AmrExtractor.html index f288a7c4e8..9788c27ead 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/extractor/amr/AmrExtractor.html +++ b/docs/doc/reference/com/google/android/exoplayer2/extractor/amr/AmrExtractor.html @@ -207,6 +207,14 @@ implements +static int +FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS + +
    Like FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, except that seeking is also enabled in + cases where the content length (and hence the duration of the media) is unknown.
    + + @@ -281,7 +281,7 @@ extends Size of the FLAC stream info block (header included) in bytes.
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -295,7 +295,7 @@ extends Minimum size of a FLAC frame header in bytes.
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -309,7 +309,7 @@ extends Maximum size of a FLAC frame header in bytes.
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -323,7 +323,7 @@ extends Stream info metadata block type.
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -337,7 +337,7 @@ extends Seek table metadata block type.
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -351,7 +351,7 @@ extends Vorbis comment metadata block type.
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -365,7 +365,7 @@ extends Picture metadata block type.
    See Also:
    -
    Constant Field Values
    +
    Constant Field Values
    @@ -389,18 +389,18 @@ extends diff --git a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.Factory.html index f335fe5099..0efbdc1d72 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.Factory.html @@ -122,7 +122,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Known Implementing Classes:
    -
    SynchronousMediaCodecAdapter.Factory
    +
    DefaultMediaCodecAdapterFactory, SynchronousMediaCodecAdapter.Factory
    Enclosing interface:
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.html b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.html index 0487ffd65e..70e564c229 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.html +++ b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6}; +var data = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6,"i7":6,"i8":6,"i9":6,"i10":6,"i11":6,"i12":6,"i13":6,"i14":6,"i15":6,"i16":6,"i17":6}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -218,27 +218,34 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +Surface +getInputSurface() + +
    Returns the input Surface, or null if the input is not a surface.
    + + + ByteBuffer getOutputBuffer​(int index)
    Returns a read-only ByteBuffer for a dequeued output buffer index.
    - + MediaFormat getOutputFormat()
    Gets the MediaFormat that was output from the MediaCodec.
    - + boolean needsReconfiguration()
    Whether the adapter needs to be reconfigured before it is used.
    - + void queueInputBuffer​(int index, int offset, @@ -249,7 +256,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Submit an input buffer for decoding.
    - + void queueSecureInputBuffer​(int index, int offset, @@ -260,14 +267,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Submit an input buffer that is potentially encrypted for decoding.
    - + void release()
    Releases the adapter and the underlying MediaCodec.
    - + void releaseOutputBuffer​(int index, boolean render) @@ -275,7 +282,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Returns the buffer to the MediaCodec.
    - + void releaseOutputBuffer​(int index, long renderTimeStampNs) @@ -284,7 +291,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); it on the output surface. - + void setOnFrameRenderedListener​(MediaCodecAdapter.OnFrameRenderedListener listener, Handler handler) @@ -292,27 +299,34 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    Registers a callback to be invoked when an output frame is rendered on the output surface.
    - + void setOutputSurface​(Surface surface)
    Dynamically sets the output surface of a MediaCodec.
    - + void setParameters​(Bundle params)
    Communicate additional parameter changes to the MediaCodec instance.
    - + void setVideoScalingMode​(int scalingMode)
    Specifies the scaling mode to use, if a surface was specified when the codec was created.
    + +void +signalEndOfInputStream() + +
    Signals the encoder of end-of-stream on input.
    + +
  • @@ -387,6 +401,21 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    + + + + @@ -567,13 +596,29 @@ void setParameters​( -
      +
      • needsReconfiguration

        boolean needsReconfiguration()
        Whether the adapter needs to be reconfigured before it is used.
      + + + +
        +
      • +

        signalEndOfInputStream

        +
        @RequiresApi(18)
        +void signalEndOfInputStream()
        +
        Signals the encoder of end-of-stream on input. The call can only be used when the encoder + receives its input from a surface.
        +
        +
        See Also:
        +
        MediaCodec.signalEndOfInputStream()
        +
        +
      • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.html b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.html index 1d886e6561..6c6aa82f26 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":6,"i14":6,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":6,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":6,"i50":9,"i51":10,"i52":10,"i53":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":6,"i11":6,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":6,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":6,"i47":9,"i48":10,"i49":10,"i50":10}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -178,7 +178,7 @@ extends Renderer -Renderer.State, Renderer.VideoScalingMode, Renderer.WakeupListener +Renderer.MessageType, Renderer.State, Renderer.WakeupListener +
  • com.google.android.exoplayer2.mediacodec.DefaultMediaCodecAdapterFactory (implements com.google.android.exoplayer2.mediacodec.MediaCodecAdapter.Factory)
  • com.google.android.exoplayer2.mediacodec.MediaCodecAdapter.Configuration
  • com.google.android.exoplayer2.mediacodec.MediaCodecInfo
  • com.google.android.exoplayer2.mediacodec.MediaCodecUtil
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html index 1ca5ab2ed7..c4938c36c7 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html @@ -126,7 +126,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    All Known Implementing Classes:
    -
    ApicFrame, AppInfoTable, BinaryFrame, ChapterFrame, ChapterTocFrame, CommentFrame, EventMessage, GeobFrame, HlsTrackMetadataEntry, IcyHeaders, IcyInfo, Id3Frame, InternalFrame, MdtaMetadataEntry, MlltFrame, MotionPhotoMetadata, PictureFrame, PrivateCommand, PrivFrame, SlowMotionData, SmtaMetadataEntry, SpliceCommand, SpliceInsertCommand, SpliceNullCommand, SpliceScheduleCommand, TextInformationFrame, TimeSignalCommand, UrlLinkFrame, VorbisComment
    +
    ApicFrame, AppInfoTable, BinaryFrame, ChapterFrame, ChapterTocFrame, CommentFrame, EventMessage, FakeMetadataEntry, GeobFrame, HlsTrackMetadataEntry, IcyHeaders, IcyInfo, Id3Frame, InternalFrame, MdtaMetadataEntry, MlltFrame, MotionPhotoMetadata, PictureFrame, PrivateCommand, PrivFrame, SlowMotionData, SmtaMetadataEntry, SpliceCommand, SpliceInsertCommand, SpliceNullCommand, SpliceScheduleCommand, TextInformationFrame, TimeSignalCommand, UrlLinkFrame, VorbisComment
    Enclosing class:
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataInputBuffer.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataInputBuffer.html index af7bb84d43..aae1d0ef8d 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataInputBuffer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataInputBuffer.html @@ -186,7 +186,7 @@ extends DecoderInputBuffer -BUFFER_REPLACEMENT_MODE_DIRECT, BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, cryptoInfo, data, supplementalData, timeUs, waitingForKeys +BUFFER_REPLACEMENT_MODE_DIRECT, BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, cryptoInfo, data, format, supplementalData, timeUs, waitingForKeys diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataOutput.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataOutput.html index 91cd1db0ad..6e70217434 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataOutput.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/MetadataOutput.html @@ -120,14 +120,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    - + static void sendAddDownload​(Context context, Class<? extends DownloadService> clazz, @@ -549,7 +532,7 @@ extends Starts the service if not started already and adds a new download. - + static void sendPauseDownloads​(Context context, Class<? extends DownloadService> clazz, @@ -558,7 +541,7 @@ extends Starts the service if not started already and pauses all downloads. - + static void sendRemoveAllDownloads​(Context context, Class<? extends DownloadService> clazz, @@ -567,7 +550,7 @@ extends Starts the service if not started already and removes all downloads. - + static void sendRemoveDownload​(Context context, Class<? extends DownloadService> clazz, @@ -577,7 +560,7 @@ extends Starts the service if not started already and removes a download. - + static void sendResumeDownloads​(Context context, Class<? extends DownloadService> clazz, @@ -586,7 +569,7 @@ extends Starts the service if not started already and resumes all downloads. - + static void sendSetRequirements​(Context context, Class<? extends DownloadService> clazz, @@ -597,7 +580,7 @@ extends + static void sendSetStopReason​(Context context, Class<? extends DownloadService> clazz, @@ -608,7 +591,7 @@ extends Starts the service if not started already and sets the stop reason for one or all downloads. - + static void start​(Context context, Class<? extends DownloadService> clazz) @@ -616,7 +599,7 @@ extends Starts a download service to resume any ongoing downloads. - + static void startForeground​(Context context, Class<? extends DownloadService> clazz) @@ -637,7 +620,7 @@ extends ContextWrapper -bindIsolatedService, bindService, bindService, bindServiceAsUser, checkCallingOrSelfPermission, checkCallingOrSelfUriPermission, checkCallingPermission, checkCallingUriPermission, checkPermission, checkSelfPermission, checkUriPermission, checkUriPermission, clearWallpaper, createAttributionContext, createConfigurationContext, createContextForSplit, createDeviceProtectedStorageContext, createDisplayContext, createPackageContext, createWindowContext, databaseList, deleteDatabase, deleteFile, deleteSharedPreferences, enforceCallingOrSelfPermission, enforceCallingOrSelfUriPermission, enforceCallingPermission, enforceCallingUriPermission, enforcePermission, enforceUriPermission, enforceUriPermission, fileList, getApplicationContext, getApplicationInfo, getAssets, getAttributionTag, getBaseContext, getCacheDir, getClassLoader, getCodeCacheDir, getContentResolver, getDatabasePath, getDataDir, getDir, getDisplay, getExternalCacheDir, getExternalCacheDirs, getExternalFilesDir, getExternalFilesDirs, getExternalMediaDirs, getFilesDir, getFileStreamPath, getMainExecutor, getMainLooper, getNoBackupFilesDir, getObbDir, getObbDirs, getOpPackageName, getPackageCodePath, getPackageManager, getPackageName, getPackageResourcePath, getResources, getSharedPreferences, getSystemService, getSystemServiceName, getTheme, getWallpaper, getWallpaperDesiredMinimumHeight, getWallpaperDesiredMinimumWidth, grantUriPermission, isDeviceProtectedStorage, isRestricted, moveDatabaseFrom, moveSharedPreferencesFrom, openFileInput, openFileOutput, openOrCreateDatabase, openOrCreateDatabase, peekWallpaper, registerReceiver, registerReceiver, registerReceiver, registerReceiver, removeStickyBroadcast, removeStickyBroadcastAsUser, revokeUriPermission, revokeUriPermission, sendBroadcast, sendBroadcast, sendBroadcastAsUser, sendBroadcastAsUser, sendOrderedBroadcast, sendOrderedBroadcast, sendOrderedBroadcast, sendOrderedBroadcast, sendOrderedBroadcastAsUser, sendStickyBroadcast, sendStickyBroadcastAsUser, sendStickyOrderedBroadcast, sendStickyOrderedBroadcastAsUser, setTheme, setWallpaper, setWallpaper, startActivities, startActivities, startActivity, startActivity, startForegroundService, startInstrumentation, startIntentSender, startIntentSender, startService, stopService, unbindService, unregisterReceiver, updateServiceGroup +bindIsolatedService, bindService, bindService, bindServiceAsUser, checkCallingOrSelfPermission, checkCallingOrSelfUriPermission, checkCallingOrSelfUriPermissions, checkCallingPermission, checkCallingUriPermission, checkCallingUriPermissions, checkPermission, checkSelfPermission, checkUriPermission, checkUriPermission, checkUriPermissions, clearWallpaper, createAttributionContext, createConfigurationContext, createContext, createContextForSplit, createDeviceProtectedStorageContext, createDisplayContext, createPackageContext, createWindowContext, createWindowContext, databaseList, deleteDatabase, deleteFile, deleteSharedPreferences, enforceCallingOrSelfPermission, enforceCallingOrSelfUriPermission, enforceCallingPermission, enforceCallingUriPermission, enforcePermission, enforceUriPermission, enforceUriPermission, fileList, getApplicationContext, getApplicationInfo, getAssets, getAttributionSource, getAttributionTag, getBaseContext, getCacheDir, getClassLoader, getCodeCacheDir, getContentResolver, getDatabasePath, getDataDir, getDir, getDisplay, getExternalCacheDir, getExternalCacheDirs, getExternalFilesDir, getExternalFilesDirs, getExternalMediaDirs, getFilesDir, getFileStreamPath, getMainExecutor, getMainLooper, getNoBackupFilesDir, getObbDir, getObbDirs, getOpPackageName, getPackageCodePath, getPackageManager, getPackageName, getPackageResourcePath, getParams, getResources, getSharedPreferences, getSystemService, getSystemServiceName, getTheme, getWallpaper, getWallpaperDesiredMinimumHeight, getWallpaperDesiredMinimumWidth, grantUriPermission, isDeviceProtectedStorage, isRestricted, isUiContext, moveDatabaseFrom, moveSharedPreferencesFrom, openFileInput, openFileOutput, openOrCreateDatabase, openOrCreateDatabase, peekWallpaper, registerReceiver, registerReceiver, registerReceiver, registerReceiver, removeStickyBroadcast, removeStickyBroadcastAsUser, revokeUriPermission, revokeUriPermission, sendBroadcast, sendBroadcast, sendBroadcastAsUser, sendBroadcastAsUser, sendOrderedBroadcast, sendOrderedBroadcast, sendOrderedBroadcast, sendOrderedBroadcast, sendOrderedBroadcastAsUser, sendStickyBroadcast, sendStickyBroadcast, sendStickyBroadcastAsUser, sendStickyOrderedBroadcast, sendStickyOrderedBroadcastAsUser, setTheme, setWallpaper, setWallpaper, startActivities, startActivities, startActivity, startActivity, startForegroundService, startInstrumentation, startIntentSender, startIntentSender, startService, stopService, unbindService, unregisterReceiver, updateServiceGroup
    • @@ -941,8 +924,7 @@ extends Creates a DownloadService.

      If foregroundNotificationId is FOREGROUND_NOTIFICATION_ID_NONE then the - service will only ever run in the background. No foreground notification will be displayed and - getScheduler() will not be called. + service will only ever run in the background, and no foreground notification will be displayed.

      If foregroundNotificationId is not FOREGROUND_NOTIFICATION_ID_NONE then the service will run in the foreground. The foreground notification will be updated at least as @@ -1502,23 +1484,43 @@ public final @Nullable protected abstract Scheduler getScheduler() -

      Returns a Scheduler to restart the service when requirements allowing downloads to take - place are met. If null, the service will only be restarted if the process is still in - memory when the requirements are met. +
      Returns a Scheduler to restart the service when requirements for downloads to continue + are met. -

      This method is not called for services whose foregroundNotificationId is set to - FOREGROUND_NOTIFICATION_ID_NONE. Such services will only be restarted if the process - is still in memory and considered non-idle, meaning that it's either in the foreground or was - backgrounded within the last few minutes.

      +

      This method is not called on all devices or for all service configurations. When it is + called, it's called only once in the life cycle of the process. If a service has unfinished + downloads that cannot make progress due to unmet requirements, it will behave according to the + first matching case below: + +

        +
      • If the service has foregroundNotificationId set to FOREGROUND_NOTIFICATION_ID_NONE, then this method will not be called. The service will + remain in the background until the downloads are able to continue to completion or the + service is killed by the platform. +
      • If the device API level is less than 31, a Scheduler is returned from this + method, and the returned Scheduler supports all of the requirements that have been specified for downloads to continue, + then the service will stop itself and the Scheduler will be used to restart it in + the foreground when the requirements are met. +
      • If the device API level is less than 31 and either null or a Scheduler + that does not support all of the requirements + is returned from this method, then the service will remain in the foreground until the + downloads are able to continue to completion. +
      • If the device API level is 31 or above, then this method will not be called and the + service will remain in the foreground until the downloads are able to continue to + completion. A Scheduler cannot be used for this case due to Android 12 + foreground service launch restrictions. +
      • +
    - +
    @@ -1535,40 +1538,11 @@ protected abstract  - @@ -199,32 +218,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    Interface Hierarchy

    @@ -263,6 +255,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.C.AudioContentType (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.AudioFlags (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.AudioFocusGain (implements java.lang.annotation.Annotation)
  • +
  • com.google.android.exoplayer2.C.AudioManagerOffloadMode (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.AudioUsage (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.BufferFlags (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.ColorRange (implements java.lang.annotation.Annotation)
  • @@ -270,6 +263,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.C.ColorTransfer (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.ContentType (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.CryptoMode (implements java.lang.annotation.Annotation)
  • +
  • com.google.android.exoplayer2.C.CryptoType (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.DataType (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.Encoding (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.FormatSupport (implements java.lang.annotation.Annotation)
  • @@ -278,12 +272,16 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.C.Projection (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.RoleFlags (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.SelectionFlags (implements java.lang.annotation.Annotation)
  • +
  • com.google.android.exoplayer2.C.SelectionReason (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.StereoMode (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.StreamType (implements java.lang.annotation.Annotation)
  • +
  • com.google.android.exoplayer2.C.TrackType (implements java.lang.annotation.Annotation)
  • +
  • com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.VideoOutputMode (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.VideoScalingMode (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.C.WakeMode (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.DefaultRenderersFactory.ExtensionRendererMode (implements java.lang.annotation.Annotation)
  • +
  • com.google.android.exoplayer2.DeviceInfo.PlaybackType (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.ExoPlaybackException.Type (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.ExoTimeoutException.TimeoutOperation (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.MediaMetadata.FolderType (implements java.lang.annotation.Annotation)
  • @@ -299,8 +297,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.Player.RepeatMode (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.Player.State (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.Player.TimelineChangeReason (implements java.lang.annotation.Annotation)
  • +
  • com.google.android.exoplayer2.Renderer.MessageType (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.Renderer.State (implements java.lang.annotation.Annotation)
  • -
  • com.google.android.exoplayer2.Renderer.VideoScalingMode (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.RendererCapabilities.AdaptiveSupport (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.RendererCapabilities.Capabilities (implements java.lang.annotation.Annotation)
  • com.google.android.exoplayer2.RendererCapabilities.FormatSupport (implements java.lang.annotation.Annotation)
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/robolectric/PlaybackOutput.html b/docs/doc/reference/com/google/android/exoplayer2/robolectric/PlaybackOutput.html index eddeb7b254..9e50aee779 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/robolectric/PlaybackOutput.html +++ b/docs/doc/reference/com/google/android/exoplayer2/robolectric/PlaybackOutput.html @@ -169,7 +169,7 @@ implements static PlaybackOutput -register​(SimpleExoPlayer player, +register​(ExoPlayer player, CapturingRenderersFactory capturingRenderersFactory)
    Create an instance that captures the metadata and text output from player and the audio @@ -200,13 +200,13 @@ implements +
    • register

      -
      public static PlaybackOutput register​(SimpleExoPlayer player,
      +
      public static PlaybackOutput register​(ExoPlayer player,
                                             CapturingRenderersFactory capturingRenderersFactory)
      Create an instance that captures the metadata and text output from player and the audio and video output via capturingRenderersFactory. @@ -215,7 +215,7 @@ implements Parameters: -
      player - The SimpleExoPlayer to capture metadata and text output from.
      +
      player - The ExoPlayer to capture metadata and text output from.
      capturingRenderersFactory - The CapturingRenderersFactory to capture audio and video output from.
      Returns:
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html b/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html index 6a48791aee..36b4271a78 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html +++ b/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html @@ -131,8 +131,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      public class TestPlayerRunHelper
       extends Object
      -
      Helper methods to block the calling thread until the provided SimpleExoPlayer instance - reaches a particular state.
      +
      Helper methods to block the calling thread until the provided ExoPlayer instance reaches + a particular state.
    @@ -156,7 +156,7 @@ extends static void playUntilPosition​(ExoPlayer player, - int windowIndex, + int mediaItemIndex, long positionMs)
    Calls Player.play(), runs tasks of the main Looper until the player @@ -165,11 +165,12 @@ extends static void -playUntilStartOfWindow​(ExoPlayer player, - int windowIndex) +playUntilStartOfMediaItem​(ExoPlayer player, + int mediaItemIndex)
    Calls Player.play(), runs tasks of the main Looper until the player - reaches the specified window or a playback error occurs, and then pauses the player.
    + reaches the specified media item or a playback error occurs, and then pauses the + player.
    @@ -189,8 +190,8 @@ extends static void -runUntilPlaybackState​(Player player, - int expectedState) +runUntilPlaybackState​(Player player, + @com.google.android.exoplayer2.Player.State int expectedState)
    Runs tasks of the main Looper until Player.getPlaybackState() matches the expected state or a playback error occurs.
    @@ -207,10 +208,10 @@ extends static void -runUntilPositionDiscontinuity​(Player player, - int expectedReason) +runUntilPositionDiscontinuity​(Player player, + @com.google.android.exoplayer2.Player.DiscontinuityReason int expectedReason) -
    Runs tasks of the main Looper until Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) is +
    Runs tasks of the main Looper until Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) is called with the specified Player.DiscontinuityReason or a playback error occurs.
    @@ -224,9 +225,9 @@ extends static void -runUntilRenderedFirstFrame​(SimpleExoPlayer player) +runUntilRenderedFirstFrame​(ExoPlayer player) -
    Runs tasks of the main Looper until the VideoListener.onRenderedFirstFrame() +
    Runs tasks of the main Looper until the Player.Listener.onRenderedFirstFrame() callback is called or a playback error occurs.
    @@ -279,7 +280,7 @@ extends

    Method Detail

    - + - + - +
    - +

    public static interface DefaultMediaSourceFactory.AdsLoaderProvider
    -
    Provides AdsLoader instances for media items that have ad tag URIs.
    +
    Provides AdsLoader instances for media items that have ad tag URIs.
    @@ -151,7 +151,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); AdsLoader getAdsLoader​(MediaItem.AdsConfiguration adsConfiguration) -
    Returns an AdsLoader for the given ads configuration, or null if no ads +
    Returns an AdsLoader for the given ads configuration, or null if no ads loader is available for the given ads configuration.
    @@ -180,11 +180,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    getAdsLoader

    @Nullable
     AdsLoader getAdsLoader​(MediaItem.AdsConfiguration adsConfiguration)
    -
    Returns an AdsLoader for the given ads configuration, or null if no ads +
    Returns an AdsLoader for the given ads configuration, or null if no ads loader is available for the given ads configuration.

    This method is called each time a MediaSource is created from a MediaItem - that defines an ads configuration.

    + that defines an ads configuration.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.html b/docs/doc/reference/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.html index c88e4910f6..cd59556427 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":42}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":42}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -87,7 +87,7 @@ loadScripts(document, 'script'); @@ -142,25 +142,25 @@ implements uri - ends in '.mpd' or if its mimeType field is +
  • DashMediaSource.Factory if the item's uri + ends in '.mpd' or if its mimeType field is explicitly set to MimeTypes.APPLICATION_MPD (Requires the exoplayer-dash module to be added to the app). -
  • HlsMediaSource.Factory if the item's uri - ends in '.m3u8' or if its mimeType field is +
  • HlsMediaSource.Factory if the item's uri + ends in '.m3u8' or if its mimeType field is explicitly set to MimeTypes.APPLICATION_M3U8 (Requires the exoplayer-hls module to be added to the app). -
  • SsMediaSource.Factory if the item's uri - ends in '.ism', '.ism/Manifest' or if its mimeType field is explicitly set to MimeTypes.APPLICATION_SS (Requires the +
  • SsMediaSource.Factory if the item's uri + ends in '.ism', '.ism/Manifest' or if its mimeType field is explicitly set to MimeTypes.APPLICATION_SS (Requires the exoplayer-smoothstreaming module to be added to the app). -
  • ProgressiveMediaSource.Factory serves as a fallback if the item's uri doesn't match one of the above. It tries to infer the +
  • ProgressiveMediaSource.Factory serves as a fallback if the item's uri doesn't match one of the above. It tries to infer the required extractor by using the DefaultExtractorsFactory or the ExtractorsFactory provided in the constructor. An UnrecognizedInputFormatException is thrown if none of the available extractors can read the stream.

    Ad support for media items with ad tag URIs

    -

    To support media items with ads +

    To support media items with ads configuration, setAdsLoaderProvider(com.google.android.exoplayer2.source.DefaultMediaSourceFactory.AdsLoaderProvider) and setAdViewProvider(com.google.android.exoplayer2.ui.AdViewProvider) need to be called to configure the factory with the required providers.

  • @@ -187,13 +187,30 @@ implements static interface  DefaultMediaSourceFactory.AdsLoaderProvider -
    Provides AdsLoader instances for media items that have ad tag URIs.
    +
    Provides AdsLoader instances for media items that have ad tag URIs.
    + +
    + +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html index feb29f196e..c998caf5f3 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html @@ -265,8 +265,8 @@ extends int -getNextWindowIndex​(int windowIndex, - int repeatMode, +getNextWindowIndex​(int windowIndex, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)
    Returns the index of the window after the window at index windowIndex depending on the @@ -291,8 +291,8 @@ extends int -getPreviousWindowIndex​(int windowIndex, - int repeatMode, +getPreviousWindowIndex​(int windowIndex, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)
    Returns the index of the window before the window at index windowIndex depending on the @@ -328,7 +328,7 @@ extends Timeline -equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundle +equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundle
    - +
      @@ -375,9 +374,10 @@ public final @DataType int dataType, - int trackType, + @com.google.android.exoplayer2.C.TrackType int trackType, @Nullable Format trackFormat, + @SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/MediaSource.html b/docs/doc/reference/com/google/android/exoplayer2/source/MediaSource.html index ec0395ff23..34102fdef1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/MediaSource.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/MediaSource.html @@ -126,8 +126,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    public interface MediaSource
    -
    Defines and provides media to be played by an ExoPlayer. A - MediaSource has two main responsibilities: +
    Defines and provides media to be played by an ExoPlayer. A MediaSource has two main + responsibilities:
    • To provide the player with a Timeline defining the structure of its media, and to @@ -140,8 +140,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); way for the player to load and read the media.
    - All methods are called on the player's internal playback thread, as described in the ExoPlayer Javadoc. They should not be called directly from - application code. Instances can be re-used, but only for one ExoPlayer instance simultaneously.
    + All methods are called on the player's internal playback thread, as described in the ExoPlayer Javadoc. They should not be called directly from application code. Instances can be + re-used, but only for one ExoPlayer instance simultaneously.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/MediaSourceEventListener.EventDispatcher.html b/docs/doc/reference/com/google/android/exoplayer2/source/MediaSourceEventListener.EventDispatcher.html index 2ea776b688..b06cfc388a 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/MediaSourceEventListener.EventDispatcher.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/MediaSourceEventListener.EventDispatcher.html @@ -221,7 +221,7 @@ extends void -downstreamFormatChanged​(int trackType, +downstreamFormatChanged​(@com.google.android.exoplayer2.C.TrackType int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, @@ -247,9 +247,9 @@ extends void -loadCanceled​(LoadEventInfo loadEventInfo, +loadCanceled​(LoadEventInfo loadEventInfo, int dataType, - int trackType, + @com.google.android.exoplayer2.C.TrackType int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, @@ -277,9 +277,9 @@ extends void -loadCompleted​(LoadEventInfo loadEventInfo, +loadCompleted​(LoadEventInfo loadEventInfo, int dataType, - int trackType, + @com.google.android.exoplayer2.C.TrackType int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, @@ -299,9 +299,9 @@ extends void -loadError​(LoadEventInfo loadEventInfo, +loadError​(LoadEventInfo loadEventInfo, int dataType, - int trackType, + @com.google.android.exoplayer2.C.TrackType int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, @@ -346,9 +346,9 @@ extends void -loadStarted​(LoadEventInfo loadEventInfo, +loadStarted​(LoadEventInfo loadEventInfo, int dataType, - int trackType, + @com.google.android.exoplayer2.C.TrackType int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, @@ -539,7 +539,7 @@ public Dispatches MediaSourceEventListener.onLoadStarted(int, MediaPeriodId, LoadEventInfo, MediaLoadData). - + - + - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/ProgressiveMediaSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/ProgressiveMediaSource.Factory.html index 299689a652..3278ee9cd5 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/ProgressiveMediaSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/ProgressiveMediaSource.Factory.html @@ -87,7 +87,7 @@ loadScripts(document, 'script'); @@ -147,6 +147,23 @@ implements
    @@ -598,7 +615,7 @@ public Returns:
    The new ProgressiveMediaSource.
    Throws:
    -
    NullPointerException - if MediaItem.playbackProperties is null.
    +
    NullPointerException - if MediaItem.localConfiguration is null.
    @@ -670,7 +687,7 @@ public 
  • Summary: 
  • Nested | 
  • -
  • Field | 
  • +
  • Field | 
  • Constr | 
  • Method
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/SilenceMediaSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/SilenceMediaSource.Factory.html index bd35fb95ea..9edf514063 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/SilenceMediaSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/SilenceMediaSource.Factory.html @@ -195,7 +195,8 @@ extends SilenceMediaSource.Factory setTag​(Object tag) -
    Sets a tag for the media source which will be published in the Timeline of the source as Window#mediaItem.playbackProperties.tag.
    +
    Sets a tag for the media source which will be published in the Timeline of the source + as Window#mediaItem.localConfiguration.tag.
    @@ -247,7 +248,8 @@ extends
  • setDurationUs

    -
    public SilenceMediaSource.Factory setDurationUs​(long durationUs)
    +
    public SilenceMediaSource.Factory setDurationUs​(@IntRange(from=1L)
    +                                                long durationUs)
    Sets the duration of the silent audio. The value needs to be a positive value.
    Parameters:
    @@ -265,7 +267,8 @@ extends setTag
    public SilenceMediaSource.Factory setTag​(@Nullable
                                              Object tag)
    -
    Sets a tag for the media source which will be published in the Timeline of the source as Window#mediaItem.playbackProperties.tag.
    +
    Sets a tag for the media source which will be published in the Timeline of the source + as Window#mediaItem.localConfiguration.tag.
    Parameters:
    tag - A tag for the media source.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/SinglePeriodTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/source/SinglePeriodTimeline.html index c657561f8c..9052d5822d 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/SinglePeriodTimeline.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/SinglePeriodTimeline.html @@ -387,7 +387,7 @@ extends Timeline -equals, getFirstWindowIndex, getLastWindowIndex, getNextPeriodIndex, getNextWindowIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPreviousWindowIndex, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundle
  • +equals, getFirstWindowIndex, getLastWindowIndex, getNextPeriodIndex, getNextWindowIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getPreviousWindowIndex, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundle
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/SingleSampleMediaSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/SingleSampleMediaSource.Factory.html index 90da393734..13f64d6167 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/SingleSampleMediaSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/SingleSampleMediaSource.Factory.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":42,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -173,7 +173,7 @@ extends

    Method Summary

    - + @@ -181,46 +181,35 @@ extends - - - - - - - + - + - + - + - + - + @@ -187,13 +189,6 @@ implements -
  • - - -

    Fields inherited from interface android.os.Parcelable

    -CONTENTS_FILE_DESCRIPTOR, PARCELABLE_WRITE_RETURN_VALUE
  • - @@ -212,7 +207,9 @@ implements - +
    All Methods Instance Methods Concrete Methods Deprecated Methods All Methods Instance Methods Concrete Methods 
    Modifier and Type Method
    SingleSampleMediaSourcecreateMediaSource​(Uri uri, - Format format, - long durationUs) - -
    SingleSampleMediaSourcecreateMediaSource​(MediaItem.Subtitle subtitle, +createMediaSource​(MediaItem.SubtitleConfiguration subtitleConfiguration, long durationUs)
    Returns a new SingleSampleMediaSource using the current parameters.
    SingleSampleMediaSource.Factory setLoadErrorHandlingPolicy​(LoadErrorHandlingPolicy loadErrorHandlingPolicy)
    SingleSampleMediaSource.Factory setTag​(Object tag)
    Sets a tag for the media source which will be published in the Timeline of the source - as Window#mediaItem.playbackProperties.tag.
    + as Window#mediaItem.localConfiguration.tag.
    SingleSampleMediaSource.Factory setTrackId​(String trackId)
    Sets an optional track id to be used.
    SingleSampleMediaSource.Factory setTreatLoadErrorsAsEndOfStream​(boolean treatLoadErrorsAsEndOfStream) @@ -286,7 +275,7 @@ extends public SingleSampleMediaSource.Factory setTag​(@Nullable Object tag)
    Sets a tag for the media source which will be published in the Timeline of the source - as Window#mediaItem.playbackProperties.tag.
    + as Window#mediaItem.localConfiguration.tag.
    Parameters:
    tag - A tag for the media source.
    @@ -348,37 +337,22 @@ extends - - - - - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroup.html b/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroup.html index dff3b570b3..12aa645ca4 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroup.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroup.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -130,12 +130,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Implemented Interfaces:
    -
    Parcelable
    +
    Bundleable

    public final class TrackGroup
     extends Object
    -implements Parcelable
    +implements Bundleable
    Defines an immutable group of tracks identified by their format identity.
  • @@ -151,11 +151,11 @@ implements -
  • +
  • -

    Nested classes/interfaces inherited from interface android.os.Parcelable

    -Parcelable.ClassLoaderCreator<T extends Object>, Parcelable.Creator<T extends Object>
  • +

    Nested classes/interfaces inherited from interface com.google.android.exoplayer2.Bundleable

    +Bundleable.Creator<T extends Bundleable> @@ -175,9 +175,11 @@ implements Description
    static Parcelable.Creator<TrackGroup>static Bundleable.Creator<TrackGroup> CREATOR  +
    Object that can restore TrackGroup from a Bundle.
    +
    int TrackGroup​(Format... formats)  +
    Constructs an instance TrackGroup containing the provided formats.
    +
    @@ -233,39 +230,35 @@ implements Description -int -describeContents() -  - - boolean equals​(Object obj)   - + Format getFormat​(int index)
    Returns the format of the track at a given index.
    - + int hashCode()   - + int indexOf​(Format format)
    Returns the index of the track with the given format in the group.
    - -void -writeToParcel​(Parcel dest, - int flags) -  + +Bundle +toBundle() + +
    Returns a Bundle representing the information stored in this object.
    + @@ -327,9 +321,10 @@ implements

    TrackGroup

    public TrackGroup​(Format... formats)
    +
    Constructs an instance TrackGroup containing the provided formats.
    Parameters:
    -
    formats - The track formats. At least one Format must be provided.
    +
    formats - Non empty array of format.
    @@ -404,30 +399,18 @@ implements - - - - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroupArray.html b/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroupArray.html index 18fb9da3af..ddd782c0ff 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroupArray.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/TrackGroupArray.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -130,12 +130,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Implemented Interfaces:
    -
    Parcelable
    +
    Bundleable

    public final class TrackGroupArray
     extends Object
    -implements Parcelable
    +implements Bundleable
    An immutable array of TrackGroups.
  • @@ -151,11 +151,11 @@ implements -
  • +
  • -

    Nested classes/interfaces inherited from interface android.os.Parcelable

    -Parcelable.ClassLoaderCreator<T extends Object>, Parcelable.Creator<T extends Object>
  • +

    Nested classes/interfaces inherited from interface com.google.android.exoplayer2.Bundleable

    +Bundleable.Creator<T extends Bundleable> @@ -175,9 +175,11 @@ implements Description -static Parcelable.Creator<TrackGroupArray> +static Bundleable.Creator<TrackGroupArray> CREATOR -  + +
    Object that can restores a TrackGroupArray from a Bundle.
    + static TrackGroupArray @@ -194,13 +196,6 @@ implements -
  • - - -

    Fields inherited from interface android.os.Parcelable

    -CONTENTS_FILE_DESCRIPTOR, PARCELABLE_WRITE_RETURN_VALUE
  • - @@ -219,7 +214,9 @@ implements TrackGroupArray​(TrackGroup... trackGroups) -  + +
    Construct a TrackGroupArray from an array of (possibly empty) trackGroups.
    + @@ -240,46 +237,42 @@ implements Description -int -describeContents() -  - - boolean equals​(Object obj)   - + TrackGroup get​(int index)
    Returns the group at a given index.
    - + int hashCode()   - + int indexOf​(TrackGroup group)
    Returns the index of a group within the array.
    - + boolean isEmpty()
    Returns whether this track group array is empty.
    - -void -writeToParcel​(Parcel dest, - int flags) -  + +Bundle +toBundle() + +
    Returns a Bundle representing the information stored in this object.
    + @@ -351,10 +345,7 @@ implements

    TrackGroupArray

    public TrackGroupArray​(TrackGroup... trackGroups)
    -
    -
    Parameters:
    -
    trackGroups - The groups. May be empty.
    -
    +
    Construct a TrackGroupArray from an array of (possibly empty) trackGroups.
    @@ -436,30 +427,18 @@ implements - - - - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.AdGroup.html b/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.AdGroup.html index 69e37b98aa..a31da04f95 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.AdGroup.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.AdGroup.html @@ -228,8 +228,7 @@ implements long timeUs -
    The time of the ad group in the Timeline.Period, in - microseconds, or C.TIME_END_OF_SOURCE to indicate a postroll ad.
    +
    The time of the ad group in the Timeline.Period, in microseconds, or C.TIME_END_OF_SOURCE to indicate a postroll ad.
    @@ -417,8 +416,7 @@ implements

    timeUs

    public final long timeUs
    -
    The time of the ad group in the Timeline.Period, in - microseconds, or C.TIME_END_OF_SOURCE to indicate a postroll ad.
    +
    The time of the ad group in the Timeline.Period, in microseconds, or C.TIME_END_OF_SOURCE to indicate a postroll ad.
    @@ -513,7 +511,8 @@ public final int[] states
    Creates a new ad group with an unspecified number of ads.
    Parameters:
    -
    timeUs - The time of the ad group in the Timeline.Period, in microseconds, or C.TIME_END_OF_SOURCE to indicate a postroll ad.
    +
    timeUs - The time of the ad group in the Timeline.Period, in microseconds, or + C.TIME_END_OF_SOURCE to indicate a postroll ad.
    @@ -544,9 +543,11 @@ public final int[] states
    • getNextAdIndexToPlay

      -
      public int getNextAdIndexToPlay​(int lastPlayedAdIndex)
      +
      public int getNextAdIndexToPlay​(@IntRange(from=-1L)
      +                                int lastPlayedAdIndex)
      Returns the index of the next ad in the ad group that should be played after playing - lastPlayedAdIndex, or count if no later ads should be played.
      + lastPlayedAdIndex
      , or count if no later ads should be played. If no ads have been + played, pass -1 to get the index of the first ad to play.
    @@ -626,6 +627,7 @@ public @CheckResult public AdPlaybackState.AdGroup withAdUri​(Uri uri, + @IntRange(from=0L) int index)
    Returns a new instance with the specified uri set for the specified ad, and the ad marked as AdPlaybackState.AD_STATE_AVAILABLE.
    @@ -640,6 +642,7 @@ public @CheckResult public AdPlaybackState.AdGroup withAdState​(@AdState int state, + @IntRange(from=0L) int index)
    Returns a new instance with the specified ad set to the specified state. The ad specified must currently either be in AdPlaybackState.AD_STATE_UNAVAILABLE or AdPlaybackState.AD_STATE_AVAILABLE. diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.html b/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.html index 56acc34b48..51526fcf7d 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/ads/AdPlaybackState.html @@ -693,8 +693,7 @@ public final Parameters:
    adsId - The opaque identifier for ads with which this instance is associated.
    adGroupTimesUs - The times of ad groups in microseconds, relative to the start of the - Timeline.Period they belong to. A final element with - the value C.TIME_END_OF_SOURCE indicates that there is a postroll ad.
    + Timeline.Period they belong to. A final element with the value C.TIME_END_OF_SOURCE indicates that there is a postroll ad.
    @@ -714,7 +713,8 @@ public final 
  • getAdGroup

    -
    public AdPlaybackState.AdGroup getAdGroup​(int adGroupIndex)
    +
    public AdPlaybackState.AdGroup getAdGroup​(@IntRange(from=0L)
    +                                          int adGroupIndex)
    Returns the specified AdPlaybackState.AdGroup.
  • @@ -769,7 +769,9 @@ public final 
  • isAdInErrorState

    -
    public boolean isAdInErrorState​(int adGroupIndex,
    +
    public boolean isAdInErrorState​(@IntRange(from=0L)
    +                                int adGroupIndex,
    +                                @IntRange(from=0L)
                                     int adIndexInAdGroup)
  • @@ -781,7 +783,8 @@ public final 

    withAdGroupTimeUs

    @CheckResult
    -public AdPlaybackState withAdGroupTimeUs​(int adGroupIndex,
    +public AdPlaybackState withAdGroupTimeUs​(@IntRange(from=0L)
    +                                         int adGroupIndex,
                                              long adGroupTimeUs)
    Returns an instance with the specified ad group time.
    @@ -801,7 +804,8 @@ public 

    withNewAdGroup

    @CheckResult
    -public AdPlaybackState withNewAdGroup​(int adGroupIndex,
    +public AdPlaybackState withNewAdGroup​(@IntRange(from=0L)
    +                                      int adGroupIndex,
                                           long adGroupTimeUs)
    Returns an instance with a new ad group.
    @@ -821,7 +825,9 @@ public 

    withAdCount

    @CheckResult
    -public AdPlaybackState withAdCount​(int adGroupIndex,
    +public AdPlaybackState withAdCount​(@IntRange(from=0L)
    +                                   int adGroupIndex,
    +                                   @IntRange(from=1L)
                                        int adCount)
    Returns an instance with the number of ads in adGroupIndex resolved to adCount. The ad count must be greater than zero.
    @@ -834,7 +840,9 @@ public 

    withAdUri

    @CheckResult
    -public AdPlaybackState withAdUri​(int adGroupIndex,
    +public AdPlaybackState withAdUri​(@IntRange(from=0L)
    +                                 int adGroupIndex,
    +                                 @IntRange(from=0L)
                                      int adIndexInAdGroup,
                                      Uri uri)
    Returns an instance with the specified ad URI.
    @@ -847,7 +855,9 @@ public 

    withPlayedAd

    @CheckResult
    -public AdPlaybackState withPlayedAd​(int adGroupIndex,
    +public AdPlaybackState withPlayedAd​(@IntRange(from=0L)
    +                                    int adGroupIndex,
    +                                    @IntRange(from=0L)
                                         int adIndexInAdGroup)
    Returns an instance with the specified ad marked as played.
    @@ -859,7 +869,9 @@ public 

    withSkippedAd

    @CheckResult
    -public AdPlaybackState withSkippedAd​(int adGroupIndex,
    +public AdPlaybackState withSkippedAd​(@IntRange(from=0L)
    +                                     int adGroupIndex,
    +                                     @IntRange(from=0L)
                                          int adIndexInAdGroup)
    Returns an instance with the specified ad marked as skipped.
    @@ -871,7 +883,9 @@ public 

    withAdLoadError

    @CheckResult
    -public AdPlaybackState withAdLoadError​(int adGroupIndex,
    +public AdPlaybackState withAdLoadError​(@IntRange(from=0L)
    +                                       int adGroupIndex,
    +                                       @IntRange(from=0L)
                                            int adIndexInAdGroup)
    Returns an instance with the specified ad marked as having a load error.
    @@ -883,7 +897,8 @@ public 

    withSkippedAdGroup

    @CheckResult
    -public AdPlaybackState withSkippedAdGroup​(int adGroupIndex)
    +public AdPlaybackState withSkippedAdGroup​(@IntRange(from=0L) + int adGroupIndex)
    Returns an instance with all ads in the specified ad group skipped (except for those already marked as played or in the error state).
    @@ -908,7 +923,8 @@ public 

    withAdDurationsUs

    @CheckResult
    -public AdPlaybackState withAdDurationsUs​(int adGroupIndex,
    +public AdPlaybackState withAdDurationsUs​(@IntRange(from=0L)
    +                                         int adGroupIndex,
                                              long... adDurationsUs)
    Returns an instance with the specified ad durations, in microseconds, in the specified ad group.
    @@ -944,7 +960,8 @@ public 

    withRemovedAdGroupCount

    @CheckResult
    -public AdPlaybackState withRemovedAdGroupCount​(int removedAdGroupCount)
    +public AdPlaybackState withRemovedAdGroupCount​(@IntRange(from=0L) + int removedAdGroupCount)
    Returns an instance with the specified number of removed ad groups. @@ -959,7 +976,8 @@ public 

    withContentResumeOffsetUs

    @CheckResult
    -public AdPlaybackState withContentResumeOffsetUs​(int adGroupIndex,
    +public AdPlaybackState withContentResumeOffsetUs​(@IntRange(from=0L)
    +                                                 int adGroupIndex,
                                                      long contentResumeOffsetUs)
    Returns an instance with the specified AdPlaybackState.AdGroup.contentResumeOffsetUs, in microseconds, for the specified ad group.
    @@ -972,7 +990,8 @@ public 

    withIsServerSideInserted

    @CheckResult
    -public AdPlaybackState withIsServerSideInserted​(int adGroupIndex,
    +public AdPlaybackState withIsServerSideInserted​(@IntRange(from=0L)
    +                                                int adGroupIndex,
                                                     boolean isServerSideInserted)
    Returns an instance with the specified value for AdPlaybackState.AdGroup.isServerSideInserted in the specified ad group.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.html index 7bc877dc8c..4a644c4bd8 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.html @@ -253,14 +253,14 @@ extends ForwardingTimeline -getFirstWindowIndex, getIndexOfPeriod, getLastWindowIndex, getNextWindowIndex, getPeriodCount, getPreviousWindowIndex, getUidOfPeriod, getWindow, getWindowCount +getFirstWindowIndex, getIndexOfPeriod, getLastWindowIndex, getNextWindowIndex, getPeriodCount, getPreviousWindowIndex, getUidOfPeriod, getWindow, getWindowCount
    @@ -336,20 +336,20 @@ implements +
    • BundledChunkExtractor

      public BundledChunkExtractor​(Extractor extractor,
      -                             int primaryTrackType,
      +                             @com.google.android.exoplayer2.C.TrackType int primaryTrackType,
                                    Format primaryTrackManifestFormat)
      Creates an instance.
      Parameters:
      extractor - The extractor to wrap.
      -
      primaryTrackType - The type of the primary track. Typically one of the C TRACK_TYPE_* constants.
      +
      primaryTrackType - The type of the primary track.
      primaryTrackManifestFormat - A manifest defined Format whose data should be merged into any sample Format output from the Extractor for the primary track.
      @@ -468,18 +468,17 @@ public public TrackOutput track​(int id, int type) -
      Description copied from interface: ExtractorOutput
      +
      Description copied from interface: ExtractorOutput
      Called by the Extractor to get the TrackOutput for a specific track.

      The same TrackOutput is returned if multiple calls are made with the same id.

      Specified by:
      -
      track in interface ExtractorOutput
      +
      track in interface ExtractorOutput
      Parameters:
      id - A track identifier.
      -
      type - The type of the track. Typically one of the C - TRACK_TYPE_* constants.
      +
      type - The track type.
      Returns:
      The TrackOutput for the given track identifier.
      @@ -494,7 +493,7 @@ public public void endTracks()
      Called when all tracks have been identified, meaning no new trackId values will be - passed to ExtractorOutput.track(int, int).
      + passed to ExtractorOutput.track(int, int).
      Specified by:
      endTracks in interface ExtractorOutput
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/Chunk.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/Chunk.html index be9b9b9637..c03339a84d 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/Chunk.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/Chunk.html @@ -215,7 +215,7 @@ implements int trackSelectionReason -
      One of the C SELECTION_REASON_* constants if the chunk belongs to a track.
      +
      One of the selection reasons if the chunk belongs to a track.
      @@ -377,10 +377,10 @@ public final int type
      • trackSelectionReason

        -
        public final int trackSelectionReason
        -
        One of the C SELECTION_REASON_* constants if the chunk belongs to a track. - C.SELECTION_REASON_UNKNOWN if the chunk does not belong to a track, or if the selection - reason is unknown.
        +
        @SelectionReason
        +public final int trackSelectionReason
        +
        One of the selection reasons if the chunk belongs to a track. C.SELECTION_REASON_UNKNOWN if the chunk does not belong to a track, or if the selection reason + is unknown.
      @@ -447,6 +447,7 @@ public final @DataType int type, Format trackFormat, + @SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.Factory.html index 659df88c70..276103876c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.Factory.html @@ -149,7 +149,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); ChunkExtractor -createProgressiveMediaExtractor​(int primaryTrackType, +createProgressiveMediaExtractor​(@com.google.android.exoplayer2.C.TrackType int primaryTrackType, Format representationFormat, boolean enableEventMessageTrack, List<Format> closedCaptionFormats, @@ -175,14 +175,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

      Method Detail

      - +
      • createProgressiveMediaExtractor

        @Nullable
        -ChunkExtractor createProgressiveMediaExtractor​(int primaryTrackType,
        +ChunkExtractor createProgressiveMediaExtractor​(@com.google.android.exoplayer2.C.TrackType int primaryTrackType,
                                                        Format representationFormat,
                                                        boolean enableEventMessageTrack,
                                                        List<Format> closedCaptionFormats,
        @@ -191,7 +191,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
         
        Returns a new ChunkExtractor instance.
        Parameters:
        -
        primaryTrackType - The type of the primary track. One of C.TRACK_TYPE_*.
        +
        primaryTrackType - The type of the primary track.
        representationFormat - The format of the representation to extract from.
        enableEventMessageTrack - Whether to enable the event message track.
        closedCaptionFormats - The Formats of the Closed-Caption tracks.
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.TrackOutputProvider.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.TrackOutputProvider.html index ba08fc5c52..ca99f386b0 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.TrackOutputProvider.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkExtractor.TrackOutputProvider.html @@ -153,8 +153,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); TrackOutput -track​(int id, - int type) +track​(int id, + @com.google.android.exoplayer2.C.TrackType int type)
        Called to get the TrackOutput for a specific track.
        @@ -176,14 +176,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

        Method Detail

        - +
        • track

          TrackOutput track​(int id,
          -                  int type)
          + @com.google.android.exoplayer2.C.TrackType int type)
        Called to get the TrackOutput for a specific track.

        The same TrackOutput is returned if multiple calls are made with the same @@ -191,8 +191,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

        Parameters:
        id - A track identifier.
        -
        type - The type of the track. Typically one of the C TRACK_TYPE_* - constants.
        +
        type - The type of the track.
        Returns:
        The TrackOutput for the given track identifier.
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.html index 70678ae3e4..0128a190f8 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.html @@ -205,7 +205,7 @@ implements Description -int +@com.google.android.exoplayer2.C.TrackType int primaryTrackType   @@ -234,7 +234,7 @@ implements Description -ChunkSampleStream​(int primaryTrackType, +ChunkSampleStream​(@com.google.android.exoplayer2.C.TrackType int primaryTrackType, int[] embeddedTrackTypes, Format[] embeddedTrackFormats, T chunkSource, @@ -453,7 +453,7 @@ implements
      • primaryTrackType

        -
        public final int primaryTrackType
        +
        public final @com.google.android.exoplayer2.C.TrackType int primaryTrackType
    • @@ -466,15 +466,15 @@ implements + - +
      • ChunkSampleStream

        -
        public ChunkSampleStream​(int primaryTrackType,
        +
        public ChunkSampleStream​(@com.google.android.exoplayer2.C.TrackType int primaryTrackType,
                                  @Nullable
                                  int[] embeddedTrackTypes,
                                  @Nullable
        @@ -490,8 +490,7 @@ implements Constructs an instance.
         
        Parameters:
        -
        primaryTrackType - The type of the primary track. One of the C - TRACK_TYPE_* constants.
        +
        primaryTrackType - The type of the primary track.
        embeddedTrackTypes - The types of any embedded tracks, or null.
        embeddedTrackFormats - The formats of the embedded tracks, or null.
        chunkSource - A ChunkSource from which chunks to load are obtained.
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.html index b875d2f7d9..95ca36b4fc 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.html @@ -317,6 +317,7 @@ extends DataSource dataSource, DataSpec dataSpec, Format trackFormat, + @SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/DataChunk.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/DataChunk.html index 38aacb6ca6..f118ee4cc1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/DataChunk.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/DataChunk.html @@ -277,6 +277,7 @@ extends @DataType int type, Format trackFormat, + @SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/InitializationChunk.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/InitializationChunk.html index c65a3e6025..1bec691171 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/InitializationChunk.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/InitializationChunk.html @@ -266,6 +266,7 @@ extends DataSource dataSource, DataSpec dataSpec, Format trackFormat, + @SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaChunk.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaChunk.html index 39362613fa..da46a0fc78 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaChunk.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaChunk.html @@ -306,6 +306,7 @@ extends DataSource dataSource, DataSpec dataSpec, Format trackFormat, + @SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.html index f16a756590..7312e4fa3d 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.html @@ -198,7 +198,7 @@ implements Description -MediaParserChunkExtractor​(int primaryTrackType, +MediaParserChunkExtractor​(@com.google.android.exoplayer2.C.TrackType int primaryTrackType, Format manifestFormat, List<Format> closedCaptionFormats) @@ -304,20 +304,19 @@ implements +
        • MediaParserChunkExtractor

          -
          public MediaParserChunkExtractor​(int primaryTrackType,
          +
          public MediaParserChunkExtractor​(@com.google.android.exoplayer2.C.TrackType int primaryTrackType,
                                            Format manifestFormat,
                                            List<Format> closedCaptionFormats)
          Creates a new instance.
          Parameters:
          -
          primaryTrackType - The type of the primary track, or C.TRACK_TYPE_NONE if there is - no primary track. Must be one of the C.TRACK_TYPE_* constants.
          +
          primaryTrackType - The type of the primary track. C.TRACK_TYPE_NONE if there is no primary track.
          manifestFormat - The chunks Format as obtained from the manifest.
          closedCaptionFormats - A list containing the Formats of the closed-caption tracks in the chunks.
          diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.html b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.html index 7249fd35c0..806a241073 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.html @@ -202,7 +202,7 @@ extends Description -SingleSampleMediaChunk​(DataSource dataSource, +SingleSampleMediaChunk​(DataSource dataSource, DataSpec dataSpec, Format trackFormat, int trackSelectionReason, @@ -210,7 +210,7 @@ extends Format sampleFormat)   @@ -298,7 +298,7 @@ extends +
            @@ -307,13 +307,14 @@ extends DataSource dataSource, DataSpec dataSpec, Format trackFormat, + @SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, long startTimeUs, long endTimeUs, long chunkIndex, - int trackType, + @com.google.android.exoplayer2.C.TrackType int trackType, Format sampleFormat)
          Parameters:
          @@ -325,8 +326,7 @@ extends C.INDEX_UNSET if it is not known. -
          trackType - The type of the chunk. Typically one of the C TRACK_TYPE_* - constants.
          +
          trackType - The track type of the chunk.
          sampleFormat - The Format of the sample in the chunk.
        • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashChunkSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashChunkSource.Factory.html index 4fb04686e4..715a926983 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashChunkSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashChunkSource.Factory.html @@ -153,13 +153,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); DashChunkSource -createDashChunkSource​(LoaderErrorThrower manifestLoaderErrorThrower, +createDashChunkSource​(LoaderErrorThrower manifestLoaderErrorThrower, DashManifest manifest, BaseUrlExclusionList baseUrlExclusionList, int periodIndex, int[] adaptationSetIndices, ExoTrackSelection trackSelection, - int type, + @com.google.android.exoplayer2.C.TrackType int trackType, long elapsedRealtimeOffsetMs, boolean enableEventMessageTrack, List<Format> closedCaptionFormats, @@ -184,7 +184,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

          Method Detail

          - +
            @@ -196,7 +196,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); int periodIndex, int[] adaptationSetIndices, ExoTrackSelection trackSelection, - int type, + @com.google.android.exoplayer2.C.TrackType int trackType, long elapsedRealtimeOffsetMs, boolean enableEventMessageTrack, List<Format> closedCaptionFormats, @@ -212,9 +212,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
            periodIndex - The index of the corresponding period in the manifest.
            adaptationSetIndices - The indices of the corresponding adaptation sets in the period.
            trackSelection - The track selection.
            +
            trackType - The track type.
            elapsedRealtimeOffsetMs - If known, an estimate of the instantaneous difference between server-side unix time and SystemClock.elapsedRealtime() in milliseconds, - specified as the server's unix time minus the local elapsed time. Or C.TIME_UNSET if unknown.
            + specified as the server's unix time minus the local elapsed time. Or C.TIME_UNSET + if unknown.
            enableEventMessageTrack - Whether to output an event message track.
            closedCaptionFormats - The Formats of closed caption tracks to be output.
            transferListener - The transfer listener which should be informed of any data transfers. diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashMediaSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashMediaSource.Factory.html index 07374b8fc0..15130b4b64 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashMediaSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashMediaSource.Factory.html @@ -87,7 +87,7 @@ loadScripts(document, 'script'); @@ -147,6 +147,23 @@ implements
        @@ -553,7 +571,8 @@ public DashMediaSource.Factory setLivePresentationDelayMs​(long livePresentationDelayMs, boolean overridesManifest)
      • @@ -686,7 +705,7 @@ public Returns:
        The new DashMediaSource.
        Throws:
        -
        NullPointerException - if MediaItem.playbackProperties is null.
        +
        NullPointerException - if MediaItem.localConfiguration is null.
    @@ -758,7 +777,7 @@ public 
  • Summary: 
  • Nested | 
  • -
  • Field | 
  • +
  • Field | 
  • Constr | 
  • Method
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashUtil.html b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashUtil.html index 66e631b456..0426145cf0 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashUtil.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DashUtil.html @@ -371,7 +371,7 @@ public static Parameters:
    dataSource - The source from which the data should be loaded.
    -
    trackType - The type of the representation. Typically one of the C TRACK_TYPE_* constants.
    +
    trackType - The type of the representation. Typically one of the com.google.android.exoplayer2.C TRACK_TYPE_* constants.
    representation - The representation which initialization chunk belongs to.
    baseUrlIndex - The index of the base URL to be picked from the list of base URLs.
    Returns:
    @@ -398,7 +398,7 @@ public static Parameters:
    dataSource - The source from which the data should be loaded.
    -
    trackType - The type of the representation. Typically one of the C TRACK_TYPE_* constants.
    +
    trackType - The type of the representation. Typically one of the com.google.android.exoplayer2.C TRACK_TYPE_* constants.
    representation - The representation which initialization chunk belongs to.
    Returns:
    the sample Format of the given representation.
    @@ -423,7 +423,7 @@ public static Parameters:
    dataSource - The source from which the data should be loaded.
    -
    trackType - The type of the representation. Typically one of the C TRACK_TYPE_* constants.
    +
    trackType - The type of the representation. Typically one of the com.google.android.exoplayer2.C TRACK_TYPE_* constants.
    representation - The representation which initialization chunk belongs to.
    baseUrlIndex - The index of the base URL with which to resolve the request URI.
    Returns:
    @@ -451,7 +451,7 @@ public static Parameters:
    dataSource - The source from which the data should be loaded.
    -
    trackType - The type of the representation. Typically one of the C TRACK_TYPE_* constants.
    +
    trackType - The type of the representation. Typically one of the com.google.android.exoplayer2.C TRACK_TYPE_* constants.
    representation - The representation which initialization chunk belongs to.
    Returns:
    The ChunkIndex of the given representation, or null if no initialization or diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.Factory.html index 8cfbd412b4..5e3826aff3 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.Factory.html @@ -202,13 +202,13 @@ implements DashChunkSource -createDashChunkSource​(LoaderErrorThrower manifestLoaderErrorThrower, +createDashChunkSource​(LoaderErrorThrower manifestLoaderErrorThrower, DashManifest manifest, BaseUrlExclusionList baseUrlExclusionList, int periodIndex, int[] adaptationSetIndices, ExoTrackSelection trackSelection, - int trackType, + @com.google.android.exoplayer2.C.TrackType int trackType, long elapsedRealtimeOffsetMs, boolean enableEventMessageTrack, List<Format> closedCaptionFormats, @@ -278,7 +278,7 @@ implements ChunkExtractor instances to use for extracting chunks.
    dataSourceFactory - Creates the DataSource to use for downloading chunks.
    -
    maxSegmentsPerLoad - See DefaultDashChunkSource(com.google.android.exoplayer2.source.chunk.ChunkExtractor.Factory, com.google.android.exoplayer2.upstream.LoaderErrorThrower, com.google.android.exoplayer2.source.dash.manifest.DashManifest, com.google.android.exoplayer2.source.dash.BaseUrlExclusionList, int, int[], com.google.android.exoplayer2.trackselection.ExoTrackSelection, int, com.google.android.exoplayer2.upstream.DataSource, long, int, boolean, java.util.List<com.google.android.exoplayer2.Format>, com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler).
    +
    maxSegmentsPerLoad - See DefaultDashChunkSource(com.google.android.exoplayer2.source.chunk.ChunkExtractor.Factory, com.google.android.exoplayer2.upstream.LoaderErrorThrower, com.google.android.exoplayer2.source.dash.manifest.DashManifest, com.google.android.exoplayer2.source.dash.BaseUrlExclusionList, int, int[], com.google.android.exoplayer2.trackselection.ExoTrackSelection, @com.google.android.exoplayer2.C.TrackType int, com.google.android.exoplayer2.upstream.DataSource, long, int, boolean, java.util.List<com.google.android.exoplayer2.Format>, com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler).
    @@ -292,7 +292,7 @@ implements + - + @@ -342,14 +341,14 @@ extends

    Constructor Detail

    - + @@ -1470,8 +1470,8 @@ protected int parseSelectionFlagsFromRoleDescriptors​(

    parseSelectionFlagsFromDashRoleScheme

    @SelectionFlags
    -protected int parseSelectionFlagsFromDashRoleScheme​(@Nullable
    -                                                    String value)
    +protected @com.google.android.exoplayer2.C.SelectionFlags int parseSelectionFlagsFromDashRoleScheme​(@Nullable + String value) @@ -1481,7 +1481,7 @@ protected int parseSelectionFlagsFromDashRoleScheme​(@Nullable
  • parseRoleFlagsFromRoleDescriptors

    @RoleFlags
    -protected int parseRoleFlagsFromRoleDescriptors​(List<Descriptor> roleDescriptors)
    +protected @com.google.android.exoplayer2.C.RoleFlags int parseRoleFlagsFromRoleDescriptors​(List<Descriptor> roleDescriptors)
  • @@ -1491,7 +1491,7 @@ protected int parseRoleFlagsFromRoleDescriptors​(

    parseRoleFlagsFromAccessibilityDescriptors

    @RoleFlags
    -protected int parseRoleFlagsFromAccessibilityDescriptors​(List<Descriptor> accessibilityDescriptors)
    +protected @com.google.android.exoplayer2.C.RoleFlags int parseRoleFlagsFromAccessibilityDescriptors​(List<Descriptor> accessibilityDescriptors) @@ -1501,7 +1501,7 @@ protected int parseRoleFlagsFromAccessibilityDescriptors​(

    parseRoleFlagsFromProperties

    @RoleFlags
    -protected int parseRoleFlagsFromProperties​(List<Descriptor> accessibilityDescriptors)
    +protected @com.google.android.exoplayer2.C.RoleFlags int parseRoleFlagsFromProperties​(List<Descriptor> accessibilityDescriptors) @@ -1511,8 +1511,8 @@ protected int parseRoleFlagsFromProperties​(

    parseRoleFlagsFromDashRoleScheme

    @RoleFlags
    -protected int parseRoleFlagsFromDashRoleScheme​(@Nullable
    -                                               String value)
    +protected @com.google.android.exoplayer2.C.RoleFlags int parseRoleFlagsFromDashRoleScheme​(@Nullable + String value) @@ -1522,8 +1522,8 @@ protected int parseRoleFlagsFromDashRoleScheme​(@Nullable
  • parseTvaAudioPurposeCsValue

    @RoleFlags
    -protected int parseTvaAudioPurposeCsValue​(@Nullable
    -                                          String value)
    +protected @com.google.android.exoplayer2.C.RoleFlags int parseTvaAudioPurposeCsValue​(@Nullable + String value)
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/dash/offline/DashDownloader.html b/docs/doc/reference/com/google/android/exoplayer2/source/dash/offline/DashDownloader.html index 38834cc617..da11d4a0cf 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/dash/offline/DashDownloader.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/dash/offline/DashDownloader.html @@ -149,7 +149,7 @@ extends Description -HlsMediaPeriod​(HlsExtractorFactory extractorFactory, +HlsMediaPeriod​(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory, TransferListener mediaTransferListener, @@ -185,7 +185,7 @@ implements Allocator allocator, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, boolean allowChunklessPreparation, - int metadataType, + @com.google.android.exoplayer2.source.hls.HlsMediaSource.MetadataType int metadataType, boolean useSessionKeys)
    Creates an HLS media period.
    @@ -384,7 +384,7 @@ implements +
    @@ -778,7 +794,7 @@ public 
  • Summary: 
  • Nested | 
  • -
  • Field | 
  • +
  • Field | 
  • Constr | 
  • Method
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/hls/HlsMediaSource.MetadataType.html b/docs/doc/reference/com/google/android/exoplayer2/source/hls/HlsMediaSource.MetadataType.html index a7882ab665..e72c27836b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/hls/HlsMediaSource.MetadataType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/hls/HlsMediaSource.MetadataType.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    @Documented
     @Retention(SOURCE)
    +@Target(TYPE_USE)
     public static @interface HlsMediaSource.MetadataType
    The types of metadata that can be extracted from HLS streams. @@ -125,7 +126,7 @@ public static @interface HlsMediaSource.MetadataTy
  • HlsMediaSource.METADATA_TYPE_EMSG -

    See HlsMediaSource.Factory.setMetadataType(int).

  • +

    See HlsMediaSource.Factory.setMetadataType(int). diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.html b/docs/doc/reference/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.html index 0eb9418bdd..172efd8dad 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.html @@ -149,7 +149,7 @@ extends OutputConsumerAdapterV30() -

    Equivalent to OutputConsumerAdapterV30(primaryTrackManifestFormat= null, primaryTrackType= C.TRACK_TYPE_NONE, + -OutputConsumerAdapterV30​(Format primaryTrackManifestFormat, - int primaryTrackType, +OutputConsumerAdapterV30​(Format primaryTrackManifestFormat, + @com.google.android.exoplayer2.C.TrackType int primaryTrackType, boolean expectDummySeekMap)
    Creates a new instance.
    @@ -324,11 +324,11 @@ implements

    OutputConsumerAdapterV30

    public OutputConsumerAdapterV30()
    -
    Equivalent to OutputConsumerAdapterV30(primaryTrackManifestFormat= null, primaryTrackType= C.TRACK_TYPE_NONE, + - +
      @@ -336,15 +336,14 @@ implements Format primaryTrackManifestFormat, - int primaryTrackType, + @com.google.android.exoplayer2.C.TrackType int primaryTrackType, boolean expectDummySeekMap)
      Creates a new instance.
      Parameters:
      primaryTrackManifestFormat - The manifest-obtained format of the primary track, or null if not applicable.
      -
      primaryTrackType - The type of the primary track, or C.TRACK_TYPE_NONE if there is - no primary track. Must be one of the C.TRACK_TYPE_* constants.
      +
      primaryTrackType - The type of the primary track. C.TRACK_TYPE_NONE if there is no primary track.
      expectDummySeekMap - Whether the output consumer should expect an initial dummy seek map which should be exposed through getDummySeekMap().
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html index fae543fbd6..b661f639c0 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html @@ -112,7 +112,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); DefaultMediaSourceFactory.AdsLoaderProvider -
      Provides AdsLoader instances for media items that have ad tag URIs.
      +
      Provides AdsLoader instances for media items that have ad tag URIs.
      @@ -288,7 +288,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); LoopingMediaSource Deprecated. -
      To loop a MediaSource indefinitely, use Player.setRepeatMode(int) +
      To loop a MediaSource indefinitely, use Player.setRepeatMode(int) instead of this class.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html index 1bc073bbd3..2f64e1f7de 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html @@ -168,8 +168,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • com.google.android.exoplayer2.source.SinglePeriodTimeline
    -
  • com.google.android.exoplayer2.source.TrackGroup (implements android.os.Parcelable)
  • -
  • com.google.android.exoplayer2.source.TrackGroupArray (implements android.os.Parcelable)
  • +
  • com.google.android.exoplayer2.source.TrackGroup (implements com.google.android.exoplayer2.Bundleable)
  • +
  • com.google.android.exoplayer2.source.TrackGroupArray (implements com.google.android.exoplayer2.Bundleable)
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.Factory.html index d792cd6754..eca7ec1dff 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.Factory.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":42,"i3":42,"i4":10,"i5":42,"i6":10,"i7":10,"i8":10,"i9":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":42,"i4":42,"i5":10,"i6":42,"i7":10,"i8":10,"i9":10,"i10":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -87,7 +87,7 @@ loadScripts(document, 'script'); @@ -157,6 +157,23 @@ implements
    @@ -515,7 +558,7 @@ public Returns:
    The new RtspMediaSource.
    Throws:
    -
    NullPointerException - if MediaItem.playbackProperties is null.
    +
    NullPointerException - if MediaItem.localConfiguration is null.
    @@ -571,7 +614,7 @@ public 
  • Summary: 
  • Nested | 
  • -
  • Field | 
  • +
  • Field | 
  • Constr | 
  • Method
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.Factory.html b/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.Factory.html index 27e12c8905..d8c91e621b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.Factory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.Factory.html @@ -87,7 +87,7 @@ loadScripts(document, 'script'); @@ -147,6 +147,23 @@ implements
    @@ -659,7 +676,7 @@ public Returns:
    The new SsMediaSource.
    Throws:
    -
    NullPointerException - if MediaItem.playbackProperties is null.
    +
    NullPointerException - if MediaItem.localConfiguration is null.
    @@ -731,7 +748,7 @@ public 
  • Summary: 
  • Nested | 
  • -
  • Field | 
  • +
  • Field | 
  • Constr | 
  • Method
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.StreamElement.html b/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.StreamElement.html index 140f5f1b33..d5fe292c2e 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.StreamElement.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.StreamElement.html @@ -207,7 +207,7 @@ extends   -int +@com.google.android.exoplayer2.C.TrackType int type   @@ -229,9 +229,9 @@ extends Description -StreamElement​(String baseUri, +StreamElement​(String baseUri, String chunkTemplate, - int type, + @com.google.android.exoplayer2.C.TrackType int type, String subType, long timescale, String name, @@ -329,7 +329,7 @@ extends
  • type

    -
    public final int type
    +
    public final @com.google.android.exoplayer2.C.TrackType int type
  • @@ -433,7 +433,7 @@ public final  +
      @@ -441,7 +441,7 @@ public final String baseUri, String chunkTemplate, - int type, + @com.google.android.exoplayer2.C.TrackType int type, String subType, long timescale, String name, diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.html b/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.html index 73c741b16a..e4974a23d2 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.html @@ -149,7 +149,7 @@ extends
      public static final class Action.AddMediaItems
       extends Action
      - +
    @@ -202,11 +202,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -216,7 +216,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -201,11 +201,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -215,7 +215,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -201,11 +201,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -215,7 +215,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -203,11 +203,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -217,7 +217,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -203,11 +203,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -217,7 +217,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -203,11 +203,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -217,7 +217,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -179,7 +179,7 @@ extends SetMediaItems​(String tag, - int windowIndex, + int mediaItemIndex, long positionMs, MediaSource... mediaSources)   @@ -204,11 +204,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -218,7 +218,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -203,11 +203,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -217,7 +217,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -178,8 +178,8 @@ extends Description -SetRepeatMode​(String tag, - int repeatMode) +SetRepeatMode​(String tag, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode)   @@ -202,11 +202,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -216,7 +216,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    @@ -201,11 +201,11 @@ extends protected void -doActionImpl​(SimpleExoPlayer player, +doActionImpl​(ExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) -
    Called by Action.doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface, + @@ -215,7 +215,7 @@ extends Action -doActionAndScheduleNext, doActionAndScheduleNextImpl +doActionAndScheduleNext, doActionAndScheduleNextImpl
    Specified by:
    @@ -678,9 +678,9 @@ implements
  • buildExoPlayer

    -
    protected SimpleExoPlayer buildExoPlayer​(HostActivity host,
    -                                         Surface surface,
    -                                         MappingTrackSelector trackSelector)
    +
    protected ExoPlayer buildExoPlayer​(HostActivity host,
    +                                   Surface surface,
    +                                   MappingTrackSelector trackSelector)
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.Builder.html index c672ef49d6..f3208e376e 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.Builder.html @@ -135,7 +135,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    public static final class ExoPlayerTestRunner.Builder
     extends Object
    -
    Builder to set-up a ExoPlayerTestRunner. Default fake implementations will be used for +
    Builder to set-up an ExoPlayerTestRunner. Default fake implementations will be used for unset test properties.
    @@ -187,7 +187,7 @@ extends ExoPlayerTestRunner.Builder -initialSeek​(int windowIndex, +initialSeek​(int mediaItemIndex, long positionMs)
    Seeks before setting the media sources and preparing the player.
    @@ -304,8 +304,7 @@ extends ExoPlayerTestRunner.Builder skipSettingMediaSources() -
    Skips calling ExoPlayer.setMediaSources(List) before - preparing.
    +
    Skips calling ExoPlayer.setMediaSources(List) before preparing.
    @@ -393,12 +392,12 @@ extends
  • initialSeek

    -
    public ExoPlayerTestRunner.Builder initialSeek​(int windowIndex,
    +
    public ExoPlayerTestRunner.Builder initialSeek​(int mediaItemIndex,
                                                    long positionMs)
    Seeks before setting the media sources and preparing the player.
    Parameters:
    -
    windowIndex - The window index to seek to.
    +
    mediaItemIndex - The media item index to seek to.
    positionMs - The position in milliseconds to seek to.
    Returns:
    This builder.
    @@ -448,8 +447,8 @@ extends

    skipSettingMediaSources

    public ExoPlayerTestRunner.Builder skipSettingMediaSources()
    -
    Skips calling ExoPlayer.setMediaSources(List) before - preparing. Calling this method is not allowed after calls to setMediaSources(MediaSource...), setTimeline(Timeline) and/or setManifest(Object).
    +
    Skips calling ExoPlayer.setMediaSources(List) before preparing. Calling this method + is not allowed after calls to setMediaSources(MediaSource...), setTimeline(Timeline) and/or setManifest(Object).
    Returns:
    This builder.
    @@ -585,7 +584,7 @@ extends setActionSchedule
    public ExoPlayerTestRunner.Builder setActionSchedule​(ActionSchedule actionSchedule)
    Sets an ActionSchedule to be run by the test runner. The first action will be - executed immediately before SimpleExoPlayer.prepare().
    + executed immediately before Player.prepare().
  • Parameters:
    actionSchedule - An ActionSchedule to be used by the test runner.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html index 998861c593..45d546ed4b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -130,7 +130,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Implemented Interfaces:
    -
    AudioListener, DeviceListener, MetadataOutput, Player.EventListener, Player.Listener, ActionSchedule.Callback, TextOutput, VideoListener
    +
    Player.EventListener, Player.Listener, ActionSchedule.Callback

    public final class ExoPlayerTestRunner
    @@ -161,7 +161,7 @@ implements static class 
     ExoPlayerTestRunner.Builder
     
    -
    Builder to set-up a ExoPlayerTestRunner.
    +
    Builder to set-up an ExoPlayerTestRunner.
    @@ -216,150 +216,119 @@ implements void -assertMediaItemsTransitionedSame​(MediaItem... mediaItems) +assertNoPositionDiscontinuities() -
    Asserts that the media items reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided media - items.
    + void -assertMediaItemsTransitionReasonsEqual​(Integer... reasons) +assertPlaybackStatesEqual​(Integer... states) -
    Asserts that the media item transition reasons reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
    +
    Asserts that the playback states reported by Player.Listener.onPlaybackStateChanged(int) are equal to the provided playback states.
    void -assertNoPositionDiscontinuities() - - - - - -void -assertPlaybackStatesEqual​(Integer... states) - -
    Asserts that the playback states reported by Player.Listener.onPlaybackStateChanged(int) are equal to the provided playback states.
    - - - -void assertPlayedPeriodIndices​(Integer... periodIndices)
    Asserts that the indices of played periods is equal to the provided list of periods.
    - + void assertPositionDiscontinuityReasonsEqual​(Integer... discontinuityReasons) -
    Asserts that the discontinuity reasons reported by Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are +
    Asserts that the discontinuity reasons reported by Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are equal to the provided values.
    - + void assertTimelineChangeReasonsEqual​(Integer... reasons) -
    Asserts that the timeline change reasons reported by Player.Listener.onTimelineChanged(Timeline, int) are equal to the provided timeline change +
    Asserts that the timeline change reasons reported by Player.Listener.onTimelineChanged(Timeline, int) are equal to the provided timeline change reasons.
    - + void assertTimelinesSame​(Timeline... timelines) -
    Asserts that the timelines reported by Player.Listener.onTimelineChanged(Timeline, int) +
    Asserts that the timelines reported by Player.Listener.onTimelineChanged(Timeline, int) are the same to the provided timelines.
    - -void -assertTrackGroupsEqual​(TrackGroupArray trackGroupArray) - -
    Asserts that the last track group array reported by Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray) is equal to the provided - track group array.
    - - - + ExoPlayerTestRunner blockUntilActionScheduleFinished​(long timeoutMs)
    Blocks the current thread until the action schedule finished.
    - + ExoPlayerTestRunner blockUntilEnded​(long timeoutMs)
    Blocks the current thread until the test runner finishes.
    - + void onActionScheduleFinished()
    Called when action schedule finished executing all its actions.
    - + void -onMediaItemTransition​(MediaItem mediaItem, - int reason) +onMediaItemTransition​(MediaItem mediaItem, + @com.google.android.exoplayer2.Player.MediaItemTransitionReason int reason)
    Called when playback transitions to a media item or starts repeating a media item according to the current repeat mode.
    - + void -onPlaybackStateChanged​(int playbackState) +onPlaybackStateChanged​(@com.google.android.exoplayer2.Player.State int playbackState)
    Called when the value returned from Player.getPlaybackState() changes.
    - + void onPlayerError​(PlaybackException error)
    Called when an error occurs.
    - + void -onPositionDiscontinuity​(Player.PositionInfo oldPosition, +onPositionDiscontinuity​(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, - int reason) + @com.google.android.exoplayer2.Player.DiscontinuityReason int reason)
    Called when a position discontinuity occurs.
    - + void -onTimelineChanged​(Timeline timeline, - int reason) +onTimelineChanged​(Timeline timeline, + @com.google.android.exoplayer2.Player.TimelineChangeReason int reason)
    Called when the timeline has been refreshed.
    - -void -onTracksChanged​(TrackGroupArray trackGroups, - TrackSelectionArray trackSelections) - -
    Called when the available or selected tracks change.
    - - - + ExoPlayerTestRunner start()
    Starts the test runner on its own thread.
    - + ExoPlayerTestRunner start​(boolean doPrepare) @@ -379,21 +348,14 @@ implements Player.EventListener -onLoadingChanged, onMaxSeekToPreviousPositionChanged, onPlayerStateChanged, onPositionDiscontinuity, onSeekProcessed, onStaticMetadataChanged
  • +onLoadingChanged, onMaxSeekToPreviousPositionChanged, onPlayerStateChanged, onPositionDiscontinuity, onSeekProcessed, onTracksChanged, onTrackSelectionParametersChanged - @@ -526,7 +488,7 @@ implements

    assertTimelinesSame

    public void assertTimelinesSame​(Timeline... timelines)
    -
    Asserts that the timelines reported by Player.Listener.onTimelineChanged(Timeline, int) +
    Asserts that the timelines reported by Player.Listener.onTimelineChanged(Timeline, int) are the same to the provided timelines. This assert differs from testing equality by not comparing period ids which may be different due to id mapping of child source period ids.
    @@ -542,39 +504,10 @@ implements

    assertTimelineChangeReasonsEqual

    public void assertTimelineChangeReasonsEqual​(Integer... reasons)
    -
    Asserts that the timeline change reasons reported by Player.Listener.onTimelineChanged(Timeline, int) are equal to the provided timeline change +
    Asserts that the timeline change reasons reported by Player.Listener.onTimelineChanged(Timeline, int) are equal to the provided timeline change reasons.
    - - - - - - - -
      -
    • -

      assertMediaItemsTransitionReasonsEqual

      -
      public void assertMediaItemsTransitionReasonsEqual​(Integer... reasons)
      -
      Asserts that the media item transition reasons reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
      -
      -
      Parameters:
      -
      reasons - A list of expected transition reasons.
      -
      -
    • -
    @@ -582,22 +515,7 @@ implements

    assertPlaybackStatesEqual

    public void assertPlaybackStatesEqual​(Integer... states)
    -
    Asserts that the playback states reported by Player.Listener.onPlaybackStateChanged(int) are equal to the provided playback states.
    - - - - - - @@ -607,7 +525,7 @@ implements

    assertNoPositionDiscontinuities

    public void assertNoPositionDiscontinuities()
    -
    Asserts that Player.Listener.onPositionDiscontinuity(Player.PositionInfo, + @@ -618,7 +536,7 @@ implements

    assertPositionDiscontinuityReasonsEqual

    public void assertPositionDiscontinuityReasonsEqual​(Integer... discontinuityReasons)
    -
    Asserts that the discontinuity reasons reported by Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are +
    Asserts that the discontinuity reasons reported by Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are equal to the provided values.
    Parameters:
    @@ -643,7 +561,7 @@ implements + - + - - - -
      -
    • -

      onTracksChanged

      -
      public void onTracksChanged​(TrackGroupArray trackGroups,
      -                            TrackSelectionArray trackSelections)
      -
      Description copied from interface: Player.EventListener
      -
      Called when the available or selected tracks change. - -

      Player.EventListener.onEvents(Player, Events) will also be called to report this event along with - other events that happen in the same Looper message queue iteration.

      -
      -
      Specified by:
      -
      onTracksChanged in interface Player.EventListener
      -
      Specified by:
      -
      onTracksChanged in interface Player.Listener
      -
      Parameters:
      -
      trackGroups - The available tracks. Never null, but may be of length zero.
      -
      trackSelections - The selected tracks. Never null, but may contain null elements. A - concrete implementation may include null elements if it has a fixed number of renderer - components, wishes to report a TrackSelection for each of them, and has one or more - renderer components that is not assigned any selected tracks.
      -
      -
    • -
    - +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.LicenseServer.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.LicenseServer.html index 66ed5c64c3..c7c54432bd 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.LicenseServer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.LicenseServer.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":9,"i1":10,"i2":10,"i3":10}; +var data = {"i0":9,"i1":10,"i2":10,"i3":10,"i4":10,"i5":9}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -183,10 +183,20 @@ implements +ImmutableList<ImmutableList<Byte>> +getReceivedProvisionRequests() +  + + ImmutableList<ImmutableList<DrmInitData.SchemeData>> getReceivedSchemeDatas()   + +static FakeExoMediaDrm.LicenseServer +requiringProvisioningThenAllowingSchemeDatas​(List<DrmInitData.SchemeData>... schemeDatas) +  + + + + + + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html index 795140981e..f04c4224c1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -305,17 +305,18 @@ implements -ExoMediaCrypto -createMediaCrypto​(byte[] sessionId) +CryptoConfig +createCryptoConfig​(byte[] sessionId) -
    Creates an ExoMediaCrypto for a given session.
    +
    Creates a CryptoConfig that can be passed to a compatible decoder to allow decryption + of protected content using the specified session.
    -Class<com.google.android.exoplayer2.testutil.FakeExoMediaDrm.FakeExoMediaCrypto> -getExoMediaCryptoType() +@com.google.android.exoplayer2.C.CryptoType int +getCryptoType() - +
    Returns the type of CryptoConfig instances returned by ExoMediaDrm.createCryptoConfig(byte[]).
    @@ -398,6 +399,14 @@ implements +boolean +requiresSecureDecoder​(byte[] sessionId, + String mimeType) + +
    Returns whether the given session requires use of a secure decoder for the given MIME type.
    + + + void resetProvisioning() @@ -405,7 +414,7 @@ implements + void restoreKeys​(byte[] sessionId, byte[] keySetId) @@ -413,28 +422,28 @@ implements Restores persisted offline keys into a session.
    - + void setOnEventListener​(ExoMediaDrm.OnEventListener listener)
    Sets the listener for DRM events.
    - + void setOnExpirationUpdateListener​(ExoMediaDrm.OnExpirationUpdateListener listener)
    Sets the listener for session expiration events.
    - + void setOnKeyStatusChangeListener​(ExoMediaDrm.OnKeyStatusChangeListener listener)
    Sets the listener for key status change events.
    - + void setPropertyByteArray​(String propertyName, byte[] value) @@ -442,7 +451,7 @@ implements Sets the value of a byte array property.
    - + void setPropertyString​(String propertyName, String value) @@ -450,7 +459,7 @@ implements Sets the value of a string property.
    - + void triggerEvent​(Predicate<byte[]> sessionIdPredicate, int event, @@ -744,8 +753,7 @@ public FakeExoMediaDrm​(int maxConcurrentSessions)
    • provideKeyResponse

      -
      @Nullable
      -public byte[] provideKeyResponse​(byte[] scope,
      +
      public byte[] provideKeyResponse​(byte[] scope,
                                        byte[] response)
                                 throws NotProvisionedException,
                                        DeniedByServerException
      @@ -825,6 +833,26 @@ public byte[] provideKeyResponse​(byte[] scope,
    + + + +
      +
    • +

      requiresSecureDecoder

      +
      public boolean requiresSecureDecoder​(byte[] sessionId,
      +                                     String mimeType)
      +
      Description copied from interface: ExoMediaDrm
      +
      Returns whether the given session requires use of a secure decoder for the given MIME type. + Assumes a license policy that requires the highest level of security supported by the session.
      +
      +
      Specified by:
      +
      requiresSecureDecoder in interface ExoMediaDrm
      +
      Parameters:
      +
      sessionId - The ID of the session.
      +
      mimeType - The content MIME type to query.
      +
      +
    • +
    @@ -971,40 +999,41 @@ public  + - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExtractorOutput.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExtractorOutput.html index 5d69d2361b..aeaab83381 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExtractorOutput.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExtractorOutput.html @@ -244,7 +244,7 @@ implements endTracks()
    Called when all tracks have been identified, meaning no new trackId values will be - passed to ExtractorOutput.track(int, int).
    + passed to ExtractorOutput.track(int, int). @@ -368,18 +368,17 @@ implements public FakeTrackOutput track​(int id, int type) -
    Description copied from interface: ExtractorOutput
    +
    Description copied from interface: ExtractorOutput
    Called by the Extractor to get the TrackOutput for a specific track.

    The same TrackOutput is returned if multiple calls are made with the same id.

    Specified by:
    -
    track in interface ExtractorOutput
    +
    track in interface ExtractorOutput
    Parameters:
    id - A track identifier.
    -
    type - The type of the track. Typically one of the C - TRACK_TYPE_* constants.
    +
    type - The track type.
    Returns:
    The TrackOutput for the given track identifier.
    @@ -394,7 +393,7 @@ implements public void endTracks()
    Called when all tracks have been identified, meaning no new trackId values will be - passed to ExtractorOutput.track(int, int).
    + passed to ExtractorOutput.track(int, int).
    Specified by:
    endTracks in interface ExtractorOutput
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaChunk.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaChunk.html index 0bbaea92f4..3432d8ac6a 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaChunk.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaChunk.html @@ -310,6 +310,7 @@ extends Format trackFormat, long startTimeUs, long endTimeUs, + @SelectionReason int selectionReason)
    Creates a fake media chunk.
    @@ -317,7 +318,7 @@ extends Format.
    startTimeUs - The start time of the media, in microseconds.
    endTimeUs - The end time of the media, in microseconds.
    -
    selectionReason - The reason for selecting this format.
    +
    selectionReason - One of the selection reasons.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.html index 5e9896d0f9..748ddb8f5b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.html @@ -165,7 +165,7 @@ implements Renderer -Renderer.State, Renderer.VideoScalingMode, Renderer.WakeupListener +Renderer.MessageType, Renderer.State, Renderer.WakeupListener + +
    • @@ -139,7 +144,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    public class StubExoPlayer
    -extends BasePlayer
    +extends StubPlayer
     implements ExoPlayer
    An abstract ExoPlayer implementation that throws UnsupportedOperationException from every method.
    @@ -161,7 +166,7 @@ implements ExoPlayer -ExoPlayer.AudioComponent, ExoPlayer.AudioOffloadListener, ExoPlayer.Builder, ExoPlayer.DeviceComponent, ExoPlayer.MetadataComponent, ExoPlayer.TextComponent, ExoPlayer.VideoComponent +ExoPlayer.AudioComponent, ExoPlayer.AudioOffloadListener, ExoPlayer.Builder, ExoPlayer.DeviceComponent, ExoPlayer.TextComponent, ExoPlayer.VideoComponent @@ -241,49 +246,41 @@ implements void +addAnalyticsListener​(AnalyticsListener listener) + +
    Adds an AnalyticsListener to receive analytics events.
    + + + +void addAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener)
    Adds a listener to receive audio offload events.
    - + void addListener​(Player.EventListener listener)
    Registers a listener to receive events from the player.
    - -void -addListener​(Player.Listener listener) - -
    Registers a listener to receive all events from the player.
    - - void -addMediaItems​(int index, - List<MediaItem> mediaItems) - -
    Adds a list of media items at the given index of the playlist.
    - - - -void addMediaSource​(int index, MediaSource mediaSource)
    Adds a media source at the given index of the playlist.
    - + void addMediaSource​(MediaSource mediaSource)
    Adds a media source to the end of the playlist.
    - + void addMediaSources​(int index, List<MediaSource> mediaSources) @@ -291,546 +288,276 @@ implements Adds a list of media sources at the given index of the playlist. - + void addMediaSources​(List<MediaSource> mediaSources)
    Adds a list of media sources to the end of the playlist.
    + +void +clearAuxEffectInfo() + +
    Detaches any previously attached auxiliary audio effect from the underlying audio track.
    + + void -clearVideoSurface() +clearCameraMotionListener​(CameraMotionListener listener) -
    Clears any Surface, SurfaceHolder, SurfaceView or TextureView - currently set on the player.
    +
    Clears the listener which receives camera motion events if it matches the one passed.
    void -clearVideoSurface​(Surface surface) +clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener) -
    Clears the Surface onto which video is being rendered if it matches the one passed.
    +
    Clears the listener which receives video frame metadata events if it matches the one passed.
    -void -clearVideoSurfaceHolder​(SurfaceHolder surfaceHolder) - -
    Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed.
    - - - -void -clearVideoSurfaceView​(SurfaceView surfaceView) - -
    Clears the SurfaceView onto which video is being rendered if it matches the one passed.
    - - - -void -clearVideoTextureView​(TextureView textureView) - -
    Clears the TextureView onto which video is being rendered if it matches the one passed.
    - - - PlayerMessage createMessage​(PlayerMessage.Target target)
    Creates a message that can be sent to a PlayerMessage.Target.
    - -void -decreaseDeviceVolume() - -
    Decreases the volume of the device.
    - - - + boolean experimentalIsSleepingForOffload()
    Returns whether the player has paused its main loop to save power in offload scheduling mode.
    - + void experimentalSetOffloadSchedulingEnabled​(boolean offloadSchedulingEnabled)
    Sets whether audio offload scheduling is enabled.
    - -Looper -getApplicationLooper() + +AnalyticsCollector +getAnalyticsCollector() -
    Returns the Looper associated with the application thread that's used to access the - player and on which player events are received.
    +
    Returns the AnalyticsCollector used for collecting analytics events.
    - -AudioAttributes -getAudioAttributes() - -
    Returns the attributes for audio playback.
    - - - + ExoPlayer.AudioComponent getAudioComponent() -
    Returns the component of this player for audio output, or null if audio is not supported.
    +
    Deprecated.
    - -Player.Commands -getAvailableCommands() + +DecoderCounters +getAudioDecoderCounters() -
    Returns the player's currently available Player.Commands.
    +
    Returns DecoderCounters for audio, or null if no audio is being played.
    - -long -getBufferedPosition() + +Format +getAudioFormat() -
    Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
    +
    Returns the audio format currently being played, or null if no audio is being played.
    - + +int +getAudioSessionId() + +
    Returns the audio session identifier, or C.AUDIO_SESSION_ID_UNSET if not set.
    + + + Clock getClock()
    Returns the Clock used for playback.
    - -long -getContentBufferedPosition() - -
    If Player.isPlayingAd() returns true, returns an estimate of the content position in - the current content window up to which data is buffered, in milliseconds.
    - - - -long -getContentPosition() - -
    If Player.isPlayingAd() returns true, returns the content position that will be - played once all ads in the ad group have finished playing, in milliseconds.
    - - - -int -getCurrentAdGroupIndex() - -
    If Player.isPlayingAd() returns true, returns the index of the ad group in the period - currently being played.
    - - - -int -getCurrentAdIndexInAdGroup() - -
    If Player.isPlayingAd() returns true, returns the index of the ad in its ad group.
    - - - -List<Cue> -getCurrentCues() - -
    Returns the current Cues.
    - - - -int -getCurrentPeriodIndex() - -
    Returns the index of the period currently being played.
    - - - -long -getCurrentPosition() - -
    Returns the playback position in the current content window or ad, in milliseconds, or the - prospective position in milliseconds if the current timeline is - empty.
    - - - -List<Metadata> -getCurrentStaticMetadata() + +ExoPlayer.DeviceComponent +getDeviceComponent()
    Deprecated.
    - -Timeline -getCurrentTimeline() - -
    Returns the current Timeline.
    - - - -TrackGroupArray -getCurrentTrackGroups() - -
    Returns the available track groups.
    - - - -TrackSelectionArray -getCurrentTrackSelections() - -
    Returns the current track selections.
    - - - -int -getCurrentWindowIndex() - -
    Returns the index of the current window in the timeline, or the prospective window index if the current timeline is empty.
    - - - -ExoPlayer.DeviceComponent -getDeviceComponent() - -
    Returns the component of this player for playback device, or null if it's not supported.
    - - - -DeviceInfo -getDeviceInfo() - -
    Gets the device information.
    - - - -int -getDeviceVolume() - -
    Gets the current volume of the device.
    - - - -long -getDuration() - -
    Returns the duration of the current content window or ad in milliseconds, or C.TIME_UNSET if the duration is not known.
    - - - -int -getMaxSeekToPreviousPosition() - -
    Returns the maximum position for which Player.seekToPrevious() seeks to the previous window, - in milliseconds.
    - - - -MediaMetadata -getMediaMetadata() - -
    Returns the current combined MediaMetadata, or MediaMetadata.EMPTY if not - supported.
    - - - -ExoPlayer.MetadataComponent -getMetadataComponent() - -
    Returns the component of this player for metadata output, or null if metadata is not supported.
    - - - + boolean getPauseAtEndOfMediaItems()
    Returns whether the player pauses playback at the end of each media item.
    - + Looper getPlaybackLooper()
    Returns the Looper associated with the playback thread.
    - -PlaybackParameters -getPlaybackParameters() - -
    Returns the currently active playback parameters.
    - - - -int -getPlaybackState() - -
    Returns the current playback state of the player.
    - - - -int -getPlaybackSuppressionReason() - -
    Returns the reason why playback is suppressed even though Player.getPlayWhenReady() is - true, or Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
    - - - + ExoPlaybackException getPlayerError() -
    Equivalent to Player.getPlayerError(), except the exception is guaranteed to be an - ExoPlaybackException.
    +
    Returns the error that caused playback to fail.
    - -MediaMetadata -getPlaylistMetadata() - -
    Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
    - - - -boolean -getPlayWhenReady() - -
    Whether playback will proceed when Player.getPlaybackState() == Player.STATE_READY.
    - - - + int getRendererCount()
    Returns the number of renderers.
    - + int getRendererType​(int index)
    Returns the track type that the renderer at a given index handles.
    - -int -getRepeatMode() - -
    Returns the current Player.RepeatMode used for playback.
    - - - -long -getSeekBackIncrement() - -
    Returns the Player.seekBack() increment.
    - - - -long -getSeekForwardIncrement() - -
    Returns the Player.seekForward() increment.
    - - - + SeekParameters getSeekParameters()
    Returns the currently active SeekParameters of the player.
    - + boolean -getShuffleModeEnabled() +getSkipSilenceEnabled() -
    Returns whether shuffling of windows is enabled.
    +
    Returns whether skipping silences in the audio stream is enabled.
    - + ExoPlayer.TextComponent getTextComponent() -
    Returns the component of this player for text output, or null if text is not supported.
    +
    Deprecated.
    - -long -getTotalBufferedDuration() - -
    Returns an estimate of the total buffered duration from the current position, in milliseconds.
    - - - + TrackSelector getTrackSelector()
    Returns the track selector that this player uses, or null if track selection is not supported.
    - + +int +getVideoChangeFrameRateStrategy() + + + + + ExoPlayer.VideoComponent getVideoComponent() -
    Returns the component of this player for video output, or null if video is not supported.
    +
    Deprecated.
    - -VideoSize -getVideoSize() + +DecoderCounters +getVideoDecoderCounters() -
    Gets the size of the video.
    +
    Returns DecoderCounters for video, or null if no video is being played.
    - -float -getVolume() + +Format +getVideoFormat() -
    Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    +
    Returns the video format currently being played, or null if no video is being played.
    - -void -increaseDeviceVolume() + +int +getVideoScalingMode() -
    Increases the volume of the device.
    +
    Returns the C.VideoScalingMode.
    - -boolean -isDeviceMuted() - -
    Gets whether the device is muted or not.
    - - - -boolean -isLoading() - -
    Whether the player is currently loading the source.
    - - - -boolean -isPlayingAd() - -
    Returns whether the player is currently playing an ad.
    - - - -void -moveMediaItems​(int fromIndex, - int toIndex, - int newIndex) - -
    Moves the media item range to the new index.
    - - - -void -prepare() - -
    Deprecated. - -
    - - - + void prepare​(MediaSource mediaSource) -
    Deprecated. - -
    +
    Deprecated.
    - + void prepare​(MediaSource mediaSource, boolean resetPosition, boolean resetState) -
    Deprecated. - -
    +
    Deprecated.
    - + void -release() +removeAnalyticsListener​(AnalyticsListener listener) -
    Releases the player.
    +
    Removes an AnalyticsListener.
    - + void removeAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener)
    Removes a listener of audio offload events.
    - + void removeListener​(Player.EventListener listener) -
    Unregister a listener registered through Player.addListener(EventListener).
    +
    Unregister a listener registered through ExoPlayer.addListener(EventListener).
    - -void -removeListener​(Player.Listener listener) - -
    Unregister a listener registered through Player.addListener(Listener).
    - - - -void -removeMediaItems​(int fromIndex, - int toIndex) - -
    Removes a range of media items from the playlist.
    - - - + void retry() -
    Deprecated. -
    Use prepare() instead.
    -
    +
    Deprecated.
    - + void -seekTo​(int windowIndex, - long positionMs) +setAudioAttributes​(AudioAttributes audioAttributes, + boolean handleAudioFocus) -
    Seeks to a position specified in milliseconds in the specified window.
    +
    Sets the attributes for audio playback, used by the underlying audio track.
    - + void -setDeviceMuted​(boolean muted) +setAudioSessionId​(int audioSessionId) -
    Sets the mute state of the device.
    +
    Sets the ID of the audio session to attach to the underlying AudioTrack.
    - + void -setDeviceVolume​(int volume) +setAuxEffectInfo​(AuxEffectInfo auxEffectInfo) -
    Sets the volume of the device.
    +
    Sets information on an auxiliary audio effect to attach to the underlying audio track.
    - + +void +setCameraMotionListener​(CameraMotionListener listener) + +
    Sets a listener of camera motion events.
    + + + void setForegroundMode​(boolean foregroundMode) @@ -838,24 +565,22 @@ implements + void -setMediaItems​(List<MediaItem> mediaItems, - boolean resetPosition) +setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy) -
    Clears the playlist and adds the specified MediaItems.
    +
    Sets whether the player should pause automatically when audio is rerouted from a headset to + device speakers.
    - + void -setMediaItems​(List<MediaItem> mediaItems, - int startWindowIndex, - long startPositionMs) +setHandleWakeLock​(boolean handleWakeLock) -
    Clears the playlist and adds the specified MediaItems.
    +
    Deprecated.
    - + void setMediaSource​(MediaSource mediaSource) @@ -863,7 +588,7 @@ implements + void setMediaSource​(MediaSource mediaSource, boolean resetPosition) @@ -871,7 +596,7 @@ implements Clears the playlist and adds the specified MediaSource. - + void setMediaSource​(MediaSource mediaSource, long startPositionMs) @@ -879,7 +604,7 @@ implements Clears the playlist and adds the specified MediaSource. - + void setMediaSources​(List<MediaSource> mediaSources) @@ -887,7 +612,7 @@ implements + void setMediaSources​(List<MediaSource> mediaSources, boolean resetPosition) @@ -895,119 +620,100 @@ implements Clears the playlist and adds the specified MediaSources. - + void setMediaSources​(List<MediaSource> mediaSources, - int startWindowIndex, + int startMediaItemIndex, long startPositionMs)
    Clears the playlist and adds the specified MediaSources.
    - + void setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems)
    Sets whether to pause playback at the end of each media item.
    - + void -setPlaybackParameters​(PlaybackParameters playbackParameters) +setPriorityTaskManager​(PriorityTaskManager priorityTaskManager) -
    Attempts to set the playback parameters.
    +
    Sets a PriorityTaskManager, or null to clear a previously set priority task manager.
    - -void -setPlaylistMetadata​(MediaMetadata mediaMetadata) - -
    Sets the playlist MediaMetadata.
    - - - -void -setPlayWhenReady​(boolean playWhenReady) - -
    Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY.
    - - - -void -setRepeatMode​(int repeatMode) - -
    Sets the Player.RepeatMode to be used for playback.
    - - - + void setSeekParameters​(SeekParameters seekParameters)
    Sets the parameters that control how seek operations are performed.
    - -void -setShuffleModeEnabled​(boolean shuffleModeEnabled) - -
    Sets whether shuffling of windows is enabled.
    - - - + void setShuffleOrder​(ShuffleOrder shuffleOrder)
    Sets the shuffle order.
    - + void -setVideoSurface​(Surface surface) +setSkipSilenceEnabled​(boolean skipSilenceEnabled) -
    Sets the Surface onto which video will be rendered.
    +
    Sets whether skipping silences in the audio stream is enabled.
    - + void -setVideoSurfaceHolder​(SurfaceHolder surfaceHolder) +setThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread) -
    Sets the SurfaceHolder that holds the Surface onto which video will be - rendered.
    +
    Deprecated.
    - + void -setVideoSurfaceView​(SurfaceView surfaceView) +setVideoChangeFrameRateStrategy​(int videoChangeFrameRateStrategy) -
    Sets the SurfaceView onto which video will be rendered.
    +
    Sets a C.VideoChangeFrameRateStrategy that will be used by the player when provided + with a video output Surface.
    - + void -setVideoTextureView​(TextureView textureView) +setVideoFrameMetadataListener​(VideoFrameMetadataListener listener) -
    Sets the TextureView onto which video will be rendered.
    +
    Sets a listener to receive video frame metadata events.
    - + void -setVolume​(float audioVolume) +setVideoScalingMode​(int videoScalingMode) -
    Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
    + - + void -stop​(boolean reset) -  +setWakeMode​(int wakeMode) + +
    Sets how the player should keep the device awake for playback when the screen is off.
    + + @@ -1064,9 +770,9 @@ implements
  • getAudioComponent

    -
    public ExoPlayer.AudioComponent getAudioComponent()
    -
    Description copied from interface: ExoPlayer
    -
    Returns the component of this player for audio output, or null if audio is not supported.
    +
    @Deprecated
    +public ExoPlayer.AudioComponent getAudioComponent()
    +
    Deprecated.
    Specified by:
    getAudioComponent in interface ExoPlayer
    @@ -1079,9 +785,9 @@ implements
  • getVideoComponent

    -
    public ExoPlayer.VideoComponent getVideoComponent()
    -
    Description copied from interface: ExoPlayer
    -
    Returns the component of this player for video output, or null if video is not supported.
    +
    @Deprecated
    +public ExoPlayer.VideoComponent getVideoComponent()
    +
    Deprecated.
    Specified by:
    getVideoComponent in interface ExoPlayer
    @@ -1094,39 +800,24 @@ implements
  • getTextComponent

    -
    public ExoPlayer.TextComponent getTextComponent()
    -
    Description copied from interface: ExoPlayer
    -
    Returns the component of this player for text output, or null if text is not supported.
    +
    @Deprecated
    +public ExoPlayer.TextComponent getTextComponent()
    +
    Deprecated.
    Specified by:
    getTextComponent in interface ExoPlayer
  • - - - -
    • getDeviceComponent

      -
      public ExoPlayer.DeviceComponent getDeviceComponent()
      -
      Description copied from interface: ExoPlayer
      -
      Returns the component of this player for playback device, or null if it's not supported.
      +
      @Deprecated
      +public ExoPlayer.DeviceComponent getDeviceComponent()
      +
      Deprecated.
      Specified by:
      getDeviceComponent in interface ExoPlayer
      @@ -1148,22 +839,6 @@ implements - - -
        -
      • -

        getApplicationLooper

        -
        public Looper getApplicationLooper()
        -
        Description copied from interface: Player
        -
        Returns the Looper associated with the application thread that's used to access the - player and on which player events are received.
        -
        -
        Specified by:
        -
        getApplicationLooper in interface Player
        -
        -
      • -
      @@ -1179,27 +854,6 @@ implements - - -
        -
      • -

        addListener

        -
        public void addListener​(Player.Listener listener)
        -
        Description copied from interface: Player
        -
        Registers a listener to receive all events from the player. - -

        The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

        -
        -
        Specified by:
        -
        addListener in interface Player
        -
        Parameters:
        -
        listener - The listener to register.
        -
        -
      • -
      @@ -1207,38 +861,18 @@ implements

      addListener

      public void addListener​(Player.EventListener listener)
      -
      Description copied from interface: Player
      +
      Description copied from interface: ExoPlayer
      Registers a listener to receive events from the player. -

      The listener's methods will be called on the thread that was used to construct the player. - However, if the thread used to construct the player does not have a Looper, then the - listener will be called on the main thread.

      +

      The listener's methods will be called on the thread associated with Player.getApplicationLooper().

      Specified by:
      -
      addListener in interface Player
      +
      addListener in interface ExoPlayer
      Parameters:
      listener - The listener to register.
    - - - -
      -
    • -

      removeListener

      -
      public void removeListener​(Player.Listener listener)
      -
      Description copied from interface: Player
      -
      Unregister a listener registered through Player.addListener(Listener). The listener will no - longer receive events.
      -
      -
      Specified by:
      -
      removeListener in interface Player
      -
      Parameters:
      -
      listener - The listener to unregister.
      -
      -
    • -
    @@ -1246,12 +880,12 @@ implements

    removeListener

    public void removeListener​(Player.EventListener listener)
    -
    Description copied from interface: Player
    -
    Unregister a listener registered through Player.addListener(EventListener). The listener will +
    Description copied from interface: ExoPlayer
    +
    Unregister a listener registered through ExoPlayer.addListener(EventListener). The listener will no longer receive events from the player.
    Specified by:
    -
    removeListener in interface Player
    +
    removeListener in interface ExoPlayer
    Parameters:
    listener - The listener to unregister.
    @@ -1291,44 +925,52 @@ implements + - + + + + + @@ -1339,14 +981,20 @@ public int getPlaybackSuppressionReason()
  • getPlayerError

    public ExoPlaybackException getPlayerError()
    -
    Description copied from interface: ExoPlayer
    -
    Equivalent to Player.getPlayerError(), except the exception is guaranteed to be an - ExoPlaybackException.
    +
    Description copied from interface: Player
    +
    Returns the error that caused playback to fail. This is the same error that will have been + reported via Player.Listener.onPlayerError(PlaybackException) at the time of failure. It can + be queried using this method until the player is re-prepared. + +

    Note that this method will always return null if Player.getPlaybackState() is not + Player.STATE_IDLE.

    Specified by:
    getPlayerError in interface ExoPlayer
    Specified by:
    getPlayerError in interface Player
    +
    Overrides:
    +
    getPlayerError in class StubPlayer
    Returns:
    The error, or null.
    See Also:
    @@ -1362,34 +1010,13 @@ public int getPlaybackSuppressionReason()

    retry

    @Deprecated
     public void retry()
    -
    Deprecated. -
    Use prepare() instead.
    -
    +
    Deprecated.
    Specified by:
    retry in interface ExoPlayer
  • - - - - @@ -1398,9 +1025,7 @@ public void prepare()

    prepare

    @Deprecated
     public void prepare​(MediaSource mediaSource)
    -
    Deprecated. - -
    +
    Deprecated.
    Specified by:
    prepare in interface ExoPlayer
    @@ -1417,61 +1042,13 @@ public void prepare​(MediaSource mediaSource, boolean resetPosition, boolean resetState) -
    Deprecated. - -
    +
    Deprecated.
    Specified by:
    prepare in interface ExoPlayer
  • - - - - - - - -
      -
    • -

      setMediaItems

      -
      public void setMediaItems​(List<MediaItem> mediaItems,
      -                          int startWindowIndex,
      -                          long startPositionMs)
      -
      Description copied from interface: Player
      -
      Clears the playlist and adds the specified MediaItems.
      -
      -
      Specified by:
      -
      setMediaItems in interface Player
      -
      Parameters:
      -
      mediaItems - The new MediaItems.
      -
      startWindowIndex - The window index to start playback from. If C.INDEX_UNSET is - passed, the current position is not reset.
      -
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given window is used. In any case, if - startWindowIndex is set to C.INDEX_UNSET, this parameter is ignored and the - position is not reset at all.
      -
      -
    • -
    @@ -1525,7 +1102,7 @@ public void prepare​(Parameters:
    mediaSource - The new MediaSource.
    resetPosition - Whether the playback position should be reset to the default position. If - false, playback will start from the position defined by Player.getCurrentWindowIndex() + false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
  • @@ -1565,7 +1142,7 @@ public void prepare​(mediaSources - The new MediaSources.
    resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined - by Player.getCurrentWindowIndex() and Player.getCurrentPosition().
    + by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
    @@ -1576,7 +1153,7 @@ public void prepare​(

    setMediaSources

    public void setMediaSources​(List<MediaSource> mediaSources,
    -                            int startWindowIndex,
    +                            int startMediaItemIndex,
                                 long startPositionMs)
    Description copied from interface: ExoPlayer
    Clears the playlist and adds the specified MediaSources.
    @@ -1585,31 +1162,10 @@ public void prepare​(setMediaSources in interface ExoPlayer
    Parameters:
    mediaSources - The new MediaSources.
    -
    startWindowIndex - The window index to start playback from. If C.INDEX_UNSET is - passed, the current position is not reset.
    -
    startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given window is used. In any case, if - startWindowIndex is set to C.INDEX_UNSET, this parameter is ignored and the - position is not reset at all.
    -
    - - - - - -
      -
    • -

      addMediaItems

      -
      public void addMediaItems​(int index,
      -                          List<MediaItem> mediaItems)
      -
      Description copied from interface: Player
      -
      Adds a list of media items at the given index of the playlist.
      -
      -
      Specified by:
      -
      addMediaItems in interface Player
      -
      Parameters:
      -
      index - The index at which to add the media items. If the index is larger than the size of - the playlist, the media items are added to the end of the playlist.
      -
      mediaItems - The MediaItems to add.
      +
      startMediaItemIndex - The media item index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
      +
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given media item is used. In any case, + if startMediaItemIndex is set to C.INDEX_UNSET, this parameter is ignored + and the position is not reset at all.
    @@ -1685,153 +1241,6 @@ public void prepare​( - - - -
      -
    • -

      moveMediaItems

      -
      public void moveMediaItems​(int fromIndex,
      -                           int toIndex,
      -                           int newIndex)
      -
      Description copied from interface: Player
      -
      Moves the media item range to the new index.
      -
      -
      Specified by:
      -
      moveMediaItems in interface Player
      -
      Parameters:
      -
      fromIndex - The start of the range to move.
      -
      toIndex - The first item not to be included in the range (exclusive).
      -
      newIndex - The new index of the first media item of the range. If the new index is larger - than the size of the remaining playlist after removing the range, the range is moved to the - end of the playlist.
      -
      -
    • -
    - - - -
      -
    • -

      removeMediaItems

      -
      public void removeMediaItems​(int fromIndex,
      -                             int toIndex)
      -
      Description copied from interface: Player
      -
      Removes a range of media items from the playlist.
      -
      -
      Specified by:
      -
      removeMediaItems in interface Player
      -
      Parameters:
      -
      fromIndex - The index at which to start removing media items.
      -
      toIndex - The index of the first item to be kept (exclusive). If the index is larger than - the size of the playlist, media items to the end of the playlist are removed.
      -
      -
    • -
    - - - - - - - -
      -
    • -

      setPlayWhenReady

      -
      public void setPlayWhenReady​(boolean playWhenReady)
      -
      Description copied from interface: Player
      -
      Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY. - -

      If the player is already in the ready state then this method pauses and resumes playback.

      -
      -
      Specified by:
      -
      setPlayWhenReady in interface Player
      -
      Parameters:
      -
      playWhenReady - Whether playback should proceed when ready.
      -
      -
    • -
    - - - - - - - -
      -
    • -

      setRepeatMode

      -
      public void setRepeatMode​(@RepeatMode
      -                          int repeatMode)
      -
      Description copied from interface: Player
      -
      Sets the Player.RepeatMode to be used for playback.
      -
      -
      Specified by:
      -
      setRepeatMode in interface Player
      -
      Parameters:
      -
      repeatMode - The repeat mode.
      -
      -
    • -
    - - - - @@ -1849,172 +1258,281 @@ public void prepare​( - +
    • -

      setShuffleModeEnabled

      -
      public void setShuffleModeEnabled​(boolean shuffleModeEnabled)
      -
      Description copied from interface: Player
      -
      Sets whether shuffling of windows is enabled.
      -
      -
      Specified by:
      -
      setShuffleModeEnabled in interface Player
      -
      Parameters:
      -
      shuffleModeEnabled - Whether shuffling is enabled.
      -
      -
    • -
    - - - - - - - - - - - -
      -
    • -

      seekTo

      -
      public void seekTo​(int windowIndex,
      -                   long positionMs)
      -
      Description copied from interface: Player
      -
      Seeks to a position specified in milliseconds in the specified window.
      -
      -
      Specified by:
      -
      seekTo in interface Player
      -
      Parameters:
      -
      windowIndex - The index of the window.
      -
      positionMs - The seek position in the specified window, or C.TIME_UNSET to seek to - the window's default position.
      -
      -
    • -
    - - - - - - - - - - - - - - - -
      -
    • -

      setPlaybackParameters

      -
      public void setPlaybackParameters​(PlaybackParameters playbackParameters)
      -
      Description copied from interface: Player
      -
      Attempts to set the playback parameters. Passing PlaybackParameters.DEFAULT resets the - player to the default, which means there is no speed or pitch adjustment. +

      setAudioAttributes

      +
      public void setAudioAttributes​(AudioAttributes audioAttributes,
      +                               boolean handleAudioFocus)
      +
      Description copied from interface: ExoPlayer
      +
      Sets the attributes for audio playback, used by the underlying audio track. If not set, the + default audio attributes will be used. They are suitable for general media playback. -

      Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently - active playback parameters change.

      +

      Setting the audio attributes during playback may introduce a short gap in audio output as + the audio track is recreated. A new audio session id will also be generated. + +

      If tunneling is enabled by the track selector, the specified audio attributes will be + ignored, but they will take effect if audio is later played without tunneling. + +

      If the device is running a build before platform API version 21, audio attributes cannot be + set directly on the underlying audio track. In this case, the usage will be mapped onto an + equivalent stream type using Util.getStreamTypeForAudioUsage(int). + +

      If audio focus should be handled, the AudioAttributes.usage must be C.USAGE_MEDIA or C.USAGE_GAME. Other usages will throw an IllegalArgumentException.

      Specified by:
      -
      setPlaybackParameters in interface Player
      +
      setAudioAttributes in interface ExoPlayer
      Parameters:
      -
      playbackParameters - The playback parameters.
      +
      audioAttributes - The attributes to use for audio playback.
      +
      handleAudioFocus - True if the player should handle audio focus, false otherwise.
    - + + + + + + + + +
      +
    • +

      setAuxEffectInfo

      +
      public void setAuxEffectInfo​(AuxEffectInfo auxEffectInfo)
      +
      Description copied from interface: ExoPlayer
      +
      Sets information on an auxiliary audio effect to attach to the underlying audio track.
      +
      +
      Specified by:
      +
      setAuxEffectInfo in interface ExoPlayer
      +
      +
    • +
    + + + +
      +
    • +

      clearAuxEffectInfo

      +
      public void clearAuxEffectInfo()
      +
      Description copied from interface: ExoPlayer
      +
      Detaches any previously attached auxiliary audio effect from the underlying audio track.
      +
      +
      Specified by:
      +
      clearAuxEffectInfo in interface ExoPlayer
      +
      +
    • +
    + + + +
      +
    • +

      setSkipSilenceEnabled

      +
      public void setSkipSilenceEnabled​(boolean skipSilenceEnabled)
      +
      Description copied from interface: ExoPlayer
      +
      Sets whether skipping silences in the audio stream is enabled.
      +
      +
      Specified by:
      +
      setSkipSilenceEnabled in interface ExoPlayer
      +
      Parameters:
      +
      skipSilenceEnabled - Whether skipping silences in the audio stream is enabled.
      +
      +
    • +
    + + + +
      +
    • +

      getSkipSilenceEnabled

      +
      public boolean getSkipSilenceEnabled()
      +
      Description copied from interface: ExoPlayer
      +
      Returns whether skipping silences in the audio stream is enabled.
      +
      +
      Specified by:
      +
      getSkipSilenceEnabled in interface ExoPlayer
      +
      +
    • +
    + + + + + + + + + + + + + + + + + + + +
      +
    • +

      setVideoFrameMetadataListener

      +
      public void setVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
      +
      Description copied from interface: ExoPlayer
      +
      Sets a listener to receive video frame metadata events. + +

      This method is intended to be called by the same component that sets the Surface + onto which video will be rendered. If using ExoPlayer's standard UI components, this method + should not be called directly from application code.

      +
      +
      Specified by:
      +
      setVideoFrameMetadataListener in interface ExoPlayer
      +
      Parameters:
      +
      listener - The listener.
      +
      +
    • +
    + + + +
      +
    • +

      clearVideoFrameMetadataListener

      +
      public void clearVideoFrameMetadataListener​(VideoFrameMetadataListener listener)
      +
      Description copied from interface: ExoPlayer
      +
      Clears the listener which receives video frame metadata events if it matches the one passed. + Else does nothing.
      +
      +
      Specified by:
      +
      clearVideoFrameMetadataListener in interface ExoPlayer
      +
      Parameters:
      +
      listener - The listener to clear.
      +
      +
    • +
    + + + + + + + +
      +
    • +

      clearCameraMotionListener

      +
      public void clearCameraMotionListener​(CameraMotionListener listener)
      +
      Description copied from interface: ExoPlayer
      +
      Clears the listener which receives camera motion events if it matches the one passed. Else does + nothing.
      +
      +
      Specified by:
      +
      clearCameraMotionListener in interface ExoPlayer
      +
      Parameters:
      +
      listener - The listener to clear.
    @@ -2051,35 +1569,6 @@ public void prepare​( - - - -
      -
    • -

      stop

      -
      public void stop​(boolean reset)
      -
      -
      Specified by:
      -
      stop in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      release

      -
      public void release()
      -
      Description copied from interface: Player
      -
      Releases the player. This method must be called when the player is no longer required. The - player must not be used after calling this method.
      -
      -
      Specified by:
      -
      release in interface Player
      -
      -
    • -
    @@ -2091,8 +1580,8 @@ public void prepare​(Creates a message that can be sent to a PlayerMessage.Target. By default, the message will be delivered immediately without blocking on the playback thread. The default PlayerMessage.getType() is 0 and the default PlayerMessage.getPayload() is null. If a position is specified with PlayerMessage.setPosition(long), the message will be - delivered at this position in the current window defined by Player.getCurrentWindowIndex(). - Alternatively, the message can be sent at a specific window using PlayerMessage.setPosition(int, long). + delivered at this position in the current media item defined by Player.getCurrentMediaItemIndex(). Alternatively, the message can be sent at a specific mediaItem + using PlayerMessage.setPosition(int, long).
    Specified by:
    createMessage in interface ExoPlayer
    @@ -2132,7 +1621,7 @@ public void prepare​(Parameters:
    index - The index of the renderer.
    Returns:
    -
    One of the TRACK_TYPE_* constants defined in C.
    +
    The track type that the renderer handles.
    @@ -2152,682 +1641,6 @@ public  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      -
    • -

      getCurrentPeriodIndex

      -
      public int getCurrentPeriodIndex()
      -
      Description copied from interface: Player
      -
      Returns the index of the period currently being played.
      -
      -
      Specified by:
      -
      getCurrentPeriodIndex in interface Player
      -
      -
    • -
    - - - - - - - -
      -
    • -

      getDuration

      -
      public long getDuration()
      -
      Description copied from interface: Player
      -
      Returns the duration of the current content window or ad in milliseconds, or C.TIME_UNSET if the duration is not known.
      -
      -
      Specified by:
      -
      getDuration in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      getCurrentPosition

      -
      public long getCurrentPosition()
      -
      Description copied from interface: Player
      -
      Returns the playback position in the current content window or ad, in milliseconds, or the - prospective position in milliseconds if the current timeline is - empty.
      -
      -
      Specified by:
      -
      getCurrentPosition in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      getBufferedPosition

      -
      public long getBufferedPosition()
      -
      Description copied from interface: Player
      -
      Returns an estimate of the position in the current content window or ad up to which data is - buffered, in milliseconds.
      -
      -
      Specified by:
      -
      getBufferedPosition in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      getTotalBufferedDuration

      -
      public long getTotalBufferedDuration()
      -
      Description copied from interface: Player
      -
      Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and windows.
      -
      -
      Specified by:
      -
      getTotalBufferedDuration in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      isPlayingAd

      -
      public boolean isPlayingAd()
      -
      Description copied from interface: Player
      -
      Returns whether the player is currently playing an ad.
      -
      -
      Specified by:
      -
      isPlayingAd in interface Player
      -
      -
    • -
    - - - - - - - - - - - -
      -
    • -

      getContentPosition

      -
      public long getContentPosition()
      -
      Description copied from interface: Player
      -
      If Player.isPlayingAd() returns true, returns the content position that will be - played once all ads in the ad group have finished playing, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by Player.getCurrentPosition().
      -
      -
      Specified by:
      -
      getContentPosition in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      getContentBufferedPosition

      -
      public long getContentBufferedPosition()
      -
      Description copied from interface: Player
      -
      If Player.isPlayingAd() returns true, returns an estimate of the content position in - the current content window up to which data is buffered, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by Player.getBufferedPosition().
      -
      -
      Specified by:
      -
      getContentBufferedPosition in interface Player
      -
      -
    • -
    - - - - - - - -
      -
    • -

      setVolume

      -
      public void setVolume​(float audioVolume)
      -
      Description copied from interface: Player
      -
      Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      -
      -
      Specified by:
      -
      setVolume in interface Player
      -
      Parameters:
      -
      audioVolume - Linear output gain to apply to all audio channels.
      -
      -
    • -
    - - - -
      -
    • -

      getVolume

      -
      public float getVolume()
      -
      Description copied from interface: Player
      -
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      -
      -
      Specified by:
      -
      getVolume in interface Player
      -
      Returns:
      -
      The linear gain applied to all audio channels.
      -
      -
    • -
    - - - - - - - -
      -
    • -

      clearVideoSurface

      -
      public void clearVideoSurface​(@Nullable
      -                              Surface surface)
      -
      Description copied from interface: Player
      -
      Clears the Surface onto which video is being rendered if it matches the one passed. - Else does nothing.
      -
      -
      Specified by:
      -
      clearVideoSurface in interface Player
      -
      Parameters:
      -
      surface - The surface to clear.
      -
      -
    • -
    - - - - - - - - - - - -
      -
    • -

      clearVideoSurfaceHolder

      -
      public void clearVideoSurfaceHolder​(@Nullable
      -                                    SurfaceHolder surfaceHolder)
      -
      Description copied from interface: Player
      -
      Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed. Else does nothing.
      -
      -
      Specified by:
      -
      clearVideoSurfaceHolder in interface Player
      -
      Parameters:
      -
      surfaceHolder - The surface holder to clear.
      -
      -
    • -
    - - - - - - - -
      -
    • -

      clearVideoSurfaceView

      -
      public void clearVideoSurfaceView​(@Nullable
      -                                  SurfaceView surfaceView)
      -
      Description copied from interface: Player
      -
      Clears the SurfaceView onto which video is being rendered if it matches the one passed. - Else does nothing.
      -
      -
      Specified by:
      -
      clearVideoSurfaceView in interface Player
      -
      Parameters:
      -
      surfaceView - The texture view to clear.
      -
      -
    • -
    - - - - - - - -
      -
    • -

      clearVideoTextureView

      -
      public void clearVideoTextureView​(@Nullable
      -                                  TextureView textureView)
      -
      Description copied from interface: Player
      -
      Clears the TextureView onto which video is being rendered if it matches the one passed. - Else does nothing.
      -
      -
      Specified by:
      -
      clearVideoTextureView in interface Player
      -
      Parameters:
      -
      textureView - The texture view to clear.
      -
      -
    • -
    - - - - - - - -
      -
    • -

      getCurrentCues

      -
      public List<Cue> getCurrentCues()
      -
      Description copied from interface: Player
      -
      Returns the current Cues. This list may be empty.
      -
      -
      Specified by:
      -
      getCurrentCues in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      getDeviceInfo

      -
      public DeviceInfo getDeviceInfo()
      -
      Description copied from interface: Player
      -
      Gets the device information.
      -
      -
      Specified by:
      -
      getDeviceInfo in interface Player
      -
      -
    • -
    - - - - - - - -
      -
    • -

      isDeviceMuted

      -
      public boolean isDeviceMuted()
      -
      Description copied from interface: Player
      -
      Gets whether the device is muted or not.
      -
      -
      Specified by:
      -
      isDeviceMuted in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      setDeviceVolume

      -
      public void setDeviceVolume​(int volume)
      -
      Description copied from interface: Player
      -
      Sets the volume of the device.
      -
      -
      Specified by:
      -
      setDeviceVolume in interface Player
      -
      Parameters:
      -
      volume - The volume to set.
      -
      -
    • -
    - - - -
      -
    • -

      increaseDeviceVolume

      -
      public void increaseDeviceVolume()
      -
      Description copied from interface: Player
      -
      Increases the volume of the device.
      -
      -
      Specified by:
      -
      increaseDeviceVolume in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      decreaseDeviceVolume

      -
      public void decreaseDeviceVolume()
      -
      Description copied from interface: Player
      -
      Decreases the volume of the device.
      -
      -
      Specified by:
      -
      decreaseDeviceVolume in interface Player
      -
      -
    • -
    - - - -
      -
    • -

      setDeviceMuted

      -
      public void setDeviceMuted​(boolean muted)
      -
      Description copied from interface: Player
      -
      Sets the mute state of the device.
      -
      -
      Specified by:
      -
      setDeviceMuted in interface Player
      -
      -
    • -
    @@ -2878,7 +1691,7 @@ public Description copied from interface: ExoPlayer
    Sets whether to pause playback at the end of each media item. -

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    +

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    Specified by:
    setPauseAtEndOfMediaItems in interface ExoPlayer
    @@ -2904,6 +1717,173 @@ public  + + +
      +
    • +

      getAudioFormat

      +
      @Nullable
      +public Format getAudioFormat()
      +
      Description copied from interface: ExoPlayer
      +
      Returns the audio format currently being played, or null if no audio is being played.
      +
      +
      Specified by:
      +
      getAudioFormat in interface ExoPlayer
      +
      +
    • +
    + + + +
      +
    • +

      getVideoFormat

      +
      @Nullable
      +public Format getVideoFormat()
      +
      Description copied from interface: ExoPlayer
      +
      Returns the video format currently being played, or null if no video is being played.
      +
      +
      Specified by:
      +
      getVideoFormat in interface ExoPlayer
      +
      +
    • +
    + + + + + + + + + + + +
      +
    • +

      setHandleAudioBecomingNoisy

      +
      public void setHandleAudioBecomingNoisy​(boolean handleAudioBecomingNoisy)
      +
      Description copied from interface: ExoPlayer
      +
      Sets whether the player should pause automatically when audio is rerouted from a headset to + device speakers. See the audio + becoming noisy documentation for more information.
      +
      +
      Specified by:
      +
      setHandleAudioBecomingNoisy in interface ExoPlayer
      +
      Parameters:
      +
      handleAudioBecomingNoisy - Whether the player should pause automatically when audio is + rerouted from a headset to device speakers.
      +
      +
    • +
    + + + + + + + +
      +
    • +

      setWakeMode

      +
      public void setWakeMode​(int wakeMode)
      +
      Description copied from interface: ExoPlayer
      +
      Sets how the player should keep the device awake for playback when the screen is off. + +

      Enabling this feature requires the Manifest.permission.WAKE_LOCK permission. + It should be used together with a foreground Service for use cases where + playback occurs and the screen is off (e.g. background audio playback). It is not useful when + the screen will be kept on during playback (e.g. foreground video playback). + +

      When enabled, the locks (PowerManager.WakeLock / WifiManager.WifiLock) will be held whenever the player is in the Player.STATE_READY or Player.STATE_BUFFERING states with playWhenReady = true. The locks + held depends on the specified C.WakeMode.

      +
      +
      Specified by:
      +
      setWakeMode in interface ExoPlayer
      +
      Parameters:
      +
      wakeMode - The C.WakeMode option to keep the device awake during playback.
      +
      +
    • +
    + + + + + + + +
      +
    • +

      setThrowsWhenUsingWrongThread

      +
      @Deprecated
      +public void setThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread)
      +
      Deprecated.
      +
      Description copied from interface: ExoPlayer
      +
      Sets whether the player should throw an IllegalStateException when methods are called + from a thread other than the one associated with Player.getApplicationLooper(). + +

      The default is true and this method will be removed in the future.

      +
      +
      Specified by:
      +
      setThrowsWhenUsingWrongThread in interface ExoPlayer
      +
      Parameters:
      +
      throwsWhenUsingWrongThread - Whether to throw when methods are called from a wrong thread.
      +
      +
    • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/StubPlayer.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/StubPlayer.html new file mode 100644 index 0000000000..b1e6c14dcb --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/StubPlayer.html @@ -0,0 +1,1958 @@ + + + + +StubPlayer (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class StubPlayer

    +
    +
    + +
    + +
    +
    + +
    +
    +
      +
    • + +
      +
        +
      • + + +

        Constructor Detail

        + + + +
          +
        • +

          StubPlayer

          +
          public StubPlayer()
          +
        • +
        +
      • +
      +
      + +
      +
        +
      • + + +

        Method Detail

        + + + +
          +
        • +

          getApplicationLooper

          +
          public Looper getApplicationLooper()
          +
          Description copied from interface: Player
          +
          Returns the Looper associated with the application thread that's used to access the + player and on which player events are received.
          +
        • +
        + + + +
          +
        • +

          addListener

          +
          public void addListener​(Player.Listener listener)
          +
          Description copied from interface: Player
          +
          Registers a listener to receive all events from the player. + +

          The listener's methods will be called on the thread associated with Player.getApplicationLooper().

          +
          +
          Parameters:
          +
          listener - The listener to register.
          +
          +
        • +
        + + + +
          +
        • +

          removeListener

          +
          public void removeListener​(Player.Listener listener)
          +
          Description copied from interface: Player
          +
          Unregister a listener registered through Player.addListener(Listener). The listener will no + longer receive events.
          +
          +
          Parameters:
          +
          listener - The listener to unregister.
          +
          +
        • +
        + + + + + + + + + + + + + + + +
          +
        • +

          prepare

          +
          public void prepare()
          +
          Description copied from interface: Player
          +
          Prepares the player.
          +
        • +
        + + + + + + + +
          +
        • +

          setMediaItems

          +
          public void setMediaItems​(List<MediaItem> mediaItems,
          +                          int startIndex,
          +                          long startPositionMs)
          +
          Description copied from interface: Player
          +
          Clears the playlist and adds the specified MediaItems.
          +
          +
          Parameters:
          +
          mediaItems - The new MediaItems.
          +
          startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET + is passed, the current position is not reset.
          +
          startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In + any case, if startIndex is set to C.INDEX_UNSET, this parameter is ignored + and the position is not reset at all.
          +
          +
        • +
        + + + +
          +
        • +

          addMediaItems

          +
          public void addMediaItems​(int index,
          +                          List<MediaItem> mediaItems)
          +
          Description copied from interface: Player
          +
          Adds a list of media items at the given index of the playlist.
          +
          +
          Parameters:
          +
          index - The index at which to add the media items. If the index is larger than the size of + the playlist, the media items are added to the end of the playlist.
          +
          mediaItems - The MediaItems to add.
          +
          +
        • +
        + + + +
          +
        • +

          moveMediaItems

          +
          public void moveMediaItems​(int fromIndex,
          +                           int toIndex,
          +                           int newIndex)
          +
          Description copied from interface: Player
          +
          Moves the media item range to the new index.
          +
          +
          Parameters:
          +
          fromIndex - The start of the range to move.
          +
          toIndex - The first item not to be included in the range (exclusive).
          +
          newIndex - The new index of the first media item of the range. If the new index is larger + than the size of the remaining playlist after removing the range, the range is moved to the + end of the playlist.
          +
          +
        • +
        + + + +
          +
        • +

          removeMediaItems

          +
          public void removeMediaItems​(int fromIndex,
          +                             int toIndex)
          +
          Description copied from interface: Player
          +
          Removes a range of media items from the playlist.
          +
          +
          Parameters:
          +
          fromIndex - The index at which to start removing media items.
          +
          toIndex - The index of the first item to be kept (exclusive). If the index is larger than + the size of the playlist, media items to the end of the playlist are removed.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          setPlayWhenReady

          +
          public void setPlayWhenReady​(boolean playWhenReady)
          +
          Description copied from interface: Player
          +
          Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY. + +

          If the player is already in the ready state then this method pauses and resumes playback.

          +
          +
          Parameters:
          +
          playWhenReady - Whether playback should proceed when ready.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          setRepeatMode

          +
          public void setRepeatMode​(@RepeatMode
          +                          @com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
          +
          Description copied from interface: Player
          +
          Sets the Player.RepeatMode to be used for playback.
          +
          +
          Parameters:
          +
          repeatMode - The repeat mode.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          setShuffleModeEnabled

          +
          public void setShuffleModeEnabled​(boolean shuffleModeEnabled)
          +
          Description copied from interface: Player
          +
          Sets whether shuffling of media items is enabled.
          +
          +
          Parameters:
          +
          shuffleModeEnabled - Whether shuffling is enabled.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          isLoading

          +
          public boolean isLoading()
          +
          Description copied from interface: Player
          +
          Whether the player is currently loading the source.
          +
          +
          Returns:
          +
          Whether the player is currently loading the source.
          +
          See Also:
          +
          Player.Listener.onIsLoadingChanged(boolean)
          +
          +
        • +
        + + + +
          +
        • +

          seekTo

          +
          public void seekTo​(int mediaItemIndex,
          +                   long positionMs)
          +
          Description copied from interface: Player
          +
          Seeks to a position specified in milliseconds in the specified MediaItem.
          +
          +
          Parameters:
          +
          mediaItemIndex - The index of the MediaItem.
          +
          positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
          +
          +
        • +
        + + + + + + + + + + + + + + + +
          +
        • +

          setPlaybackParameters

          +
          public void setPlaybackParameters​(PlaybackParameters playbackParameters)
          +
          Description copied from interface: Player
          +
          Attempts to set the playback parameters. Passing PlaybackParameters.DEFAULT resets the + player to the default, which means there is no speed or pitch adjustment. + +

          Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently + active playback parameters change.

          +
          +
          Parameters:
          +
          playbackParameters - The playback parameters.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          stop

          +
          public void stop()
          +
          Description copied from interface: Player
          +
          Stops playback without resetting the player. Use Player.pause() rather than this method if + the intention is to pause playback. + +

          Calling this method will cause the playback state to transition to Player.STATE_IDLE. The + player instance can still be used, and Player.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.

          +
        • +
        + + + +
          +
        • +

          stop

          +
          @Deprecated
          +public void stop​(boolean reset)
          +
          Deprecated.
          +
        • +
        + + + +
          +
        • +

          release

          +
          public void release()
          +
          Description copied from interface: Player
          +
          Releases the player. This method must be called when the player is no longer required. The + player must not be used after calling this method.
          +
        • +
        + + + + + + + +
          +
        • +

          getCurrentTrackSelections

          +
          public TrackSelectionArray getCurrentTrackSelections()
          +
          Description copied from interface: Player
          +
          Returns the current track selections. + +

          A concrete implementation may include null elements if it has a fixed number of renderer + components, wishes to report a TrackSelection for each of them, and has one or more renderer + components that is not assigned any selected tracks.

          +
          +
          See Also:
          +
          Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
          +
          +
        • +
        + + + + + + + +
          +
        • +

          getTrackSelectionParameters

          +
          public TrackSelectionParameters getTrackSelectionParameters()
          +
          Description copied from interface: Player
          +
          Returns the parameters constraining the track selection.
          +
          +
          See Also:
          +
          }
          +
          +
        • +
        + + + +
          +
        • +

          setTrackSelectionParameters

          +
          public void setTrackSelectionParameters​(TrackSelectionParameters parameters)
          +
          Description copied from interface: Player
          +
          Sets the parameters constraining the track selection. + +

          Unsupported parameters will be silently ignored. + +

          Use Player.getTrackSelectionParameters() to retrieve the current parameters. For example, + the following snippet restricts video to SD whilst keep other track selection parameters + unchanged: + +

          
          + player.setTrackSelectionParameters(
          +   player.getTrackSelectionParameters()
          +         .buildUpon()
          +         .setMaxVideoSizeSd()
          +         .build())
          + 
          +
        • +
        + + + + + + + + + + + +
          +
        • +

          setPlaylistMetadata

          +
          public void setPlaylistMetadata​(MediaMetadata mediaMetadata)
          +
          Description copied from interface: Player
          +
          Sets the playlist MediaMetadata.
          +
        • +
        + + + + + + + +
          +
        • +

          getCurrentPeriodIndex

          +
          public int getCurrentPeriodIndex()
          +
          Description copied from interface: Player
          +
          Returns the index of the period currently being played.
          +
        • +
        + + + +
          +
        • +

          getCurrentMediaItemIndex

          +
          public int getCurrentMediaItemIndex()
          +
          Description copied from interface: Player
          +
          Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is + empty.
          +
        • +
        + + + +
          +
        • +

          getDuration

          +
          public long getDuration()
          +
          Description copied from interface: Player
          +
          Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if + the duration is not known.
          +
        • +
        + + + +
          +
        • +

          getCurrentPosition

          +
          public long getCurrentPosition()
          +
          Description copied from interface: Player
          +
          Returns the playback position in the current content or ad, in milliseconds, or the prospective + position in milliseconds if the current timeline is empty.
          +
        • +
        + + + +
          +
        • +

          getBufferedPosition

          +
          public long getBufferedPosition()
          +
          Description copied from interface: Player
          +
          Returns an estimate of the position in the current content or ad up to which data is buffered, + in milliseconds.
          +
        • +
        + + + +
          +
        • +

          getTotalBufferedDuration

          +
          public long getTotalBufferedDuration()
          +
          Description copied from interface: Player
          +
          Returns an estimate of the total buffered duration from the current position, in milliseconds. + This includes pre-buffered data for subsequent ads and media items.
          +
        • +
        + + + +
          +
        • +

          isPlayingAd

          +
          public boolean isPlayingAd()
          +
          Description copied from interface: Player
          +
          Returns whether the player is currently playing an ad.
          +
        • +
        + + + +
          +
        • +

          getCurrentAdGroupIndex

          +
          public int getCurrentAdGroupIndex()
          +
          Description copied from interface: Player
          +
          If Player.isPlayingAd() returns true, returns the index of the ad group in the period + currently being played. Returns C.INDEX_UNSET otherwise.
          +
        • +
        + + + +
          +
        • +

          getCurrentAdIndexInAdGroup

          +
          public int getCurrentAdIndexInAdGroup()
          +
          Description copied from interface: Player
          +
          If Player.isPlayingAd() returns true, returns the index of the ad in its ad group. Returns + C.INDEX_UNSET otherwise.
          +
        • +
        + + + +
          +
        • +

          getContentPosition

          +
          public long getContentPosition()
          +
          Description copied from interface: Player
          +
          If Player.isPlayingAd() returns true, returns the content position that will be + played once all ads in the ad group have finished playing, in milliseconds. If there is no ad + playing, the returned position is the same as that returned by Player.getCurrentPosition().
          +
        • +
        + + + +
          +
        • +

          getContentBufferedPosition

          +
          public long getContentBufferedPosition()
          +
          Description copied from interface: Player
          +
          If Player.isPlayingAd() returns true, returns an estimate of the content position in + the current content up to which data is buffered, in milliseconds. If there is no ad playing, + the returned position is the same as that returned by Player.getBufferedPosition().
          +
        • +
        + + + +
          +
        • +

          getAudioAttributes

          +
          public AudioAttributes getAudioAttributes()
          +
          Description copied from interface: Player
          +
          Returns the attributes for audio playback.
          +
        • +
        + + + +
          +
        • +

          setVolume

          +
          public void setVolume​(float volume)
          +
          Description copied from interface: Player
          +
          Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal + unchanged), inclusive.
          +
          +
          Parameters:
          +
          volume - Linear output gain to apply to all audio channels.
          +
          +
        • +
        + + + +
          +
        • +

          getVolume

          +
          public float getVolume()
          +
          Description copied from interface: Player
          +
          Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
          +
          +
          Returns:
          +
          The linear gain applied to all audio channels.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          clearVideoSurface

          +
          public void clearVideoSurface​(@Nullable
          +                              Surface surface)
          +
          Description copied from interface: Player
          +
          Clears the Surface onto which video is being rendered if it matches the one passed. + Else does nothing.
          +
          +
          Parameters:
          +
          surface - The surface to clear.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          setVideoSurfaceHolder

          +
          public void setVideoSurfaceHolder​(@Nullable
          +                                  SurfaceHolder surfaceHolder)
          +
          Description copied from interface: Player
          +
          Sets the SurfaceHolder that holds the Surface onto which video will be + rendered. The player will track the lifecycle of the surface automatically. + +

          The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with Player.getApplicationLooper().

          +
          +
          Parameters:
          +
          surfaceHolder - The surface holder.
          +
          +
        • +
        + + + +
          +
        • +

          clearVideoSurfaceHolder

          +
          public void clearVideoSurfaceHolder​(@Nullable
          +                                    SurfaceHolder surfaceHolder)
          +
          Description copied from interface: Player
          +
          Clears the SurfaceHolder that holds the Surface onto which video is being + rendered if it matches the one passed. Else does nothing.
          +
          +
          Parameters:
          +
          surfaceHolder - The surface holder to clear.
          +
          +
        • +
        + + + +
          +
        • +

          setVideoSurfaceView

          +
          public void setVideoSurfaceView​(@Nullable
          +                                SurfaceView surfaceView)
          +
          Description copied from interface: Player
          +
          Sets the SurfaceView onto which video will be rendered. The player will track the + lifecycle of the surface automatically. + +

          The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with Player.getApplicationLooper().

          +
          +
          Parameters:
          +
          surfaceView - The surface view.
          +
          +
        • +
        + + + +
          +
        • +

          clearVideoSurfaceView

          +
          public void clearVideoSurfaceView​(@Nullable
          +                                  SurfaceView surfaceView)
          +
          Description copied from interface: Player
          +
          Clears the SurfaceView onto which video is being rendered if it matches the one passed. + Else does nothing.
          +
          +
          Parameters:
          +
          surfaceView - The texture view to clear.
          +
          +
        • +
        + + + +
          +
        • +

          setVideoTextureView

          +
          public void setVideoTextureView​(@Nullable
          +                                TextureView textureView)
          +
          Description copied from interface: Player
          +
          Sets the TextureView onto which video will be rendered. The player will track the + lifecycle of the surface automatically. + +

          The thread that calls the TextureView.SurfaceTextureListener methods must be the + thread associated with Player.getApplicationLooper().

          +
          +
          Parameters:
          +
          textureView - The texture view.
          +
          +
        • +
        + + + +
          +
        • +

          clearVideoTextureView

          +
          public void clearVideoTextureView​(@Nullable
          +                                  TextureView textureView)
          +
          Description copied from interface: Player
          +
          Clears the TextureView onto which video is being rendered if it matches the one passed. + Else does nothing.
          +
          +
          Parameters:
          +
          textureView - The texture view to clear.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          getCurrentCues

          +
          public List<Cue> getCurrentCues()
          +
          Description copied from interface: Player
          +
          Returns the current Cues. This list may be empty.
          +
        • +
        + + + +
          +
        • +

          getDeviceInfo

          +
          public DeviceInfo getDeviceInfo()
          +
          Description copied from interface: Player
          +
          Gets the device information.
          +
        • +
        + + + + + + + +
          +
        • +

          isDeviceMuted

          +
          public boolean isDeviceMuted()
          +
          Description copied from interface: Player
          +
          Gets whether the device is muted or not.
          +
        • +
        + + + +
          +
        • +

          setDeviceVolume

          +
          public void setDeviceVolume​(int volume)
          +
          Description copied from interface: Player
          +
          Sets the volume of the device.
          +
          +
          Parameters:
          +
          volume - The volume to set.
          +
          +
        • +
        + + + +
          +
        • +

          increaseDeviceVolume

          +
          public void increaseDeviceVolume()
          +
          Description copied from interface: Player
          +
          Increases the volume of the device.
          +
        • +
        + + + +
          +
        • +

          decreaseDeviceVolume

          +
          public void decreaseDeviceVolume()
          +
          Description copied from interface: Player
          +
          Decreases the volume of the device.
          +
        • +
        + + + +
          +
        • +

          setDeviceMuted

          +
          public void setDeviceMuted​(boolean muted)
          +
          Description copied from interface: Player
          +
          Sets the mute state of the device.
          +
        • +
        +
      • +
      +
      +
    • +
    +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.html index 44255b405d..35579becf9 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -210,6 +210,13 @@ extends +MediaSourceFactory +getMediaSourceFactory() + +
    Returns the MediaSourceFactory that will be used by the player, or null if no MediaSourceFactory has been set yet and no default is available.
    + + + Renderer[] getRenderers() @@ -217,7 +224,7 @@ extends Renderers have been explicitly set. - + RenderersFactory getRenderersFactory() @@ -225,98 +232,105 @@ extends - + long getSeekBackIncrementMs()
    Returns the seek back increment used by the player.
    - + long getSeekForwardIncrementMs()
    Returns the seek forward increment used by the player.
    - + DefaultTrackSelector getTrackSelector()
    Returns the track selector used by the player.
    - + boolean getUseLazyPreparation()
    Returns whether the player will use lazy preparation.
    - + TestExoPlayerBuilder setBandwidthMeter​(BandwidthMeter bandwidthMeter)
    Sets the BandwidthMeter.
    - + TestExoPlayerBuilder setClock​(Clock clock)
    Sets the Clock to be used by the player.
    - + TestExoPlayerBuilder setLoadControl​(LoadControl loadControl)
    Sets a LoadControl to be used by the player.
    - + TestExoPlayerBuilder setLooper​(Looper looper)
    Sets the Looper to be used by the player.
    - + +TestExoPlayerBuilder +setMediaSourceFactory​(MediaSourceFactory mediaSourceFactory) + +
    Sets the MediaSourceFactory to be used by the player.
    + + + TestExoPlayerBuilder setRenderers​(Renderer... renderers)
    Sets the Renderers.
    - + TestExoPlayerBuilder setRenderersFactory​(RenderersFactory renderersFactory) - + TestExoPlayerBuilder setSeekBackIncrementMs​(long seekBackIncrementMs)
    Sets the seek back increment to be used by the player.
    - + TestExoPlayerBuilder setSeekForwardIncrementMs​(long seekForwardIncrementMs)
    Sets the seek forward increment to be used by the player.
    - + TestExoPlayerBuilder setTrackSelector​(DefaultTrackSelector trackSelector) - + TestExoPlayerBuilder setUseLazyPreparation​(boolean useLazyPreparation) @@ -585,6 +599,33 @@ public  + + + + + + + @@ -645,10 +686,6 @@ public public SimpleExoPlayer build()
    Builds an SimpleExoPlayer using the provided values or their defaults.
    -
    -
    Returns:
    -
    The built ExoPlayerTestRunner.
    -
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/TestUtil.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/TestUtil.html index 0eb30f2d9b..67441c8699 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/TestUtil.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/TestUtil.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9,"i12":9,"i13":9,"i14":9,"i15":9,"i16":9,"i17":9,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9}; +var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9,"i12":9,"i13":9,"i14":9,"i15":9,"i16":9,"i17":9,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9,"i23":9}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -180,20 +180,28 @@ extends +static void +assertTimelinesSame​(List<Timeline> actualTimelines, + List<Timeline> expectedTimelines) + +
    Asserts that the actual timelines are the same to the expected timelines.
    + + + static Uri buildAssetUri​(String assetPath)
    Returns the Uri for the given asset path.
    - + static byte[] buildTestData​(int length)
    Equivalent to buildTestData(length, length).
    - + static byte[] buildTestData​(int length, int seed) @@ -201,7 +209,7 @@ extends Generates an array of random bytes with the specified length. - + static byte[] buildTestData​(int length, Random random) @@ -209,7 +217,7 @@ extends Generates an array of random bytes with the specified length. - + static String buildTestString​(int length, Random random) @@ -217,28 +225,28 @@ extends Generates a random string with the specified length. - + static byte[] createByteArray​(int... bytes)
    Converts an array of integers in the range [0, 255] into an equivalent byte array.
    - + static ImmutableList<Byte> createByteList​(int... bytes)
    Converts an array of integers in the range [0, 255] into an equivalent byte list.
    - + static MetadataInputBuffer createMetadataInputBuffer​(byte[] data)
    Create a new MetadataInputBuffer and copy data into the backing ByteBuffer.
    - + static File createTestFile​(File file, long length) @@ -246,7 +254,7 @@ extends Writes test data with the specified length to the file and returns it. - + static File createTestFile​(File directory, String name) @@ -254,7 +262,7 @@ extends Writes one byte long test data to the file and returns it. - + static File createTestFile​(File directory, String name, @@ -263,7 +271,7 @@ extends Writes test data with the specified length to the file and returns it. - + static FakeExtractorOutput extractAllSamplesFromFile​(Extractor extractor, Context context, @@ -272,7 +280,7 @@ extends Extracts all samples from the given file into a FakeTrackOutput. - + static SeekMap extractSeekMap​(Extractor extractor, FakeExtractorOutput output, @@ -283,7 +291,7 @@ extends - + static Bitmap getBitmap​(Context context, String fileName) @@ -291,7 +299,7 @@ extends Returns a Bitmap read from an asset file. - + static byte[] getByteArray​(Context context, String fileName) @@ -299,7 +307,7 @@ extends Returns the bytes of an asset file. - + static ExtractorInput getExtractorInputFromPosition​(DataSource dataSource, long position, @@ -308,14 +316,14 @@ extends Returns an ExtractorInput to read from the given input at given position. - + static DatabaseProvider getInMemoryDatabaseProvider()
    Returns a DatabaseProvider that provides an in-memory database.
    - + static InputStream getInputStream​(Context context, String fileName) @@ -323,7 +331,7 @@ extends Returns an InputStream for reading from an asset file. - + static String getString​(Context context, String fileName) @@ -331,7 +339,7 @@ extends Returns a String read from an asset file. - + static int seekToTimeUs​(Extractor extractor, SeekMap seekMap, @@ -594,6 +602,24 @@ extends Returns a DatabaseProvider that provides an in-memory database. + + + +
      +
    • +

      assertTimelinesSame

      +
      public static void assertTimelinesSame​(List<Timeline> actualTimelines,
      +                                       List<Timeline> expectedTimelines)
      +
      Asserts that the actual timelines are the same to the expected timelines. This assert differs + from testing equality by not comparing period ids which may be different due to id mapping of + child source period ids.
      +
      +
      Parameters:
      +
      actualTimelines - A list of actual timelines.
      +
      expectedTimelines - A list of expected timelines.
      +
      +
    • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/TimelineAsserts.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/TimelineAsserts.html index f54b01ef26..58767ef775 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/TimelineAsserts.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/TimelineAsserts.html @@ -169,9 +169,9 @@ extends static void -assertEqualNextWindowIndices​(Timeline expectedTimeline, +assertEqualNextWindowIndices​(Timeline expectedTimeline, Timeline actualTimeline, - int repeatMode, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)
    Asserts that next window indices for each window of the actual timeline are equal to the @@ -180,9 +180,9 @@ extends static void -assertEqualPreviousWindowIndices​(Timeline expectedTimeline, +assertEqualPreviousWindowIndices​(Timeline expectedTimeline, Timeline actualTimeline, - int repeatMode, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)
    Asserts that previous window indices for each window of the actual timeline are equal to the @@ -199,8 +199,8 @@ extends static void -assertNextWindowIndices​(Timeline timeline, - int repeatMode, +assertNextWindowIndices​(Timeline timeline, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled, int... expectedNextWindowIndices) @@ -235,8 +235,8 @@ extends static void -assertPreviousWindowIndices​(Timeline timeline, - int repeatMode, +assertPreviousWindowIndices​(Timeline timeline, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled, int... expectedPreviousWindowIndices) @@ -329,7 +329,7 @@ extends Asserts that window properties Timeline.Window.isDynamic are set correctly.
    - + - + - +
      @@ -368,13 +368,13 @@ extends public static void assertEqualPreviousWindowIndices​(Timeline expectedTimeline, Timeline actualTimeline, @RepeatMode - int repeatMode, + @com.google.android.exoplayer2.Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)
      Asserts that previous window indices for each window of the actual timeline are equal to the indices of the expected timeline depending on the repeat mode and the shuffle mode.
    - +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html index 9f805fe524..f5167b8b35 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html @@ -177,19 +177,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.AddMediaItems - + Action.ClearMediaItems - + Action.ClearVideoSurface - + @@ -201,7 +201,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.MoveMediaItem - + @@ -220,13 +220,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.RemoveMediaItem - + Action.RemoveMediaItems - + @@ -244,19 +244,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.SetAudioAttributes - + Action.SetMediaItems - + Action.SetMediaItemsResetPosition - + @@ -281,7 +281,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.SetRepeatMode - + @@ -299,7 +299,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.SetVideoSurface - + @@ -335,27 +335,27 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.WaitForPlaybackState -
    Waits for a specified playback state, returning either immediately or after a call to Player.Listener.onPlaybackStateChanged(int).
    +
    Waits for a specified playback state, returning either immediately or after a call to Player.Listener.onPlaybackStateChanged(int).
    Action.WaitForPlayWhenReady
    Waits for a specified playWhenReady value, returning either immediately or after a call to - Player.Listener.onPlayWhenReadyChanged(boolean, int).
    + Player.Listener.onPlayWhenReadyChanged(boolean, int). Action.WaitForPositionDiscontinuity -
    Waits for Player.Listener.onPositionDiscontinuity(Player.PositionInfo, + Action.WaitForTimelineChanged - + @@ -389,205 +389,217 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +AssetContentProvider + +
    A ContentProvider for reading asset data.
    + + + CacheAsserts
    Assertion methods for Cache.
    - + CacheAsserts.RequestSet
    Defines a set of data requests.
    - + CapturingAudioSink
    A ForwardingAudioSink that captures configuration, discontinuity and buffer events.
    - + CapturingRenderersFactory
    A RenderersFactory that captures interactions with the audio and video MediaCodecAdapter instances.
    - + DataSourceContractTest
    A collection of contract tests for DataSource implementations.
    - + DataSourceContractTest.FakeTransferListener
    A TransferListener that only keeps track of the transferred bytes.
    - + DataSourceContractTest.TestResource
    Information about a resource that can be used to test the DataSource instance.
    - + DataSourceContractTest.TestResource.Builder - + DecoderCountersUtil
    Assertions for DecoderCounters.
    - + DefaultRenderersFactoryAsserts
    Assertions for DefaultRenderersFactory.
    - + DownloadBuilder
    Builder for Download.
    - + DummyMainThread
    Helper class to simulate main/UI thread in tests.
    - + DumpableFormat
    Wraps a Format to allow dumping it.
    - + Dumper
    Helper utility to dump field values.
    - + DumpFileAsserts
    Helper class to enable assertions based on golden-data dump files.
    - + ExoHostedTest
    A HostActivity.HostedTest for ExoPlayer playback tests.
    - + ExoPlayerTestRunner
    Helper class to run an ExoPlayer test.
    - + ExoPlayerTestRunner.Builder -
    Builder to set-up a ExoPlayerTestRunner.
    +
    Builder to set-up an ExoPlayerTestRunner.
    - + ExtractorAsserts
    Assertion methods for Extractor.
    - + ExtractorAsserts.AssertionConfig
    A config for the assertions made (e.g.
    - + ExtractorAsserts.AssertionConfig.Builder
    Builder for ExtractorAsserts.AssertionConfig instances.
    - + ExtractorAsserts.SimulationConfig
    A config of different environments to simulate and extractor behaviours to test.
    - + FailOnCloseDataSink
    A DataSink that can simulate caching the bytes being written to it, and then failing to persist them when FailOnCloseDataSink.close() is called.
    - + FailOnCloseDataSink.Factory
    Factory to create a FailOnCloseDataSink.
    - + FakeAdaptiveDataSet
    Fake data set emulating the data of an adaptive media source.
    - + FakeAdaptiveDataSet.Factory
    Factory for FakeAdaptiveDataSets.
    - + FakeAdaptiveDataSet.Iterator
    MediaChunkIterator for the chunks defined by a fake adaptive data set.
    - + FakeAdaptiveMediaPeriod
    Fake MediaPeriod that provides tracks from the given TrackGroupArray.
    - + FakeAdaptiveMediaSource
    Fake MediaSource that provides a given timeline.
    - + FakeAudioRenderer - + FakeChunkSource
    Fake ChunkSource with adaptive media chunks of a given duration.
    - + FakeChunkSource.Factory
    Factory for a FakeChunkSource.
    - + FakeClock
    Fake Clock implementation that allows to advance the time manually to trigger pending timed messages.
    + +FakeCryptoConfig + + + + FakeDataSet @@ -691,6 +703,18 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +FakeMediaSourceFactory + +
    Fake MediaSourceFactory that creates a FakeMediaSource.
    + + + +FakeMetadataEntry + + + + + FakeRenderer
    Fake Renderer that supports any format with the matching track type.
    @@ -789,37 +813,44 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +StubPlayer + +
    An abstract Player implementation that throws UnsupportedOperationException from + every method.
    + + + TestExoPlayerBuilder
    A builder of SimpleExoPlayer instances for testing.
    - + TestUtil
    Utility methods for tests.
    - + TimelineAsserts
    Assertion methods for Timeline.
    - + WebServerDispatcher
    A Dispatcher for MockWebServer that allows per-path customisation of the static data served.
    - + WebServerDispatcher.Resource
    A resource served by WebServerDispatcher.
    - + WebServerDispatcher.Resource.Builder diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html index 1962b15df7..55bc854cd1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html @@ -164,9 +164,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.BasePlayer (implements com.google.android.exoplayer2.Player)
      +
    • com.google.android.exoplayer2.testutil.StubPlayer +
    • +
    +
  • com.google.android.exoplayer2.BaseRenderer (implements com.google.android.exoplayer2.Renderer, com.google.android.exoplayer2.RendererCapabilities)
    • com.google.android.exoplayer2.testutil.FakeRenderer @@ -190,6 +194,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • +
  • android.content.ContentProvider (implements android.content.ComponentCallbacks2) + +
  • android.content.Context
    • android.content.ContextWrapper @@ -243,6 +252,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • com.google.android.exoplayer2.testutil.FakeChunkSource.Factory
    • com.google.android.exoplayer2.testutil.FakeClock (implements com.google.android.exoplayer2.util.Clock)
    • com.google.android.exoplayer2.testutil.FakeClock.HandlerMessage (implements java.lang.Comparable<T>, com.google.android.exoplayer2.util.HandlerWrapper.Message)
    • +
    • com.google.android.exoplayer2.testutil.FakeCryptoConfig (implements com.google.android.exoplayer2.decoder.CryptoConfig)
    • com.google.android.exoplayer2.testutil.FakeDataSet
      • com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet
      • @@ -258,6 +268,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      • com.google.android.exoplayer2.testutil.FakeExtractorInput.Builder
      • com.google.android.exoplayer2.testutil.FakeExtractorOutput (implements com.google.android.exoplayer2.testutil.Dumper.Dumpable, com.google.android.exoplayer2.extractor.ExtractorOutput)
      • com.google.android.exoplayer2.testutil.FakeMediaPeriod (implements com.google.android.exoplayer2.source.MediaPeriod)
      • +
      • com.google.android.exoplayer2.testutil.FakeMediaSourceFactory (implements com.google.android.exoplayer2.source.MediaSourceFactory)
      • +
      • com.google.android.exoplayer2.testutil.FakeMetadataEntry (implements com.google.android.exoplayer2.metadata.Metadata.Entry)
      • com.google.android.exoplayer2.testutil.FakeSampleStream (implements com.google.android.exoplayer2.source.SampleStream)
      • com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem
      • com.google.android.exoplayer2.testutil.FakeShuffleOrder (implements com.google.android.exoplayer2.source.ShuffleOrder)
      • diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.AnchorType.html b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.AnchorType.html index 6eb23858c9..273b95a2dd 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.AnchorType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.AnchorType.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
        @Documented
         @Retention(SOURCE)
        +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
         public static @interface Cue.AnchorType
        The type of anchor, which may be unset. One of Cue.TYPE_UNSET, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_MIDDLE or Cue.ANCHOR_TYPE_END.
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.Builder.html index 9cd6a35fdb..3e903c8d40 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.Builder.html @@ -214,14 +214,14 @@ extends -int +@com.google.android.exoplayer2.text.Cue.AnchorType int getLineAnchor() -
        Gets the cue box anchor positioned by line.
        +
        Gets the cue box anchor positioned by line.
        -int +@com.google.android.exoplayer2.text.Cue.LineType int getLineType()
        Gets the type of the value of getLine().
        @@ -231,12 +231,12 @@ extends float getPosition() -
        Gets the fractional position of the positionAnchor of the cue - box within the viewport in the direction orthogonal to line.
        +
        Gets the fractional position of the positionAnchor of the cue + box within the viewport in the direction orthogonal to line.
        -int +@com.google.android.exoplayer2.text.Cue.AnchorType int getPositionAnchor()
        Gets the cue box anchor positioned by position.
        @@ -272,14 +272,14 @@ extends -int +@com.google.android.exoplayer2.text.Cue.TextSizeType int getTextSizeType()
        Gets the default text size type for this cue's text.
        -int +@com.google.android.exoplayer2.text.Cue.VerticalType int getVerticalType()
        Gets the vertical formatting for this Cue.
        @@ -315,8 +315,8 @@ extends Cue.Builder -setLine​(float line, - int lineType) +setLine​(float line, + @com.google.android.exoplayer2.text.Cue.LineType int lineType)
        Sets the position of the cue box within the viewport in the direction orthogonal to the writing direction.
        @@ -324,9 +324,9 @@ extends Cue.Builder -setLineAnchor​(int lineAnchor) +setLineAnchor​(@com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor) -
        Sets the cue box anchor positioned by line.
        +
        Sets the cue box anchor positioned by line.
        @@ -340,13 +340,13 @@ extends Cue.Builder setPosition​(float position) -
        Sets the fractional position of the positionAnchor of the cue - box within the viewport in the direction orthogonal to line.
        +
        Sets the fractional position of the positionAnchor of the cue + box within the viewport in the direction orthogonal to line.
        Cue.Builder -setPositionAnchor​(int positionAnchor) +setPositionAnchor​(@com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor)
        Sets the cue box anchor positioned by position.
        @@ -382,15 +382,15 @@ extends Cue.Builder -setTextSize​(float textSize, - int textSizeType) +setTextSize​(float textSize, + @com.google.android.exoplayer2.text.Cue.TextSizeType int textSizeType)
        Sets the default text size and type for this cue's text.
        Cue.Builder -setVerticalType​(int verticalType) +setVerticalType​(@com.google.android.exoplayer2.text.Cue.VerticalType int verticalType)
        Sets the vertical formatting for this Cue.
        @@ -557,7 +557,7 @@ public  +
          @@ -565,7 +565,7 @@ public public Cue.Builder setLine​(float line, @LineType - int lineType) + @com.google.android.exoplayer2.text.Cue.LineType int lineType)
          Sets the position of the cue box within the viewport in the direction orthogonal to the writing direction.
          @@ -599,7 +599,7 @@ public float getLine()

          getLineType

          @Pure
           @LineType
          -public int getLineType()
          +public @com.google.android.exoplayer2.text.Cue.LineType int getLineType()
          Gets the type of the value of getLine().
          See Also:
          @@ -607,15 +607,15 @@ public int getLineType()
        - +
        • setLineAnchor

          public Cue.Builder setLineAnchor​(@AnchorType
          -                                 int lineAnchor)
          -
          Sets the cue box anchor positioned by line.
          + @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor) +
          Sets the cue box anchor positioned by line.
          See Also:
          Cue.lineAnchor
          @@ -630,8 +630,8 @@ public int getLineType()

          getLineAnchor

          @Pure
           @AnchorType
          -public int getLineAnchor()
          -
          Gets the cue box anchor positioned by line.
          +public @com.google.android.exoplayer2.text.Cue.AnchorType int getLineAnchor() +
          Gets the cue box anchor positioned by line.
          See Also:
          Cue.lineAnchor
          @@ -645,8 +645,8 @@ public int getLineAnchor()
        • setPosition

          public Cue.Builder setPosition​(float position)
          -
          Sets the fractional position of the positionAnchor of the cue - box within the viewport in the direction orthogonal to line.
          +
          Sets the fractional position of the positionAnchor of the cue + box within the viewport in the direction orthogonal to line.
          See Also:
          Cue.position
          @@ -661,22 +661,22 @@ public int getLineAnchor()

          getPosition

          @Pure
           public float getPosition()
          -
          Gets the fractional position of the positionAnchor of the cue - box within the viewport in the direction orthogonal to line.
          +
          Gets the fractional position of the positionAnchor of the cue + box within the viewport in the direction orthogonal to line.
          See Also:
          Cue.position
        - +
        • setPositionAnchor

          public Cue.Builder setPositionAnchor​(@AnchorType
          -                                     int positionAnchor)
          + @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor)
          Sets the cue box anchor positioned by position.
          See Also:
          @@ -692,7 +692,7 @@ public float getPosition()

          getPositionAnchor

          @Pure
           @AnchorType
          -public int getPositionAnchor()
          +public @com.google.android.exoplayer2.text.Cue.AnchorType int getPositionAnchor()
          Gets the cue box anchor positioned by position.
          See Also:
          @@ -700,7 +700,7 @@ public int getPositionAnchor()
        - +
          @@ -708,7 +708,7 @@ public int getPositionAnchor()

          setTextSize

          public Cue.Builder setTextSize​(float textSize,
                                          @TextSizeType
          -                               int textSizeType)
          + @com.google.android.exoplayer2.text.Cue.TextSizeType int textSizeType)
          Sets the default text size and type for this cue's text.
          See Also:
          @@ -725,7 +725,7 @@ public int getPositionAnchor()

          getTextSizeType

          @Pure
           @TextSizeType
          -public int getTextSizeType()
          +public @com.google.android.exoplayer2.text.Cue.TextSizeType int getTextSizeType()
          Gets the default text size type for this cue's text.
          See Also:
          @@ -866,14 +866,14 @@ public int getWindowColor()
        - +
        • setVerticalType

          public Cue.Builder setVerticalType​(@VerticalType
          -                                   int verticalType)
          + @com.google.android.exoplayer2.text.Cue.VerticalType int verticalType)
          Sets the vertical formatting for this Cue.
          See Also:
          @@ -899,7 +899,7 @@ public int getWindowColor()

          getVerticalType

          @Pure
           @VerticalType
          -public int getVerticalType()
          +public @com.google.android.exoplayer2.text.Cue.VerticalType int getVerticalType()
          Gets the vertical formatting for this Cue.
          See Also:
          diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.LineType.html b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.LineType.html index 632df7691c..ab5f914c7b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.LineType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.LineType.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
          @Documented
           @Retention(SOURCE)
          +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
           public static @interface Cue.LineType
          The type of line, which may be unset. One of Cue.TYPE_UNSET, Cue.LINE_TYPE_FRACTION or Cue.LINE_TYPE_NUMBER.
          diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.TextSizeType.html b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.TextSizeType.html index 9cd2ad0ae6..b7ea166bb5 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.TextSizeType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.TextSizeType.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
          @Documented
           @Retention(SOURCE)
          +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
           public static @interface Cue.TextSizeType
          The type of default text size for this cue, which may be unset. One of Cue.TYPE_UNSET, Cue.TEXT_SIZE_TYPE_FRACTIONAL, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING or Cue.TEXT_SIZE_TYPE_ABSOLUTE.
          diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.VerticalType.html b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.VerticalType.html index 8ffed3f999..d10ea25ec3 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.VerticalType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.VerticalType.html @@ -115,6 +115,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
          @Documented
           @Retention(SOURCE)
          +@Target({FIELD,METHOD,PARAMETER,LOCAL_VARIABLE,TYPE_USE})
           public static @interface Cue.VerticalType
          The type of vertical layout for this cue, which may be unset (i.e. horizontal). One of Cue.TYPE_UNSET, Cue.VERTICAL_TYPE_RL or Cue.VERTICAL_TYPE_LR.
        • diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.html b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.html index 71fde14006..4f2f64667c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/Cue.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/Cue.html @@ -298,14 +298,14 @@ implements -int +@com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor
          The cue box anchor positioned by line when lineType is LINE_TYPE_FRACTION.
          -int +@com.google.android.exoplayer2.text.Cue.LineType int lineType
          The type of the line value.
          @@ -328,7 +328,7 @@ implements -int +@com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor
          The cue box anchor positioned by position.
          @@ -393,7 +393,7 @@ implements -int +@com.google.android.exoplayer2.text.Cue.TextSizeType int textSizeType
          The default text size type for this cue's text, or TYPE_UNSET if this cue has no @@ -422,7 +422,7 @@ implements -int +@com.google.android.exoplayer2.text.Cue.VerticalType int verticalType
          The vertical formatting of this Cue, or TYPE_UNSET if the cue has no vertical setting @@ -469,13 +469,13 @@ implements -Cue​(CharSequence text, +Cue​(CharSequence text, Layout.Alignment textAlignment, float line, - int lineType, - int lineAnchor, + @com.google.android.exoplayer2.text.Cue.LineType int lineType, + @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor, float position, - int positionAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor, float size)
          Deprecated. @@ -484,16 +484,16 @@ implements -Cue​(CharSequence text, +Cue​(CharSequence text, Layout.Alignment textAlignment, float line, - int lineType, - int lineAnchor, + @com.google.android.exoplayer2.text.Cue.LineType int lineType, + @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor, float position, - int positionAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor, float size, - boolean windowColorSet, - int windowColor) + @com.google.android.exoplayer2.text.Cue.TextSizeType int textSizeType, + float textSize)
          Deprecated. @@ -501,16 +501,16 @@ implements -Cue​(CharSequence text, +Cue​(CharSequence text, Layout.Alignment textAlignment, float line, - int lineType, - int lineAnchor, + @com.google.android.exoplayer2.text.Cue.LineType int lineType, + @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor, float position, - int positionAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor, float size, - int textSizeType, - float textSize) + boolean windowColorSet, + int windowColor)
          Deprecated. @@ -839,7 +839,7 @@ public final 

          lineType

          @LineType
          -public final int lineType
          +public final @com.google.android.exoplayer2.text.Cue.LineType int lineType
          The type of the line value.
            @@ -867,7 +867,7 @@ public final int lineType
          • lineAnchor

            @AnchorType
            -public final int lineAnchor
            +public final @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor
            The cue box anchor positioned by line when lineType is LINE_TYPE_FRACTION.

            One of: @@ -911,7 +911,7 @@ public final int lineAnchor

          • positionAnchor

            @AnchorType
            -public final int positionAnchor
            +public final @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor
            The cue box anchor positioned by position. One of ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END and TYPE_UNSET.

            For the normal case of horizontal text, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE and ANCHOR_TYPE_END correspond to the left, middle and right of @@ -968,7 +968,7 @@ public final int positionAnchor

          • textSizeType

            @TextSizeType
            -public final int textSizeType
            +public final @com.google.android.exoplayer2.text.Cue.TextSizeType int textSizeType
            The default text size type for this cue's text, or TYPE_UNSET if this cue has no default text size.
          • @@ -991,7 +991,7 @@ public final int textSizeType
          • verticalType

            @VerticalType
            -public final int verticalType
            +public final @com.google.android.exoplayer2.text.Cue.VerticalType int verticalType
            The vertical formatting of this Cue, or TYPE_UNSET if the cue has no vertical setting (and so should be horizontal).
          • @@ -1045,7 +1045,7 @@ public Cue​( +
              @@ -1057,12 +1057,12 @@ public Cue​(Layout.Alignment textAlignment, float line, @LineType - int lineType, + @com.google.android.exoplayer2.text.Cue.LineType int lineType, @AnchorType - int lineAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor, float position, @AnchorType - int positionAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor, float size)
              Deprecated. @@ -1081,7 +1081,7 @@ public Cue​( +
                @@ -1093,15 +1093,15 @@ public Cue​(Layout.Alignment textAlignment, float line, @LineType - int lineType, + @com.google.android.exoplayer2.text.Cue.LineType int lineType, @AnchorType - int lineAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor, float position, @AnchorType - int positionAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor, float size, @TextSizeType - int textSizeType, + @com.google.android.exoplayer2.text.Cue.TextSizeType int textSizeType, float textSize)
                Deprecated. @@ -1122,7 +1122,7 @@ public Cue​( +
                  @@ -1134,12 +1134,12 @@ public Cue​(Layout.Alignment textAlignment, float line, @LineType - int lineType, + @com.google.android.exoplayer2.text.Cue.LineType int lineType, @AnchorType - int lineAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int lineAnchor, float position, @AnchorType - int positionAnchor, + @com.google.android.exoplayer2.text.Cue.AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor) diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/IntArrayQueue.html b/docs/doc/reference/com/google/android/exoplayer2/text/CueDecoder.html similarity index 72% rename from docs/doc/reference/com/google/android/exoplayer2/util/IntArrayQueue.html rename to docs/doc/reference/com/google/android/exoplayer2/text/CueDecoder.html index adf9c49028..7a845cd713 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/util/IntArrayQueue.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/CueDecoder.html @@ -2,7 +2,7 @@ -IntArrayQueue (ExoPlayer library) +CueDecoder (ExoPlayer library) @@ -19,13 +19,13 @@ + + + + + + + + + +
                  + +
                  + +
                  +
                  + +

                  Class CueEncoder

                  +
                  +
                  + +
                  +
                    +
                  • +
                    +
                    public final class CueEncoder
                    +extends Object
                    +
                    Encodes data that can be decoded by CueDecoder.
                    +
                  • +
                  +
                  +
                  + +
                  +
                  +
                    +
                  • + +
                    +
                      +
                    • + + +

                      Constructor Detail

                      + + + +
                        +
                      • +

                        CueEncoder

                        +
                        public CueEncoder()
                        +
                      • +
                      +
                    • +
                    +
                    + +
                    +
                      +
                    • + + +

                      Method Detail

                      + + + +
                        +
                      • +

                        encode

                        +
                        public byte[] encode​(List<Cue> cues)
                        +
                        Encodes an List of Cue to a byte array that can be decoded by CueDecoder.
                        +
                        +
                        Parameters:
                        +
                        cues - Cues to be encoded.
                        +
                        Returns:
                        +
                        The serialized byte array.
                        +
                        +
                      • +
                      +
                    • +
                    +
                    +
                  • +
                  +
                  +
                  +
                  + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/ExoplayerCuesDecoder.html b/docs/doc/reference/com/google/android/exoplayer2/text/ExoplayerCuesDecoder.html new file mode 100644 index 0000000000..ca24d1adde --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/text/ExoplayerCuesDecoder.html @@ -0,0 +1,472 @@ + + + + +ExoplayerCuesDecoder (ExoPlayer library) + + + + + + + + + + + + + +
                  + +
                  + +
                  +
                  + +

                  Class ExoplayerCuesDecoder

                  +
                  +
                  +
                    +
                  • java.lang.Object
                  • +
                  • +
                      +
                    • com.google.android.exoplayer2.text.ExoplayerCuesDecoder
                    • +
                    +
                  • +
                  +
                  + +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoder.html b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoder.html index 9adf827c7c..92be05fc3c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoder.html @@ -126,7 +126,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Known Implementing Classes:
    -
    Cea608Decoder, Cea708Decoder, DvbDecoder, Mp4WebvttDecoder, PgsDecoder, SimpleSubtitleDecoder, SsaDecoder, SubripDecoder, TtmlDecoder, Tx3gDecoder, WebvttDecoder
    +
    Cea608Decoder, Cea708Decoder, DvbDecoder, ExoplayerCuesDecoder, Mp4WebvttDecoder, PgsDecoder, SimpleSubtitleDecoder, SsaDecoder, SubripDecoder, TtmlDecoder, Tx3gDecoder, WebvttDecoder

    public interface SubtitleDecoder
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoderFactory.html b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoderFactory.html
    index 86680e3a65..0fb0e3b828 100644
    --- a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoderFactory.html
    +++ b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleDecoderFactory.html
    @@ -222,6 +222,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
        
  • Cea708 (Cea708Decoder)
  • DVB (DvbDecoder)
  • PGS (PgsDecoder) +
  • Exoplayer Cues (ExoplayerCuesDecoder)
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleExtractor.html b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleExtractor.html new file mode 100644 index 0000000000..a565d2e869 --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleExtractor.html @@ -0,0 +1,493 @@ + + + + +SubtitleExtractor (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class SubtitleExtractor

    +
    +
    +
      +
    • java.lang.Object
    • +
    • +
        +
      • com.google.android.exoplayer2.text.SubtitleExtractor
      • +
      +
    • +
    +
    +
      +
    • +
      +
      All Implemented Interfaces:
      +
      Extractor
      +
      +
      +
      public class SubtitleExtractor
      +extends Object
      +implements Extractor
      +
      Generic extractor for extracting subtitles from various subtitle formats.
      +
    • +
    +
    +
    + +
    +
    +
      +
    • + +
      +
        +
      • + + +

        Constructor Detail

        + + + +
          +
        • +

          SubtitleExtractor

          +
          public SubtitleExtractor​(SubtitleDecoder subtitleDecoder,
          +                         Format format)
          +
          +
          Parameters:
          +
          subtitleDecoder - The decoder used for decoding the subtitle data. The extractor will + release the decoder in release().
          +
          format - Format that describes subtitle data.
          +
          +
        • +
        +
      • +
      +
      + +
      +
        +
      • + + +

        Method Detail

        + + + +
          +
        • +

          sniff

          +
          public boolean sniff​(ExtractorInput input)
          +              throws IOException
          +
          Description copied from interface: Extractor
          +
          Returns whether this extractor can extract samples from the ExtractorInput, which must + provide data from the start of the stream. + +

          If true is returned, the input's reading position may have been modified. + Otherwise, only its peek position may have been modified.

          +
          +
          Specified by:
          +
          sniff in interface Extractor
          +
          Parameters:
          +
          input - The ExtractorInput from which data should be peeked/read.
          +
          Returns:
          +
          Whether this extractor can read the provided input.
          +
          Throws:
          +
          IOException - If an error occurred reading from the input.
          +
          +
        • +
        + + + + + + + +
          +
        • +

          read

          +
          public int read​(ExtractorInput input,
          +                PositionHolder seekPosition)
          +         throws IOException
          +
          Description copied from interface: Extractor
          +
          Extracts data read from a provided ExtractorInput. Must not be called before Extractor.init(ExtractorOutput). + +

          A single call to this method will block until some progress has been made, but will not + block for longer than this. Hence each call will consume only a small amount of input data. + +

          In the common case, Extractor.RESULT_CONTINUE is returned to indicate that the ExtractorInput passed to the next read is required to provide data continuing from the + position in the stream reached by the returning call. If the extractor requires data to be + provided from a different position, then that position is set in seekPosition and + Extractor.RESULT_SEEK is returned. If the extractor reached the end of the data provided by the + ExtractorInput, then Extractor.RESULT_END_OF_INPUT is returned. + +

          When this method throws an IOException, extraction may continue by providing an + ExtractorInput with an unchanged read position to + a subsequent call to this method.

          +
          +
          Specified by:
          +
          read in interface Extractor
          +
          Parameters:
          +
          input - The ExtractorInput from which data should be read.
          +
          seekPosition - If Extractor.RESULT_SEEK is returned, this holder is updated to hold the + position of the required data.
          +
          Returns:
          +
          One of the RESULT_ values defined in this interface.
          +
          Throws:
          +
          IOException - If an error occurred reading from or parsing the input.
          +
          +
        • +
        + + + +
          +
        • +

          seek

          +
          public void seek​(long position,
          +                 long timeUs)
          +
          Description copied from interface: Extractor
          +
          Notifies the extractor that a seek has occurred. + +

          Following a call to this method, the ExtractorInput passed to the next invocation of + Extractor.read(ExtractorInput, PositionHolder) is required to provide data starting from + position in the stream. Valid random access positions are the start of the stream and + positions that can be obtained from any SeekMap passed to the ExtractorOutput.

          +
          +
          Specified by:
          +
          seek in interface Extractor
          +
          Parameters:
          +
          position - The byte offset in the stream from which data will be provided.
          +
          timeUs - The seek time in microseconds.
          +
          +
        • +
        + + + +
          +
        • +

          release

          +
          public void release()
          +
          Releases the extractor's resources, including the SubtitleDecoder.
          +
          +
          Specified by:
          +
          release in interface Extractor
          +
          +
        • +
        +
      • +
      +
      +
    • +
    +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleInputBuffer.html b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleInputBuffer.html index c1fc18ff01..86f34c676f 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleInputBuffer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleInputBuffer.html @@ -186,7 +186,7 @@ extends DecoderInputBuffer -BUFFER_REPLACEMENT_MODE_DIRECT, BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, cryptoInfo, data, supplementalData, timeUs, waitingForKeys +BUFFER_REPLACEMENT_MODE_DIRECT, BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, cryptoInfo, data, format, supplementalData, timeUs, waitingForKeys diff --git a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleOutputBuffer.html b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleOutputBuffer.html index 41665a12c8..410332b356 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleOutputBuffer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/text/SubtitleOutputBuffer.html @@ -124,7 +124,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.decoder.Buffer
  • @@ -178,11 +178,11 @@ implements -
  • +
  • -

    Fields inherited from class com.google.android.exoplayer2.decoder.OutputBuffer

    -skippedOutputBufferCount, timeUs
  • +

    Fields inherited from class com.google.android.exoplayer2.decoder.DecoderOutputBuffer

    +skippedOutputBufferCount, timeUs @@ -270,11 +270,11 @@ implements -
  • +
  • -

    Methods inherited from class com.google.android.exoplayer2.decoder.OutputBuffer

    -release
  • +

    Methods inherited from class com.google.android.exoplayer2.decoder.DecoderOutputBuffer

    +release @@ -218,6 +225,16 @@ extends static int +DEFAULT_MAX_HEIGHT_TO_DISCARD +  + + +static int +DEFAULT_MAX_WIDTH_TO_DISCARD +  + + +static int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS   @@ -260,13 +277,15 @@ extends protected -AdaptiveTrackSelection​(TrackGroup group, +AdaptiveTrackSelection​(TrackGroup group, int[] tracks, - int type, + @com.google.android.exoplayer2.trackselection.TrackSelection.Type int type, BandwidthMeter bandwidthMeter, long minDurationForQualityIncreaseMs, long maxDurationForQualityDecreaseMs, long minDurationToRetainAfterDiscardMs, + int maxWidthToDiscard, + int maxHeightToDiscard, float bandwidthFraction, float bufferedFractionToLiveEdgeForQualityIncrease, List<AdaptiveTrackSelection.AdaptationCheckpoint> adaptationCheckpoints, @@ -462,6 +481,32 @@ extends + + +
      +
    • +

      DEFAULT_MAX_WIDTH_TO_DISCARD

      +
      public static final int DEFAULT_MAX_WIDTH_TO_DISCARD
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    + + + +
      +
    • +

      DEFAULT_MAX_HEIGHT_TO_DISCARD

      +
      public static final int DEFAULT_MAX_HEIGHT_TO_DISCARD
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
    • +
    @@ -516,7 +561,7 @@ extends + + @@ -229,9 +236,9 @@ implements   -BaseTrackSelection​(TrackGroup group, +BaseTrackSelection​(TrackGroup group, int[] tracks, - int type) + @com.google.android.exoplayer2.trackselection.TrackSelection.Type int type)   @@ -460,7 +467,7 @@ implements +
      @@ -468,7 +475,7 @@ implements TrackGroup group, int[] tracks, - int type)
    + @com.google.android.exoplayer2.trackselection.TrackSelection.Type int type)
    Parameters:
    group - The TrackGroup. Must not be null.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html index d02f0486da..d855721581 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":9,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10}; +var data = {"i0":10,"i1":10,"i2":9,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -135,7 +135,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Implemented Interfaces:
    -
    Parcelable
    +
    Bundleable
    Enclosing class:
    @@ -144,7 +144,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    public static final class DefaultTrackSelector.Parameters
     extends TrackSelectionParameters
    -implements Parcelable
    +implements Bundleable
    Extends DefaultTrackSelector.Parameters by adding fields that are specific to DefaultTrackSelector.
  • @@ -167,11 +167,11 @@ implements TrackSelectionParameters.Builder
    @@ -233,9 +233,11 @@ implements -static Parcelable.Creator<DefaultTrackSelector.Parameters> +static Bundleable.Creator<DefaultTrackSelector.Parameters> CREATOR -  + +
    Object that can restore Parameters from a Bundle.
    + static DefaultTrackSelector.Parameters @@ -254,7 +256,7 @@ implements -int +@com.google.android.exoplayer2.C.SelectionFlags int disabledTextTrackSelectionFlags
    Bitmask of selection flags that are disabled for text track selections.
    @@ -295,14 +297,7 @@ implements TrackSelectionParameters -forceHighestSupportedBitrate, forceLowestBitrate, maxAudioBitrate, maxAudioChannelCount, maxVideoBitrate, maxVideoFrameRate, maxVideoHeight, maxVideoWidth, minVideoBitrate, minVideoFrameRate, minVideoHeight, minVideoWidth, preferredAudioLanguages, preferredAudioMimeTypes, preferredAudioRoleFlags, preferredTextLanguages, preferredTextRoleFlags, preferredVideoMimeTypes, selectUndeterminedTextLanguage, viewportHeight, viewportOrientationMayChange, viewportWidth - - @@ -329,30 +324,25 @@ implements -int -describeContents() -  - - boolean equals​(Object obj)   - + static DefaultTrackSelector.Parameters getDefaults​(Context context)
    Returns an instance configured with default values.
    - + boolean getRendererDisabled​(int rendererIndex)
    Returns whether the renderer is disabled.
    - + DefaultTrackSelector.SelectionOverride getSelectionOverride​(int rendererIndex, TrackGroupArray groups) @@ -360,12 +350,12 @@ implements Returns the override for the specified renderer and TrackGroupArray. - + int hashCode()   - + boolean hasSelectionOverride​(int rendererIndex, TrackGroupArray groups) @@ -373,11 +363,12 @@ implements Returns whether there is an override for the specified renderer and TrackGroupArray. - -void -writeToParcel​(Parcel dest, - int flags) -  + +Bundle +toBundle() + +
    Returns a Bundle representing the information stored in this object.
    + @@ -564,7 +546,7 @@ public final int disabledTextTrackSelectionFlags -
      +
      • allowMultipleAdaptiveSelections

        public final boolean allowMultipleAdaptiveSelections
        @@ -576,6 +558,16 @@ public final int disabledTextTrackSelectionFlags other track selection parameters.
      + + + +
    @@ -690,34 +682,20 @@ public final  - - - - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.ParametersBuilder.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.ParametersBuilder.html index 168ceb60a4..dda76457ee 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.ParametersBuilder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.ParametersBuilder.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; +var data = {"i0":10,"i1":42,"i2":42,"i3":42,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":42,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -187,7 +187,7 @@ extends -All Methods Instance Methods Concrete Methods  +All Methods Instance Methods Concrete Methods Deprecated Methods  Modifier and Type Method @@ -205,21 +205,27 @@ extends clearSelectionOverride​(int rendererIndex, TrackGroupArray groups) -
    Clears a track selection override for the specified renderer and TrackGroupArray.
    + DefaultTrackSelector.ParametersBuilder clearSelectionOverrides() -
    Clears all track selection overrides for all renderers.
    + DefaultTrackSelector.ParametersBuilder clearSelectionOverrides​(int rendererIndex) -
    Clears all track selection overrides for the specified renderer.
    + @@ -238,41 +244,48 @@ extends +protected DefaultTrackSelector.ParametersBuilder +set​(TrackSelectionParameters parameters) + +
    Overrides the value of the builder with the value of TrackSelectionParameters.
    + + + DefaultTrackSelector.ParametersBuilder setAllowAudioMixedChannelCountAdaptiveness​(boolean allowAudioMixedChannelCountAdaptiveness)
    Sets whether to allow adaptive audio selections containing mixed channel counts.
    - + DefaultTrackSelector.ParametersBuilder setAllowAudioMixedMimeTypeAdaptiveness​(boolean allowAudioMixedMimeTypeAdaptiveness)
    Sets whether to allow adaptive audio selections containing mixed MIME types.
    - + DefaultTrackSelector.ParametersBuilder setAllowAudioMixedSampleRateAdaptiveness​(boolean allowAudioMixedSampleRateAdaptiveness)
    Sets whether to allow adaptive audio selections containing mixed sample rates.
    - + DefaultTrackSelector.ParametersBuilder setAllowMultipleAdaptiveSelections​(boolean allowMultipleAdaptiveSelections)
    Sets whether multiple adaptive selections with more than one track are allowed.
    - + DefaultTrackSelector.ParametersBuilder setAllowVideoMixedMimeTypeAdaptiveness​(boolean allowVideoMixedMimeTypeAdaptiveness)
    Sets whether to allow adaptive video selections containing mixed MIME types.
    - + DefaultTrackSelector.ParametersBuilder setAllowVideoNonSeamlessAdaptiveness​(boolean allowVideoNonSeamlessAdaptiveness) @@ -280,28 +293,36 @@ extends + DefaultTrackSelector.ParametersBuilder -setDisabledTextTrackSelectionFlags​(int disabledTextTrackSelectionFlags) +setDisabledTextTrackSelectionFlags​(@com.google.android.exoplayer2.C.SelectionFlags int disabledTextTrackSelectionFlags)
    Sets a bitmask of selection flags that are disabled for text track selections.
    - + +DefaultTrackSelector.ParametersBuilder +setDisabledTrackTypes​(Set<@TrackType Integer> disabledTrackTypes) + +
    Sets the disabled track types, preventing all tracks of those types from being selected for + playback.
    + + + DefaultTrackSelector.ParametersBuilder setExceedAudioConstraintsIfNecessary​(boolean exceedAudioConstraintsIfNecessary)
    Sets whether to exceed the setMaxAudioChannelCount(int) and setMaxAudioBitrate(int) constraints when no selection can be made otherwise.
    - + DefaultTrackSelector.ParametersBuilder setExceedRendererCapabilitiesIfNecessary​(boolean exceedRendererCapabilitiesIfNecessary)
    Sets whether to exceed renderer capabilities when no selection can be made otherwise.
    - + DefaultTrackSelector.ParametersBuilder setExceedVideoConstraintsIfNecessary​(boolean exceedVideoConstraintsIfNecessary) @@ -309,7 +330,7 @@ extends setMaxVideoFrameRate(int) constraints when no selection can be made otherwise. - + DefaultTrackSelector.ParametersBuilder setForceHighestSupportedBitrate​(boolean forceHighestSupportedBitrate) @@ -317,7 +338,7 @@ extends + DefaultTrackSelector.ParametersBuilder setForceLowestBitrate​(boolean forceLowestBitrate) @@ -325,35 +346,35 @@ extends + DefaultTrackSelector.ParametersBuilder setMaxAudioBitrate​(int maxAudioBitrate)
    Sets the maximum allowed audio bitrate.
    - + DefaultTrackSelector.ParametersBuilder setMaxAudioChannelCount​(int maxAudioChannelCount)
    Sets the maximum allowed audio channel count.
    - + DefaultTrackSelector.ParametersBuilder setMaxVideoBitrate​(int maxVideoBitrate)
    Sets the maximum allowed video bitrate.
    - + DefaultTrackSelector.ParametersBuilder setMaxVideoFrameRate​(int maxVideoFrameRate)
    Sets the maximum allowed video frame rate.
    - + DefaultTrackSelector.ParametersBuilder setMaxVideoSize​(int maxVideoWidth, int maxVideoHeight) @@ -361,28 +382,28 @@ extends Sets the maximum allowed video width and height. - + DefaultTrackSelector.ParametersBuilder setMaxVideoSizeSd() - + DefaultTrackSelector.ParametersBuilder setMinVideoBitrate​(int minVideoBitrate)
    Sets the minimum allowed video bitrate.
    - + DefaultTrackSelector.ParametersBuilder setMinVideoFrameRate​(int minVideoFrameRate)
    Sets the minimum allowed video frame rate.
    - + DefaultTrackSelector.ParametersBuilder setMinVideoSize​(int minVideoWidth, int minVideoHeight) @@ -390,49 +411,49 @@ extends Sets the minimum allowed video width and height. - + DefaultTrackSelector.ParametersBuilder setPreferredAudioLanguage​(String preferredAudioLanguage)
    Sets the preferred language for audio and forced text tracks.
    - + DefaultTrackSelector.ParametersBuilder setPreferredAudioLanguages​(String... preferredAudioLanguages)
    Sets the preferred languages for audio and forced text tracks.
    - + DefaultTrackSelector.ParametersBuilder setPreferredAudioMimeType​(String mimeType)
    Sets the preferred sample MIME type for audio tracks.
    - + DefaultTrackSelector.ParametersBuilder setPreferredAudioMimeTypes​(String... mimeTypes)
    Sets the preferred sample MIME types for audio tracks.
    - + DefaultTrackSelector.ParametersBuilder -setPreferredAudioRoleFlags​(int preferredAudioRoleFlags) +setPreferredAudioRoleFlags​(@com.google.android.exoplayer2.C.RoleFlags int preferredAudioRoleFlags)
    Sets the preferred C.RoleFlags for audio tracks.
    - + DefaultTrackSelector.ParametersBuilder setPreferredTextLanguage​(String preferredTextLanguage)
    Sets the preferred language for text tracks.
    - + DefaultTrackSelector.ParametersBuilder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings​(Context context) @@ -440,35 +461,35 @@ extends CaptioningManager. - + DefaultTrackSelector.ParametersBuilder setPreferredTextLanguages​(String... preferredTextLanguages)
    Sets the preferred languages for text tracks.
    - + DefaultTrackSelector.ParametersBuilder -setPreferredTextRoleFlags​(int preferredTextRoleFlags) +setPreferredTextRoleFlags​(@com.google.android.exoplayer2.C.RoleFlags int preferredTextRoleFlags)
    Sets the preferred C.RoleFlags for text tracks.
    - + DefaultTrackSelector.ParametersBuilder setPreferredVideoMimeType​(String mimeType)
    Sets the preferred sample MIME type for video tracks.
    - + DefaultTrackSelector.ParametersBuilder setPreferredVideoMimeTypes​(String... mimeTypes)
    Sets the preferred sample MIME types for video tracks.
    - + DefaultTrackSelector.ParametersBuilder setRendererDisabled​(int rendererIndex, boolean disabled) @@ -476,16 +497,18 @@ extends Sets whether the renderer at the specified index is disabled. - + DefaultTrackSelector.ParametersBuilder setSelectionOverride​(int rendererIndex, TrackGroupArray groups, DefaultTrackSelector.SelectionOverride override) -
    Overrides the track selection for the renderer at the specified index.
    + - + DefaultTrackSelector.ParametersBuilder setSelectUndeterminedTextLanguage​(boolean selectUndeterminedTextLanguage) @@ -494,14 +517,21 @@ extends + +DefaultTrackSelector.ParametersBuilder +setTrackSelectionOverrides​(TrackSelectionOverrides trackSelectionOverrides) + +
    Sets the selection overrides.
    + + + DefaultTrackSelector.ParametersBuilder setTunnelingEnabled​(boolean tunnelingEnabled)
    Sets whether to enable tunneling if possible.
    - + DefaultTrackSelector.ParametersBuilder setViewportSize​(int viewportWidth, int viewportHeight, @@ -511,7 +541,7 @@ extends + DefaultTrackSelector.ParametersBuilder setViewportSizeToPhysicalDisplaySize​(Context context, boolean viewportOrientationMayChange) @@ -581,6 +611,21 @@ public ParametersBuilder()

    Method Detail

    + + + + @@ -939,19 +984,19 @@ public ParametersBuilder()
    - + - + - +
    • setDisabledTextTrackSelectionFlags

      public DefaultTrackSelector.ParametersBuilder setDisabledTextTrackSelectionFlags​(@SelectionFlags
      -                                                                                 int disabledTextTrackSelectionFlags)
      + @com.google.android.exoplayer2.C.SelectionFlags int disabledTextTrackSelectionFlags)
      Sets a bitmask of selection flags that are disabled for text track selections.
      Parameters:
      @@ -1278,6 +1323,45 @@ public ParametersBuilder()
    + + + + + + + + @@ -1358,10 +1442,14 @@ public ParametersBuilder() @@ -230,13 +225,17 @@ implements SelectionOverride​(int groupIndex, int... tracks) -  + +
    Constructs a SelectionOverride to override tracks of a group.
    + -SelectionOverride​(int groupIndex, +SelectionOverride​(int groupIndex, int[] tracks, - int type) -  + @com.google.android.exoplayer2.trackselection.TrackSelection.Type int type) + +
    Constructs a SelectionOverride of the given type to override tracks of a group.
    + @@ -264,25 +263,21 @@ implements -int -describeContents() -  - - boolean equals​(Object obj)   - + int hashCode()   - -void -writeToParcel​(Parcel dest, - int flags) -  + +Bundle +toBundle() + +
    Returns a Bundle representing the information stored in this object.
    + @@ -350,7 +345,8 @@ implements
  • CREATOR

    -
    public static final Parcelable.Creator<DefaultTrackSelector.SelectionOverride> CREATOR
    +
    public static final Bundleable.Creator<DefaultTrackSelector.SelectionOverride> CREATOR
    +
    Object that can restore SelectionOverride from a Bundle.
  • @@ -371,6 +367,7 @@ implements Constructs a SelectionOverride to override tracks of a group.
    Parameters:
    groupIndex - The overriding track group index.
    @@ -378,7 +375,7 @@ implements
    + @@ -289,7 +289,7 @@ extends - +
      @@ -297,7 +297,7 @@ extends Definition
      public Definition​(TrackGroup group,
                         int[] tracks,
      -                  int type)
      + @com.google.android.exoplayer2.trackselection.TrackSelection.Type int type)
      Parameters:
      group - The TrackGroup. Must not be null.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/ExoTrackSelection.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/ExoTrackSelection.html index 3d1c5ea47c..10e07433b1 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/ExoTrackSelection.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/ExoTrackSelection.html @@ -168,6 +168,13 @@ extends +
    • + + +

      Nested classes/interfaces inherited from interface com.google.android.exoplayer2.trackselection.TrackSelection

      +TrackSelection.Type
    • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/FixedTrackSelection.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/FixedTrackSelection.html index da40da4b81..cd0d6e34ac 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/FixedTrackSelection.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/FixedTrackSelection.html @@ -161,6 +161,13 @@ extends ExoTrackSelection ExoTrackSelection.Definition, ExoTrackSelection.Factory + @@ -207,15 +214,15 @@ extends   -FixedTrackSelection​(TrackGroup group, +FixedTrackSelection​(TrackGroup group, int track, - int type) + @com.google.android.exoplayer2.trackselection.TrackSelection.Type int type)   -FixedTrackSelection​(TrackGroup group, +FixedTrackSelection​(TrackGroup group, int track, - int type, + @com.google.android.exoplayer2.trackselection.TrackSelection.Type int type, int reason, Object data)   @@ -323,7 +330,7 @@ extends + - +
    • getTypeSupport

      @RendererSupport
      -public int getTypeSupport​(int trackType)
      +public int getTypeSupport​(@com.google.android.exoplayer2.C.TrackType int trackType)
      Returns the extent to which tracks of a specified type are supported. This is the best level of support obtained from getRendererSupport(int) for all renderers that handle the specified type. If no such renderers exist then RENDERER_SUPPORT_NO_TRACKS is returned.
      Parameters:
      -
      trackType - The track type. One of the C TRACK_TYPE_* constants.
      +
      trackType - The track type.
      Returns:
      The MappingTrackSelector.MappedTrackInfo.RendererSupport.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/MappingTrackSelector.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/MappingTrackSelector.html index cb94cbd21e..0ed5d7f45c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/MappingTrackSelector.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/MappingTrackSelector.html @@ -258,7 +258,7 @@ extends TrackSelector -getBandwidthMeter, init, invalidate
    • +getBandwidthMeter, getParameters, init, invalidate, isSetParametersSupported, setParameters
    + diff --git a/docs/doc/reference/com/google/android/exoplayer2/device/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelection.Type.html similarity index 66% rename from docs/doc/reference/com/google/android/exoplayer2/device/package-summary.html rename to docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelection.Type.html index 4b3d17cefc..f6817691e2 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/device/package-summary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelection.Type.html @@ -2,7 +2,7 @@ -com.google.android.exoplayer2.device (ExoPlayer library) +TrackSelection.Type (ExoPlayer library) @@ -19,7 +19,7 @@
    - +
    • setPreferredAudioRoleFlags

      public TrackSelectionParameters.Builder setPreferredAudioRoleFlags​(@RoleFlags
      -                                                                   int preferredAudioRoleFlags)
      + @com.google.android.exoplayer2.C.RoleFlags int preferredAudioRoleFlags)
      Sets the preferred C.RoleFlags for audio tracks.
      Parameters:
      @@ -879,14 +926,14 @@ public Builder()
    - +
    • setPreferredTextRoleFlags

      public TrackSelectionParameters.Builder setPreferredTextRoleFlags​(@RoleFlags
      -                                                                  int preferredTextRoleFlags)
      + @com.google.android.exoplayer2.C.RoleFlags int preferredTextRoleFlags)
      Sets the preferred C.RoleFlags for text tracks.
      Parameters:
      @@ -951,6 +998,39 @@ public Builder()
    + + + +
      +
    • +

      setTrackSelectionOverrides

      +
      public TrackSelectionParameters.Builder setTrackSelectionOverrides​(TrackSelectionOverrides trackSelectionOverrides)
      +
      Sets the selection overrides.
      +
      +
      Parameters:
      +
      trackSelectionOverrides - The track selection overrides.
      +
      Returns:
      +
      This builder.
      +
      +
    • +
    + + + +
      +
    • +

      setDisabledTrackTypes

      +
      public TrackSelectionParameters.Builder setDisabledTrackTypes​(Set<@TrackType Integer> disabledTrackTypes)
      +
      Sets the disabled track types, preventing all tracks of those types from being selected for + playback.
      +
      +
      Parameters:
      +
      disabledTrackTypes - The track types to disable.
      +
      Returns:
      +
      This builder.
      +
      +
    • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.html index fcfdea1465..390d48545b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":9,"i4":10,"i5":10}; +var data = {"i0":10,"i1":10,"i2":9,"i3":10,"i4":10}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -130,7 +130,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • All Implemented Interfaces:
    -
    Parcelable
    +
    Bundleable
    Direct Known Subclasses:
    @@ -139,8 +139,24 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    public class TrackSelectionParameters
     extends Object
    -implements Parcelable
    -
    Constraint parameters for track selection.
    +implements Bundleable +
    Constraint parameters for track selection. + +

    For example the following code modifies the parameters to restrict video track selections to + SD, and to select a German audio track if there is one: + +

    
    + // Build on the current parameters.
    + TrackSelectionParameters currentParameters = player.getTrackSelectionParameters()
    + // Build the resulting parameters.
    + TrackSelectionParameters newParameters = currentParameters
    +     .buildUpon()
    +     .setMaxVideoSizeSd()
    +     .setPreferredAudioLanguage("deu")
    +     .build();
    + // Set the new parameters.
    + player.setTrackSelectionParameters(newParameters);
    + 
  • @@ -170,11 +186,11 @@ implements -
  • +
  • -

    Nested classes/interfaces inherited from interface android.os.Parcelable

    -Parcelable.ClassLoaderCreator<T extends Object>, Parcelable.Creator<T extends Object>
  • +

    Nested classes/interfaces inherited from interface com.google.android.exoplayer2.Bundleable

    +Bundleable.Creator<T extends Bundleable> @@ -194,9 +210,11 @@ implements Description -static Parcelable.Creator<TrackSelectionParameters> +static Bundleable.Creator<TrackSelectionParameters> CREATOR -  + +
    Object that can restore TrackSelectionParameters from a Bundle.
    + static TrackSelectionParameters @@ -215,6 +233,13 @@ implements +ImmutableSet<@TrackType Integer> +disabledTrackTypes + +
    The track types that are disabled.
    + + + boolean forceHighestSupportedBitrate @@ -222,7 +247,7 @@ implements + boolean forceLowestBitrate @@ -230,77 +255,77 @@ implements + int maxAudioBitrate
    Maximum allowed audio bitrate in bits per second.
    - + int maxAudioChannelCount
    Maximum allowed audio channel count.
    - + int maxVideoBitrate
    Maximum allowed video bitrate in bits per second.
    - + int maxVideoFrameRate
    Maximum allowed video frame rate in hertz.
    - + int maxVideoHeight
    Maximum allowed video height in pixels.
    - + int maxVideoWidth
    Maximum allowed video width in pixels.
    - + int minVideoBitrate
    Minimum allowed video bitrate in bits per second.
    - + int minVideoFrameRate
    Minimum allowed video frame rate in hertz.
    - + int minVideoHeight
    Minimum allowed video height in pixels.
    - + int minVideoWidth
    Minimum allowed video width in pixels.
    - + ImmutableList<String> preferredAudioLanguages @@ -308,7 +333,7 @@ implements + ImmutableList<String> preferredAudioMimeTypes @@ -316,28 +341,28 @@ implements -int + +@com.google.android.exoplayer2.C.RoleFlags int preferredAudioRoleFlags
    The preferred C.RoleFlags for audio tracks.
    - + ImmutableList<String> preferredTextLanguages
    The preferred languages for text tracks as IETF BCP 47 conformant tags in order of preference.
    - -int + +@com.google.android.exoplayer2.C.RoleFlags int preferredTextRoleFlags
    The preferred C.RoleFlags for text tracks.
    - + ImmutableList<String> preferredVideoMimeTypes @@ -345,13 +370,20 @@ implements + boolean selectUndeterminedTextLanguage
    Whether a text track with undetermined language should be selected if no track with preferredTextLanguages is available, or if preferredTextLanguages is unset.
    + +TrackSelectionOverrides +trackSelectionOverrides + +
    Overrides to force tracks to be selected.
    + + int viewportHeight @@ -374,13 +406,6 @@ implements -
  • - - -

    Fields inherited from interface android.os.Parcelable

    -CONTENTS_FILE_DESCRIPTOR, PARCELABLE_WRITE_RETURN_VALUE
  • - @@ -429,32 +454,28 @@ implements -int -describeContents() -  - - boolean equals​(Object obj)   - + static TrackSelectionParameters getDefaults​(Context context)
    Returns an instance configured with default values.
    - + int hashCode()   - -void -writeToParcel​(Parcel dest, - int flags) -  + +Bundle +toBundle() + +
    Returns a Bundle representing the information stored in this object.
    +
      @@ -514,15 +535,6 @@ public static final  - - - @@ -678,7 +690,7 @@ public static final 

      preferredAudioRoleFlags

      @RoleFlags
      -public final int preferredAudioRoleFlags
      +public final @com.google.android.exoplayer2.C.RoleFlags int preferredAudioRoleFlags
      The preferred C.RoleFlags for audio tracks. 0 selects the default track if there is one, or the first track if there's no default. The default value is 0.
      @@ -735,7 +747,7 @@ public final int preferredAudioRoleFlags
    • preferredTextRoleFlags

      @RoleFlags
      -public final int preferredTextRoleFlags
      +public final @com.google.android.exoplayer2.C.RoleFlags int preferredTextRoleFlags
      The preferred C.RoleFlags for text tracks. 0 selects the default track if there is one, or no track otherwise. The default value is 0, or C.ROLE_FLAG_SUBTITLE | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND if the accessibility CaptioningManager @@ -767,7 +779,7 @@ public final int preferredTextRoleFlags -
        +
        • forceHighestSupportedBitrate

          public final boolean forceHighestSupportedBitrate
          @@ -775,6 +787,38 @@ public final int preferredTextRoleFlags other constraints. The default value is false.
    + + + +
      +
    • +

      trackSelectionOverrides

      +
      public final TrackSelectionOverrides trackSelectionOverrides
      +
      Overrides to force tracks to be selected.
      +
    • +
    + + + +
      +
    • +

      disabledTrackTypes

      +
      public final ImmutableSet<@TrackType Integer> disabledTrackTypes
      +
      The track types that are disabled. No track of a disabled type will be selected, thus no track + type contained in the set will be played. The default value is that no track type is disabled + (empty set).
      +
    • +
    + + + + @@ -851,30 +895,18 @@ public final int preferredTextRoleFlags - - - - - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.html index aa3d848324..e6cfb850dd 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":9,"i1":9,"i2":9,"i3":9}; +var data = {"i0":9,"i1":9,"i2":9}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -195,14 +195,6 @@ extends -static boolean -hasTrackOfType​(TrackSelectionArray trackSelections, - int trackType) - -
    Returns if a TrackSelectionArray has at least one track of the given type.
    - - - static DefaultTrackSelector.Parameters updateParametersWithOverride​(DefaultTrackSelector.Parameters parameters, int rendererIndex, @@ -284,17 +276,6 @@ extends - - - -
      -
    • -

      hasTrackOfType

      -
      public static boolean hasTrackOfType​(TrackSelectionArray trackSelections,
      -                                     int trackType)
      -
      Returns if a TrackSelectionArray has at least one track of the given type.
      -
    • -
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelector.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelector.html index 78f23a7318..2e8741f757 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelector.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelector.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":6,"i4":6}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":6,"i6":6,"i7":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -259,6 +259,13 @@ extends +TrackSelectionParameters +getParameters() + +
    Returns the current parameters for track selection.
    + + + void init​(TrackSelector.InvalidationListener listener, BandwidthMeter bandwidthMeter) @@ -266,7 +273,7 @@ extends Called by the player to initialize the selector. - + protected void invalidate() @@ -274,14 +281,21 @@ extends - + +boolean +isSetParametersSupported() + +
    Returns if this TrackSelector supports setParameters(TrackSelectionParameters).
    + + + abstract void onSelectionActivated​(Object info)
    Called by the player when a TrackSelectorResult previously generated by selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId, Timeline) is activated.
    - + abstract TrackSelectorResult selectTracks​(RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups, @@ -291,6 +305,13 @@ extends Called by the player to perform a track selection. + +void +setParameters​(TrackSelectionParameters parameters) + +
    Called by the player to provide parameters for track selection.
    + + + + + +
      +
    • +

      getParameters

      +
      public TrackSelectionParameters getParameters()
      +
      Returns the current parameters for track selection.
      +
    • +
    + + + +
      +
    • +

      setParameters

      +
      public void setParameters​(TrackSelectionParameters parameters)
      +
      Called by the player to provide parameters for track selection. + +

      Only supported if isSetParametersSupported() returns true.

      +
      +
      Parameters:
      +
      parameters - The parameters for track selection.
      +
      +
    • +
    + + + +
      +
    • +

      isSetParametersSupported

      +
      public boolean isSetParametersSupported()
      +
      Returns if this TrackSelector supports setParameters(TrackSelectionParameters). + +

      The same value is always returned for a given TrackSelector instance.

      +
    • +
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectorResult.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectorResult.html index 8746eb6f88..4273f16111 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectorResult.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/TrackSelectorResult.html @@ -181,6 +181,13 @@ extends A ExoTrackSelection array containing the track selection for each renderer. + +TracksInfo +tracksInfo + +
    Describe the tracks and which one were selected.
    + + @@ -199,10 +206,22 @@ extends Description +TrackSelectorResult​(@NullableType RendererConfiguration[] rendererConfigurations, + @NullableType ExoTrackSelection[] selections, + TracksInfo tracksInfo, + Object info) +  + + TrackSelectorResult​(@NullableType RendererConfiguration[] rendererConfigurations, @NullableType ExoTrackSelection[] selections, Object info) -  + + + @@ -299,6 +318,16 @@ extends A ExoTrackSelection array containing the track selection for each renderer. + + + +
      +
    • +

      tracksInfo

      +
      public final TracksInfo tracksInfo
      +
      Describe the tracks and which one were selected.
      +
    • +
    @@ -324,11 +353,37 @@ public final  + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/package-tree.html index 7464e1a1b0..d513b87acc 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/package-tree.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/package-tree.html @@ -114,16 +114,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.trackselection.DefaultTrackSelector.AudioTrackScore (implements java.lang.Comparable<T>)
  • com.google.android.exoplayer2.trackselection.DefaultTrackSelector.OtherTrackScore (implements java.lang.Comparable<T>)
  • -
  • com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride (implements android.os.Parcelable)
  • +
  • com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride (implements com.google.android.exoplayer2.Bundleable)
  • com.google.android.exoplayer2.trackselection.DefaultTrackSelector.TextTrackScore (implements java.lang.Comparable<T>)
  • com.google.android.exoplayer2.trackselection.DefaultTrackSelector.VideoTrackScore (implements java.lang.Comparable<T>)
  • com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition
  • com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo
  • com.google.android.exoplayer2.trackselection.RandomTrackSelection.Factory (implements com.google.android.exoplayer2.trackselection.ExoTrackSelection.Factory)
  • com.google.android.exoplayer2.trackselection.TrackSelectionArray
  • -
  • com.google.android.exoplayer2.trackselection.TrackSelectionParameters (implements android.os.Parcelable) +
  • com.google.android.exoplayer2.trackselection.TrackSelectionOverrides (implements com.google.android.exoplayer2.Bundleable)
  • +
  • com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.Builder
  • +
  • com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride (implements com.google.android.exoplayer2.Bundleable)
  • +
  • com.google.android.exoplayer2.trackselection.TrackSelectionParameters (implements com.google.android.exoplayer2.Bundleable)
  • com.google.android.exoplayer2.trackselection.TrackSelectionParameters.Builder @@ -159,6 +162,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener
  • +
    +

    Annotation Type Hierarchy

    + +