diff --git a/.hgignore b/.hgignore index e2c54bcf61..5681fe9625 100644 --- a/.hgignore +++ b/.hgignore @@ -75,9 +75,3 @@ extensions/flac/src/main/jni/flac # FFmpeg extension extensions/ffmpeg/src/main/jni/ffmpeg - -# Cronet extension -extensions/cronet/jniLibs/* -!extensions/cronet/jniLibs/README.md -extensions/cronet/libs/* -!extensions/cronet/libs/README.md diff --git a/README.md b/README.md index 73f57c92a1..856f961ae9 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,10 @@ git checkout release-v2 ``` Next, add the following to your project's `settings.gradle` file, replacing -`path/to/exoplayer` with the path to your local copy: +`/absolute/path/to/exoplayer` with the absolute path to your local copy: ```gradle -gradle.ext.exoplayerRoot = 'path/to/exoplayer' +gradle.ext.exoplayerRoot = '/absolute/path/to/exoplayer' gradle.ext.exoplayerModulePrefix = 'exoplayer-' apply from: new File(gradle.ext.exoplayerRoot, 'core_settings.gradle') ``` diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 33fbdb7c09..db85258240 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,179 @@ # Release notes +### dev-v2 (not yet released) + +* Remove deprecated symbols: + * Remove `PlaybackPreparer`. UI components that previously had + `setPlaybackPreparer` methods will now call `Player.prepare` by default. + If this behavior is sufficient, use of `PlaybackPreparer` can be removed + from application code without replacement. For custom preparation logic, + replace calls to `setPlaybackPreparer` with calls to + `setControlDispatcher` on the same components, passing a + `ControlDispatcher` that implements custom preparation logic in + `dispatchPrepare`. Extend `DefaultControlDispatcher` to avoid having to + implement the other `ControlDispatcher` methods. + * Remove `setRewindIncrementMs` and `setFastForwardIncrementMs` from UI + components. Use `setControlDispatcher` on the same components, passing a + `DefaultControlDispatcher` built using `DefaultControlDispatcher(long, + long)`. + * Remove `PlayerNotificationManager` constructors and `createWith` + methods. Use `PlayerNotificationManager.Builder` instead. + * Remove `PlayerNotificationManager.setNotificationListener`. Use + `PlayerNotificationManager.Builder.setNotificationListener` instead. + * Remove `PlayerNotificationManager` `setUseNavigationActions` and + `setUseNavigationActionsInCompactView`. Use `setUseNextAction`, + `setUsePreviousAction`, `setUseNextActionInCompactView` and + `setUsePreviousActionInCompactView` instead. + * Remove `Format.create` methods. Use `Format.Builder` instead. + * Remove `CastPlayer` specific playlist manipulation methods. Use + `setMediaItems`, `addMediaItems`, `removeMediaItem` and `moveMediaItem` + instead. + +### 2.14.0 (2021-05-13) + +* Core Library: + * Move `Player` components to `ExoPlayer`. For example + `Player.VideoComponent` is now `ExoPlayer.VideoComponent`. + * The most used methods of `Player`'s audio, video, text and metadata + components have been added directly to `Player`. + * Add `Player.getAvailableCommands`, `Player.isCommandAvailable` and + `Listener.onAvailableCommandsChanged` to query which commands + that can be executed on the player. + * Add a `Player.Listener` interface to receive all player events. + Component listeners and `EventListener` have been deprecated. + * Add `Player.getMediaMetadata`, which returns a combined and structured + `MediaMetadata` object. Changes to metadata are reported to + `Listener.onMediaMetadataChanged`. + * `Player.setPlaybackParameters` no longer accepts null, use + `PlaybackParameters.DEFAULT` instead. + * Report information about the old and the new playback positions to + `Listener.onPositionDiscontinuity`. Add `DISCONTINUITY_REASON_SKIP` + and `DISCONTINUITY_REASON_REMOVE` as discontinuity reasons, and rename + `DISCONTINUITY_REASON_PERIOD_TRANSITION` to + `DISCONTINUITY_REASON_AUTO_TRANSITION`. Remove + `DISCONTINUITY_REASON_AD_INSERTION`, for which + `DISCONTINUITY_REASON_AUTO_TRANSITION` is used instead + ([#6163](https://github.com/google/ExoPlayer/issues/6163), + [#4768](https://github.com/google/ExoPlayer/issues/4768)). + * Deprecate `ExoPlayer.Builder`. Use `SimpleExoPlayer.Builder` instead. + * Move `Player.getRendererCount` and `Player.getRendererType` to + `ExoPlayer`. + * Use an empty string instead of the URI if the media ID is not explicitly + set with `MediaItem.Builder.setMediaId(String)`. + * Remove `MediaCodecRenderer.configureCodec()` and add + `MediaCodecRenderer.getMediaCodecConfiguration()`. The new method is + called just before the `MediaCodec` is created and returns the + parameters needed to create and configure the `MediaCodec` instance. + Applications can override `MediaCodecRenderer.onCodecInitialized()` to + be notified after a `MediaCodec` is initialized, or they can inject a + custom `MediaCodecAdapter.Factory` if they want to control how the + `MediaCodec` is configured. + * Promote `AdaptiveTrackSelection.AdaptationCheckpoint` to `public` + visibility to allow Kotlin subclasses of + `AdaptiveTrackSelection.Factory` + ([#8830](https://github.com/google/ExoPlayer/issues/8830)). + * Fix bug when transitions from content to ad periods called + `onMediaItemTransition` by mistake. + * `AdsLoader.AdViewProvider` and `AdsLoader.OverlayInfo` have been renamed + `com.google.android.exoplayer2.ui.AdViewProvider` and + `com.google.android.exoplayer2.ui.AdOverlayInfo` respectively. + * `CaptionStyleCompat` has been moved to the + `com.google.android.exoplayer2.ui` package. + * `DebugTextViewHelper` has been moved from the `ui` package to the `util` + package. +* RTSP: + * Initial support for RTSP playbacks + ([#55](https://github.com/google/ExoPlayer/issues/55)). +* Downloads and caching: + * Fix `CacheWriter` to correctly handle cases where the request `DataSpec` + extends beyond the end of the underlying resource. Caching will now + succeed in this case, with data up to the end of the resource being + cached. This behaviour is enabled by default, and so the + `allowShortContent` parameter has been removed + ([#7326](https://github.com/google/ExoPlayer/issues/7326)). + * Fix `CacheWriter` to correctly handle `DataSource.close` failures, for + which it cannot be assumed that data was successfully written to the + cache. +* DRM: + * Prepare DRM sessions (and fetch keys) ahead of the playback position + ([#4133](https://github.com/google/ExoPlayer/issues/4133)). + * Only dispatch DRM session acquire and release events once per period + when playing content that uses the same encryption keys for both audio & + video tracks. Previously, separate acquire and release events were + dispatched for each track in each period. + * Include the session state in DRM session-acquired listener methods. +* UI: + * Add `PlayerNotificationManager.Builder`, with the ability to + specify which group the notification should belong to. + * Remove `setUseSensorRotation` from `PlayerView` and `StyledPlayerView`. + Instead, cast the view returned by `getVideoSurfaceView` to + `SphericalGLSurfaceView`, and then call `setUseSensorRotation` on the + `SphericalGLSurfaceView` directly. +* Analytics: + * Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`. +* Video: + * Add `Player.getVideoSize()` to retrieve the current size of the video + stream. Add `Listener.onVideoSizeChanged(VideoSize)` and deprecate + `Listener.onVideoSizeChanged(int, int, int, float)`. +* Audio: + * Report unexpected audio discontinuities to + `AnalyticsListener.onAudioSinkError` + ([#6384](https://github.com/google/ExoPlayer/issues/6384)). + * Allow forcing offload for gapless content even if gapless playback is + not supported. + * Allow fall back from DTS-HD to DTS when playing via passthrough. +* Text: + * Fix overlapping lines when using `SubtitleView.VIEW_TYPE_WEB`. + * Parse SSA/ASS underline & strikethrough info in `Style:` lines + ([#8435](https://github.com/google/ExoPlayer/issues/8435)). + * Ensure TTML `tts:textAlign` is correctly propagated from `

` nodes to + child nodes. + * Support TTML `ebutts:multiRowAlign` attributes. +* Allow the use of Android platform extractors through + [MediaParser](https://developer.android.com/reference/android/media/MediaParser). + Only supported on API 30+. + * You can use platform extractors for progressive media by passing + `MediaParserExtractorAdapter.FACTORY` when creating a + `ProgressiveMediaSource.Factory`. + * You can use platform extractors for HLS by passing + `MediaParserHlsMediaChunkExtractor.FACTORY` when creating a + `HlsMediaSource.Factory`. + * You can use platform extractors for DASH by passing a + `DefaultDashChunkSource` that uses `MediaParserChunkExtractor.FACTORY` + when creating a `DashMediaSource.Factory`. +* Cast extension: + * Trigger `onMediaItemTransition` event for all reasons except + `MEDIA_ITEM_TRANSITION_REASON_REPEAT`. +* MediaSession extension: + * Remove dependency on `exoplayer-core`, relying only `exoplayer-common` + instead. To achieve this, `TimelineQueueEditor` uses a new + `MediaDescriptionConverter` interface, and no longer relies on + `ConcatenatingMediaSource`. +* Remove deprecated symbols: + * Remove `ExoPlayerFactory`. Use `SimpleExoPlayer.Builder` instead. + * Remove `Player.DefaultEventListener`. Use `Player.Listener` instead. + * Remove `ExtractorMediaSource`. Use `ProgressiveMediaSource` instead. + * Remove `DefaultMediaSourceEventListener`. Use `MediaSourceEventListener` + instead. + * Remove `DashManifest` constructor. Use the remaining constructor with + `programInformation` and `serviceDescription` set to `null` instead. + * Remove `CryptoInfo.getFrameworkCryptoInfoV16`. Use + `CryptoInfo.getFrameworkCryptoInfo` instead. + * Remove `NotificationUtil.createNotificationChannel(Context, String, int, + int)`. Use `createNotificationChannel(Context, String, int, int, int)` + instead. + * Remove `PlayerNotificationManager.setNotificationListener`. Use + `PlayerNotificationManager.Builder.setNotificationListener` instead. + * Remove `PlayerNotificationManager.NotificationListener` + `onNotificationStarted(int, Notification)` and + `onNotificationCancelled(int)`. Use `onNotificationPosted(int, + Notification, boolean)` and `onNotificationCancelled(int, boolean)` + instead. + * Remove `DownloadNotificationUtil`. Use `DownloadNotificationHelper` + instead. + * Remove `extension-jobdispatcher` module. Use the `extension-workmanager` + module instead. + ### 2.13.3 (2021-04-14) * Published via the Google Maven repository (i.e., google()) rather than JCenter. @@ -7,7 +181,7 @@ * Reset playback speed when live playback speed control becomes unused ([#8664](https://github.com/google/ExoPlayer/issues/8664)). * Fix playback position issue when re-preparing playback after a - BehindLiveWindowException + `BehindLiveWindowException` ([#8675](https://github.com/google/ExoPlayer/issues/8675)). * Assume Dolby Vision content is encoded as H264 when calculating maximum codec input size @@ -24,6 +198,10 @@ * DASH: * Parse `forced_subtitle` role from DASH manifests ([#8781](https://github.com/google/ExoPlayer/issues/8781)). +* DASH: + * Fix rounding error that could cause `SegmentTemplate.getSegmentCount()` + to return incorrect values + ([#8804](https://github.com/google/ExoPlayer/issues/8804)). * HLS: * Fix bug of ignoring `EXT-X-START` when setting the live target offset ([#8764](https://github.com/google/ExoPlayer/pull/8764)). @@ -171,7 +349,7 @@ * Low latency live streaming: * Support low-latency DASH (also known as ULL-CMAF) and Apple's low-latency HLS extension. - * Add `LiveConfiguration` to `MediaItem` to define live offset and + * Add `LiveConfiguration` to `MediaItem` to define live offset and playback speed adjustment parameters. The same parameters can be set on `DefaultMediaSourceFactory` to apply for all `MediaItems`. * Add `LivePlaybackSpeedControl` to control playback speed adjustments diff --git a/constants.gradle b/constants.gradle index be83f68dad..e3acfd0dc7 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,14 +13,14 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.13.3' - releaseVersionCode = 2013003 + releaseVersion = '2.14.0' + releaseVersionCode = 2014000 minSdkVersion = 16 appTargetSdkVersion = 29 targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest. compileSdkVersion = 30 dexmakerVersion = '2.21.0' - junitVersion = '4.13-rc-2' + junitVersion = '4.13.2' guavaVersion = '27.1-android' mockitoVersion = '2.28.2' mockWebServerVersion = '3.12.0' @@ -32,9 +32,10 @@ project.ext { androidxAnnotationVersion = '1.1.0' androidxAppCompatVersion = '1.1.0' androidxCollectionVersion = '1.1.0' + androidxCoreVersion = '1.3.2' androidxFuturesVersion = '1.1.0' androidxMediaVersion = '1.2.1' - androidxMedia2Version = '1.1.0' + androidxMedia2Version = '1.1.2' androidxMultidexVersion = '2.0.0' androidxRecyclerViewVersion = '1.1.0' androidxTestCoreVersion = '1.3.0' @@ -42,6 +43,7 @@ project.ext { androidxTestRunnerVersion = '1.3.0' androidxTestRulesVersion = '1.3.0' androidxTestServicesStorageVersion = '1.3.0' + androidxTestTruthVersion = '1.3.0' truthVersion = '1.0' modulePrefix = ':' if (gradle.ext.has('exoplayerModulePrefix')) { diff --git a/core_settings.gradle b/core_settings.gradle index 241b94a19b..c0c19abf80 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -27,6 +27,7 @@ include modulePrefix + 'library-core' include modulePrefix + 'library-dash' include modulePrefix + 'library-extractor' include modulePrefix + 'library-hls' +include modulePrefix + 'library-rtsp' include modulePrefix + 'library-smoothstreaming' include modulePrefix + 'library-transformer' include modulePrefix + 'library-ui' @@ -47,7 +48,6 @@ include modulePrefix + 'extension-opus' include modulePrefix + 'extension-vp9' include modulePrefix + 'extension-rtmp' include modulePrefix + 'extension-leanback' -include modulePrefix + 'extension-jobdispatcher' include modulePrefix + 'extension-workmanager' project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all') @@ -56,6 +56,7 @@ project(modulePrefix + 'library-core').projectDir = new File(rootDir, 'library/c project(modulePrefix + 'library-dash').projectDir = new File(rootDir, 'library/dash') project(modulePrefix + 'library-extractor').projectDir = new File(rootDir, 'library/extractor') project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hls') +project(modulePrefix + 'library-rtsp').projectDir = new File(rootDir, 'library/rtsp') project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming') project(modulePrefix + 'library-transformer').projectDir = new File(rootDir, 'library/transformer') project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui') @@ -76,5 +77,4 @@ project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensi project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9') project(modulePrefix + 'extension-rtmp').projectDir = new File(rootDir, 'extensions/rtmp') project(modulePrefix + 'extension-leanback').projectDir = new File(rootDir, 'extensions/leanback') -project(modulePrefix + 'extension-jobdispatcher').projectDir = new File(rootDir, 'extensions/jobdispatcher') project(modulePrefix + 'extension-workmanager').projectDir = new File(rootDir, 'extensions/workmanager') diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle index a3c13b382d..8636c71ab0 100644 --- a/demos/cast/build.gradle +++ b/demos/cast/build.gradle @@ -55,6 +55,7 @@ dependencies { implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-dash') implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-rtsp') implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'extension-cast') diff --git a/demos/cast/src/main/AndroidManifest.xml b/demos/cast/src/main/AndroidManifest.xml index d92d9e2303..6bb16ed734 100644 --- a/demos/cast/src/main/AndroidManifest.xml +++ b/demos/cast/src/main/AndroidManifest.xml @@ -31,7 +31,8 @@ + android:theme="@style/Theme.AppCompat" + android:exported="true"> diff --git a/demos/gl/build.gradle b/demos/gl/build.gradle index a2ffa7f41f..01b5808fe7 100644 --- a/demos/gl/build.gradle +++ b/demos/gl/build.gradle @@ -46,8 +46,11 @@ android { dependencies { implementation project(modulePrefix + 'library-core') - implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'library-dash') + implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-rtsp') + implementation project(modulePrefix + 'library-smoothstreaming') + implementation project(modulePrefix + 'library-ui') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion 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 89bea32581..02399ba86c 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 @@ -140,6 +140,7 @@ import javax.microedition.khronos.opengles.GL10; case "scaleY": uniform.setFloat(bitmapScaleY); break; + default: // fall out } } for (GlUtil.Attribute copyExternalAttribute : attributes) { 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 7aee74801f..d1202052fe 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 @@ -25,8 +25,8 @@ import android.os.Handler; import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.TimedValueQueue; @@ -72,7 +72,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { @Nullable private SurfaceTexture surfaceTexture; @Nullable private Surface surface; - @Nullable private Player.VideoComponent videoComponent; + @Nullable private ExoPlayer.VideoComponent videoComponent; /** * Creates a new instance. Pass {@code true} for {@code requireSecureContext} if the {@link @@ -151,7 +151,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { * * @param newVideoComponent The new video component, or {@code null} to detach this view. */ - public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) { + public void setVideoComponent(@Nullable ExoPlayer.VideoComponent newVideoComponent) { if (newVideoComponent == videoComponent) { return; } diff --git a/demos/main/build.gradle b/demos/main/build.gradle index d9ec74e2c2..e63fb70673 100644 --- a/demos/main/build.gradle +++ b/demos/main/build.gradle @@ -74,6 +74,7 @@ dependencies { implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-dash') implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-rtsp') implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'extension-cronet') diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 053665502b..39a2ec1709 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -41,7 +41,8 @@ + android:theme="@style/Theme.AppCompat" + android:exported="true"> @@ -65,7 +66,8 @@ android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:launchMode="singleTop" android:label="@string/application_name" - android:theme="@style/PlayerTheme"> + android:theme="@style/PlayerTheme" + android:exported="true"> diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 24d92bab57..05a36b7fa0 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -513,6 +513,13 @@ "subtitle_mime_type": "text/x-ssa", "subtitle_language": "en" }, + { + "name": "SubStation Alpha styling", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4", + "subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ssa/test-subs-styling.ass", + "subtitle_mime_type": "text/x-ssa", + "subtitle_language": "en" + }, { "name": "MPEG-4 Timed Text", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4" 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 2cf2671aba..027d3846cf 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 @@ -363,7 +363,8 @@ public class DownloadTracker { private DownloadRequest buildDownloadRequest() { return downloadHelper - .getDownloadRequest(Util.getUtf8Bytes(checkNotNull(mediaItem.mediaMetadata.title))) + .getDownloadRequest( + Util.getUtf8Bytes(checkNotNull(mediaItem.mediaMetadata.title.toString()))) .copyWithKeySetId(keySetId); } } 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 30fd014504..21dee820c9 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 @@ -51,10 +51,10 @@ import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.ui.DebugTextViewHelper; import com.google.android.exoplayer2.ui.StyledPlayerControlView; import com.google.android.exoplayer2.ui.StyledPlayerView; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.util.DebugTextViewHelper; import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.EventLogger; import com.google.android.exoplayer2.util.Util; @@ -77,7 +77,7 @@ public class PlayerActivity extends AppCompatActivity protected StyledPlayerView playerView; protected LinearLayout debugRootView; protected TextView debugTextView; - protected SimpleExoPlayer player; + protected @Nullable SimpleExoPlayer player; private boolean isShowingTrackSelectionDialog; private Button selectTracksButton; diff --git a/demos/surface/build.gradle b/demos/surface/build.gradle index 38de169ae5..9b75d7998b 100644 --- a/demos/surface/build.gradle +++ b/demos/surface/build.gradle @@ -46,7 +46,10 @@ android { dependencies { implementation project(modulePrefix + 'library-core') - implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'library-dash') + implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-rtsp') + implementation project(modulePrefix + 'library-smoothstreaming') + implementation project(modulePrefix + 'library-ui') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion } diff --git a/demos/surface/src/main/AndroidManifest.xml b/demos/surface/src/main/AndroidManifest.xml index c33a9e646b..5fd2890915 100644 --- a/demos/surface/src/main/AndroidManifest.xml +++ b/demos/surface/src/main/AndroidManifest.xml @@ -21,7 +21,8 @@ + android:label="@string/application_name" + android:exported="true"> diff --git a/docs/.hgignore b/docs/.hgignore new file mode 100644 index 0000000000..55ca9f0054 --- /dev/null +++ b/docs/.hgignore @@ -0,0 +1,9 @@ +# Mercurial's .hgignore files can only be used in the root directory. +# You can still apply these rules by adding +# include:path/to/this/directory/.hgignore to the top-level .hgignore file. + +# Ensure same syntax as in .gitignore can be used +syntax:glob + +_site +Gemfile.lock diff --git a/docs/CNAME b/docs/CNAME index ea244c2530..6b8f5dba1c 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -exoplayer.dev \ No newline at end of file +exoplayer.dev diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index f524eea5f1..612e2fd37f 100644 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -41,6 +41,8 @@ en: url: downloading-media.html - title: Ad insertion url: ad-insertion.html + - title: Retrieving metadata + url: retrieving-metadata.html - title: Live streaming url: live-streaming.html - title: Debug logging @@ -57,6 +59,8 @@ en: url: smoothstreaming.html - title: Progressive url: progressive.html + - title: RTSP + url: rtsp.html - title: Advanced topics children: - title: Digital rights management diff --git a/docs/_includes/.DS_Store b/docs/_includes/.DS_Store deleted file mode 100644 index 83398c199c..0000000000 Binary files a/docs/_includes/.DS_Store and /dev/null differ diff --git a/docs/_page_fragments/supported-formats-rtsp.md b/docs/_page_fragments/supported-formats-rtsp.md new file mode 100644 index 0000000000..c87dd690ff --- /dev/null +++ b/docs/_page_fragments/supported-formats-rtsp.md @@ -0,0 +1,11 @@ +ExoPlayer supports both live and on demand RTSP. Supported formats and network +types are listed below. + +**Supported formats** +* H264 +* AAC (with ADTS bitstream) +* AC3 + +**Supported network types** +* RTP over UDP unicast (multicast is not supported) +* Interleaved RTSP, RTP over RTSP using TCP diff --git a/docs/assets/.DS_Store b/docs/assets/.DS_Store deleted file mode 100644 index 5008ddfcf5..0000000000 Binary files a/docs/assets/.DS_Store and /dev/null differ diff --git a/docs/customization.md b/docs/customization.md index 256543d972..7a5cf7e51a 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -136,7 +136,7 @@ just-in-time modifications of the URI, as shown in the following snippet: DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. - (DataSpec dataSpec) -> dataSpec.withUri(resolveUri(dataSpec.uri))); + dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri))); ~~~ {: .language-java} diff --git a/docs/dash.md b/docs/dash.md index f4010bc0c2..8b3ef2571e 100644 --- a/docs/dash.md +++ b/docs/dash.md @@ -57,14 +57,14 @@ player.prepare(); You can retrieve the current manifest by calling `Player.getCurrentManifest`. For DASH you should cast the returned object to `DashManifest`. The -`onTimelineChanged` callback of `Player.EventListener` is also called whenever +`onTimelineChanged` callback of `Player.Listener` is also called whenever the manifest is loaded. This will happen once for a on-demand content, and possibly many times for live content. The code snippet below shows how an app can do something whenever the manifest is loaded. ~~~ player.addListener( - new Player.EventListener() { + new Player.Listener() { @Override public void onTimelineChanged( Timeline timeline, @Player.TimelineChangeReason int reason) { diff --git a/docs/doc/reference-v1/allclasses-frame.html b/docs/doc/reference-v1/allclasses-frame.html deleted file mode 100644 index 73e8c5360a..0000000000 --- a/docs/doc/reference-v1/allclasses-frame.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - -All Classes (ExoPlayer library) - - - - - -

All Classes

-
- -
- - diff --git a/docs/doc/reference-v1/allclasses-noframe.html b/docs/doc/reference-v1/allclasses-noframe.html deleted file mode 100644 index 421c982965..0000000000 --- a/docs/doc/reference-v1/allclasses-noframe.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - -All Classes (ExoPlayer library) - - - - - -

All Classes

-
- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/AspectRatioFrameLayout.html b/docs/doc/reference-v1/com/google/android/exoplayer/AspectRatioFrameLayout.html deleted file mode 100644 index d666374738..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/AspectRatioFrameLayout.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - -AspectRatioFrameLayout (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class AspectRatioFrameLayout

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/BehindLiveWindowException.html b/docs/doc/reference-v1/com/google/android/exoplayer/BehindLiveWindowException.html deleted file mode 100644 index 206d2d9a2b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/BehindLiveWindowException.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - -BehindLiveWindowException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class BehindLiveWindowException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/C.html b/docs/doc/reference-v1/com/google/android/exoplayer/C.html deleted file mode 100644 index 1d26bd3393..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/C.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - -C (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class C

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/CodecCounters.html b/docs/doc/reference-v1/com/google/android/exoplayer/CodecCounters.html deleted file mode 100644 index 37fbf2bbff..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/CodecCounters.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - - -CodecCounters (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class CodecCounters

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/CryptoInfo.html b/docs/doc/reference-v1/com/google/android/exoplayer/CryptoInfo.html deleted file mode 100644 index 3f5470f6ea..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/CryptoInfo.html +++ /dev/null @@ -1,453 +0,0 @@ - - - - - -CryptoInfo (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class CryptoInfo

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/DecoderInfo.html b/docs/doc/reference-v1/com/google/android/exoplayer/DecoderInfo.html deleted file mode 100644 index 6848b181dd..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/DecoderInfo.html +++ /dev/null @@ -1,281 +0,0 @@ - - - - - -DecoderInfo (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class DecoderInfo

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/DefaultLoadControl.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/DefaultLoadControl.EventListener.html deleted file mode 100644 index 37885bf62f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/DefaultLoadControl.EventListener.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - -DefaultLoadControl.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface DefaultLoadControl.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/DefaultLoadControl.html b/docs/doc/reference-v1/com/google/android/exoplayer/DefaultLoadControl.html deleted file mode 100644 index 9390ec080a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/DefaultLoadControl.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - -DefaultLoadControl (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class DefaultLoadControl

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/DummyTrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/DummyTrackRenderer.html deleted file mode 100644 index 5bc548802c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/DummyTrackRenderer.html +++ /dev/null @@ -1,586 +0,0 @@ - - - - - -DummyTrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class DummyTrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlaybackException.html b/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlaybackException.html deleted file mode 100644 index 546235a055..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlaybackException.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - - -ExoPlaybackException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class ExoPlaybackException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.ExoPlayerComponent.html b/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.ExoPlayerComponent.html deleted file mode 100644 index 9afdf5b6ca..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.ExoPlayerComponent.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - -ExoPlayer.ExoPlayerComponent (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface ExoPlayer.ExoPlayerComponent

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.Factory.html b/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.Factory.html deleted file mode 100644 index b0fcd044f4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.Factory.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - -ExoPlayer.Factory (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class ExoPlayer.Factory

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.Listener.html b/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.Listener.html deleted file mode 100644 index d2e6b225d9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.Listener.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - -ExoPlayer.Listener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface ExoPlayer.Listener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.html b/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.html deleted file mode 100644 index 246c70ad0c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayer.html +++ /dev/null @@ -1,962 +0,0 @@ - - - - - -ExoPlayer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface ExoPlayer

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayerLibraryInfo.html b/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayerLibraryInfo.html deleted file mode 100644 index 961edd9af4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/ExoPlayerLibraryInfo.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - -ExoPlayerLibraryInfo (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class ExoPlayerLibraryInfo

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/FrameworkSampleSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/FrameworkSampleSource.html deleted file mode 100644 index 3660beedc2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/FrameworkSampleSource.html +++ /dev/null @@ -1,753 +0,0 @@ - - - - - -FrameworkSampleSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class FrameworkSampleSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/LoadControl.html b/docs/doc/reference-v1/com/google/android/exoplayer/LoadControl.html deleted file mode 100644 index 15914fa382..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/LoadControl.html +++ /dev/null @@ -1,335 +0,0 @@ - - - - - -LoadControl (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface LoadControl

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaClock.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaClock.html deleted file mode 100644 index 3ba76b4880..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaClock.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - -MediaClock (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface MediaClock

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.EventListener.html deleted file mode 100644 index 013260aedb..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.EventListener.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - -MediaCodecAudioTrackRenderer.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface MediaCodecAudioTrackRenderer.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.html deleted file mode 100644 index 45be04cbb4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.html +++ /dev/null @@ -1,1149 +0,0 @@ - - - - - -MediaCodecAudioTrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaCodecAudioTrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecSelector.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecSelector.html deleted file mode 100644 index 176410b8f4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecSelector.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - -MediaCodecSelector (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface MediaCodecSelector

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.DecoderInitializationException.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.DecoderInitializationException.html deleted file mode 100644 index a293723da8..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.DecoderInitializationException.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - - -MediaCodecTrackRenderer.DecoderInitializationException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaCodecTrackRenderer.DecoderInitializationException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.EventListener.html deleted file mode 100644 index 33bce42fe4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.EventListener.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - -MediaCodecTrackRenderer.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface MediaCodecTrackRenderer.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.html deleted file mode 100644 index 90e3b0bf23..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecTrackRenderer.html +++ /dev/null @@ -1,1143 +0,0 @@ - - - - - -MediaCodecTrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaCodecTrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecUtil.DecoderQueryException.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecUtil.DecoderQueryException.html deleted file mode 100644 index 6fca29a8e2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecUtil.DecoderQueryException.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - -MediaCodecUtil.DecoderQueryException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaCodecUtil.DecoderQueryException

-
-
- -
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecUtil.html deleted file mode 100644 index 1b809d6e55..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecUtil.html +++ /dev/null @@ -1,483 +0,0 @@ - - - - - -MediaCodecUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaCodecUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.EventListener.html deleted file mode 100644 index 2e61428f0f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.EventListener.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - -MediaCodecVideoTrackRenderer.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface MediaCodecVideoTrackRenderer.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.html deleted file mode 100644 index d0c186af56..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.html +++ /dev/null @@ -1,1011 +0,0 @@ - - - - - -MediaCodecVideoTrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaCodecVideoTrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaFormat.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaFormat.html deleted file mode 100644 index 49141581e5..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaFormat.html +++ /dev/null @@ -1,1177 +0,0 @@ - - - - - -MediaFormat (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaFormat

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/MediaFormatHolder.html b/docs/doc/reference-v1/com/google/android/exoplayer/MediaFormatHolder.html deleted file mode 100644 index 10c6d86446..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/MediaFormatHolder.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - -MediaFormatHolder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class MediaFormatHolder

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/ParserException.html b/docs/doc/reference-v1/com/google/android/exoplayer/ParserException.html deleted file mode 100644 index 68c65fbbde..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/ParserException.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - - -ParserException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class ParserException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/SampleHolder.html b/docs/doc/reference-v1/com/google/android/exoplayer/SampleHolder.html deleted file mode 100644 index 0e2d255ebe..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/SampleHolder.html +++ /dev/null @@ -1,518 +0,0 @@ - - - - - -SampleHolder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class SampleHolder

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/SampleSource.SampleSourceReader.html b/docs/doc/reference-v1/com/google/android/exoplayer/SampleSource.SampleSourceReader.html deleted file mode 100644 index e9f4893e52..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/SampleSource.SampleSourceReader.html +++ /dev/null @@ -1,528 +0,0 @@ - - - - - -SampleSource.SampleSourceReader (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface SampleSource.SampleSourceReader

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/SampleSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/SampleSource.html deleted file mode 100644 index c3b8c9d375..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/SampleSource.html +++ /dev/null @@ -1,387 +0,0 @@ - - - - - -SampleSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface SampleSource

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/SampleSourceTrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/SampleSourceTrackRenderer.html deleted file mode 100644 index 6e77e6a75e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/SampleSourceTrackRenderer.html +++ /dev/null @@ -1,767 +0,0 @@ - - - - - -SampleSourceTrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class SampleSourceTrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/SingleSampleSource.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/SingleSampleSource.EventListener.html deleted file mode 100644 index fc81284284..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/SingleSampleSource.EventListener.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - -SingleSampleSource.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface SingleSampleSource.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/SingleSampleSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/SingleSampleSource.html deleted file mode 100644 index 5df677d2d9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/SingleSampleSource.html +++ /dev/null @@ -1,906 +0,0 @@ - - - - - -SingleSampleSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class SingleSampleSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.DynamicTimeRange.html b/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.DynamicTimeRange.html deleted file mode 100644 index d551c9c32f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.DynamicTimeRange.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - -TimeRange.DynamicTimeRange (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class TimeRange.DynamicTimeRange

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.StaticTimeRange.html b/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.StaticTimeRange.html deleted file mode 100644 index 25ee2b9e23..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.StaticTimeRange.html +++ /dev/null @@ -1,401 +0,0 @@ - - - - - -TimeRange.StaticTimeRange (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class TimeRange.StaticTimeRange

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.html b/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.html deleted file mode 100644 index 38fabedc37..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/TimeRange.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - - -TimeRange (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Interface TimeRange

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/TrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/TrackRenderer.html deleted file mode 100644 index a9440783bf..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/TrackRenderer.html +++ /dev/null @@ -1,937 +0,0 @@ - - - - - -TrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class TrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/VideoFrameReleaseTimeHelper.html b/docs/doc/reference-v1/com/google/android/exoplayer/VideoFrameReleaseTimeHelper.html deleted file mode 100644 index 2fcc117252..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/VideoFrameReleaseTimeHelper.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - - -VideoFrameReleaseTimeHelper (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer
-

Class VideoFrameReleaseTimeHelper

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilities.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilities.html deleted file mode 100644 index af827f24b0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilities.html +++ /dev/null @@ -1,374 +0,0 @@ - - - - - -AudioCapabilities (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.audio
-

Class AudioCapabilities

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.Listener.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.Listener.html deleted file mode 100644 index 9a3d3484b0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.Listener.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - -AudioCapabilitiesReceiver.Listener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.audio
-

Interface AudioCapabilitiesReceiver.Listener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.html deleted file mode 100644 index f893527ce6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - -AudioCapabilitiesReceiver (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.audio
-

Class AudioCapabilitiesReceiver

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.InitializationException.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.InitializationException.html deleted file mode 100644 index a3d9b0bf01..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.InitializationException.html +++ /dev/null @@ -1,310 +0,0 @@ - - - - - -AudioTrack.InitializationException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.audio
-

Class AudioTrack.InitializationException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.InvalidAudioTrackTimestampException.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.InvalidAudioTrackTimestampException.html deleted file mode 100644 index 42c3d3e657..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.InvalidAudioTrackTimestampException.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - -AudioTrack.InvalidAudioTrackTimestampException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.audio
-

Class AudioTrack.InvalidAudioTrackTimestampException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.WriteException.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.WriteException.html deleted file mode 100644 index aa0762c26f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.WriteException.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - -AudioTrack.WriteException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.audio
-

Class AudioTrack.WriteException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.html deleted file mode 100644 index 3f6e7e056f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/AudioTrack.html +++ /dev/null @@ -1,924 +0,0 @@ - - - - - -AudioTrack (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.audio
-

Class AudioTrack

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-frame.html deleted file mode 100644 index 29e91ed480..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-frame.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - -com.google.android.exoplayer.audio (ExoPlayer library) - - - - - -

com.google.android.exoplayer.audio

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-summary.html deleted file mode 100644 index 3d186e3537..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-summary.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - -com.google.android.exoplayer.audio (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.audio

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-tree.html deleted file mode 100644 index 506c26d49e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/audio/package-tree.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - -com.google.android.exoplayer.audio Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.audio

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/BaseChunkSampleSourceEventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/BaseChunkSampleSourceEventListener.html deleted file mode 100644 index a144748f9b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/BaseChunkSampleSourceEventListener.html +++ /dev/null @@ -1,405 +0,0 @@ - - - - - -BaseChunkSampleSourceEventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Interface BaseChunkSampleSourceEventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/BaseMediaChunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/BaseMediaChunk.html deleted file mode 100644 index 417e94c7bd..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/BaseMediaChunk.html +++ /dev/null @@ -1,487 +0,0 @@ - - - - - -BaseMediaChunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class BaseMediaChunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Chunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Chunk.html deleted file mode 100644 index 0581e114c6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Chunk.html +++ /dev/null @@ -1,694 +0,0 @@ - - - - - -Chunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class Chunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.SingleTrackOutput.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.SingleTrackOutput.html deleted file mode 100644 index 29fc1054ce..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.SingleTrackOutput.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - -ChunkExtractorWrapper.SingleTrackOutput (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Interface ChunkExtractorWrapper.SingleTrackOutput

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.html deleted file mode 100644 index 4167405ac1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.html +++ /dev/null @@ -1,564 +0,0 @@ - - - - - -ChunkExtractorWrapper (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class ChunkExtractorWrapper

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkOperationHolder.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkOperationHolder.html deleted file mode 100644 index 6beb104261..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkOperationHolder.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - -ChunkOperationHolder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class ChunkOperationHolder

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSampleSource.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSampleSource.EventListener.html deleted file mode 100644 index 2b0464090d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSampleSource.EventListener.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - -ChunkSampleSource.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Interface ChunkSampleSource.EventListener

-
-
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSampleSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSampleSource.html deleted file mode 100644 index 0c3fdf0735..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSampleSource.html +++ /dev/null @@ -1,919 +0,0 @@ - - - - - -ChunkSampleSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class ChunkSampleSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSource.html deleted file mode 100644 index 8f9bb0f86a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ChunkSource.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - - -ChunkSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Interface ChunkSource

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ContainerMediaChunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ContainerMediaChunk.html deleted file mode 100644 index 8f3174355a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/ContainerMediaChunk.html +++ /dev/null @@ -1,685 +0,0 @@ - - - - - -ContainerMediaChunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class ContainerMediaChunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/DataChunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/DataChunk.html deleted file mode 100644 index 52263f7a74..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/DataChunk.html +++ /dev/null @@ -1,441 +0,0 @@ - - - - - -DataChunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class DataChunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Format.DecreasingBandwidthComparator.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Format.DecreasingBandwidthComparator.html deleted file mode 100644 index b5af64341b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Format.DecreasingBandwidthComparator.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - -Format.DecreasingBandwidthComparator (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class Format.DecreasingBandwidthComparator

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Format.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Format.html deleted file mode 100644 index c22a8165fe..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/Format.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - -Format (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class Format

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.AdaptiveEvaluator.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.AdaptiveEvaluator.html deleted file mode 100644 index ca20f1e64a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.AdaptiveEvaluator.html +++ /dev/null @@ -1,519 +0,0 @@ - - - - - -FormatEvaluator.AdaptiveEvaluator (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class FormatEvaluator.AdaptiveEvaluator

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.Evaluation.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.Evaluation.html deleted file mode 100644 index 8f1a87f4ae..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.Evaluation.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - -FormatEvaluator.Evaluation (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class FormatEvaluator.Evaluation

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.FixedEvaluator.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.FixedEvaluator.html deleted file mode 100644 index d3c2afda5a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.FixedEvaluator.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - -FormatEvaluator.FixedEvaluator (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class FormatEvaluator.FixedEvaluator

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.RandomEvaluator.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.RandomEvaluator.html deleted file mode 100644 index 499c02f623..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.RandomEvaluator.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - -FormatEvaluator.RandomEvaluator (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class FormatEvaluator.RandomEvaluator

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.html deleted file mode 100644 index 7244136fec..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatEvaluator.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - -FormatEvaluator (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Interface FormatEvaluator

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatWrapper.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatWrapper.html deleted file mode 100644 index 2f84f8be57..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/FormatWrapper.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - -FormatWrapper (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Interface FormatWrapper

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/InitializationChunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/InitializationChunk.html deleted file mode 100644 index 6ffa81e1d1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/InitializationChunk.html +++ /dev/null @@ -1,692 +0,0 @@ - - - - - -InitializationChunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class InitializationChunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/MediaChunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/MediaChunk.html deleted file mode 100644 index 4978a5fb0f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/MediaChunk.html +++ /dev/null @@ -1,437 +0,0 @@ - - - - - -MediaChunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class MediaChunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.html deleted file mode 100644 index b4399e90df..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.html +++ /dev/null @@ -1,488 +0,0 @@ - - - - - -SingleSampleMediaChunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class SingleSampleMediaChunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.html deleted file mode 100644 index e0e02be74d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - -VideoFormatSelectorUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.chunk
-

Class VideoFormatSelectorUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-frame.html deleted file mode 100644 index 271dd287c0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-frame.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - -com.google.android.exoplayer.chunk (ExoPlayer library) - - - - - -

com.google.android.exoplayer.chunk

-
-

Interfaces

- -

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-summary.html deleted file mode 100644 index 4f1d71bc09..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-summary.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - -com.google.android.exoplayer.chunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.chunk

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-tree.html deleted file mode 100644 index 67d80fb249..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/chunk/package-tree.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - -com.google.android.exoplayer.chunk Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.chunk

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.EventListener.html deleted file mode 100644 index 15e400c4d1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.EventListener.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - -DashChunkSource.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Interface DashChunkSource.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.ExposedTrack.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.ExposedTrack.html deleted file mode 100644 index 38f8efbea5..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.ExposedTrack.html +++ /dev/null @@ -1,359 +0,0 @@ - - - - - -DashChunkSource.ExposedTrack (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Class DashChunkSource.ExposedTrack

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.NoAdaptationSetException.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.NoAdaptationSetException.html deleted file mode 100644 index 06c69cef8e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.NoAdaptationSetException.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - -DashChunkSource.NoAdaptationSetException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Class DashChunkSource.NoAdaptationSetException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.PeriodHolder.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.PeriodHolder.html deleted file mode 100644 index c177266b3d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.PeriodHolder.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - - -DashChunkSource.PeriodHolder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Class DashChunkSource.PeriodHolder

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.RepresentationHolder.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.RepresentationHolder.html deleted file mode 100644 index 38b52762ea..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.RepresentationHolder.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - -DashChunkSource.RepresentationHolder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Class DashChunkSource.RepresentationHolder

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.html deleted file mode 100644 index 40386f7252..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashChunkSource.html +++ /dev/null @@ -1,880 +0,0 @@ - - - - - -DashChunkSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Class DashChunkSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashSegmentIndex.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashSegmentIndex.html deleted file mode 100644 index fc53bd1468..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashSegmentIndex.html +++ /dev/null @@ -1,428 +0,0 @@ - - - - - -DashSegmentIndex (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Interface DashSegmentIndex

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashTrackSelector.Output.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashTrackSelector.Output.html deleted file mode 100644 index e14f3bb3e7..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashTrackSelector.Output.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - -DashTrackSelector.Output (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Interface DashTrackSelector.Output

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashTrackSelector.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashTrackSelector.html deleted file mode 100644 index 06fafda194..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DashTrackSelector.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - -DashTrackSelector (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Interface DashTrackSelector

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DefaultDashTrackSelector.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/DefaultDashTrackSelector.html deleted file mode 100644 index 1bceab4b96..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/DefaultDashTrackSelector.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - -DefaultDashTrackSelector (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash
-

Class DefaultDashTrackSelector

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/AdaptationSet.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/AdaptationSet.html deleted file mode 100644 index fdf6335a4b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/AdaptationSet.html +++ /dev/null @@ -1,435 +0,0 @@ - - - - - -AdaptationSet (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class AdaptationSet

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/ContentProtection.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/ContentProtection.html deleted file mode 100644 index 97caa04983..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/ContentProtection.html +++ /dev/null @@ -1,372 +0,0 @@ - - - - - -ContentProtection (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class ContentProtection

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescription.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescription.html deleted file mode 100644 index d8e2502334..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescription.html +++ /dev/null @@ -1,467 +0,0 @@ - - - - - -MediaPresentationDescription (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class MediaPresentationDescription

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.ContentProtectionsBuilder.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.ContentProtectionsBuilder.html deleted file mode 100644 index dc0a5bbcc4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.ContentProtectionsBuilder.html +++ /dev/null @@ -1,373 +0,0 @@ - - - - - -MediaPresentationDescriptionParser.ContentProtectionsBuilder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class MediaPresentationDescriptionParser.ContentProtectionsBuilder

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.html deleted file mode 100644 index b829a16b93..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.html +++ /dev/null @@ -1,1127 +0,0 @@ - - - - - -MediaPresentationDescriptionParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class MediaPresentationDescriptionParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Period.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Period.html deleted file mode 100644 index 35e1b4dd46..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Period.html +++ /dev/null @@ -1,362 +0,0 @@ - - - - - -Period (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class Period

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/RangedUri.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/RangedUri.html deleted file mode 100644 index fbaf2fdc7f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/RangedUri.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - - -RangedUri (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class RangedUri

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.MultiSegmentRepresentation.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.MultiSegmentRepresentation.html deleted file mode 100644 index 8dda21b770..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.MultiSegmentRepresentation.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - -Representation.MultiSegmentRepresentation (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class Representation.MultiSegmentRepresentation

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.SingleSegmentRepresentation.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.SingleSegmentRepresentation.html deleted file mode 100644 index 9b38586634..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.SingleSegmentRepresentation.html +++ /dev/null @@ -1,467 +0,0 @@ - - - - - -Representation.SingleSegmentRepresentation (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class Representation.SingleSegmentRepresentation

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.html deleted file mode 100644 index 8553456c0f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/Representation.html +++ /dev/null @@ -1,547 +0,0 @@ - - - - - -Representation (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class Representation

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.MultiSegmentBase.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.MultiSegmentBase.html deleted file mode 100644 index 70345512ce..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.MultiSegmentBase.html +++ /dev/null @@ -1,447 +0,0 @@ - - - - - -SegmentBase.MultiSegmentBase (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class SegmentBase.MultiSegmentBase

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentList.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentList.html deleted file mode 100644 index e476d39e80..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentList.html +++ /dev/null @@ -1,393 +0,0 @@ - - - - - -SegmentBase.SegmentList (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class SegmentBase.SegmentList

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentTemplate.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentTemplate.html deleted file mode 100644 index be8c9b8975..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentTemplate.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - -SegmentBase.SegmentTemplate (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class SegmentBase.SegmentTemplate

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentTimelineElement.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentTimelineElement.html deleted file mode 100644 index 99c3d097df..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SegmentTimelineElement.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - -SegmentBase.SegmentTimelineElement (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class SegmentBase.SegmentTimelineElement

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SingleSegmentBase.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SingleSegmentBase.html deleted file mode 100644 index 5992f257b8..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.SingleSegmentBase.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - - -SegmentBase.SingleSegmentBase (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class SegmentBase.SingleSegmentBase

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.html deleted file mode 100644 index 963818bb9f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/SegmentBase.html +++ /dev/null @@ -1,362 +0,0 @@ - - - - - -SegmentBase (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class SegmentBase

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UrlTemplate.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UrlTemplate.html deleted file mode 100644 index fb0860d173..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UrlTemplate.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - -UrlTemplate (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class UrlTemplate

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElement.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElement.html deleted file mode 100644 index 509eac0ce2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElement.html +++ /dev/null @@ -1,325 +0,0 @@ - - - - - -UtcTimingElement (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class UtcTimingElement

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.UtcTimingCallback.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.UtcTimingCallback.html deleted file mode 100644 index 9ad8e203de..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.UtcTimingCallback.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - -UtcTimingElementResolver.UtcTimingCallback (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Interface UtcTimingElementResolver.UtcTimingCallback

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.html deleted file mode 100644 index 0c5ad2eea8..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - -UtcTimingElementResolver (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.dash.mpd
-

Class UtcTimingElementResolver

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-frame.html deleted file mode 100644 index 6d69886f85..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-frame.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -com.google.android.exoplayer.dash.mpd (ExoPlayer library) - - - - - -

com.google.android.exoplayer.dash.mpd

-
-

Interfaces

- -

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-summary.html deleted file mode 100644 index c23f6deffc..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-summary.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - -com.google.android.exoplayer.dash.mpd (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.dash.mpd

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-tree.html deleted file mode 100644 index c60c462b5e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/mpd/package-tree.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - -com.google.android.exoplayer.dash.mpd Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.dash.mpd

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-frame.html deleted file mode 100644 index 46c490861a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-frame.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - -com.google.android.exoplayer.dash (ExoPlayer library) - - - - - -

com.google.android.exoplayer.dash

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-summary.html deleted file mode 100644 index e52e64a6c4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-summary.html +++ /dev/null @@ -1,212 +0,0 @@ - - - - - -com.google.android.exoplayer.dash (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.dash

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-tree.html deleted file mode 100644 index 4fc9c09616..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/dash/package-tree.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - -com.google.android.exoplayer.dash Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.dash

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-playbackstate.png b/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-playbackstate.png deleted file mode 100644 index fb0ba72a60..0000000000 Binary files a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-playbackstate.png and /dev/null differ diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-state.png b/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-state.png deleted file mode 100644 index d37a51e23a..0000000000 Binary files a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-state.png and /dev/null differ diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-threading-model.png b/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-threading-model.png deleted file mode 100644 index 9f0306c111..0000000000 Binary files a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/exoplayer-threading-model.png and /dev/null differ diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/trackrenderer-states.png b/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/trackrenderer-states.png deleted file mode 100644 index 604a447a6a..0000000000 Binary files a/docs/doc/reference-v1/com/google/android/exoplayer/doc-files/trackrenderer-states.png and /dev/null differ diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.Mapped.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.Mapped.html deleted file mode 100644 index 5c4c281c40..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.Mapped.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - -DrmInitData.Mapped (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class DrmInitData.Mapped

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.SchemeInitData.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.SchemeInitData.html deleted file mode 100644 index 820e9d1bec..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.SchemeInitData.html +++ /dev/null @@ -1,357 +0,0 @@ - - - - - -DrmInitData.SchemeInitData (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class DrmInitData.SchemeInitData

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.Universal.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.Universal.html deleted file mode 100644 index d3572c8289..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.Universal.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - -DrmInitData.Universal (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class DrmInitData.Universal

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.html deleted file mode 100644 index 7e894169fe..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmInitData.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - -DrmInitData (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface DrmInitData

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmSessionManager.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmSessionManager.html deleted file mode 100644 index 70f8cb5623..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/DrmSessionManager.html +++ /dev/null @@ -1,468 +0,0 @@ - - - - - -DrmSessionManager (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface DrmSessionManager<T extends ExoMediaCrypto>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaCrypto.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaCrypto.html deleted file mode 100644 index 0edf750eaf..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaCrypto.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - -ExoMediaCrypto (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface ExoMediaCrypto

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.KeyRequest.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.KeyRequest.html deleted file mode 100644 index 440697bcdb..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.KeyRequest.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - -ExoMediaDrm.KeyRequest (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface ExoMediaDrm.KeyRequest

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.OnEventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.OnEventListener.html deleted file mode 100644 index 2c3cd635b8..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.OnEventListener.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - -ExoMediaDrm.OnEventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface ExoMediaDrm.OnEventListener<T extends ExoMediaCrypto>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.ProvisionRequest.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.ProvisionRequest.html deleted file mode 100644 index 3499c6c555..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.ProvisionRequest.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - -ExoMediaDrm.ProvisionRequest (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface ExoMediaDrm.ProvisionRequest

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.html deleted file mode 100644 index c4a94d8453..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/ExoMediaDrm.html +++ /dev/null @@ -1,535 +0,0 @@ - - - - - -ExoMediaDrm (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface ExoMediaDrm<T extends ExoMediaCrypto>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/FrameworkMediaCrypto.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/FrameworkMediaCrypto.html deleted file mode 100644 index 74ec6b91b9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/FrameworkMediaCrypto.html +++ /dev/null @@ -1,260 +0,0 @@ - - - - - -FrameworkMediaCrypto (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class FrameworkMediaCrypto

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/FrameworkMediaDrm.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/FrameworkMediaDrm.html deleted file mode 100644 index c444c7a3a2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/FrameworkMediaDrm.html +++ /dev/null @@ -1,609 +0,0 @@ - - - - - -FrameworkMediaDrm (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class FrameworkMediaDrm

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/KeysExpiredException.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/KeysExpiredException.html deleted file mode 100644 index 9919c01013..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/KeysExpiredException.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - -KeysExpiredException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class KeysExpiredException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/MediaDrmCallback.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/MediaDrmCallback.html deleted file mode 100644 index fc9d1f849f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/MediaDrmCallback.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - - -MediaDrmCallback (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface MediaDrmCallback

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/StreamingDrmSessionManager.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/StreamingDrmSessionManager.EventListener.html deleted file mode 100644 index 3561b2ccfb..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/StreamingDrmSessionManager.EventListener.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - - -StreamingDrmSessionManager.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Interface StreamingDrmSessionManager.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/StreamingDrmSessionManager.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/StreamingDrmSessionManager.html deleted file mode 100644 index a54949d7ce..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/StreamingDrmSessionManager.html +++ /dev/null @@ -1,740 +0,0 @@ - - - - - -StreamingDrmSessionManager (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class StreamingDrmSessionManager<T extends ExoMediaCrypto>

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/UnsupportedDrmException.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/UnsupportedDrmException.html deleted file mode 100644 index a1430ac143..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/UnsupportedDrmException.html +++ /dev/null @@ -1,353 +0,0 @@ - - - - - -UnsupportedDrmException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.drm
-

Class UnsupportedDrmException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-frame.html deleted file mode 100644 index bf8bac0612..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-frame.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -com.google.android.exoplayer.drm (ExoPlayer library) - - - - - -

com.google.android.exoplayer.drm

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-summary.html deleted file mode 100644 index 11f1eae41a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-summary.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - -com.google.android.exoplayer.drm (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.drm

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-tree.html deleted file mode 100644 index 961f09396b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/drm/package-tree.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - -com.google.android.exoplayer.drm Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.drm

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ChunkIndex.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ChunkIndex.html deleted file mode 100644 index db301953e0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ChunkIndex.html +++ /dev/null @@ -1,461 +0,0 @@ - - - - - -ChunkIndex (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class ChunkIndex

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DefaultExtractorInput.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DefaultExtractorInput.html deleted file mode 100644 index 44419c28d6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DefaultExtractorInput.html +++ /dev/null @@ -1,757 +0,0 @@ - - - - - -DefaultExtractorInput (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class DefaultExtractorInput

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DefaultTrackOutput.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DefaultTrackOutput.html deleted file mode 100644 index c254dfd062..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DefaultTrackOutput.html +++ /dev/null @@ -1,652 +0,0 @@ - - - - - -DefaultTrackOutput (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class DefaultTrackOutput

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DummyTrackOutput.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DummyTrackOutput.html deleted file mode 100644 index 6f0bc31500..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/DummyTrackOutput.html +++ /dev/null @@ -1,391 +0,0 @@ - - - - - -DummyTrackOutput (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class DummyTrackOutput

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/Extractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/Extractor.html deleted file mode 100644 index 49f47b245b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/Extractor.html +++ /dev/null @@ -1,435 +0,0 @@ - - - - - -Extractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Interface Extractor

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorInput.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorInput.html deleted file mode 100644 index 6fb09bba3d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorInput.html +++ /dev/null @@ -1,652 +0,0 @@ - - - - - -ExtractorInput (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Interface ExtractorInput

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorOutput.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorOutput.html deleted file mode 100644 index cd13143439..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorOutput.html +++ /dev/null @@ -1,294 +0,0 @@ - - - - - -ExtractorOutput (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Interface ExtractorOutput

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.EventListener.html deleted file mode 100644 index 0b81a82175..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.EventListener.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - -ExtractorSampleSource.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Interface ExtractorSampleSource.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.UnrecognizedInputFormatException.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.UnrecognizedInputFormatException.html deleted file mode 100644 index 86b79b79e2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.UnrecognizedInputFormatException.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - -ExtractorSampleSource.UnrecognizedInputFormatException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class ExtractorSampleSource.UnrecognizedInputFormatException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.html deleted file mode 100644 index c83b5a7396..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ExtractorSampleSource.html +++ /dev/null @@ -1,1072 +0,0 @@ - - - - - -ExtractorSampleSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class ExtractorSampleSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/GaplessInfo.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/GaplessInfo.html deleted file mode 100644 index 091a9a236c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/GaplessInfo.html +++ /dev/null @@ -1,325 +0,0 @@ - - - - - -GaplessInfo (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class GaplessInfo

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/PositionHolder.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/PositionHolder.html deleted file mode 100644 index a86ad82fe3..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/PositionHolder.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - -PositionHolder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Class PositionHolder

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/SeekMap.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/SeekMap.html deleted file mode 100644 index 908f4cc8f3..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/SeekMap.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - -SeekMap (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Interface SeekMap

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/TrackOutput.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/TrackOutput.html deleted file mode 100644 index bf442d4ce1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/TrackOutput.html +++ /dev/null @@ -1,332 +0,0 @@ - - - - - -TrackOutput (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor
-

Interface TrackOutput

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/FlvExtractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/FlvExtractor.html deleted file mode 100644 index 939d6cf2a6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/FlvExtractor.html +++ /dev/null @@ -1,542 +0,0 @@ - - - - - -FlvExtractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.flv
-

Class FlvExtractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-frame.html deleted file mode 100644 index 6c12e632b7..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-frame.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.flv (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor.flv

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-summary.html deleted file mode 100644 index 954a2d5609..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-summary.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.flv (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor.flv

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-tree.html deleted file mode 100644 index 7956818748..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/flv/package-tree.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.flv Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor.flv

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.html deleted file mode 100644 index 09d1326a25..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.html +++ /dev/null @@ -1,450 +0,0 @@ - - - - - -Mp3Extractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.mp3
-

Class Mp3Extractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-frame.html deleted file mode 100644 index abff371212..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-frame.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.mp3 (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor.mp3

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-summary.html deleted file mode 100644 index 20abd69376..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-summary.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.mp3 (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor.mp3

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-tree.html deleted file mode 100644 index 12b48ec82d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp3/package-tree.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.mp3 Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor.mp3

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.html deleted file mode 100644 index f965da848a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.html +++ /dev/null @@ -1,542 +0,0 @@ - - - - - -FragmentedMp4Extractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.mp4
-

Class FragmentedMp4Extractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.html deleted file mode 100644 index f584fab31e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.html +++ /dev/null @@ -1,488 +0,0 @@ - - - - - -Mp4Extractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.mp4
-

Class Mp4Extractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/PsshAtomUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/PsshAtomUtil.html deleted file mode 100644 index 7583f9515c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/PsshAtomUtil.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - - -PsshAtomUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.mp4
-

Class PsshAtomUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/Track.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/Track.html deleted file mode 100644 index a3cf1dd27a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/Track.html +++ /dev/null @@ -1,518 +0,0 @@ - - - - - -Track (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.mp4
-

Class Track

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.html deleted file mode 100644 index e0dd049e81..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - -TrackEncryptionBox (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.mp4
-

Class TrackEncryptionBox

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-frame.html deleted file mode 100644 index be7c6e3706..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-frame.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.mp4 (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor.mp4

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-summary.html deleted file mode 100644 index 00e2b1b411..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-summary.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.mp4 (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor.mp4

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-tree.html deleted file mode 100644 index 9d92aa7417..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/mp4/package-tree.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.mp4 Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor.mp4

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/OggExtractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/OggExtractor.html deleted file mode 100644 index 645a1e2a79..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/OggExtractor.html +++ /dev/null @@ -1,427 +0,0 @@ - - - - - -OggExtractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.ogg
-

Class OggExtractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-frame.html deleted file mode 100644 index 19b6505d4a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-frame.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.ogg (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor.ogg

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-summary.html deleted file mode 100644 index 64faa3f6ae..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-summary.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.ogg (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor.ogg

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-tree.html deleted file mode 100644 index 5b9237498e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ogg/package-tree.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.ogg Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor.ogg

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-frame.html deleted file mode 100644 index b57e3f1baa..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-frame.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-summary.html deleted file mode 100644 index 4cb99fcacc..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-summary.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-tree.html deleted file mode 100644 index 5e287dc4cf..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/package-tree.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/AdtsExtractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/AdtsExtractor.html deleted file mode 100644 index 5a7ab2c594..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/AdtsExtractor.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - -AdtsExtractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.ts
-

Class AdtsExtractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/PsExtractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/PsExtractor.html deleted file mode 100644 index 99b858664e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/PsExtractor.html +++ /dev/null @@ -1,539 +0,0 @@ - - - - - -PsExtractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.ts
-

Class PsExtractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.html deleted file mode 100644 index 086bdc54c3..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - -PtsTimestampAdjuster (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.ts
-

Class PtsTimestampAdjuster

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/TsExtractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/TsExtractor.html deleted file mode 100644 index 535aec4cd2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/TsExtractor.html +++ /dev/null @@ -1,553 +0,0 @@ - - - - - -TsExtractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.ts
-

Class TsExtractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-frame.html deleted file mode 100644 index f2afb01b83..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-frame.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.ts (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor.ts

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-summary.html deleted file mode 100644 index 85a1bfdcb4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-summary.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.ts (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor.ts

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-tree.html deleted file mode 100644 index 5731901e64..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/ts/package-tree.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.ts Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor.ts

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/WavExtractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/WavExtractor.html deleted file mode 100644 index afa583e81a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/WavExtractor.html +++ /dev/null @@ -1,488 +0,0 @@ - - - - - -WavExtractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.wav
-

Class WavExtractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-frame.html deleted file mode 100644 index 1bc6806641..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-frame.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.wav (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor.wav

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-summary.html deleted file mode 100644 index c930c23cc2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-summary.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.wav (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor.wav

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-tree.html deleted file mode 100644 index 145576301f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/wav/package-tree.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.wav Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor.wav

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/WebmExtractor.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/WebmExtractor.html deleted file mode 100644 index 835bfe1064..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/WebmExtractor.html +++ /dev/null @@ -1,433 +0,0 @@ - - - - - -WebmExtractor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.extractor.webm
-

Class WebmExtractor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-frame.html deleted file mode 100644 index 9790c40631..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-frame.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.webm (ExoPlayer library) - - - - - -

com.google.android.exoplayer.extractor.webm

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-summary.html deleted file mode 100644 index 93f5adf58e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-summary.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.webm (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.extractor.webm

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-tree.html deleted file mode 100644 index 9ad1a06571..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/extractor/webm/package-tree.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -com.google.android.exoplayer.extractor.webm Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.extractor.webm

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/DefaultHlsTrackSelector.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/DefaultHlsTrackSelector.html deleted file mode 100644 index 52898090ba..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/DefaultHlsTrackSelector.html +++ /dev/null @@ -1,334 +0,0 @@ - - - - - -DefaultHlsTrackSelector (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class DefaultHlsTrackSelector

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsChunkSource.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsChunkSource.EventListener.html deleted file mode 100644 index 48cfd9168e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsChunkSource.EventListener.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - -HlsChunkSource.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Interface HlsChunkSource.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsChunkSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsChunkSource.html deleted file mode 100644 index ae7174881b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsChunkSource.html +++ /dev/null @@ -1,894 +0,0 @@ - - - - - -HlsChunkSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsChunkSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsExtractorWrapper.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsExtractorWrapper.html deleted file mode 100644 index 65961cc25d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsExtractorWrapper.html +++ /dev/null @@ -1,703 +0,0 @@ - - - - - -HlsExtractorWrapper (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsExtractorWrapper

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMasterPlaylist.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMasterPlaylist.html deleted file mode 100644 index bf11b40d57..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMasterPlaylist.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - -HlsMasterPlaylist (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsMasterPlaylist

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMediaPlaylist.Segment.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMediaPlaylist.Segment.html deleted file mode 100644 index d19e0a2b48..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMediaPlaylist.Segment.html +++ /dev/null @@ -1,439 +0,0 @@ - - - - - -HlsMediaPlaylist.Segment (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsMediaPlaylist.Segment

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMediaPlaylist.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMediaPlaylist.html deleted file mode 100644 index 9d744514bb..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsMediaPlaylist.html +++ /dev/null @@ -1,414 +0,0 @@ - - - - - -HlsMediaPlaylist (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsMediaPlaylist

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsPlaylist.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsPlaylist.html deleted file mode 100644 index 001a76b4c4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsPlaylist.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - -HlsPlaylist (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsPlaylist

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsPlaylistParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsPlaylistParser.html deleted file mode 100644 index 99b798d6b6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsPlaylistParser.html +++ /dev/null @@ -1,295 +0,0 @@ - - - - - -HlsPlaylistParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsPlaylistParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsSampleSource.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsSampleSource.EventListener.html deleted file mode 100644 index 50ef814a3c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsSampleSource.EventListener.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - -HlsSampleSource.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Interface HlsSampleSource.EventListener

-
-
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsSampleSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsSampleSource.html deleted file mode 100644 index 7a4e13fda0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsSampleSource.html +++ /dev/null @@ -1,840 +0,0 @@ - - - - - -HlsSampleSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class HlsSampleSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsTrackSelector.Output.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsTrackSelector.Output.html deleted file mode 100644 index cf2273fb49..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsTrackSelector.Output.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - -HlsTrackSelector.Output (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Interface HlsTrackSelector.Output

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsTrackSelector.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsTrackSelector.html deleted file mode 100644 index a85b3767f9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/HlsTrackSelector.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - -HlsTrackSelector (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Interface HlsTrackSelector

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/PtsTimestampAdjusterProvider.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/PtsTimestampAdjusterProvider.html deleted file mode 100644 index 72e0f9e48b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/PtsTimestampAdjusterProvider.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - -PtsTimestampAdjusterProvider (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class PtsTimestampAdjusterProvider

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/TsChunk.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/TsChunk.html deleted file mode 100644 index 33ddad470c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/TsChunk.html +++ /dev/null @@ -1,479 +0,0 @@ - - - - - -TsChunk (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class TsChunk

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/Variant.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/Variant.html deleted file mode 100644 index 7747263920..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/Variant.html +++ /dev/null @@ -1,334 +0,0 @@ - - - - - -Variant (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.hls
-

Class Variant

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-frame.html deleted file mode 100644 index 04643bc87e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-frame.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - -com.google.android.exoplayer.hls (ExoPlayer library) - - - - - -

com.google.android.exoplayer.hls

-
-

Interfaces

- -

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-summary.html deleted file mode 100644 index 1a2fa682a9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-summary.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - -com.google.android.exoplayer.hls (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.hls

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-tree.html deleted file mode 100644 index 840a76842e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/hls/package-tree.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - -com.google.android.exoplayer.hls Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.hls

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataParser.html deleted file mode 100644 index 38e1314738..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataParser.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - -MetadataParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata
-

Interface MetadataParser<T>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataTrackRenderer.MetadataRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataTrackRenderer.MetadataRenderer.html deleted file mode 100644 index 21285d642a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataTrackRenderer.MetadataRenderer.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - -MetadataTrackRenderer.MetadataRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata
-

Interface MetadataTrackRenderer.MetadataRenderer<T>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataTrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataTrackRenderer.html deleted file mode 100644 index a792073713..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/MetadataTrackRenderer.html +++ /dev/null @@ -1,561 +0,0 @@ - - - - - -MetadataTrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata
-

Class MetadataTrackRenderer<T>

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/ApicFrame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/ApicFrame.html deleted file mode 100644 index 03e6589b06..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/ApicFrame.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - -ApicFrame (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class ApicFrame

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/BinaryFrame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/BinaryFrame.html deleted file mode 100644 index ef884e79e1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/BinaryFrame.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - -BinaryFrame (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class BinaryFrame

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/GeobFrame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/GeobFrame.html deleted file mode 100644 index 8a3b53a891..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/GeobFrame.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - -GeobFrame (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class GeobFrame

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/Id3Frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/Id3Frame.html deleted file mode 100644 index c2ebc33a26..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/Id3Frame.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - -Id3Frame (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class Id3Frame

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/Id3Parser.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/Id3Parser.html deleted file mode 100644 index 93d4db368f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/Id3Parser.html +++ /dev/null @@ -1,318 +0,0 @@ - - - - - -Id3Parser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class Id3Parser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/PrivFrame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/PrivFrame.html deleted file mode 100644 index 42fe3c5122..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/PrivFrame.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - -PrivFrame (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class PrivFrame

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/TextInformationFrame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/TextInformationFrame.html deleted file mode 100644 index 4db8c9cea4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/TextInformationFrame.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - -TextInformationFrame (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class TextInformationFrame

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/TxxxFrame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/TxxxFrame.html deleted file mode 100644 index c27e7b16dc..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/TxxxFrame.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - -TxxxFrame (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.metadata.id3
-

Class TxxxFrame

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-frame.html deleted file mode 100644 index e15c834972..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-frame.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - -com.google.android.exoplayer.metadata.id3 (ExoPlayer library) - - - - - -

com.google.android.exoplayer.metadata.id3

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-summary.html deleted file mode 100644 index ce02fa6b0d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-summary.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - -com.google.android.exoplayer.metadata.id3 (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.metadata.id3

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-tree.html deleted file mode 100644 index d0eec1557c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/id3/package-tree.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - -com.google.android.exoplayer.metadata.id3 Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.metadata.id3

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-frame.html deleted file mode 100644 index 8cb4ae7c8c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-frame.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - -com.google.android.exoplayer.metadata (ExoPlayer library) - - - - - -

com.google.android.exoplayer.metadata

-
-

Interfaces

- -

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-summary.html deleted file mode 100644 index 9a46a44182..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-summary.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - -com.google.android.exoplayer.metadata (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.metadata

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-tree.html deleted file mode 100644 index 5f1a094baf..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/metadata/package-tree.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - -com.google.android.exoplayer.metadata Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.metadata

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/package-frame.html deleted file mode 100644 index 45c4987d89..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/package-frame.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - -com.google.android.exoplayer (ExoPlayer library) - - - - - -

com.google.android.exoplayer

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/package-summary.html deleted file mode 100644 index 3d8e6662ee..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/package-summary.html +++ /dev/null @@ -1,413 +0,0 @@ - - - - - -com.google.android.exoplayer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/package-tree.html deleted file mode 100644 index b19f742770..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/package-tree.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - -com.google.android.exoplayer Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/DefaultSmoothStreamingTrackSelector.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/DefaultSmoothStreamingTrackSelector.html deleted file mode 100644 index ee2e036c8c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/DefaultSmoothStreamingTrackSelector.html +++ /dev/null @@ -1,325 +0,0 @@ - - - - - -DefaultSmoothStreamingTrackSelector (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class DefaultSmoothStreamingTrackSelector

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.html deleted file mode 100644 index 6131ccd0f1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.html +++ /dev/null @@ -1,652 +0,0 @@ - - - - - -SmoothStreamingChunkSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class SmoothStreamingChunkSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.ProtectionElement.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.ProtectionElement.html deleted file mode 100644 index 51a1b1e8ee..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.ProtectionElement.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - -SmoothStreamingManifest.ProtectionElement (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class SmoothStreamingManifest.ProtectionElement

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.StreamElement.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.StreamElement.html deleted file mode 100644 index 1c4808effe..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.StreamElement.html +++ /dev/null @@ -1,627 +0,0 @@ - - - - - -SmoothStreamingManifest.StreamElement (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class SmoothStreamingManifest.StreamElement

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.TrackElement.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.TrackElement.html deleted file mode 100644 index 0233625d40..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.TrackElement.html +++ /dev/null @@ -1,352 +0,0 @@ - - - - - -SmoothStreamingManifest.TrackElement (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class SmoothStreamingManifest.TrackElement

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.html deleted file mode 100644 index 9357e4288d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.html +++ /dev/null @@ -1,462 +0,0 @@ - - - - - -SmoothStreamingManifest (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class SmoothStreamingManifest

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.MissingFieldException.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.MissingFieldException.html deleted file mode 100644 index d7446f5181..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.MissingFieldException.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - -SmoothStreamingManifestParser.MissingFieldException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class SmoothStreamingManifestParser.MissingFieldException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.html deleted file mode 100644 index f77cd17477..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.html +++ /dev/null @@ -1,321 +0,0 @@ - - - - - -SmoothStreamingManifestParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Class SmoothStreamingManifestParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.Output.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.Output.html deleted file mode 100644 index 8065455c8d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.Output.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - -SmoothStreamingTrackSelector.Output (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Interface SmoothStreamingTrackSelector.Output

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.html deleted file mode 100644 index c92f1686dd..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - -SmoothStreamingTrackSelector (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.smoothstreaming
-

Interface SmoothStreamingTrackSelector

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-frame.html deleted file mode 100644 index f8f1c2f363..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-frame.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - -com.google.android.exoplayer.smoothstreaming (ExoPlayer library) - - - - - -

com.google.android.exoplayer.smoothstreaming

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-summary.html deleted file mode 100644 index 6efd0e3dbc..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-summary.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - -com.google.android.exoplayer.smoothstreaming (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.smoothstreaming

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-tree.html deleted file mode 100644 index b145ac1bde..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/smoothstreaming/package-tree.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - -com.google.android.exoplayer.smoothstreaming Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.smoothstreaming

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/CaptionStyleCompat.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/CaptionStyleCompat.html deleted file mode 100644 index e4846195c8..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/CaptionStyleCompat.html +++ /dev/null @@ -1,560 +0,0 @@ - - - - - -CaptionStyleCompat (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text
-

Class CaptionStyleCompat

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/Cue.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/Cue.html deleted file mode 100644 index 9f0a085e23..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/Cue.html +++ /dev/null @@ -1,603 +0,0 @@ - - - - - -Cue (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text
-

Class Cue

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/Subtitle.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/Subtitle.html deleted file mode 100644 index 50b589899a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/Subtitle.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - -Subtitle (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text
-

Interface Subtitle

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/SubtitleLayout.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/SubtitleLayout.html deleted file mode 100644 index 78880bfcb1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/SubtitleLayout.html +++ /dev/null @@ -1,554 +0,0 @@ - - - - - -SubtitleLayout (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text
-

Class SubtitleLayout

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/SubtitleParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/SubtitleParser.html deleted file mode 100644 index 66ff181768..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/SubtitleParser.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - - -SubtitleParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text
-

Interface SubtitleParser

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/TextRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/TextRenderer.html deleted file mode 100644 index 8be84d41e4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/TextRenderer.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - -TextRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text
-

Interface TextRenderer

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/TextTrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/TextTrackRenderer.html deleted file mode 100644 index 7833b243ff..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/TextTrackRenderer.html +++ /dev/null @@ -1,620 +0,0 @@ - - - - - -TextTrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text
-

Class TextTrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/Eia608Parser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/Eia608Parser.html deleted file mode 100644 index 1f59fa9d38..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/Eia608Parser.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - -Eia608Parser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.eia608
-

Class Eia608Parser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.html deleted file mode 100644 index 394d2688c9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.html +++ /dev/null @@ -1,541 +0,0 @@ - - - - - -Eia608TrackRenderer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.eia608
-

Class Eia608TrackRenderer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-frame.html deleted file mode 100644 index 00a9bb5c7d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-frame.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - -com.google.android.exoplayer.text.eia608 (ExoPlayer library) - - - - - -

com.google.android.exoplayer.text.eia608

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-summary.html deleted file mode 100644 index d4e32768f2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-summary.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - -com.google.android.exoplayer.text.eia608 (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.text.eia608

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-tree.html deleted file mode 100644 index 5f6a152619..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/eia608/package-tree.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - -com.google.android.exoplayer.text.eia608 Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.text.eia608

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/package-frame.html deleted file mode 100644 index 5b1eaf337e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/package-frame.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - -com.google.android.exoplayer.text (ExoPlayer library) - - - - - -

com.google.android.exoplayer.text

-
-

Interfaces

- -

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/package-summary.html deleted file mode 100644 index ec99bad33b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/package-summary.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - -com.google.android.exoplayer.text (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.text

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/package-tree.html deleted file mode 100644 index a1f84404f0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/package-tree.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - -com.google.android.exoplayer.text Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.text

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/SubripParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/SubripParser.html deleted file mode 100644 index f373bf5ef3..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/SubripParser.html +++ /dev/null @@ -1,318 +0,0 @@ - - - - - -SubripParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.subrip
-

Class SubripParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-frame.html deleted file mode 100644 index 96ad6e8da9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-frame.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -com.google.android.exoplayer.text.subrip (ExoPlayer library) - - - - - -

com.google.android.exoplayer.text.subrip

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-summary.html deleted file mode 100644 index 59addaebbf..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-summary.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - -com.google.android.exoplayer.text.subrip (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.text.subrip

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-tree.html deleted file mode 100644 index 15463e0c41..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/subrip/package-tree.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -com.google.android.exoplayer.text.subrip Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.text.subrip

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/TtmlParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/TtmlParser.html deleted file mode 100644 index a94dd478ed..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/TtmlParser.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - -TtmlParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.ttml
-

Class TtmlParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/TtmlSubtitle.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/TtmlSubtitle.html deleted file mode 100644 index 5a876ccc5f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/TtmlSubtitle.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - -TtmlSubtitle (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.ttml
-

Class TtmlSubtitle

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-frame.html deleted file mode 100644 index 7d81982038..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-frame.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - -com.google.android.exoplayer.text.ttml (ExoPlayer library) - - - - - -

com.google.android.exoplayer.text.ttml

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-summary.html deleted file mode 100644 index 8f08ff09d0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-summary.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - -com.google.android.exoplayer.text.ttml (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.text.ttml

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-tree.html deleted file mode 100644 index f446ebd2b5..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/ttml/package-tree.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - -com.google.android.exoplayer.text.ttml Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.text.ttml

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/Tx3gParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/Tx3gParser.html deleted file mode 100644 index 514a87a239..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/Tx3gParser.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - -Tx3gParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.tx3g
-

Class Tx3gParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-frame.html deleted file mode 100644 index 6d2f04e02d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-frame.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -com.google.android.exoplayer.text.tx3g (ExoPlayer library) - - - - - -

com.google.android.exoplayer.text.tx3g

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-summary.html deleted file mode 100644 index 5dc63652ae..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-summary.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - -com.google.android.exoplayer.text.tx3g (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.text.tx3g

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-tree.html deleted file mode 100644 index d174f143cc..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/tx3g/package-tree.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -com.google.android.exoplayer.text.tx3g Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.text.tx3g

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/Mp4WebvttParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/Mp4WebvttParser.html deleted file mode 100644 index 5bf136b8ee..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/Mp4WebvttParser.html +++ /dev/null @@ -1,321 +0,0 @@ - - - - - -Mp4WebvttParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.webvtt
-

Class Mp4WebvttParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttCueParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttCueParser.html deleted file mode 100644 index 0fbdea5022..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttCueParser.html +++ /dev/null @@ -1,317 +0,0 @@ - - - - - -WebvttCueParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.webvtt
-

Class WebvttCueParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttParser.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttParser.html deleted file mode 100644 index 55c7106454..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttParser.html +++ /dev/null @@ -1,326 +0,0 @@ - - - - - -WebvttParser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.webvtt
-

Class WebvttParser

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttParserUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttParserUtil.html deleted file mode 100644 index e7e2b961e2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttParserUtil.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - -WebvttParserUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.webvtt
-

Class WebvttParserUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.html deleted file mode 100644 index f39b2d2643..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - -WebvttSubtitle (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.text.webvtt
-

Class WebvttSubtitle

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-frame.html deleted file mode 100644 index 2f7683b9e6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-frame.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - -com.google.android.exoplayer.text.webvtt (ExoPlayer library) - - - - - -

com.google.android.exoplayer.text.webvtt

-
-

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-summary.html deleted file mode 100644 index 177656b9dd..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-summary.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - -com.google.android.exoplayer.text.webvtt (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.text.webvtt

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-tree.html deleted file mode 100644 index beb0c94039..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/text/webvtt/package-tree.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - -com.google.android.exoplayer.text.webvtt Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.text.webvtt

-Package Hierarchies: - -
-
-

Class Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Allocation.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Allocation.html deleted file mode 100644 index ad4e8593a4..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Allocation.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - - -Allocation (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class Allocation

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Allocator.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Allocator.html deleted file mode 100644 index 60642d0cb9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Allocator.html +++ /dev/null @@ -1,353 +0,0 @@ - - - - - -Allocator (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface Allocator

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/AssetDataSource.AssetDataSourceException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/AssetDataSource.AssetDataSourceException.html deleted file mode 100644 index 453bd7b1ca..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/AssetDataSource.AssetDataSourceException.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - -AssetDataSource.AssetDataSourceException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class AssetDataSource.AssetDataSourceException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/AssetDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/AssetDataSource.html deleted file mode 100644 index 2d61b014aa..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/AssetDataSource.html +++ /dev/null @@ -1,437 +0,0 @@ - - - - - -AssetDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class AssetDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/BandwidthMeter.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/BandwidthMeter.EventListener.html deleted file mode 100644 index 4a620c49d7..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/BandwidthMeter.EventListener.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - - -BandwidthMeter.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface BandwidthMeter.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/BandwidthMeter.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/BandwidthMeter.html deleted file mode 100644 index 971fab1181..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/BandwidthMeter.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - -BandwidthMeter (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface BandwidthMeter

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ByteArrayDataSink.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ByteArrayDataSink.html deleted file mode 100644 index e02b4160be..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ByteArrayDataSink.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - -ByteArrayDataSink (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class ByteArrayDataSink

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ByteArrayDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ByteArrayDataSource.html deleted file mode 100644 index fed2d711dd..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ByteArrayDataSource.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - -ByteArrayDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class ByteArrayDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ContentDataSource.ContentDataSourceException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ContentDataSource.ContentDataSourceException.html deleted file mode 100644 index 6fd5f443cc..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ContentDataSource.ContentDataSourceException.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - -ContentDataSource.ContentDataSourceException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class ContentDataSource.ContentDataSourceException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ContentDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ContentDataSource.html deleted file mode 100644 index 9accbec857..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/ContentDataSource.html +++ /dev/null @@ -1,437 +0,0 @@ - - - - - -ContentDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class ContentDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSink.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSink.html deleted file mode 100644 index 070e45e07b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSink.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - -DataSink (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface DataSink

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSource.html deleted file mode 100644 index 7235eb9b10..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSource.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - -DataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface DataSource

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSourceInputStream.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSourceInputStream.html deleted file mode 100644 index 0b9cf2af52..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSourceInputStream.html +++ /dev/null @@ -1,414 +0,0 @@ - - - - - -DataSourceInputStream (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class DataSourceInputStream

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSpec.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSpec.html deleted file mode 100644 index 3c4a4aebad..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DataSpec.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - -DataSpec (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class DataSpec

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultAllocator.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultAllocator.html deleted file mode 100644 index 0db9c8a3f8..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultAllocator.html +++ /dev/null @@ -1,460 +0,0 @@ - - - - - -DefaultAllocator (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class DefaultAllocator

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.html deleted file mode 100644 index 41c307bbe8..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.html +++ /dev/null @@ -1,478 +0,0 @@ - - - - - -DefaultBandwidthMeter (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class DefaultBandwidthMeter

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultHttpDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultHttpDataSource.html deleted file mode 100644 index 5e4c70419b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultHttpDataSource.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - -DefaultHttpDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class DefaultHttpDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultUriDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultUriDataSource.html deleted file mode 100644 index 86574dde10..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/DefaultUriDataSource.html +++ /dev/null @@ -1,500 +0,0 @@ - - - - - -DefaultUriDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class DefaultUriDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/FileDataSource.FileDataSourceException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/FileDataSource.FileDataSourceException.html deleted file mode 100644 index 1c776a40d9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/FileDataSource.FileDataSourceException.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - -FileDataSource.FileDataSourceException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class FileDataSource.FileDataSourceException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/FileDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/FileDataSource.html deleted file mode 100644 index 7e3a8de272..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/FileDataSource.html +++ /dev/null @@ -1,435 +0,0 @@ - - - - - -FileDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class FileDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.HttpDataSourceException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.HttpDataSourceException.html deleted file mode 100644 index 569b490883..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.HttpDataSourceException.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - -HttpDataSource.HttpDataSourceException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class HttpDataSource.HttpDataSourceException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.InvalidContentTypeException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.InvalidContentTypeException.html deleted file mode 100644 index 254740a8ac..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.InvalidContentTypeException.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - -HttpDataSource.InvalidContentTypeException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class HttpDataSource.InvalidContentTypeException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.InvalidResponseCodeException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.InvalidResponseCodeException.html deleted file mode 100644 index b90606e405..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.InvalidResponseCodeException.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - -HttpDataSource.InvalidResponseCodeException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class HttpDataSource.InvalidResponseCodeException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.html deleted file mode 100644 index c2ae5234be..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/HttpDataSource.html +++ /dev/null @@ -1,481 +0,0 @@ - - - - - -HttpDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface HttpDataSource

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.Callback.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.Callback.html deleted file mode 100644 index 66720e6667..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.Callback.html +++ /dev/null @@ -1,277 +0,0 @@ - - - - - -Loader.Callback (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface Loader.Callback

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.Loadable.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.Loadable.html deleted file mode 100644 index 11389ae5ae..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.Loadable.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - - -Loader.Loadable (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface Loader.Loadable

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.UnexpectedLoaderException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.UnexpectedLoaderException.html deleted file mode 100644 index 9b03e7ad0b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.UnexpectedLoaderException.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - -Loader.UnexpectedLoaderException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class Loader.UnexpectedLoaderException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.html deleted file mode 100644 index bc3294c707..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/Loader.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - -Loader (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class Loader

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/NetworkLock.PriorityTooLowException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/NetworkLock.PriorityTooLowException.html deleted file mode 100644 index 9c07889ac1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/NetworkLock.PriorityTooLowException.html +++ /dev/null @@ -1,272 +0,0 @@ - - - - - -NetworkLock.PriorityTooLowException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class NetworkLock.PriorityTooLowException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/NetworkLock.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/NetworkLock.html deleted file mode 100644 index ea0f94de0f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/NetworkLock.html +++ /dev/null @@ -1,438 +0,0 @@ - - - - - -NetworkLock (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class NetworkLock

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/PriorityDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/PriorityDataSource.html deleted file mode 100644 index 8b70ddac06..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/PriorityDataSource.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - - -PriorityDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class PriorityDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/TeeDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/TeeDataSource.html deleted file mode 100644 index 60c39f591e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/TeeDataSource.html +++ /dev/null @@ -1,374 +0,0 @@ - - - - - -TeeDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class TeeDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/TransferListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/TransferListener.html deleted file mode 100644 index 98eb8f9691..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/TransferListener.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - -TransferListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface TransferListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UdpDataSource.UdpDataSourceException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UdpDataSource.UdpDataSourceException.html deleted file mode 100644 index ed08158efb..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UdpDataSource.UdpDataSourceException.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - -UdpDataSource.UdpDataSourceException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class UdpDataSource.UdpDataSourceException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UdpDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UdpDataSource.html deleted file mode 100644 index 4876cf7ff3..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UdpDataSource.html +++ /dev/null @@ -1,519 +0,0 @@ - - - - - -UdpDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class UdpDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriDataSource.html deleted file mode 100644 index eb57dc3bd0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriDataSource.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - -UriDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface UriDataSource

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriLoadable.Parser.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriLoadable.Parser.html deleted file mode 100644 index 75ff135b71..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriLoadable.Parser.html +++ /dev/null @@ -1,245 +0,0 @@ - - - - - -UriLoadable.Parser (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Interface UriLoadable.Parser<T>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriLoadable.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriLoadable.html deleted file mode 100644 index 062777112a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/UriLoadable.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - -UriLoadable (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream
-

Class UriLoadable<T>

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.CacheException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.CacheException.html deleted file mode 100644 index 71e447065e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.CacheException.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - -Cache.CacheException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class Cache.CacheException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.Listener.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.Listener.html deleted file mode 100644 index 88e093d75b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.Listener.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - -Cache.Listener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Interface Cache.Listener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.html deleted file mode 100644 index d7157530e9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/Cache.html +++ /dev/null @@ -1,555 +0,0 @@ - - - - - -Cache (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Interface Cache

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSink.CacheDataSinkException.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSink.CacheDataSinkException.html deleted file mode 100644 index 3006688474..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSink.CacheDataSinkException.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - -CacheDataSink.CacheDataSinkException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class CacheDataSink.CacheDataSinkException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSink.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSink.html deleted file mode 100644 index cdcd95b1c9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSink.html +++ /dev/null @@ -1,402 +0,0 @@ - - - - - -CacheDataSink (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class CacheDataSink

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSource.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSource.EventListener.html deleted file mode 100644 index eecf2ec843..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSource.EventListener.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - -CacheDataSource.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Interface CacheDataSource.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSource.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSource.html deleted file mode 100644 index 99242e8bd9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheDataSource.html +++ /dev/null @@ -1,470 +0,0 @@ - - - - - -CacheDataSource (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class CacheDataSource

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheEvictor.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheEvictor.html deleted file mode 100644 index 0881ee3fd0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheEvictor.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - - -CacheEvictor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Interface CacheEvictor

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheSpan.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheSpan.html deleted file mode 100644 index 08e96aafaa..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/CacheSpan.html +++ /dev/null @@ -1,479 +0,0 @@ - - - - - -CacheSpan (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class CacheSpan

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/LeastRecentlyUsedCacheEvictor.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/LeastRecentlyUsedCacheEvictor.html deleted file mode 100644 index 621ca6f8a0..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/LeastRecentlyUsedCacheEvictor.html +++ /dev/null @@ -1,427 +0,0 @@ - - - - - -LeastRecentlyUsedCacheEvictor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class LeastRecentlyUsedCacheEvictor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/NoOpCacheEvictor.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/NoOpCacheEvictor.html deleted file mode 100644 index a5fd693a7d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/NoOpCacheEvictor.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - - -NoOpCacheEvictor (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class NoOpCacheEvictor

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/SimpleCache.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/SimpleCache.html deleted file mode 100644 index bb5cc46d98..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/SimpleCache.html +++ /dev/null @@ -1,668 +0,0 @@ - - - - - -SimpleCache (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.upstream.cache
-

Class SimpleCache

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-frame.html deleted file mode 100644 index 9bcc2728ae..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-frame.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - -com.google.android.exoplayer.upstream.cache (ExoPlayer library) - - - - - -

com.google.android.exoplayer.upstream.cache

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-summary.html deleted file mode 100644 index a0e715926c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-summary.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - -com.google.android.exoplayer.upstream.cache (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.upstream.cache

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-tree.html deleted file mode 100644 index 3fb5f498b2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/cache/package-tree.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - -com.google.android.exoplayer.upstream.cache Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.upstream.cache

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-frame.html deleted file mode 100644 index e7ef77a219..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-frame.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - -com.google.android.exoplayer.upstream (ExoPlayer library) - - - - - -

com.google.android.exoplayer.upstream

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-summary.html deleted file mode 100644 index f4461ba6d3..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-summary.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - -com.google.android.exoplayer.upstream (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.upstream

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-tree.html deleted file mode 100644 index f99b9a3288..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/upstream/package-tree.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - -com.google.android.exoplayer.upstream Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.upstream

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/Ac3Util.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/Ac3Util.html deleted file mode 100644 index 744c5fa62f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/Ac3Util.html +++ /dev/null @@ -1,456 +0,0 @@ - - - - - -Ac3Util (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class Ac3Util

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/Assertions.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/Assertions.html deleted file mode 100644 index 88189bfcc2..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/Assertions.html +++ /dev/null @@ -1,451 +0,0 @@ - - - - - -Assertions (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class Assertions

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/AtomicFile.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/AtomicFile.html deleted file mode 100644 index 2dd6cc6adf..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/AtomicFile.html +++ /dev/null @@ -1,384 +0,0 @@ - - - - - -AtomicFile (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class AtomicFile

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/Clock.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/Clock.html deleted file mode 100644 index 1cdfc1a9f5..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/Clock.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - -Clock (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Interface Clock

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/CodecSpecificDataUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/CodecSpecificDataUtil.html deleted file mode 100644 index fcb66e6597..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/CodecSpecificDataUtil.html +++ /dev/null @@ -1,353 +0,0 @@ - - - - - -CodecSpecificDataUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class CodecSpecificDataUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/DebugTextViewHelper.Provider.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/DebugTextViewHelper.Provider.html deleted file mode 100644 index 57e236996b..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/DebugTextViewHelper.Provider.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - -DebugTextViewHelper.Provider (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Interface DebugTextViewHelper.Provider

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/DebugTextViewHelper.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/DebugTextViewHelper.html deleted file mode 100644 index 727c835f21..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/DebugTextViewHelper.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - -DebugTextViewHelper (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class DebugTextViewHelper

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/DtsUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/DtsUtil.html deleted file mode 100644 index 3a03e44986..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/DtsUtil.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - -DtsUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class DtsUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacSeekTable.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacSeekTable.html deleted file mode 100644 index 520ce3f6b6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacSeekTable.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - - -FlacSeekTable (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class FlacSeekTable

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacStreamInfo.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacStreamInfo.html deleted file mode 100644 index 51f727d144..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacStreamInfo.html +++ /dev/null @@ -1,462 +0,0 @@ - - - - - -FlacStreamInfo (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class FlacStreamInfo

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacUtil.html deleted file mode 100644 index 798975c41f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/FlacUtil.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - -FlacUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class FlacUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/LongArray.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/LongArray.html deleted file mode 100644 index eaefc45a3a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/LongArray.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - -LongArray (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class LongArray

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.EventListener.html deleted file mode 100644 index ad00626717..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.EventListener.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - -ManifestFetcher.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Interface ManifestFetcher.EventListener

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.ManifestCallback.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.ManifestCallback.html deleted file mode 100644 index 2f47f66c13..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.ManifestCallback.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - -ManifestFetcher.ManifestCallback (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Interface ManifestFetcher.ManifestCallback<T>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.ManifestIOException.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.ManifestIOException.html deleted file mode 100644 index 3bda22c65f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.ManifestIOException.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - -ManifestFetcher.ManifestIOException (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class ManifestFetcher.ManifestIOException

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.RedirectingManifest.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.RedirectingManifest.html deleted file mode 100644 index c097192abe..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.RedirectingManifest.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - -ManifestFetcher.RedirectingManifest (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Interface ManifestFetcher.RedirectingManifest

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.html deleted file mode 100644 index 278e3629e6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ManifestFetcher.html +++ /dev/null @@ -1,607 +0,0 @@ - - - - - -ManifestFetcher (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class ManifestFetcher<T>

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/MimeTypes.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/MimeTypes.html deleted file mode 100644 index c443fc145c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/MimeTypes.html +++ /dev/null @@ -1,1194 +0,0 @@ - - - - - -MimeTypes (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class MimeTypes

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/MpegAudioHeader.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/MpegAudioHeader.html deleted file mode 100644 index fa1dc453c1..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/MpegAudioHeader.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - - -MpegAudioHeader (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class MpegAudioHeader

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.PpsData.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.PpsData.html deleted file mode 100644 index 15b5ac4520..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.PpsData.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - -NalUnitUtil.PpsData (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class NalUnitUtil.PpsData

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.SpsData.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.SpsData.html deleted file mode 100644 index 602fa4ffb6..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.SpsData.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - -NalUnitUtil.SpsData (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class NalUnitUtil.SpsData

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.html deleted file mode 100644 index 3870ef115a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/NalUnitUtil.html +++ /dev/null @@ -1,561 +0,0 @@ - - - - - -NalUnitUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class NalUnitUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ParsableBitArray.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ParsableBitArray.html deleted file mode 100644 index aed528081d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ParsableBitArray.html +++ /dev/null @@ -1,560 +0,0 @@ - - - - - -ParsableBitArray (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class ParsableBitArray

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ParsableByteArray.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ParsableByteArray.html deleted file mode 100644 index a10be7c91f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ParsableByteArray.html +++ /dev/null @@ -1,1080 +0,0 @@ - - - - - -ParsableByteArray (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class ParsableByteArray

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ParserUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ParserUtil.html deleted file mode 100644 index c26e187599..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ParserUtil.html +++ /dev/null @@ -1,314 +0,0 @@ - - - - - -ParserUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class ParserUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/PlayerControl.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/PlayerControl.html deleted file mode 100644 index fc91703a69..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/PlayerControl.html +++ /dev/null @@ -1,462 +0,0 @@ - - - - - -PlayerControl (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class PlayerControl

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/Predicate.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/Predicate.html deleted file mode 100644 index 8b19b17bb5..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/Predicate.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - -Predicate (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Interface Predicate<T>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/PriorityHandlerThread.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/PriorityHandlerThread.html deleted file mode 100644 index 21d631fec9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/PriorityHandlerThread.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - -PriorityHandlerThread (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class PriorityHandlerThread

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/ReusableBufferedOutputStream.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/ReusableBufferedOutputStream.html deleted file mode 100644 index 89b3096fea..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/ReusableBufferedOutputStream.html +++ /dev/null @@ -1,374 +0,0 @@ - - - - - -ReusableBufferedOutputStream (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class ReusableBufferedOutputStream

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/SlidingPercentile.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/SlidingPercentile.html deleted file mode 100644 index 050fcceb5e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/SlidingPercentile.html +++ /dev/null @@ -1,314 +0,0 @@ - - - - - -SlidingPercentile (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class SlidingPercentile

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/SystemClock.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/SystemClock.html deleted file mode 100644 index d45fe32807..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/SystemClock.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - -SystemClock (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class SystemClock

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/TraceUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/TraceUtil.html deleted file mode 100644 index b11f6226db..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/TraceUtil.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - - -TraceUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class TraceUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/UriUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/UriUtil.html deleted file mode 100644 index e324eab1aa..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/UriUtil.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - -UriUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class UriUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/Util.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/Util.html deleted file mode 100644 index 67c941e5f7..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/Util.html +++ /dev/null @@ -1,1436 +0,0 @@ - - - - - -Util (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class Util

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/VerboseLogUtil.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/VerboseLogUtil.html deleted file mode 100644 index 6e9e3f8df5..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/VerboseLogUtil.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - -VerboseLogUtil (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util
-

Class VerboseLogUtil

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/Buffer.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/Buffer.html deleted file mode 100644 index 4fc7d6f275..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/Buffer.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - -Buffer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util.extensions
-

Class Buffer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/Decoder.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/Decoder.html deleted file mode 100644 index 60f9f12936..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/Decoder.html +++ /dev/null @@ -1,326 +0,0 @@ - - - - - -Decoder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util.extensions
-

Interface Decoder<I,O,E extends Exception>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/InputBuffer.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/InputBuffer.html deleted file mode 100644 index 97fd0e4f5e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/InputBuffer.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - - -InputBuffer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util.extensions
-

Class InputBuffer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/OutputBuffer.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/OutputBuffer.html deleted file mode 100644 index f45ffae35c..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/OutputBuffer.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - - -OutputBuffer (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util.extensions
-

Class OutputBuffer

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/SimpleDecoder.EventListener.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/SimpleDecoder.EventListener.html deleted file mode 100644 index 3c2aafc38e..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/SimpleDecoder.EventListener.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - -SimpleDecoder.EventListener (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util.extensions
-

Interface SimpleDecoder.EventListener<E>

-
-
-
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/SimpleDecoder.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/SimpleDecoder.html deleted file mode 100644 index 7d7ec3c03a..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/SimpleDecoder.html +++ /dev/null @@ -1,585 +0,0 @@ - - - - - -SimpleDecoder (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - - -
-
com.google.android.exoplayer.util.extensions
-

Class SimpleDecoder<I extends InputBuffer,O extends OutputBuffer,E extends Exception>

-
-
- -
- -
-
- -
-
- -
-
- - -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-frame.html deleted file mode 100644 index 3fe5ba96e9..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-frame.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - -com.google.android.exoplayer.util.extensions (ExoPlayer library) - - - - - -

com.google.android.exoplayer.util.extensions

-
-

Interfaces

- -

Classes

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-summary.html deleted file mode 100644 index 1654e13f4d..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-summary.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - -com.google.android.exoplayer.util.extensions (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.util.extensions

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-tree.html deleted file mode 100644 index e837c47741..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/extensions/package-tree.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - - -com.google.android.exoplayer.util.extensions Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.util.extensions

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/package-frame.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/package-frame.html deleted file mode 100644 index d3518fc498..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/package-frame.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - -com.google.android.exoplayer.util (ExoPlayer library) - - - - - -

com.google.android.exoplayer.util

-
-

Interfaces

- -

Classes

- -

Exceptions

- -
- - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/package-summary.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/package-summary.html deleted file mode 100644 index 9a7c1f4bbf..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/package-summary.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - -com.google.android.exoplayer.util (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Package com.google.android.exoplayer.util

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/com/google/android/exoplayer/util/package-tree.html b/docs/doc/reference-v1/com/google/android/exoplayer/util/package-tree.html deleted file mode 100644 index 5b1a565a7f..0000000000 --- a/docs/doc/reference-v1/com/google/android/exoplayer/util/package-tree.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - -com.google.android.exoplayer.util Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For Package com.google.android.exoplayer.util

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/constant-values.html b/docs/doc/reference-v1/constant-values.html deleted file mode 100644 index b98d7a185d..0000000000 --- a/docs/doc/reference-v1/constant-values.html +++ /dev/null @@ -1,2326 +0,0 @@ - - - - - -Constant Field Values (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Constant Field Values

-

Contents

- -
-
- - -

com.google.*

- - - - - - - - - - - - - - - - -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/deprecated-list.html b/docs/doc/reference-v1/deprecated-list.html deleted file mode 100644 index 3ddbffed03..0000000000 --- a/docs/doc/reference-v1/deprecated-list.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - -Deprecated List (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Deprecated API

-

Contents

- -
-
- - - - - - - -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/help-doc.html b/docs/doc/reference-v1/help-doc.html deleted file mode 100644 index f161b32ca4..0000000000 --- a/docs/doc/reference-v1/help-doc.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - -API Help (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

How This API Document Is Organized

-
This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.
-
-
- -This help file applies to API documentation generated using the standard doclet.
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/index-all.html b/docs/doc/reference-v1/index-all.html deleted file mode 100644 index 07bd73b0b0..0000000000 --- a/docs/doc/reference-v1/index-all.html +++ /dev/null @@ -1,7892 +0,0 @@ - - - - - -Index (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
A B C D E F G H I K L M N O P Q R S T U V W  - - -

A

-
-
absoluteStreamPosition - Variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
The absolute position of the data in the full stream.
-
-
Ac3Util - Class in com.google.android.exoplayer.util
-
-
Utility methods for parsing (E-)AC-3 syncframes, which are access units in (E-)AC-3 bitstreams.
-
-
AdaptationSet - Class in com.google.android.exoplayer.dash.mpd
-
-
Represents a set of interchangeable encoded versions of a media content component.
-
-
AdaptationSet(int, int, List<Representation>, List<ContentProtection>) - Constructor for class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
AdaptationSet(int, int, List<Representation>) - Constructor for class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
adaptationSets - Variable in class com.google.android.exoplayer.dash.mpd.Period
-
-
The adaptation sets belonging to the period.
-
-
adaptive - Variable in class com.google.android.exoplayer.DecoderInfo
-
-
Whether the decoder supports seamless resolution switches.
-
-
adaptive - Variable in class com.google.android.exoplayer.MediaFormat
-
-
Whether the format represents an adaptive track, meaning that the format of the actual media - data may change (e.g.
-
-
AdaptiveEvaluator(BandwidthMeter) - Constructor for class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
AdaptiveEvaluator(BandwidthMeter, int, int, int, int, float) - Constructor for class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
adaptiveMaxHeight - Variable in class com.google.android.exoplayer.dash.DashChunkSource.ExposedTrack
-
 
-
adaptiveMaxWidth - Variable in class com.google.android.exoplayer.dash.DashChunkSource.ExposedTrack
-
 
-
adaptiveTrack(MediaPresentationDescription, int, int, int[]) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
adaptiveTrack(MediaPresentationDescription, int, int, int[]) - Method in interface com.google.android.exoplayer.dash.DashTrackSelector.Output
-
-
Outputs an adaptive track, covering the specified representations in the specified - adaptation set.
-
-
adaptiveTrack(HlsMasterPlaylist, Variant[]) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
 
-
adaptiveTrack(HlsMasterPlaylist, Variant[]) - Method in interface com.google.android.exoplayer.hls.HlsTrackSelector.Output
-
-
Outputs an adaptive track, covering the specified representations in the specified - adaptation set.
-
-
adaptiveTrack(SmoothStreamingManifest, int, int[]) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
adaptiveTrack(SmoothStreamingManifest, int, int[]) - Method in interface com.google.android.exoplayer.smoothstreaming.SmoothStreamingTrackSelector.Output
-
-
Outputs an adaptive track, covering the specified tracks in the specified element.
-
-
add(int) - Method in class com.google.android.exoplayer.upstream.NetworkLock
-
-
Register a new task.
-
-
add(long) - Method in class com.google.android.exoplayer.util.LongArray
-
-
Appends a value.
-
-
addAdaptationSetProtection(ContentProtection) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser.ContentProtectionsBuilder
-
-
Adds a ContentProtection found in the AdaptationSet element.
-
-
addListener(ExoPlayer.Listener) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Register a listener to receive events from the player.
-
-
addListener(String, Cache.Listener) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Registers a listener to listen for changes to a given key.
-
-
addListener(String, Cache.Listener) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
addRepresentationProtection(ContentProtection) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser.ContentProtectionsBuilder
-
-
Adds a ContentProtection found in a child Representation element.
-
-
addSample(int, float) - Method in class com.google.android.exoplayer.util.SlidingPercentile
-
-
Record a new observation.
-
-
adjustReleaseTime(long, long) - Method in class com.google.android.exoplayer.VideoFrameReleaseTimeHelper
-
-
Called to make a fine-grained adjustment to a frame release time.
-
-
adjustTimestamp(long) - Method in class com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster
-
-
Scales and offsets an MPEG-2 TS presentation timestamp.
-
-
AdtsExtractor - Class in com.google.android.exoplayer.extractor.ts
-
-
Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS - headers.
-
-
AdtsExtractor() - Constructor for class com.google.android.exoplayer.extractor.ts.AdtsExtractor
-
 
-
AdtsExtractor(long) - Constructor for class com.google.android.exoplayer.extractor.ts.AdtsExtractor
-
 
-
advancePeekPosition(int, boolean) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
advancePeekPosition(int) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
advancePeekPosition(int, boolean) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Advances the peek position by length bytes.
-
-
advancePeekPosition(int) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Advances the peek position by length bytes.
-
-
allocate() - Method in interface com.google.android.exoplayer.upstream.Allocator
-
-
Obtain an Allocation.
-
-
allocate() - Method in class com.google.android.exoplayer.upstream.DefaultAllocator
-
 
-
Allocation - Class in com.google.android.exoplayer.upstream
-
-
An allocation within a byte array.
-
-
Allocation(byte[], int) - Constructor for class com.google.android.exoplayer.upstream.Allocation
-
 
-
Allocator - Interface in com.google.android.exoplayer.upstream
-
-
A source of allocations.
-
-
allowPassthrough(String) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
-
Returns whether encoded audio passthrough should be used for playing back the input format.
-
-
ANCHOR_TYPE_END - Static variable in class com.google.android.exoplayer.text.Cue
-
-
Anchors the right (for horizontal positions) or bottom (for vertical positions) edge of the cue - box.
-
-
ANCHOR_TYPE_MIDDLE - Static variable in class com.google.android.exoplayer.text.Cue
-
-
Anchors the middle of the cue box.
-
-
ANCHOR_TYPE_START - Static variable in class com.google.android.exoplayer.text.Cue
-
-
Anchors the left (for horizontal positions) or top (for vertical positions) edge of the cue - box.
-
-
ApicFrame - Class in com.google.android.exoplayer.metadata.id3
-
-
APIC (Attached Picture) ID3 frame.
-
-
ApicFrame(String, String, int, byte[]) - Constructor for class com.google.android.exoplayer.metadata.id3.ApicFrame
-
 
-
APPLICATION_CAMERA_MOTION - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_EIA608 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_ID3 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_M3U8 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_MP4 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_MP4VTT - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_PGS - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_SUBRIP - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_TTML - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_TX3G - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_VOBSUB - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
APPLICATION_WEBM - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
areAllTagsEnabled() - Static method in class com.google.android.exoplayer.util.VerboseLogUtil
-
-
Checks whether all logging is enabled;
-
-
areEqual(Object, Object) - Static method in class com.google.android.exoplayer.util.Util
-
-
Tests two objects for Object.equals(Object) equality, handling the case where one or - both may be null.
-
-
ASPECT_RATIO_IDC_VALUES - Static variable in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Aspect ratios indexed by aspect_ratio_idc, in H.264 and H.265 SPSs.
-
-
AspectRatioFrameLayout - Class in com.google.android.exoplayer
-
-
A FrameLayout that resizes itself to match a specified aspect ratio.
-
-
AspectRatioFrameLayout(Context) - Constructor for class com.google.android.exoplayer.AspectRatioFrameLayout
-
 
-
AspectRatioFrameLayout(Context, AttributeSet) - Constructor for class com.google.android.exoplayer.AspectRatioFrameLayout
-
 
-
Assertions - Class in com.google.android.exoplayer.util
-
-
Provides methods for asserting the truth of expressions and properties.
-
-
ASSERTIONS_ENABLED - Static variable in class com.google.android.exoplayer.ExoPlayerLibraryInfo
-
-
Whether the library was compiled with Assertions - checks enabled.
-
-
AssetDataSource - Class in com.google.android.exoplayer.upstream
-
-
A local asset UriDataSource.
-
-
AssetDataSource(Context) - Constructor for class com.google.android.exoplayer.upstream.AssetDataSource
-
-
Constructs a new DataSource that retrieves data from a local asset.
-
-
AssetDataSource(Context, TransferListener) - Constructor for class com.google.android.exoplayer.upstream.AssetDataSource
-
-
Constructs a new DataSource that retrieves data from a local asset.
-
-
AssetDataSource.AssetDataSourceException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when an IOException is encountered reading a local asset.
-
-
AssetDataSourceException(IOException) - Constructor for exception com.google.android.exoplayer.upstream.AssetDataSource.AssetDataSourceException
-
 
-
AtomicFile - Class in com.google.android.exoplayer.util
-
-
A helper class for performing atomic operations on a file by creating a backup file until a write - has successfully completed.
-
-
AtomicFile(File) - Constructor for class com.google.android.exoplayer.util.AtomicFile
-
-
Create a new AtomicFile for a file located at the given File path.
-
-
attemptMerge(RangedUri, String) - Method in class com.google.android.exoplayer.dash.mpd.RangedUri
-
-
Attempts to merge this RangedUri with another and an optional common base uri.
-
-
AUDIO_AAC - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_AC3 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_AMR_NB - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_AMR_WB - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_DTS - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_DTS_EXPRESS - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_DTS_HD - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_E_AC3 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_FLAC - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_MP4 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_MPEG - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_MPEG_L1 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_MPEG_L2 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_OPUS - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_RAW - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_STREAM - Static variable in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
AUDIO_STREAM_MASK - Static variable in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
AUDIO_TRUEHD - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_UNKNOWN - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_VORBIS - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AUDIO_WEBM - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
AudioCapabilities - Class in com.google.android.exoplayer.audio
-
-
Represents the set of audio formats a device is capable of playing back.
-
-
AudioCapabilitiesReceiver - Class in com.google.android.exoplayer.audio
-
-
Notifies a listener when the audio playback capabilities change.
-
-
AudioCapabilitiesReceiver(Context, AudioCapabilitiesReceiver.Listener) - Constructor for class com.google.android.exoplayer.audio.AudioCapabilitiesReceiver
-
-
Constructs a new audio capabilities receiver.
-
-
AudioCapabilitiesReceiver.Listener - Interface in com.google.android.exoplayer.audio
-
-
Listener notified when audio capabilities change.
-
-
audioChannels - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The number of audio channels, or -1 if unknown or not applicable.
-
-
audios - Variable in class com.google.android.exoplayer.hls.HlsMasterPlaylist
-
 
-
audioSamplingRate - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The audio sampling rate in Hz, or -1 if unknown or not applicable.
-
-
AudioTrack - Class in com.google.android.exoplayer.audio
-
-
Plays audio data.
-
-
AudioTrack() - Constructor for class com.google.android.exoplayer.audio.AudioTrack
-
-
Creates an audio track with default audio capabilities (no encoded audio passthrough support).
-
-
AudioTrack(AudioCapabilities, int) - Constructor for class com.google.android.exoplayer.audio.AudioTrack
-
-
Creates an audio track using the specified audio capabilities and stream type.
-
-
AudioTrack.InitializationException - Exception in com.google.android.exoplayer.audio
-
-
Thrown when a failure occurs instantiating an AudioTrack.
-
-
AudioTrack.InvalidAudioTrackTimestampException - Exception in com.google.android.exoplayer.audio
-
-
Thrown when AudioTrack.getTimestamp(android.media.AudioTimestamp) returns a spurious timestamp, if - AudioTrack#failOnSpuriousAudioTimestamp is set.
-
-
AudioTrack.WriteException - Exception in com.google.android.exoplayer.audio
-
-
Thrown when a failure occurs writing to an AudioTrack.
-
-
audioTrackState - Variable in exception com.google.android.exoplayer.audio.AudioTrack.InitializationException
-
-
The state as reported by AudioTrack.getState().
-
-
availabilityStartTime - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
- - - -

B

-
-
backgroundColor - Variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
The preferred background color.
-
-
BandwidthMeter - Interface in com.google.android.exoplayer.upstream
-
-
Provides estimates of the currently available bandwidth.
-
-
BandwidthMeter.EventListener - Interface in com.google.android.exoplayer.upstream
-
-
Interface definition for a callback to be notified of BandwidthMeter events.
-
-
BASE_TYPE_APPLICATION - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
BASE_TYPE_AUDIO - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
BASE_TYPE_TEXT - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
BASE_TYPE_VIDEO - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
BaseChunkSampleSourceEventListener - Interface in com.google.android.exoplayer.chunk
-
-
Interface for callbacks to be notified of chunk based SampleSource events.
-
-
BaseMediaChunk - Class in com.google.android.exoplayer.chunk
-
-
A base implementation of MediaChunk, for chunks that contain a single track.
-
-
BaseMediaChunk(DataSource, DataSpec, int, Format, long, long, int, boolean, int) - Constructor for class com.google.android.exoplayer.chunk.BaseMediaChunk
-
 
-
baseUri - Variable in class com.google.android.exoplayer.hls.HlsPlaylist
-
 
-
baseUrl - Variable in class com.google.android.exoplayer.dash.mpd.Representation
-
-
The base URL of the representation.
-
-
beginSection(String) - Static method in class com.google.android.exoplayer.util.TraceUtil
-
-
Writes a trace message to indicate that a given section of code has begun.
-
-
BehindLiveWindowException - Exception in com.google.android.exoplayer
-
-
Thrown when a live playback falls behind the available media window.
-
-
BehindLiveWindowException() - Constructor for exception com.google.android.exoplayer.BehindLiveWindowException
-
 
-
BehindLiveWindowException(String) - Constructor for exception com.google.android.exoplayer.BehindLiveWindowException
-
 
-
BinaryFrame - Class in com.google.android.exoplayer.metadata.id3
-
-
Binary ID3 frame.
-
-
BinaryFrame(String, byte[]) - Constructor for class com.google.android.exoplayer.metadata.id3.BinaryFrame
-
 
-
binarySearchCeil(long[], long, boolean, boolean) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the index of the smallest value in an array that is greater than (or optionally equal - to) a specified key.
-
-
binarySearchCeil(List<? extends Comparable<? super T>>, T, boolean, boolean) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the index of the smallest value in an list that is greater than (or optionally equal - to) a specified key.
-
-
binarySearchFloor(long[], long, boolean, boolean) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the index of the largest value in an array that is less than (or optionally equal to) - a specified key.
-
-
binarySearchFloor(List<? extends Comparable<? super T>>, T, boolean, boolean) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the index of the largest value in an list that is less than (or optionally equal to) - a specified key.
-
-
bitrate - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The average bandwidth in bits per second.
-
-
bitrate - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The average bandwidth in bits per second, or MediaFormat.NO_VALUE if unknown or not applicable.
-
-
bitRate() - Method in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
bitrate - Variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Bitrate of the frame in bit/s.
-
-
bitsLeft() - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Returns the number of bits yet to be read.
-
-
bitsPerSample - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
blockingSendMessage(ExoPlayer.ExoPlayerComponent, int, Object) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Blocking variant of ExoPlayer.sendMessage(ExoPlayerComponent, int, Object) that does not return - until after the message has been delivered.
-
-
blockWhileTotalBytesAllocatedExceeds(int) - Method in interface com.google.android.exoplayer.upstream.Allocator
-
-
Blocks execution until the number of bytes allocated is not greater than the limit, or the - thread is interrupted.
-
-
blockWhileTotalBytesAllocatedExceeds(int) - Method in class com.google.android.exoplayer.upstream.DefaultAllocator
-
 
-
bottomFieldPicOrderInFramePresentFlag - Variable in class com.google.android.exoplayer.util.NalUnitUtil.PpsData
-
 
-
Buffer - Class in com.google.android.exoplayer.util.extensions
-
-
Base class for Decoder buffers with flags.
-
-
Buffer() - Constructor for class com.google.android.exoplayer.util.extensions.Buffer
-
 
-
BUFFER_REPLACEMENT_MODE_DIRECT - Static variable in class com.google.android.exoplayer.SampleHolder
-
-
Allows buffer replacement using ByteBuffer.allocateDirect(int).
-
-
BUFFER_REPLACEMENT_MODE_DISABLED - Static variable in class com.google.android.exoplayer.SampleHolder
-
-
Disallows buffer replacement.
-
-
BUFFER_REPLACEMENT_MODE_NORMAL - Static variable in class com.google.android.exoplayer.SampleHolder
-
-
Allows buffer replacement using ByteBuffer.allocate(int).
-
-
build() - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser.ContentProtectionsBuilder
-
-
Returns the final list of consistent ContentProtection elements.
-
-
buildAacAudioSpecificConfig(int, int, int) - Static method in class com.google.android.exoplayer.util.CodecSpecificDataUtil
-
-
Builds a simple AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
-
-
buildAacAudioSpecificConfig(int, int) - Static method in class com.google.android.exoplayer.util.CodecSpecificDataUtil
-
-
Builds a simple HE-AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
-
-
buildAdaptationSet(int, int, List<Representation>, List<ContentProtection>) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildContentProtection(String, UUID, DrmInitData.SchemeInitData) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildFormat(String, String, int, int, float, int, int, int, String, String) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildMediaPresentationDescription(long, long, long, boolean, long, long, UtcTimingElement, String, List<Period>) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildNalUnit(byte[], int, int) - Static method in class com.google.android.exoplayer.util.CodecSpecificDataUtil
-
-
Constructs a NAL unit consisting of the NAL start code followed by the specified data.
-
-
buildPeriod(String, long, List<AdaptationSet>) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildPsshAtom(UUID, byte[]) - Static method in class com.google.android.exoplayer.extractor.mp4.PsshAtomUtil
-
-
Builds a PSSH atom for a given UUID containing the given scheme specific data.
-
-
buildRangedUri(String, long, long) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildRepresentation(String, int, Format, SegmentBase, String) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildRequestUri(int, int) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
-
Builds a uri for requesting the specified chunk of the specified track.
-
-
buildSegmentList(RangedUri, long, long, int, long, List<SegmentBase.SegmentTimelineElement>, List<RangedUri>) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildSegmentTemplate(RangedUri, long, long, int, long, List<SegmentBase.SegmentTimelineElement>, UrlTemplate, UrlTemplate) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildSegmentTimelineElement(long, long) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildSingleSegmentBase(RangedUri, long, long, long, long) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
buildUri(String, int, int, long) - Method in class com.google.android.exoplayer.dash.mpd.UrlTemplate
-
-
Constructs a Uri from the template, substituting in the provided arguments.
-
-
buildUtcTimingElement(String, String) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
ByteArrayDataSink - Class in com.google.android.exoplayer.upstream
-
-
A DataSink for writing to a byte array.
-
-
ByteArrayDataSink() - Constructor for class com.google.android.exoplayer.upstream.ByteArrayDataSink
-
 
-
ByteArrayDataSource - Class in com.google.android.exoplayer.upstream
-
-
A DataSource for reading from a byte array.
-
-
ByteArrayDataSource(byte[]) - Constructor for class com.google.android.exoplayer.upstream.ByteArrayDataSource
-
 
-
byterangeLength - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
byterangeOffset - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
bytesLeft() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Returns the number of bytes yet to be read.
-
-
bytesLoaded() - Method in class com.google.android.exoplayer.chunk.Chunk
-
-
Gets the number of bytes that have been loaded.
-
-
bytesLoaded() - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
bytesLoaded() - Method in class com.google.android.exoplayer.chunk.DataChunk
-
 
-
bytesLoaded() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
bytesLoaded() - Method in class com.google.android.exoplayer.chunk.SingleSampleMediaChunk
-
 
-
bytesLoaded() - Method in class com.google.android.exoplayer.hls.TsChunk
-
 
-
bytesRead() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
-
Returns the number of bytes that have been read since the most recent call to - DefaultHttpDataSource.open(DataSpec).
-
-
bytesRemaining() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
-
Returns the number of bytes that are still to be read for the current DataSpec.
-
-
bytesSkipped() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
-
Returns the number of bytes that have been skipped since the most recent call to - DefaultHttpDataSource.open(DataSpec).
-
-
- - - -

C

-
-
C - Class in com.google.android.exoplayer
-
-
Defines constants that are generally useful throughout the library.
-
-
Cache - Interface in com.google.android.exoplayer.upstream.cache
-
-
An interface for cache.
-
-
Cache.CacheException - Exception in com.google.android.exoplayer.upstream.cache
-
-
Thrown when an error is encountered when writing data.
-
-
Cache.Listener - Interface in com.google.android.exoplayer.upstream.cache
-
-
Interface definition for a callback to be notified of Cache events.
-
-
CacheDataSink - Class in com.google.android.exoplayer.upstream.cache
-
-
Writes data into a cache.
-
-
CacheDataSink(Cache, long) - Constructor for class com.google.android.exoplayer.upstream.cache.CacheDataSink
-
 
-
CacheDataSink(Cache, long, int) - Constructor for class com.google.android.exoplayer.upstream.cache.CacheDataSink
-
 
-
CacheDataSink.CacheDataSinkException - Exception in com.google.android.exoplayer.upstream.cache
-
-
Thrown when IOException is encountered when writing data into sink.
-
-
CacheDataSinkException(IOException) - Constructor for exception com.google.android.exoplayer.upstream.cache.CacheDataSink.CacheDataSinkException
-
 
-
CacheDataSource - Class in com.google.android.exoplayer.upstream.cache
-
-
A DataSource that reads and writes a Cache.
-
-
CacheDataSource(Cache, DataSource, boolean, boolean) - Constructor for class com.google.android.exoplayer.upstream.cache.CacheDataSource
-
-
Constructs an instance with default DataSource and DataSink instances for - reading and writing the cache.
-
-
CacheDataSource(Cache, DataSource, boolean, boolean, long) - Constructor for class com.google.android.exoplayer.upstream.cache.CacheDataSource
-
-
Constructs an instance with default DataSource and DataSink instances for - reading and writing the cache.
-
-
CacheDataSource(Cache, DataSource, DataSource, DataSink, boolean, boolean, CacheDataSource.EventListener) - Constructor for class com.google.android.exoplayer.upstream.cache.CacheDataSource
-
-
Constructs an instance with arbitrary DataSource and DataSink instances for - reading and writing the cache.
-
-
CacheDataSource.EventListener - Interface in com.google.android.exoplayer.upstream.cache
-
-
Interface definition for a callback to be notified of CacheDataSource events.
-
-
CacheEvictor - Interface in com.google.android.exoplayer.upstream.cache
-
-
Evicts data from a Cache.
-
-
CacheException(String) - Constructor for exception com.google.android.exoplayer.upstream.cache.Cache.CacheException
-
 
-
CacheException(IOException) - Constructor for exception com.google.android.exoplayer.upstream.cache.Cache.CacheException
-
 
-
CacheSpan - Class in com.google.android.exoplayer.upstream.cache
-
-
Defines a span of data that may or may not be cached (as indicated by CacheSpan.isCached).
-
-
CacheSpan(String, long, long) - Constructor for class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
Creates a hole CacheSpan which isn't cached, has no last access time and no file associated.
-
-
CacheSpan(String, long, long, long, File) - Constructor for class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
Creates a CacheSpan.
-
-
cancelLoad() - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
cancelLoad() - Method in class com.google.android.exoplayer.chunk.DataChunk
-
 
-
cancelLoad() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
cancelLoad() - Method in class com.google.android.exoplayer.chunk.SingleSampleMediaChunk
-
 
-
cancelLoad() - Method in class com.google.android.exoplayer.hls.TsChunk
-
 
-
cancelLoad() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
cancelLoad() - Method in interface com.google.android.exoplayer.upstream.Loader.Loadable
-
-
Cancels the load.
-
-
cancelLoad() - Method in class com.google.android.exoplayer.upstream.UriLoadable
-
 
-
cancelLoading() - Method in class com.google.android.exoplayer.upstream.Loader
-
-
Cancels the current load.
-
-
canParse(String) - Method in class com.google.android.exoplayer.metadata.id3.Id3Parser
-
 
-
canParse(String) - Method in interface com.google.android.exoplayer.metadata.MetadataParser
-
-
Checks whether the parser supports a given mime type.
-
-
canParse(String) - Method in class com.google.android.exoplayer.text.subrip.SubripParser
-
 
-
canParse(String) - Method in interface com.google.android.exoplayer.text.SubtitleParser
-
-
Checks whether the parser supports a given subtitle mime type.
-
-
canParse(String) - Method in class com.google.android.exoplayer.text.ttml.TtmlParser
-
 
-
canParse(String) - Method in class com.google.android.exoplayer.text.tx3g.Tx3gParser
-
 
-
canParse(String) - Method in class com.google.android.exoplayer.text.webvtt.Mp4WebvttParser
-
 
-
canParse(String) - Method in class com.google.android.exoplayer.text.webvtt.WebvttParser
-
 
-
canPause() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
canReadExpGolombCodedNum() - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current - offset.
-
-
canReconfigureCodec(MediaCodec, boolean, MediaFormat, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Determines whether the existing MediaCodec should be reconfigured for a new format by - sending codec specific initialization data at the start of the next input buffer.
-
-
canReconfigureCodec(MediaCodec, boolean, MediaFormat, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
canSeekBackward() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
canSeekForward() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
capabilities - Variable in class com.google.android.exoplayer.DecoderInfo
-
- -
-
capacity() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Returns the capacity of the array, which may be larger than the limit.
-
-
CaptionStyleCompat - Class in com.google.android.exoplayer.text
-
-
A compatibility wrapper for CaptioningManager.CaptionStyle.
-
-
CaptionStyleCompat(int, int, int, int, int, Typeface) - Constructor for class com.google.android.exoplayer.text.CaptionStyleCompat
-
 
-
caughtAtTopLevel - Variable in exception com.google.android.exoplayer.ExoPlaybackException
-
-
True if the cause (i.e.
-
-
ceilDivide(int, int) - Static method in class com.google.android.exoplayer.util.Util
-
-
Divides a numerator by a denominator, returning the ceiled result.
-
-
ceilDivide(long, long) - Static method in class com.google.android.exoplayer.util.Util
-
-
Divides a numerator by a denominator, returning the ceiled result.
-
-
CHANNEL_OUT_7POINT1_SURROUND - Static variable in class com.google.android.exoplayer.C
-
 
-
channelCount - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The number of audio channels, or MediaFormat.NO_VALUE if unknown or not applicable.
-
-
channels - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
channels - Variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Number of audio channels in the frame.
-
-
checkArgument(boolean) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures the truth of an expression involving one or more arguments passed to the calling - method.
-
-
checkArgument(boolean, Object) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures the truth of an expression involving one or more arguments passed to the calling - method.
-
-
checkMainThread() - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures that the calling thread is the application's main thread.
-
-
checkNotEmpty(String) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures that a string passed as an argument to the calling method is not null or 0-length.
-
-
checkNotEmpty(String, Object) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures that a string passed as an argument to the calling method is not null or 0-length.
-
-
checkNotNull(T) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures that an object reference is not null.
-
-
checkNotNull(T, Object) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures that an object reference is not null.
-
-
checkState(boolean) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures the truth of an expression involving the state of the calling instance.
-
-
checkState(boolean, Object) - Static method in class com.google.android.exoplayer.util.Assertions
-
-
Ensures the truth of an expression involving the state of the calling instance.
-
-
Chunk - Class in com.google.android.exoplayer.chunk
-
-
An abstract base class for Loader.Loadable implementations that load chunks of data required - for the playback of streams.
-
-
Chunk(DataSource, DataSpec, int, int, Format, int) - Constructor for class com.google.android.exoplayer.chunk.Chunk
-
 
-
chunk - Variable in class com.google.android.exoplayer.chunk.ChunkOperationHolder
-
-
The chunk.
-
-
chunkCount - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
ChunkExtractorWrapper - Class in com.google.android.exoplayer.chunk
-
-
An Extractor wrapper for loading chunks containing a single track.
-
-
ChunkExtractorWrapper(Extractor) - Constructor for class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
ChunkExtractorWrapper.SingleTrackOutput - Interface in com.google.android.exoplayer.chunk
-
-
Receives stream level data extracted by the wrapped Extractor.
-
-
chunkIndex - Variable in class com.google.android.exoplayer.chunk.MediaChunk
-
-
The chunk index.
-
-
ChunkIndex - Class in com.google.android.exoplayer.extractor
-
-
Defines chunks of samples within a media stream.
-
-
ChunkIndex(int[], long[], long[], long[]) - Constructor for class com.google.android.exoplayer.extractor.ChunkIndex
-
 
-
ChunkOperationHolder - Class in com.google.android.exoplayer.chunk
-
-
Holds a chunk operation, which consists of a either: - - The number of MediaChunks that should be retained on the queue (ChunkOperationHolder.queueSize) - together with the next Chunk to load (ChunkOperationHolder.chunk).
-
-
ChunkOperationHolder() - Constructor for class com.google.android.exoplayer.chunk.ChunkOperationHolder
-
 
-
ChunkSampleSource - Class in com.google.android.exoplayer.chunk
-
-
A SampleSource that loads media in Chunks, which are themselves obtained from a - ChunkSource.
-
-
ChunkSampleSource(ChunkSource, LoadControl, int) - Constructor for class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
ChunkSampleSource(ChunkSource, LoadControl, int, Handler, ChunkSampleSource.EventListener, int) - Constructor for class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
ChunkSampleSource(ChunkSource, LoadControl, int, Handler, ChunkSampleSource.EventListener, int, int) - Constructor for class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
ChunkSampleSource.EventListener - Interface in com.google.android.exoplayer.chunk
-
-
Interface definition for a callback to be notified of ChunkSampleSource events.
-
-
ChunkSource - Interface in com.google.android.exoplayer.chunk
-
-
A provider of Chunks for a ChunkSampleSource to load.
-
-
clear() - Method in class com.google.android.exoplayer.chunk.ChunkOperationHolder
-
-
Clears the holder.
-
-
clear() - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Clears the queue, returning all allocations to the allocator.
-
-
clear() - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Clears queues for all tracks, returning all allocations to the allocator.
-
-
clearAllRequestProperties() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
clearAllRequestProperties() - Method in interface com.google.android.exoplayer.upstream.HttpDataSource
-
-
Clears all request header fields that were set by HttpDataSource.setRequestProperty(String, String).
-
-
clearData() - Method in class com.google.android.exoplayer.SampleHolder
-
- -
-
clearPrefixFlags(boolean[]) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
- -
-
clearRequestProperty(String) - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
clearRequestProperty(String) - Method in interface com.google.android.exoplayer.upstream.HttpDataSource
-
-
Clears the value of a request header field.
-
-
Clock - Interface in com.google.android.exoplayer.util
-
-
An interface through which system clocks can be read.
-
-
close() - Method in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
Closes the session.
-
-
close() - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.AssetDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.ByteArrayDataSink
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.ByteArrayDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.cache.CacheDataSink
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.cache.CacheDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.ContentDataSource
-
 
-
close() - Method in interface com.google.android.exoplayer.upstream.DataSink
-
-
Closes the DataSink.
-
-
close() - Method in interface com.google.android.exoplayer.upstream.DataSource
-
-
Closes the DataSource.
-
-
close() - Method in class com.google.android.exoplayer.upstream.DataSourceInputStream
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.FileDataSource
-
 
-
close() - Method in interface com.google.android.exoplayer.upstream.HttpDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.PriorityDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.TeeDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.upstream.UdpDataSource
-
 
-
close() - Method in class com.google.android.exoplayer.util.ReusableBufferedOutputStream
-
 
-
closeQuietly(DataSource) - Static method in class com.google.android.exoplayer.util.Util
-
-
Closes a DataSource, suppressing any IOException that may occur.
-
-
closeQuietly(Closeable) - Static method in class com.google.android.exoplayer.util.Util
-
-
Closes a Closeable, suppressing any IOException that may occur.
-
-
closeSession(byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
closeSession(byte[]) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
CodecCounters - Class in com.google.android.exoplayer
-
-
Maintains codec event counts, for debugging purposes only.
-
-
CodecCounters() - Constructor for class com.google.android.exoplayer.CodecCounters
-
 
-
codecCounters - Variable in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
codecInitCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
codecInitialized() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
codecReleaseCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
codecs - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The codecs used to decode the format.
-
-
CodecSpecificDataUtil - Class in com.google.android.exoplayer.util
-
-
Provides static utility methods for manipulating various types of codec specific data.
-
-
com.google.android.exoplayer - package com.google.android.exoplayer
-
 
-
com.google.android.exoplayer.audio - package com.google.android.exoplayer.audio
-
 
-
com.google.android.exoplayer.chunk - package com.google.android.exoplayer.chunk
-
 
-
com.google.android.exoplayer.dash - package com.google.android.exoplayer.dash
-
 
-
com.google.android.exoplayer.dash.mpd - package com.google.android.exoplayer.dash.mpd
-
 
-
com.google.android.exoplayer.drm - package com.google.android.exoplayer.drm
-
 
-
com.google.android.exoplayer.extractor - package com.google.android.exoplayer.extractor
-
 
-
com.google.android.exoplayer.extractor.flv - package com.google.android.exoplayer.extractor.flv
-
 
-
com.google.android.exoplayer.extractor.mp3 - package com.google.android.exoplayer.extractor.mp3
-
 
-
com.google.android.exoplayer.extractor.mp4 - package com.google.android.exoplayer.extractor.mp4
-
 
-
com.google.android.exoplayer.extractor.ogg - package com.google.android.exoplayer.extractor.ogg
-
 
-
com.google.android.exoplayer.extractor.ts - package com.google.android.exoplayer.extractor.ts
-
 
-
com.google.android.exoplayer.extractor.wav - package com.google.android.exoplayer.extractor.wav
-
 
-
com.google.android.exoplayer.extractor.webm - package com.google.android.exoplayer.extractor.webm
-
 
-
com.google.android.exoplayer.hls - package com.google.android.exoplayer.hls
-
 
-
com.google.android.exoplayer.metadata - package com.google.android.exoplayer.metadata
-
 
-
com.google.android.exoplayer.metadata.id3 - package com.google.android.exoplayer.metadata.id3
-
 
-
com.google.android.exoplayer.smoothstreaming - package com.google.android.exoplayer.smoothstreaming
-
 
-
com.google.android.exoplayer.text - package com.google.android.exoplayer.text
-
 
-
com.google.android.exoplayer.text.eia608 - package com.google.android.exoplayer.text.eia608
-
 
-
com.google.android.exoplayer.text.subrip - package com.google.android.exoplayer.text.subrip
-
 
-
com.google.android.exoplayer.text.ttml - package com.google.android.exoplayer.text.ttml
-
 
-
com.google.android.exoplayer.text.tx3g - package com.google.android.exoplayer.text.tx3g
-
 
-
com.google.android.exoplayer.text.webvtt - package com.google.android.exoplayer.text.webvtt
-
 
-
com.google.android.exoplayer.upstream - package com.google.android.exoplayer.upstream
-
 
-
com.google.android.exoplayer.upstream.cache - package com.google.android.exoplayer.upstream.cache
-
 
-
com.google.android.exoplayer.util - package com.google.android.exoplayer.util
-
 
-
com.google.android.exoplayer.util.extensions - package com.google.android.exoplayer.util.extensions
-
 
-
commitFile(File) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Commits a file into the cache.
-
-
commitFile(File) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
compare(Format, Format) - Method in class com.google.android.exoplayer.chunk.Format.DecreasingBandwidthComparator
-
 
-
compare(ContentProtection, ContentProtection) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser.ContentProtectionsBuilder
-
 
-
compare(CacheSpan, CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.LeastRecentlyUsedCacheEvictor
-
 
-
compareTo(Long) - Method in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
compareTo(CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
 
-
compile(String) - Static method in class com.google.android.exoplayer.dash.mpd.UrlTemplate
-
-
Compile an instance from the provided template string.
-
-
computeDefaultVariantIndex(HlsMasterPlaylist, Variant[], BandwidthMeter) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
 
-
configure(String, int, int, int) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Configures (or reconfigures) the audio track, inferring a suitable buffer size automatically.
-
-
configure(String, int, int, int, int) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Configures (or reconfigures) the audio track.
-
-
configureCodec(MediaCodec, boolean, MediaFormat, MediaCrypto) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
configureCodec(MediaCodec, boolean, MediaFormat, MediaCrypto) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Configures a newly created MediaCodec.
-
-
configureCodec(MediaCodec, boolean, MediaFormat, MediaCrypto) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
configureSpliceTo(DefaultTrackOutput) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Attempts to configure a splice from this queue to the next.
-
-
configureSpliceTo(HlsExtractorWrapper) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Attempts to configure a splice from this extractor to the next.
-
-
consume(byte[], int) - Method in class com.google.android.exoplayer.chunk.DataChunk
-
-
Invoked by DataChunk.load().
-
-
ContainerMediaChunk - Class in com.google.android.exoplayer.chunk
-
-
A BaseMediaChunk that uses an Extractor to parse sample data.
-
-
ContainerMediaChunk(DataSource, DataSpec, int, Format, long, long, int, long, ChunkExtractorWrapper, MediaFormat, int, int, DrmInitData, boolean, int) - Constructor for class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
contains(Object[], Object) - Static method in class com.google.android.exoplayer.util.Util
-
-
Tests whether an items array contains an object equal to item, according to - Object.equals(Object).
-
-
ContentDataSource - Class in com.google.android.exoplayer.upstream
-
-
A content URI UriDataSource.
-
-
ContentDataSource(Context) - Constructor for class com.google.android.exoplayer.upstream.ContentDataSource
-
-
Constructs a new DataSource that retrieves data from a content provider.
-
-
ContentDataSource(Context, TransferListener) - Constructor for class com.google.android.exoplayer.upstream.ContentDataSource
-
-
Constructs a new DataSource that retrieves data from a content provider.
-
-
ContentDataSource.ContentDataSourceException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when an IOException is encountered reading from a content URI.
-
-
ContentDataSourceException(IOException) - Constructor for exception com.google.android.exoplayer.upstream.ContentDataSource.ContentDataSourceException
-
 
-
contentId - Variable in class com.google.android.exoplayer.dash.mpd.Representation
-
-
Identifies the piece of content to which this Representation belongs.
-
-
contentLength - Variable in class com.google.android.exoplayer.dash.mpd.Representation.SingleSegmentRepresentation
-
-
The content length, or -1 if unknown.
-
-
ContentProtection - Class in com.google.android.exoplayer.dash.mpd
-
-
Represents a ContentProtection tag in an AdaptationSet.
-
-
ContentProtection(String, UUID, DrmInitData.SchemeInitData) - Constructor for class com.google.android.exoplayer.dash.mpd.ContentProtection
-
 
-
contentProtections - Variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
ContentProtectionsBuilder() - Constructor for class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser.ContentProtectionsBuilder
-
 
-
contentType - Variable in exception com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException
-
 
-
continueBuffering(int, long) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
continueBuffering(long) - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Indicates to the source that it should still be checking for updates to the stream.
-
-
continueBuffering(long) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
continueBuffering(int, long) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
continueBuffering(int, long) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
continueBuffering(int, long) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
continueBuffering(int, long) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Indicates to the source that it should still be buffering data for the specified track.
-
-
continueBuffering(int, long) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
continueBuffering(long) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
copyAsAdaptive(String) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
copyWithDurationUs(long) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
copyWithFixedTrackInfo(String, int, int, int, String) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
copyWithGaplessInfo(int, int) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
copyWithLanguage(String) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
copyWithMaxInputSize(int) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
copyWithMaxVideoDimensions(int, int) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
copyWithSubsampleOffsetUs(long) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
crc(byte[], int, int, int) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the result of updating a CRC with the specified bytes in a "most significant bit first" - order.
-
-
createAudioFormat(String, String, int, int, long, int, int, List<byte[]>, String) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createAudioFormat(String, String, int, int, long, int, int, List<byte[]>, String, int) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createFormatForMimeType(String, String, int, long) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createFromCaptionStyle(CaptioningManager.CaptionStyle) - Static method in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Creates a CaptionStyleCompat equivalent to a provided CaptioningManager.CaptionStyle.
-
-
createFromComment(String, String) - Static method in class com.google.android.exoplayer.extractor.GaplessInfo
-
-
Parses a gapless playback comment (stored in an ID3 header or MPEG 4 user data).
-
-
createFromXingHeaderValue(int) - Static method in class com.google.android.exoplayer.extractor.GaplessInfo
-
-
Parses gapless playback information associated with an MP3 Xing header.
-
-
createId3Format() - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createImageFormat(String, String, int, long, List<byte[]>, String) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createInputBuffer() - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
-
Creates a new input buffer.
-
-
createMediaCrypto(UUID, byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
createMediaCrypto(UUID, byte[]) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
createOutputBuffer() - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
-
Creates a new output buffer.
-
-
createSeekMap(long, long) - Method in class com.google.android.exoplayer.util.FlacSeekTable
-
-
Creates a SeekMap wrapper for this FlacSeekTable.
-
-
createTextFormat(String, String, int, long, String) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createTextFormat(String, String, int, long, String, long) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createVideoFormat(String, String, int, int, long, int, int, List<byte[]>) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createVideoFormat(String, String, int, int, long, int, int, List<byte[]>, int, float) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
createVideoFormat(String, String, int, int, long, int, int, List<byte[]>, int, float, byte[], int) - Static method in class com.google.android.exoplayer.MediaFormat
-
 
-
CREATOR - Static variable in class com.google.android.exoplayer.MediaFormat
-
 
-
CRYPTO_MODE_AES_CTR - Static variable in class com.google.android.exoplayer.C
-
 
-
CryptoInfo - Class in com.google.android.exoplayer
-
-
Compatibility wrapper around MediaCodec.CryptoInfo.
-
-
CryptoInfo() - Constructor for class com.google.android.exoplayer.CryptoInfo
-
 
-
cryptoInfo - Variable in class com.google.android.exoplayer.SampleHolder
-
 
-
csd - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
-
 
-
Cue - Class in com.google.android.exoplayer.text
-
-
Contains information about a specific cue, including textual content and formatting data.
-
-
Cue() - Constructor for class com.google.android.exoplayer.text.Cue
-
 
-
Cue(CharSequence) - Constructor for class com.google.android.exoplayer.text.Cue
-
 
-
Cue(CharSequence, Layout.Alignment, float, int, int, float, int, float) - Constructor for class com.google.android.exoplayer.text.Cue
-
 
-
CUE_HEADER_PATTERN - Static variable in class com.google.android.exoplayer.text.webvtt.WebvttCueParser
-
 
-
CURRENT_POSITION_NOT_SET - Static variable in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returned by AudioTrack.getCurrentPositionUs(boolean) when the position is not set.
-
-
- - - -

D

-
-
DashChunkSource - Class in com.google.android.exoplayer.dash
-
-
An ChunkSource for DASH streams.
-
-
DashChunkSource(DashTrackSelector, DataSource, FormatEvaluator, long, int, Representation...) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource
-
-
Lightweight constructor to use for fixed duration content.
-
-
DashChunkSource(DashTrackSelector, DataSource, FormatEvaluator, long, int, List<Representation>) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource
-
-
Lightweight constructor to use for fixed duration content.
-
-
DashChunkSource(MediaPresentationDescription, DashTrackSelector, DataSource, FormatEvaluator) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource
-
-
Constructor to use for fixed duration content.
-
-
DashChunkSource(ManifestFetcher<MediaPresentationDescription>, DashTrackSelector, DataSource, FormatEvaluator, long, long, Handler, DashChunkSource.EventListener, int) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource
-
-
Constructor to use for live streaming.
-
-
DashChunkSource(ManifestFetcher<MediaPresentationDescription>, DashTrackSelector, DataSource, FormatEvaluator, long, long, boolean, Handler, DashChunkSource.EventListener, int) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource
-
-
Constructor to use for live DVR streaming.
-
-
DashChunkSource.EventListener - Interface in com.google.android.exoplayer.dash
-
-
Interface definition for a callback to be notified of DashChunkSource events.
-
-
DashChunkSource.ExposedTrack - Class in com.google.android.exoplayer.dash
-
 
-
DashChunkSource.NoAdaptationSetException - Exception in com.google.android.exoplayer.dash
-
-
Thrown when an AdaptationSet is missing from the MPD.
-
-
DashChunkSource.PeriodHolder - Class in com.google.android.exoplayer.dash
-
 
-
DashChunkSource.RepresentationHolder - Class in com.google.android.exoplayer.dash
-
 
-
DashSegmentIndex - Interface in com.google.android.exoplayer.dash
-
-
Indexes the segments within a media stream.
-
-
DashTrackSelector - Interface in com.google.android.exoplayer.dash
-
-
Specifies a track selection from a Period of a media presentation description.
-
-
DashTrackSelector.Output - Interface in com.google.android.exoplayer.dash
-
-
Defines a selector output.
-
-
data - Variable in class com.google.android.exoplayer.dash.mpd.ContentProtection
-
-
Protection scheme specific initialization data.
-
-
data - Variable in class com.google.android.exoplayer.drm.DrmInitData.SchemeInitData
-
-
The initialization data.
-
-
data - Variable in class com.google.android.exoplayer.metadata.id3.BinaryFrame
-
 
-
data - Variable in class com.google.android.exoplayer.metadata.id3.GeobFrame
-
 
-
data - Variable in class com.google.android.exoplayer.SampleHolder
-
-
A buffer holding the sample data.
-
-
data - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement
-
 
-
data - Variable in class com.google.android.exoplayer.upstream.Allocation
-
-
The array containing the allocated space.
-
-
data - Variable in class com.google.android.exoplayer.util.ParsableBitArray
-
 
-
data - Variable in class com.google.android.exoplayer.util.ParsableByteArray
-
 
-
DataChunk - Class in com.google.android.exoplayer.chunk
-
-
A base class for Chunk implementations where the data should be loaded into a - byte[] before being consumed.
-
-
DataChunk(DataSource, DataSpec, int, int, Format, int, byte[]) - Constructor for class com.google.android.exoplayer.chunk.DataChunk
-
 
-
DataSink - Interface in com.google.android.exoplayer.upstream
-
-
A component that consumes media data.
-
-
dataSource - Variable in class com.google.android.exoplayer.chunk.Chunk
-
 
-
DataSource - Interface in com.google.android.exoplayer.upstream
-
-
A component that provides media data.
-
-
DataSourceInputStream - Class in com.google.android.exoplayer.upstream
-
-
Allows data corresponding to a given DataSpec to be read from a DataSource and - consumed as an InputStream.
-
-
DataSourceInputStream(DataSource, DataSpec) - Constructor for class com.google.android.exoplayer.upstream.DataSourceInputStream
-
 
-
dataSpec - Variable in class com.google.android.exoplayer.chunk.Chunk
-
-
The DataSpec that defines the data to be loaded.
-
-
DataSpec - Class in com.google.android.exoplayer.upstream
-
-
Defines a region of media data.
-
-
DataSpec(Uri) - Constructor for class com.google.android.exoplayer.upstream.DataSpec
-
-
Construct a DataSpec for the given uri and with DataSpec.key set to null.
-
-
DataSpec(Uri, int) - Constructor for class com.google.android.exoplayer.upstream.DataSpec
-
-
Construct a DataSpec for the given uri and with DataSpec.key set to null.
-
-
DataSpec(Uri, long, long, String) - Constructor for class com.google.android.exoplayer.upstream.DataSpec
-
- -
-
DataSpec(Uri, long, long, String, int) - Constructor for class com.google.android.exoplayer.upstream.DataSpec
-
- -
-
DataSpec(Uri, long, long, long, String, int) - Constructor for class com.google.android.exoplayer.upstream.DataSpec
-
-
Construct a DataSpec where DataSpec.position may differ from - DataSpec.absoluteStreamPosition.
-
-
DataSpec(Uri, byte[], long, long, long, String, int) - Constructor for class com.google.android.exoplayer.upstream.DataSpec
-
-
Construct a DataSpec where DataSpec.position may differ from - DataSpec.absoluteStreamPosition.
-
-
dataSpec - Variable in exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
-
The DataSpec associated with the current connection.
-
-
DEAFULT_SOCKET_TIMEOUT_MILLIS - Static variable in class com.google.android.exoplayer.upstream.UdpDataSource
-
-
The default socket timeout, in milliseconds.
-
-
DebugTextViewHelper - Class in com.google.android.exoplayer.util
-
-
A helper class for periodically updating debug information displayed by a TextView.
-
-
DebugTextViewHelper(DebugTextViewHelper.Provider, TextView) - Constructor for class com.google.android.exoplayer.util.DebugTextViewHelper
-
 
-
DebugTextViewHelper.Provider - Interface in com.google.android.exoplayer.util
-
-
Provides debug information about an ongoing playback.
-
-
decode(I, O, boolean) - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
-
Decodes the inputBuffer and stores any decoded output in outputBuffer.
-
-
Decoder<I,O,E extends Exception> - Interface in com.google.android.exoplayer.util.extensions
-
-
A media decoder.
-
-
DecoderInfo - Class in com.google.android.exoplayer
-
-
Contains information about a media decoder.
-
-
DecoderInitializationException(MediaFormat, Throwable, boolean, int) - Constructor for exception com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
-
 
-
DecoderInitializationException(MediaFormat, Throwable, boolean, String) - Constructor for exception com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
-
 
-
decoderName - Variable in exception com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
-
-
The name of the decoder that failed to initialize.
-
-
DecreasingBandwidthComparator() - Constructor for class com.google.android.exoplayer.chunk.Format.DecreasingBandwidthComparator
-
 
-
DEFAULT - Static variable in interface com.google.android.exoplayer.MediaCodecSelector
-
-
Default implementation of MediaCodecSelector.
-
-
DEFAULT - Static variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Default caption style.
-
-
DEFAULT_AUDIO_CAPABILITIES - Static variable in class com.google.android.exoplayer.audio.AudioCapabilities
-
-
The minimum audio capabilities supported by all devices.
-
-
DEFAULT_BANDWIDTH_FRACTION - Static variable in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
DEFAULT_BOTTOM_PADDING_FRACTION - Static variable in class com.google.android.exoplayer.text.SubtitleLayout
-
-
The default bottom padding to apply when Cue.line is Cue.DIMEN_UNSET, as a - fraction of the viewport height.
-
-
DEFAULT_CONNECT_TIMEOUT_MILLIS - Static variable in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
-
The default connection timeout, in milliseconds.
-
-
DEFAULT_HIGH_BUFFER_LOAD - Static variable in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
DEFAULT_HIGH_WATERMARK_MS - Static variable in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
DEFAULT_LOW_BUFFER_LOAD - Static variable in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
DEFAULT_LOW_WATERMARK_MS - Static variable in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS - Static variable in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
The default maximum duration of media that needs to be buffered for a switch to a lower - quality variant to be considered.
-
-
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS - Static variable in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
DEFAULT_MAX_INITIAL_BITRATE - Static variable in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
DEFAULT_MAX_PACKET_SIZE - Static variable in class com.google.android.exoplayer.upstream.UdpDataSource
-
-
The default maximum datagram packet size, in bytes.
-
-
DEFAULT_MAX_WEIGHT - Static variable in class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
DEFAULT_MIN_BUFFER_MS - Static variable in class com.google.android.exoplayer.ExoPlayer.Factory
-
-
The default minimum duration of data that must be buffered for playback to start or resume - following a user action such as a seek.
-
-
DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS - Static variable in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
The default minimum duration of media that needs to be buffered for a switch to a higher - quality variant to be considered.
-
-
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS - Static variable in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS - Static variable in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
DEFAULT_MIN_LOADABLE_RETRY_COUNT - Static variable in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
-
The default minimum number of times to retry loading data prior to failing.
-
-
DEFAULT_MIN_LOADABLE_RETRY_COUNT - Static variable in class com.google.android.exoplayer.hls.HlsSampleSource
-
-
The default minimum number of times to retry loading data prior to failing.
-
-
DEFAULT_MIN_LOADABLE_RETRY_COUNT - Static variable in class com.google.android.exoplayer.SingleSampleSource
-
-
The default minimum number of times to retry loading data prior to failing.
-
-
DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE - Static variable in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
-
The default minimum number of times to retry loading prior to failing for live streams.
-
-
DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND - Static variable in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
-
The default minimum number of times to retry loading prior to failing for on-demand streams.
-
-
DEFAULT_MIN_REBUFFER_MS - Static variable in class com.google.android.exoplayer.ExoPlayer.Factory
-
-
The default minimum duration of data that must be buffered for playback to resume - after a player invoked rebuffer (i.e.
-
-
DEFAULT_PLAYLIST_BLACKLIST_MS - Static variable in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
The default time for which a media playlist should be blacklisted.
-
-
DEFAULT_READ_TIMEOUT_MILLIS - Static variable in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
-
The default read timeout, in milliseconds.
-
-
DEFAULT_TEXT_SIZE_FRACTION - Static variable in class com.google.android.exoplayer.text.SubtitleLayout
-
-
The default fractional text size.
-
-
DefaultAllocator - Class in com.google.android.exoplayer.upstream
-
-
Default implementation of Allocator.
-
-
DefaultAllocator(int) - Constructor for class com.google.android.exoplayer.upstream.DefaultAllocator
-
-
Constructs an initially empty pool.
-
-
DefaultAllocator(int, int) - Constructor for class com.google.android.exoplayer.upstream.DefaultAllocator
-
-
Constructs a pool with some Allocations created up front.
-
-
DefaultBandwidthMeter - Class in com.google.android.exoplayer.upstream
-
-
Counts transferred bytes while transfers are open and creates a bandwidth sample and updated - bandwidth estimate each time a transfer ends.
-
-
DefaultBandwidthMeter() - Constructor for class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
DefaultBandwidthMeter(Handler, BandwidthMeter.EventListener) - Constructor for class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
DefaultBandwidthMeter(Handler, BandwidthMeter.EventListener, Clock) - Constructor for class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
DefaultBandwidthMeter(Handler, BandwidthMeter.EventListener, int) - Constructor for class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
DefaultBandwidthMeter(Handler, BandwidthMeter.EventListener, Clock, int) - Constructor for class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
DefaultDashTrackSelector - Class in com.google.android.exoplayer.dash
-
-
A default DashTrackSelector implementation.
-
-
DefaultExtractorInput - Class in com.google.android.exoplayer.extractor
-
-
An ExtractorInput that wraps a DataSource.
-
-
DefaultExtractorInput(DataSource, long, long) - Constructor for class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
DefaultHlsTrackSelector - Class in com.google.android.exoplayer.hls
-
-
A default HlsTrackSelector implementation.
-
-
DefaultHttpDataSource - Class in com.google.android.exoplayer.upstream
-
-
A HttpDataSource that uses Android's HttpURLConnection.
-
-
DefaultHttpDataSource(String, Predicate<String>) - Constructor for class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
DefaultHttpDataSource(String, Predicate<String>, TransferListener) - Constructor for class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
DefaultHttpDataSource(String, Predicate<String>, TransferListener, int, int) - Constructor for class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
DefaultHttpDataSource(String, Predicate<String>, TransferListener, int, int, boolean) - Constructor for class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
DefaultLoadControl - Class in com.google.android.exoplayer
-
-
A LoadControl implementation that allows loads to continue in a sequence that prevents - any loader from getting too far ahead or behind any of the other loaders.
-
-
DefaultLoadControl(Allocator) - Constructor for class com.google.android.exoplayer.DefaultLoadControl
-
-
Constructs a new instance, using the DEFAULT_* constants defined in this class.
-
-
DefaultLoadControl(Allocator, Handler, DefaultLoadControl.EventListener) - Constructor for class com.google.android.exoplayer.DefaultLoadControl
-
-
Constructs a new instance, using the DEFAULT_* constants defined in this class.
-
-
DefaultLoadControl(Allocator, Handler, DefaultLoadControl.EventListener, int, int, float, float) - Constructor for class com.google.android.exoplayer.DefaultLoadControl
-
-
Constructs a new instance.
-
-
DefaultLoadControl.EventListener - Interface in com.google.android.exoplayer
-
-
Interface definition for a callback to be notified of DefaultLoadControl events.
-
-
DefaultSmoothStreamingTrackSelector - Class in com.google.android.exoplayer.smoothstreaming
-
-
A default SmoothStreamingTrackSelector implementation.
-
-
DefaultTrackOutput - Class in com.google.android.exoplayer.extractor
-
-
A TrackOutput that buffers extracted samples in a queue, and allows for consumption from - that queue.
-
-
DefaultTrackOutput(Allocator) - Constructor for class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
 
-
DefaultUriDataSource - Class in com.google.android.exoplayer.upstream
-
-
A UriDataSource that supports multiple URI schemes.
-
-
DefaultUriDataSource(Context, String) - Constructor for class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
-
Constructs a new instance.
-
-
DefaultUriDataSource(Context, TransferListener, String) - Constructor for class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
-
Constructs a new instance.
-
-
DefaultUriDataSource(Context, TransferListener, String, boolean) - Constructor for class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
-
Constructs a new instance, optionally configured to follow cross-protocol redirects.
-
-
DefaultUriDataSource(Context, TransferListener, UriDataSource) - Constructor for class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
-
Constructs a new instance, using a provided HttpDataSource for fetching remote data.
-
-
delete() - Method in class com.google.android.exoplayer.util.AtomicFile
-
-
Delete the atomic file.
-
-
deltaPicOrderAlwaysZeroFlag - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
dequeueInputBuffer() - Method in interface com.google.android.exoplayer.util.extensions.Decoder
-
-
Dequeues the next input buffer to be filled and queued to the decoder.
-
-
dequeueInputBuffer() - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
 
-
dequeueOutputBuffer() - Method in interface com.google.android.exoplayer.util.extensions.Decoder
-
-
Dequeues the next output buffer from the decoder.
-
-
dequeueOutputBuffer() - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
 
-
describeContents() - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
description - Variable in class com.google.android.exoplayer.metadata.id3.ApicFrame
-
 
-
description - Variable in class com.google.android.exoplayer.metadata.id3.GeobFrame
-
 
-
description - Variable in class com.google.android.exoplayer.metadata.id3.TextInformationFrame
-
 
-
description - Variable in class com.google.android.exoplayer.metadata.id3.TxxxFrame
-
 
-
DEVICE - Static variable in class com.google.android.exoplayer.util.Util
-
-
Like Build.DEVICE, but in a place where it can be conveniently overridden for local - testing.
-
-
diagnosticInfo - Variable in exception com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
-
-
An optional developer-readable diagnostic information string.
-
-
DIMEN_UNSET - Static variable in class com.google.android.exoplayer.text.Cue
-
-
An unset position or width.
-
-
disable(int) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
disable(List<? extends MediaChunk>) - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Disables the source.
-
-
disable() - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
disable() - Method in interface com.google.android.exoplayer.chunk.FormatEvaluator
-
-
Disables the evaluator.
-
-
disable() - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.FixedEvaluator
-
 
-
disable() - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.RandomEvaluator
-
 
-
disable(List<? extends MediaChunk>) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
disable(int) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
disable(int) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
disable(int) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
disable(int) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Disable the specified track.
-
-
disable(int) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
disable(List<? extends MediaChunk>) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
disable() - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Disables refresh functionality.
-
-
disable() - Method in class com.google.android.exoplayer.VideoFrameReleaseTimeHelper
-
-
Disables the helper.
-
-
discardToSps(ByteBuffer) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Discards data from the buffer up to the first SPS, where data.position() is interpreted - as the length of the buffer.
-
-
discardUntil(long) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Discards samples from the queue up to the specified time.
-
-
discardUntil(int, long) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Discards samples for the specified track up to the specified time.
-
-
discardUpstreamSamples(int) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Discards samples from the write side of the queue.
-
-
discontinuitySequenceNumber - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
discontinuitySequenceNumber - Variable in class com.google.android.exoplayer.hls.TsChunk
-
-
The discontinuity sequence number of the chunk.
-
-
dispatchDraw(Canvas) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
 
-
displayHeight - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
displayWidth - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
DO_NOT_OFFSET - Static variable in class com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster
-
-
A special firstSampleTimestampUs value indicating that presentation timestamps should - not be offset.
-
-
doPrepare(long) - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
doPrepare(long) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
doPrepare(long) - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Invoked to make progress when the renderer is in the TrackRenderer.STATE_UNPREPARED state.
-
-
doSomeWork(long, long) - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
doSomeWork(long, long, boolean) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
doSomeWork(long, long, boolean) - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
doSomeWork(long, long) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
doSomeWork(long, long, boolean) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
- -
-
doSomeWork(long, long, boolean) - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
doSomeWork(long, long, boolean) - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
doSomeWork(long, long) - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Invoked to make progress when the renderer is in the TrackRenderer.STATE_ENABLED or - TrackRenderer.STATE_STARTED states.
-
-
DOWNLOAD_PRIORITY - Static variable in class com.google.android.exoplayer.upstream.NetworkLock
-
-
Priority for network tasks associated with background downloads.
-
-
drmInitData(DrmInitData) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
drmInitData(DrmInitData) - Method in interface com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput
-
 
-
drmInitData(DrmInitData) - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
drmInitData(DrmInitData) - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
DrmInitData - Interface in com.google.android.exoplayer.drm
-
-
Encapsulates initialization data required by a MediaDrm instances.
-
-
drmInitData(DrmInitData) - Method in interface com.google.android.exoplayer.extractor.ExtractorOutput
-
-
Invoked when DrmInitData has been extracted from the stream.
-
-
drmInitData(DrmInitData) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
drmInitData(DrmInitData) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
drmInitData - Variable in class com.google.android.exoplayer.MediaFormatHolder
-
-
Initialization data for drm schemes supported by the media.
-
-
DrmInitData.Mapped - Class in com.google.android.exoplayer.drm
-
-
A DrmInitData implementation that maps UUID onto scheme specific data.
-
-
DrmInitData.SchemeInitData - Class in com.google.android.exoplayer.drm
-
-
Scheme initialization data.
-
-
DrmInitData.Universal - Class in com.google.android.exoplayer.drm
-
-
A DrmInitData implementation that returns the same initialization data for all schemes.
-
-
DrmSessionManager<T extends ExoMediaCrypto> - Interface in com.google.android.exoplayer.drm
-
-
Manages a DRM session.
-
-
dropOutputBuffer(MediaCodec, int) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
droppedOutputBufferCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
DtsUtil - Class in com.google.android.exoplayer.util
-
-
Utility methods for parsing DTS frames.
-
-
DummyTrackOutput - Class in com.google.android.exoplayer.extractor
-
-
A dummy TrackOutput implementation.
-
-
DummyTrackOutput() - Constructor for class com.google.android.exoplayer.extractor.DummyTrackOutput
-
 
-
DummyTrackRenderer - Class in com.google.android.exoplayer
-
-
A TrackRenderer that does nothing.
-
-
DummyTrackRenderer() - Constructor for class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
duration - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
durationSecs - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
durationsUs - Variable in class com.google.android.exoplayer.extractor.ChunkIndex
-
-
The chunk durations, in microseconds.
-
-
durationUs - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
The duration of the track in microseconds, or C.UNKNOWN_TIME_US if unknown.
-
-
durationUs - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
durationUs - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The duration in microseconds, or C.UNKNOWN_TIME_US if the duration is unknown, or - C.MATCH_LONGEST_US if the duration should match the duration of the longest track whose - duration is known.
-
-
durationUs - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
The overall presentation duration of the media in microseconds, or C.UNKNOWN_TIME_US - if the duration is unknown.
-
-
durationUs() - Method in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
dvrWindowLengthUs - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
The length of the trailing window for a live broadcast in microseconds, or - C.UNKNOWN_TIME_US if the stream is not live or if the window length is unspecified.
-
-
dynamic - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
DynamicTimeRange(long, long, long, long, Clock) - Constructor for class com.google.android.exoplayer.TimeRange.DynamicTimeRange
-
 
-
- - - -

E

-
-
EDGE_TYPE_DEPRESSED - Static variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Edge type value specifying depressed bevel character edges.
-
-
EDGE_TYPE_DROP_SHADOW - Static variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Edge type value specifying drop-shadowed character edges.
-
-
EDGE_TYPE_NONE - Static variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Edge type value specifying no character edges.
-
-
EDGE_TYPE_OUTLINE - Static variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Edge type value specifying uniformly outlined character edges.
-
-
EDGE_TYPE_RAISED - Static variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Edge type value specifying raised bevel character edges.
-
-
edgeColor - Variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
The preferred edge color, if using an edge type other than CaptionStyleCompat.EDGE_TYPE_NONE.
-
-
edgeType - Variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
The preferred edge type.
-
-
editListDurations - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
Durations of edit list segments in the movie timescale.
-
-
editListMediaTimes - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
Media times for edit list segments in the track timescale.
-
-
Eia608Parser - Class in com.google.android.exoplayer.text.eia608
-
-
Facilitates the extraction and parsing of EIA-608 (a.k.a.
-
-
Eia608TrackRenderer - Class in com.google.android.exoplayer.text.eia608
-
-
A TrackRenderer for EIA-608 closed captions in a media stream.
-
-
Eia608TrackRenderer(SampleSource, TextRenderer, Looper) - Constructor for class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
elapsedRealtime() - Method in interface com.google.android.exoplayer.util.Clock
-
- -
-
elapsedRealtime() - Method in class com.google.android.exoplayer.util.SystemClock
-
 
-
enable(int, long) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
enable(int) - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Enable the source for the specified track.
-
-
enable() - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
enable() - Method in interface com.google.android.exoplayer.chunk.FormatEvaluator
-
-
Enables the evaluator.
-
-
enable() - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.FixedEvaluator
-
 
-
enable() - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.RandomEvaluator
-
 
-
enable(int) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
enable(int, long) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
enable(int, long) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
enable(int, long) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
enable(int, long) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Enable the specified track.
-
-
enable(int, long) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
enable(int) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
enable() - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Enables refresh functionality.
-
-
enable() - Method in class com.google.android.exoplayer.VideoFrameReleaseTimeHelper
-
-
Enables the helper.
-
-
enablePreV21AudioSessionWorkaround - Static variable in class com.google.android.exoplayer.audio.AudioTrack
-
-
Whether to enable a workaround for an issue where an audio effect does not keep its session - active across releasing/initializing a new audio track, on platform API version < 21.
-
-
encoderDelay - Variable in class com.google.android.exoplayer.extractor.GaplessInfo
-
-
The number of samples to trim from the start of the decoded audio stream.
-
-
encoderDelay - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The number of samples to trim from the start of the decoded audio stream.
-
-
encoderPadding - Variable in class com.google.android.exoplayer.extractor.GaplessInfo
-
-
The number of samples to trim from the end of the decoded audio stream.
-
-
encoderPadding - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The number of samples to trim from the end of the decoded audio stream.
-
-
ENCODING_AC3 - Static variable in class com.google.android.exoplayer.C
-
 
-
ENCODING_DTS - Static variable in class com.google.android.exoplayer.C
-
 
-
ENCODING_DTS_HD - Static variable in class com.google.android.exoplayer.C
-
 
-
ENCODING_E_AC3 - Static variable in class com.google.android.exoplayer.C
-
 
-
ENCODING_INVALID - Static variable in class com.google.android.exoplayer.C
-
 
-
ENCODING_PCM_16BIT - Static variable in class com.google.android.exoplayer.C
-
 
-
ENCODING_PCM_24BIT - Static variable in class com.google.android.exoplayer.C
-
-
PCM encoding with 24 bits per sample.
-
-
ENCODING_PCM_32BIT - Static variable in class com.google.android.exoplayer.C
-
-
PCM encoding with 32 bits per sample.
-
-
ENCODING_PCM_8BIT - Static variable in class com.google.android.exoplayer.C
-
 
-
ENCRYPTION_METHOD_AES_128 - Static variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
ENCRYPTION_METHOD_NONE - Static variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
encryptionIV - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
encryptionKeyUri - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
END_OF_STREAM - Static variable in interface com.google.android.exoplayer.SampleSource
-
-
The end of stream has been reached.
-
-
END_OF_TRACK_US - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
Represents the time of the end of the track.
-
-
endOfStream - Variable in class com.google.android.exoplayer.chunk.ChunkOperationHolder
-
-
Indicates that the end of the stream has been reached.
-
-
endRepresentation() - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser.ContentProtectionsBuilder
-
-
Should be invoked after processing each child Representation element, in order to apply - consistency checks.
-
-
endSection() - Static method in class com.google.android.exoplayer.util.TraceUtil
-
-
Writes a trace message to indicate that a given section of code has ended.
-
-
endTimeUs - Variable in class com.google.android.exoplayer.chunk.MediaChunk
-
-
The end time of the media contained by the chunk.
-
-
endTracks() - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
endTracks() - Method in interface com.google.android.exoplayer.extractor.ExtractorOutput
-
-
Invoked when all tracks have been identified, meaning that ExtractorOutput.track(int) will not be - invoked again.
-
-
endTracks() - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
endTracks() - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
endWrite(OutputStream) - Method in class com.google.android.exoplayer.util.AtomicFile
-
-
Call when you have successfully finished writing to the stream returned by AtomicFile.startWrite().
-
-
ensureSpaceForWrite(int) - Method in class com.google.android.exoplayer.SampleHolder
-
-
Ensures that SampleHolder.data is large enough to accommodate a write of a given length at its - current position.
-
-
ensureUpdated() - Method in class com.google.android.exoplayer.CodecCounters
-
-
Should be invoked from the playback thread after the counters have been updated.
-
-
equals(Object) - Method in class com.google.android.exoplayer.audio.AudioCapabilities
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.chunk.Format
-
-
Implements equality based on Format.id only.
-
-
equals(Object) - Method in class com.google.android.exoplayer.dash.mpd.ContentProtection
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.dash.mpd.RangedUri
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.drm.DrmInitData.Mapped
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.drm.DrmInitData.SchemeInitData
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.drm.DrmInitData.Universal
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.TimeRange.DynamicTimeRange
-
 
-
equals(Object) - Method in class com.google.android.exoplayer.TimeRange.StaticTimeRange
-
 
-
errorCode - Variable in exception com.google.android.exoplayer.audio.AudioTrack.WriteException
-
-
The value returned from AudioTrack.write(byte[], int, int).
-
-
escapeFileName(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Escapes a string so that it's safe for use as a file or directory name on at least FAT32 - filesystems.
-
-
evaluate(List<? extends MediaChunk>, long, Format[], FormatEvaluator.Evaluation) - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
-
 
-
evaluate(List<? extends MediaChunk>, long, Format[], FormatEvaluator.Evaluation) - Method in interface com.google.android.exoplayer.chunk.FormatEvaluator
-
-
Update the supplied evaluation.
-
-
evaluate(List<? extends MediaChunk>, long, Format[], FormatEvaluator.Evaluation) - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.FixedEvaluator
-
 
-
evaluate(List<? extends MediaChunk>, long, Format[], FormatEvaluator.Evaluation) - Method in class com.google.android.exoplayer.chunk.FormatEvaluator.RandomEvaluator
-
 
-
evaluate(T) - Method in interface com.google.android.exoplayer.util.Predicate
-
-
Evaluates an input.
-
-
Evaluation() - Constructor for class com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
-
 
-
eventHandler - Variable in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
executeKeyRequest(UUID, ExoMediaDrm.KeyRequest) - Method in interface com.google.android.exoplayer.drm.MediaDrmCallback
-
-
Executes a key request.
-
-
executePost(String, byte[], Map<String, String>) - Static method in class com.google.android.exoplayer.util.Util
-
-
Executes a post request using HttpURLConnection.
-
-
executeProvisionRequest(UUID, ExoMediaDrm.ProvisionRequest) - Method in interface com.google.android.exoplayer.drm.MediaDrmCallback
-
-
Executes a provisioning request.
-
-
ExoMediaCrypto - Interface in com.google.android.exoplayer.drm
-
-
An opaque MediaCrypto equivalent.
-
-
ExoMediaDrm<T extends ExoMediaCrypto> - Interface in com.google.android.exoplayer.drm
-
-
Used to obtain keys for decrypting protected media streams.
-
-
ExoMediaDrm.KeyRequest - Interface in com.google.android.exoplayer.drm
-
 
-
ExoMediaDrm.OnEventListener<T extends ExoMediaCrypto> - Interface in com.google.android.exoplayer.drm
-
 
-
ExoMediaDrm.ProvisionRequest - Interface in com.google.android.exoplayer.drm
-
 
-
ExoPlaybackException - Exception in com.google.android.exoplayer
-
-
Thrown when a non-recoverable playback failure occurs.
-
-
ExoPlaybackException(String) - Constructor for exception com.google.android.exoplayer.ExoPlaybackException
-
 
-
ExoPlaybackException(Throwable) - Constructor for exception com.google.android.exoplayer.ExoPlaybackException
-
 
-
ExoPlaybackException(String, Throwable) - Constructor for exception com.google.android.exoplayer.ExoPlaybackException
-
 
-
ExoPlayer - Interface in com.google.android.exoplayer
-
-
An extensible media player exposing traditional high-level media player functionality, such as - the ability to prepare, play, pause and seek.
-
-
ExoPlayer.ExoPlayerComponent - Interface in com.google.android.exoplayer
-
-
A component of an ExoPlayer that can receive messages on the playback thread.
-
-
ExoPlayer.Factory - Class in com.google.android.exoplayer
-
-
A factory for instantiating ExoPlayer instances.
-
-
ExoPlayer.Listener - Interface in com.google.android.exoplayer
-
-
Interface definition for a callback to be notified of changes in player state.
-
-
ExoPlayerLibraryInfo - Class in com.google.android.exoplayer
-
-
Information about the ExoPlayer library.
-
-
ExposedTrack(MediaFormat, int, Format) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource.ExposedTrack
-
 
-
ExposedTrack(MediaFormat, int, Format[], int, int) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource.ExposedTrack
-
 
-
EXTENDED_SAR - Static variable in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Value for aspect_ratio_idc indicating an extended aspect ratio, in H.264 and H.265 SPSs.
-
-
Extractor - Interface in com.google.android.exoplayer.extractor
-
-
Facilitates extraction of data from a container format.
-
-
ExtractorInput - Interface in com.google.android.exoplayer.extractor
-
-
Provides data to be consumed by an Extractor.
-
-
ExtractorOutput - Interface in com.google.android.exoplayer.extractor
-
-
Receives stream level data extracted by an Extractor.
-
-
ExtractorSampleSource - Class in com.google.android.exoplayer.extractor
-
-
A SampleSource that extracts sample data using an Extractor.
-
-
ExtractorSampleSource(Uri, DataSource, Allocator, int, Extractor...) - Constructor for class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
ExtractorSampleSource(Uri, DataSource, Allocator, int, Handler, ExtractorSampleSource.EventListener, int, Extractor...) - Constructor for class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
ExtractorSampleSource(Uri, DataSource, Allocator, int, int, Extractor...) - Constructor for class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
ExtractorSampleSource(Uri, DataSource, Allocator, int, int, Handler, ExtractorSampleSource.EventListener, int, Extractor...) - Constructor for class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
ExtractorSampleSource.EventListener - Interface in com.google.android.exoplayer.extractor
-
-
Interface definition for a callback to be notified of ExtractorSampleSource events.
-
-
ExtractorSampleSource.UnrecognizedInputFormatException - Exception in com.google.android.exoplayer.extractor
-
-
Thrown if the input format could not recognized.
-
-
extractorWrapper - Variable in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
extractorWrapper - Variable in class com.google.android.exoplayer.hls.TsChunk
-
-
The wrapped extractor into which this chunk is being consumed.
-
-
extractSampleTimestamp(FlacStreamInfo, ParsableByteArray) - Static method in class com.google.android.exoplayer.util.FlacUtil
-
-
Extracts sample timestamp from the given binary FLAC frame header data structure.
-
-
- - - -

F

-
-
failOnSpuriousAudioTimestamp - Static variable in class com.google.android.exoplayer.audio.AudioTrack
-
-
Whether to throw an AudioTrack.InvalidAudioTrackTimestampException when a spurious timestamp is - reported from AudioTrack.getTimestamp(android.media.AudioTimestamp).
-
-
file - Variable in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
The file corresponding to this CacheSpan, or null if CacheSpan.isCached is false.
-
-
FileDataSource - Class in com.google.android.exoplayer.upstream
-
-
A local file UriDataSource.
-
-
FileDataSource() - Constructor for class com.google.android.exoplayer.upstream.FileDataSource
-
-
Constructs a new DataSource that retrieves data from a file.
-
-
FileDataSource(TransferListener) - Constructor for class com.google.android.exoplayer.upstream.FileDataSource
-
-
Constructs a new DataSource that retrieves data from a file.
-
-
FileDataSource.FileDataSourceException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when IOException is encountered during local file read operation.
-
-
FileDataSourceException(IOException) - Constructor for exception com.google.android.exoplayer.upstream.FileDataSource.FileDataSourceException
-
 
-
filename - Variable in class com.google.android.exoplayer.metadata.id3.GeobFrame
-
 
-
findNalUnit(byte[], int, int, boolean[]) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Finds the first NAL unit in data.
-
-
findNextCueHeader(ParsableByteArray) - Static method in class com.google.android.exoplayer.text.webvtt.WebvttCueParser
-
-
Reads lines up to and including the next WebVTT cue header.
-
-
firstIntegersArray(int) - Static method in class com.google.android.exoplayer.util.Util
-
-
Creates an integer array containing the integers from 0 to length - 1.
-
-
FixedEvaluator() - Constructor for class com.google.android.exoplayer.chunk.FormatEvaluator.FixedEvaluator
-
 
-
fixedTrack(MediaPresentationDescription, int, int, int) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
fixedTrack(MediaPresentationDescription, int, int, int) - Method in interface com.google.android.exoplayer.dash.DashTrackSelector.Output
-
-
Outputs an fixed track corresponding to the specified representation in the specified - adaptation set.
-
-
fixedTrack(HlsMasterPlaylist, Variant) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
 
-
fixedTrack(HlsMasterPlaylist, Variant) - Method in interface com.google.android.exoplayer.hls.HlsTrackSelector.Output
-
-
Outputs an fixed track corresponding to the specified representation in the specified - adaptation set.
-
-
fixedTrack(SmoothStreamingManifest, int, int) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
fixedTrack(SmoothStreamingManifest, int, int) - Method in interface com.google.android.exoplayer.smoothstreaming.SmoothStreamingTrackSelector.Output
-
-
Outputs a fixed track corresponding to the specified track in the specified element.
-
-
FlacSeekTable - Class in com.google.android.exoplayer.util
-
-
FLAC seek table class
-
-
FlacStreamInfo - Class in com.google.android.exoplayer.util
-
-
Holder for FLAC stream info.
-
-
FlacStreamInfo(byte[], int) - Constructor for class com.google.android.exoplayer.util.FlacStreamInfo
-
-
Constructs a FlacStreamInfo parsing the given binary FLAC stream info metadata structure.
-
-
FlacStreamInfo(int, int, int, int, int, int, int, long) - Constructor for class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
FlacUtil - Class in com.google.android.exoplayer.util
-
-
Utility functions for FLAC
-
-
FLAG_ALLOW_GZIP - Static variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
Permits an underlying network stack to request that the server use gzip compression.
-
-
FLAG_DECODE_ONLY - Static variable in class com.google.android.exoplayer.util.extensions.Buffer
-
-
Flag for non-empty input/output buffers that should only be decoded (not rendered).
-
-
FLAG_END_OF_STREAM - Static variable in class com.google.android.exoplayer.util.extensions.Buffer
-
-
Flag for empty input/output buffers that signal that the end of the stream was reached.
-
-
FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME - Static variable in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
-
Flag to work around an issue in some video streams where every frame is marked as a sync frame.
-
-
FLAG_WORKAROUND_IGNORE_TFDT_BOX - Static variable in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
-
Flag to ignore any tfdt boxes in the stream.
-
-
flags - Variable in class com.google.android.exoplayer.SampleHolder
-
-
Flags that accompany the sample.
-
-
flags - Variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
Request flags.
-
-
flush() - Method in interface com.google.android.exoplayer.util.extensions.Decoder
-
-
Flushes input/output buffers that have not been dequeued yet and returns ownership of any - dequeued input buffer to the decoder.
-
-
flush() - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
 
-
flushCodec() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
FlvExtractor - Class in com.google.android.exoplayer.extractor.flv
-
-
Facilitates the extraction of data from the FLV container format.
-
-
FlvExtractor() - Constructor for class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
foregroundColor - Variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
The preferred foreground color.
-
-
format - Variable in class com.google.android.exoplayer.chunk.Chunk
-
-
The format associated with the data being loaded, or null if the data being loaded is not - associated with a specific format.
-
-
format(MediaFormat) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
format(MediaFormat) - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
Format - Class in com.google.android.exoplayer.chunk
-
-
Defines the high level format of a media stream.
-
-
Format(String, String, int, int, float, int, int, int) - Constructor for class com.google.android.exoplayer.chunk.Format
-
 
-
Format(String, String, int, int, float, int, int, int, String) - Constructor for class com.google.android.exoplayer.chunk.Format
-
 
-
Format(String, String, int, int, float, int, int, int, String, String) - Constructor for class com.google.android.exoplayer.chunk.Format
-
 
-
format - Variable in class com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
-
-
The selected format.
-
-
format(MediaFormat) - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
format - Variable in class com.google.android.exoplayer.dash.mpd.Representation
-
-
The format of the representation.
-
-
format(MediaFormat) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
 
-
format(MediaFormat) - Method in class com.google.android.exoplayer.extractor.DummyTrackOutput
-
 
-
format(MediaFormat) - Method in interface com.google.android.exoplayer.extractor.TrackOutput
-
-
Invoked when the MediaFormat of the track has been extracted from the stream.
-
-
format - Variable in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
format - Variable in class com.google.android.exoplayer.hls.Variant
-
 
-
format - Variable in class com.google.android.exoplayer.MediaFormatHolder
-
-
The format of the media.
-
-
format - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
-
 
-
Format.DecreasingBandwidthComparator - Class in com.google.android.exoplayer.chunk
-
-
Sorts Format objects in order of decreasing bandwidth.
-
-
FORMAT_READ - Static variable in interface com.google.android.exoplayer.SampleSource
-
-
A format was read.
-
-
FormatEvaluator - Interface in com.google.android.exoplayer.chunk
-
-
Selects from a number of available formats during playback.
-
-
FormatEvaluator.AdaptiveEvaluator - Class in com.google.android.exoplayer.chunk
-
-
An adaptive evaluator for video formats, which attempts to select the best quality possible - given the current network conditions and state of the buffer.
-
-
FormatEvaluator.Evaluation - Class in com.google.android.exoplayer.chunk
-
-
A format evaluation.
-
-
FormatEvaluator.FixedEvaluator - Class in com.google.android.exoplayer.chunk
-
-
Always selects the first format.
-
-
FormatEvaluator.RandomEvaluator - Class in com.google.android.exoplayer.chunk
-
-
Selects randomly between the available formats.
-
-
FormatWrapper - Interface in com.google.android.exoplayer.chunk
-
-
Represents an object that wraps a Format.
-
-
FragmentedMp4Extractor - Class in com.google.android.exoplayer.extractor.mp4
-
-
Facilitates the extraction of data from the fragmented mp4 container format.
-
-
FragmentedMp4Extractor() - Constructor for class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
FragmentedMp4Extractor(int) - Constructor for class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
FragmentedMp4Extractor(int, Track) - Constructor for class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
frameMbsOnlyFlag - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
frameNumLength - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
frameRate - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The video frame rate in frames per second, or -1 if unknown or not applicable.
-
-
frameSize - Variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Size of the frame associated with this header, in bytes.
-
-
FrameworkMediaCrypto - Class in com.google.android.exoplayer.drm
-
-
An ExoMediaCrypto implementation that wraps the framework MediaCrypto.
-
-
FrameworkMediaDrm - Class in com.google.android.exoplayer.drm
-
-
An ExoMediaDrm implementation that wraps the framework MediaDrm.
-
-
FrameworkMediaDrm(UUID) - Constructor for class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
FrameworkSampleSource - Class in com.google.android.exoplayer
-
-
Deprecated.
-
-
FrameworkSampleSource(Context, Uri, Map<String, String>) - Constructor for class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
Instantiates a new sample extractor reading from the specified uri.
-
-
FrameworkSampleSource(FileDescriptor, long, long) - Constructor for class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
Instantiates a new sample extractor reading from the specified seekable fileDescriptor.
-
-
- - - -

G

-
-
GaplessInfo - Class in com.google.android.exoplayer.extractor
-
-
Utility for parsing and representing gapless playback information.
-
-
GeobFrame - Class in com.google.android.exoplayer.metadata.id3
-
-
GEOB (General Encapsulated Object) ID3 frame.
-
-
GeobFrame(String, String, String, byte[]) - Constructor for class com.google.android.exoplayer.metadata.id3.GeobFrame
-
 
-
get(UUID) - Method in interface com.google.android.exoplayer.drm.DrmInitData
-
-
Retrieves initialization data for a given DRM scheme, specified by its UUID.
-
-
get(UUID) - Method in class com.google.android.exoplayer.drm.DrmInitData.Mapped
-
 
-
get(UUID) - Method in class com.google.android.exoplayer.drm.DrmInitData.Universal
-
 
-
get(int) - Method in class com.google.android.exoplayer.util.LongArray
-
-
Gets a value.
-
-
getAc3SyncframeAudioSampleCount() - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the number of audio samples in an AC-3 syncframe.
-
-
getAdaptationSetIndex(int) - Method in class com.google.android.exoplayer.dash.mpd.Period
-
-
Returns the index of the first adaptation set of a given type, or -1 if no adaptation set of - the specified type exists.
-
-
getAdjustedEndTimeUs() - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
getAdjustedEndTimeUs() - Method in class com.google.android.exoplayer.hls.TsChunk
-
 
-
getAdjuster(boolean, int, long) - Method in class com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider
-
-
Gets a PtsTimestampAdjuster suitable for adjusting the pts timestamps contained in - a chunk with a given discontinuity sequence.
-
-
getAllocator() - Method in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
getAllocator() - Method in interface com.google.android.exoplayer.LoadControl
-
-
Gets the Allocator that loaders should use to obtain memory allocations into which - data can be loaded.
-
-
getAttributeValue(XmlPullParser, String) - Static method in class com.google.android.exoplayer.util.ParserUtil
-
 
-
getAudioMediaMimeType(String) - Static method in class com.google.android.exoplayer.util.MimeTypes
-
-
Returns the audio mimeType type of codecs.
-
-
getAudioSessionId() - Method in class com.google.android.exoplayer.util.PlayerControl
-
-
This is an unsupported operation.
-
-
getAvailableEndTimeUs() - Method in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
getAvailableStartTimeUs() - Method in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
getBandwidthMeter() - Method in interface com.google.android.exoplayer.util.DebugTextViewHelper.Provider
-
-
Returns a BandwidthMeter whose estimate should be displayed, or null.
-
-
getBitrateEstimate() - Method in interface com.google.android.exoplayer.upstream.BandwidthMeter
-
-
Gets the estimated bandwidth, in bits/sec.
-
-
getBitrateEstimate() - Method in class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
getBottomInt(long) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the bottom 32 bits of a long as an integer.
-
-
getBufferedPercentage() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Gets an estimate of the percentage into the media up to which data is buffered.
-
-
getBufferedPosition() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Gets an estimate of the absolute position in milliseconds up to which data is buffered.
-
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
getBufferedPositionUs() - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Returns an estimate of the position up to which data is buffered.
-
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
getBufferedPositionUs() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Returns an estimate of the absolute position in microseconds up to which data is buffered.
-
-
getBufferPercentage() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
getBufferSize() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returns the size of this AudioTrack's buffer in bytes.
-
-
getBufferSizeUs() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returns the size of the buffer in microseconds for PCM AudioTracks, or - C.UNKNOWN_TIME_US for passthrough AudioTracks.
-
-
getBytesFromHexString(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns a byte array containing values parsed from the hex string provided.
-
-
getCachedSpans(String) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Returns the cached spans for a given cache key.
-
-
getCachedSpans(String) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
getCacheKey() - Method in class com.google.android.exoplayer.dash.mpd.Representation
-
-
A cache key for the Representation, in the format - contentId + "." + format.id + "." + revisionId.
-
-
getCacheSpace() - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Returns the total disk space in bytes used by the cache.
-
-
getCacheSpace() - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
getCapabilities(Context) - Static method in class com.google.android.exoplayer.audio.AudioCapabilities
-
-
Gets the current audio capabilities.
-
-
getChunkDurationUs(int) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
-
Gets the duration of the specified chunk.
-
-
getChunkIndex(long) - Method in class com.google.android.exoplayer.extractor.ChunkIndex
-
-
Obtains the index of the chunk corresponding to a given time.
-
-
getChunkIndex(long) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
-
Gets the index of the chunk that contains the specified time.
-
-
getChunkOperation(List<? extends MediaChunk>, long, ChunkOperationHolder) - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Updates the provided ChunkOperationHolder to contain the next operation that should - be performed by the calling ChunkSampleSource.
-
-
getChunkOperation(List<? extends MediaChunk>, long, ChunkOperationHolder) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
getChunkOperation(TsChunk, long, ChunkOperationHolder) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Updates the provided ChunkOperationHolder to contain the next operation that should - be performed by the calling HlsSampleSource.
-
-
getChunkOperation(List<? extends MediaChunk>, long, ChunkOperationHolder) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
getCodecCounters() - Method in interface com.google.android.exoplayer.util.DebugTextViewHelper.Provider
-
-
Returns a CodecCounters whose information should be displayed, or null.
-
-
getCommaDelimitedSimpleClassNames(T[]) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns a string with comma delimited simple names of each object's class.
-
-
getConnection() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
-
Returns the current connection, or null if the source is not currently opened.
-
-
getContentType(Representation) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
getCues(long) - Method in interface com.google.android.exoplayer.text.Subtitle
-
-
Retrieve the subtitle cues that should be displayed at a given time.
-
-
getCues(long) - Method in class com.google.android.exoplayer.text.ttml.TtmlSubtitle
-
 
-
getCues(long) - Method in class com.google.android.exoplayer.text.webvtt.WebvttSubtitle
-
 
-
getCurrentBoundsMs(long[]) - Method in class com.google.android.exoplayer.TimeRange.DynamicTimeRange
-
 
-
getCurrentBoundsMs(long[]) - Method in interface com.google.android.exoplayer.TimeRange
-
-
Returns the start and end times (in milliseconds) of the TimeRange in the provided array, - or creates a new one.
-
-
getCurrentBoundsMs(long[]) - Method in class com.google.android.exoplayer.TimeRange.StaticTimeRange
-
 
-
getCurrentBoundsUs(long[]) - Method in class com.google.android.exoplayer.TimeRange.DynamicTimeRange
-
 
-
getCurrentBoundsUs(long[]) - Method in interface com.google.android.exoplayer.TimeRange
-
-
Returns the start and end times (in microseconds) of the TimeRange in the provided array, - or creates a new one.
-
-
getCurrentBoundsUs(long[]) - Method in class com.google.android.exoplayer.TimeRange.StaticTimeRange
-
 
-
getCurrentPosition() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Gets the current playback position in milliseconds.
-
-
getCurrentPosition() - Method in interface com.google.android.exoplayer.util.DebugTextViewHelper.Provider
-
-
Returns the current playback position, in milliseconds.
-
-
getCurrentPosition() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
getCurrentPositionUs(boolean) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returns the playback position in the stream starting at zero, in microseconds, or - AudioTrack.CURRENT_POSITION_NOT_SET if it is not yet available.
-
-
getData() - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm.KeyRequest
-
 
-
getData() - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm.ProvisionRequest
-
 
-
getData() - Method in class com.google.android.exoplayer.upstream.ByteArrayDataSink
-
-
Returns the data written to the sink since the last call to ByteArrayDataSink.open(DataSpec).
-
-
getDataHolder() - Method in class com.google.android.exoplayer.chunk.DataChunk
-
-
Returns the array in which the data is held.
-
-
getDebugString() - Method in class com.google.android.exoplayer.CodecCounters
-
 
-
getDecoderInfo(MediaCodecSelector, String, boolean) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
getDecoderInfo(String, boolean) - Method in interface com.google.android.exoplayer.MediaCodecSelector
-
-
Selects a decoder to instantiate for a given mime type.
-
-
getDecoderInfo(MediaCodecSelector, String, boolean) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Returns a DecoderInfo for a given format.
-
-
getDecoderInfo(String, boolean) - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
-
Get information about the preferred decoder for a given mime type.
-
-
getDecoderInfos(String, boolean) - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
-
Returns all @{link DecoderInfo}s for a given mime type, in the order given by - MediaCodecList.
-
-
getDefaultUrl() - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm.KeyRequest
-
 
-
getDefaultUrl() - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm.ProvisionRequest
-
 
-
getDequeueOutputBufferTimeoutUs() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Returns the maximum time to block whilst waiting for a decoded output buffer.
-
-
getDrmInitData() - Method in class com.google.android.exoplayer.chunk.BaseMediaChunk
-
-
Gets the DrmInitData corresponding to the chunk.
-
-
getDrmInitData() - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
getDrmInitData() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
-
Returns a DrmInitData parsed from the chunk, or null.
-
-
getDrmInitData() - Method in class com.google.android.exoplayer.chunk.SingleSampleMediaChunk
-
 
-
getDrmInitData() - Method in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
getDtsFrameSize(byte[]) - Static method in class com.google.android.exoplayer.util.DtsUtil
-
-
Returns the size in bytes of the given DTS frame.
-
-
getDuration() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Gets the duration of the track in milliseconds.
-
-
getDuration() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
getDurationUs() - Method in class com.google.android.exoplayer.chunk.MediaChunk
-
 
-
getDurationUs(int, long) - Method in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
-
Returns the duration of a segment.
-
-
getDurationUs(int, long) - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getDurationUs() - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
getDurationUs() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Returns the duration of the source, or C.UNKNOWN_TIME_US if the duration is unknown.
-
-
getDurationUs() - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
getDurationUs() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Returns the duration of the media being rendered.
-
-
getError() - Method in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
Gets the cause of the error state.
-
-
getError() - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
getEventTime(int) - Method in interface com.google.android.exoplayer.text.Subtitle
-
-
Gets the event time at a specified index.
-
-
getEventTime(int) - Method in class com.google.android.exoplayer.text.ttml.TtmlSubtitle
-
 
-
getEventTime(int) - Method in class com.google.android.exoplayer.text.webvtt.WebvttSubtitle
-
 
-
getEventTimeCount() - Method in interface com.google.android.exoplayer.text.Subtitle
-
-
Gets the number of event times, where events are defined as points in time at which the cues - returned by Subtitle.getCues(long) changes.
-
-
getEventTimeCount() - Method in class com.google.android.exoplayer.text.ttml.TtmlSubtitle
-
 
-
getEventTimeCount() - Method in class com.google.android.exoplayer.text.webvtt.WebvttSubtitle
-
 
-
getFirstAvailableSegmentNum() - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
getFirstSampleIndex() - Method in class com.google.android.exoplayer.chunk.BaseMediaChunk
-
-
Returns the index of the first sample in the output that was passed to - BaseMediaChunk.init(DefaultTrackOutput) that will originate from this chunk.
-
-
getFirstSegmentNum() - Method in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
-
Returns the segment number of the first segment.
-
-
getFirstSegmentNum() - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getFirstSegmentNum() - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
 
-
getFixedTrackVariant(int) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Returns the variant corresponding to the fixed track at the specified index, or null if the - track at the specified index is adaptive.
-
-
getFlag(int) - Method in class com.google.android.exoplayer.util.extensions.Buffer
-
 
-
getFormat(int) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
getFormat(int) - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Gets the format of the specified track.
-
-
getFormat() - Method in interface com.google.android.exoplayer.chunk.FormatWrapper
-
-
Returns the wrapped format.
-
-
getFormat() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
-
Returns a MediaFormat parsed from the chunk, or null.
-
-
getFormat(int) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
getFormat() - Method in class com.google.android.exoplayer.dash.mpd.Representation
-
 
-
getFormat(int) - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
getFormat() - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
The format most recently received by the output, or null if a format has yet to be received.
-
-
getFormat(int) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
getFormat(int) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
getFormat(int) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
getFormat() - Method in class com.google.android.exoplayer.hls.Variant
-
 
-
getFormat(int) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Returns the format of the specified track.
-
-
getFormat(int) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
getFormat(int) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
getFormat(int) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
getFormat() - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
-
 
-
getFormat(int) - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Returns the format of the specified track.
-
-
getFormat() - Method in interface com.google.android.exoplayer.util.DebugTextViewHelper.Provider
-
-
Returns a format whose information should be displayed, or null.
-
-
getFrameSize(int) - Static method in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Returns the size of the frame associated with header, or -1 if it is invalid.
-
-
getFrameworkCryptoInfoV16() - Method in class com.google.android.exoplayer.CryptoInfo
-
-
Returns an equivalent MediaCodec.CryptoInfo instance.
-
-
getFrameworkMediaFormatV16() - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
getH265NalUnitType(byte[], int) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Gets the type of the H.265 NAL unit in data that starts at offset.
-
-
getHexStringFromBytes(byte[], int, int) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns a hex string representation of the data provided.
-
-
getIndex() - Method in class com.google.android.exoplayer.dash.mpd.Representation
-
-
Gets a segment index, if the representation is able to provide one directly.
-
-
getIndex() - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getIndex() - Method in class com.google.android.exoplayer.dash.mpd.Representation.SingleSegmentRepresentation
-
 
-
getIndex() - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase
-
 
-
getIndexUri() - Method in class com.google.android.exoplayer.dash.mpd.Representation
-
-
Gets a RangedUri defining the location of the representation's segment index.
-
-
getIndexUri() - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getIndexUri() - Method in class com.google.android.exoplayer.dash.mpd.Representation.SingleSegmentRepresentation
-
 
-
getIndividualAllocationLength() - Method in interface com.google.android.exoplayer.upstream.Allocator
-
-
Returns the length of each individual Allocation.
-
-
getIndividualAllocationLength() - Method in class com.google.android.exoplayer.upstream.DefaultAllocator
-
 
-
getInitialization(Representation) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase
-
-
Gets the RangedUri defining the location of initialization data for a given - representation.
-
-
getInitialization(Representation) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate
-
 
-
getInitializationUri() - Method in class com.google.android.exoplayer.dash.mpd.Representation
-
-
Gets a RangedUri defining the location of the representation's initialization data.
-
-
getIntegerCodeForString(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the integer equal to the big-endian concatenation of the characters in string - as bytes.
-
-
getKeyRequest(byte[], byte[], String, int, HashMap<String, String>) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
getKeyRequest(byte[], byte[], String, int, HashMap<String, String>) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
getKeys() - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Returns all keys in the cache.
-
-
getKeys() - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
getLargestParsedTimestampUs() - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
The largest timestamp of any sample received by the output, or Long.MIN_VALUE if a - sample has yet to be received.
-
-
getLargestParsedTimestampUs() - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Gets the largest timestamp of any sample parsed by the extractor.
-
-
getLastEventTime() - Method in interface com.google.android.exoplayer.text.Subtitle
-
-
Convenience method for obtaining the last event time.
-
-
getLastEventTime() - Method in class com.google.android.exoplayer.text.ttml.TtmlSubtitle
-
 
-
getLastEventTime() - Method in class com.google.android.exoplayer.text.webvtt.WebvttSubtitle
-
 
-
getLastSegmentNum() - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
getLastSegmentNum(long) - Method in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
-
Returns the segment number of the last segment, or DashSegmentIndex.INDEX_UNBOUNDED.
-
-
getLastSegmentNum(long) - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getLastSegmentNum(long) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
 
-
getLastSegmentNum(long) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList
-
 
-
getLastSegmentNum(long) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate
-
 
-
getLength() - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
getLength() - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Returns the length of the source stream, or C.LENGTH_UNBOUNDED if it is unknown.
-
-
getLong(int, int) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns a long created by concatenating the bits of two integers.
-
-
getManifest() - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Gets a Pair containing the most recently loaded manifest together with the timestamp - at which the load completed.
-
-
getManifestLoadCompleteTimestamp() - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Gets the value of SystemClock.elapsedRealtime() when the last load completed.
-
-
getManifestLoadStartTimestamp() - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Gets the value of SystemClock.elapsedRealtime() when the last completed load started.
-
-
getMaxChannelCount() - Method in class com.google.android.exoplayer.audio.AudioCapabilities
-
-
Returns the maximum number of channels the device can play at the same time.
-
-
getMediaClock() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
getMediaClock() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
If the renderer advances its own playback position then this method returns a corresponding - MediaClock.
-
-
getMediaCrypto() - Method in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
Gets an ExoMediaCrypto for the open session.
-
-
getMediaCrypto() - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
getMediaFormat() - Method in class com.google.android.exoplayer.chunk.BaseMediaChunk
-
-
Gets the MediaFormat corresponding to the chunk.
-
-
getMediaFormat() - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
getMediaFormat() - Method in class com.google.android.exoplayer.chunk.SingleSampleMediaChunk
-
 
-
getMediaFormat(int) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Gets the MediaFormat of the specified track.
-
-
getMuxedAudioLanguage() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Returns the language of the audio muxed into variants, or null if unknown.
-
-
getMuxedCaptionLanguage() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Returns the language of the captions muxed into variants, or null if unknown.
-
-
getNalUnitType(byte[], int) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Gets the type of the NAL unit in data that starts at offset.
-
-
getNextChunkIndex() - Method in class com.google.android.exoplayer.chunk.MediaChunk
-
 
-
getNextEventTimeIndex(long) - Method in interface com.google.android.exoplayer.text.Subtitle
-
-
Gets the index of the first event that occurs after a given time (exclusive).
-
-
getNextEventTimeIndex(long) - Method in class com.google.android.exoplayer.text.ttml.TtmlSubtitle
-
 
-
getNextEventTimeIndex(long) - Method in class com.google.android.exoplayer.text.webvtt.WebvttSubtitle
-
 
-
getNextManifestUri() - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
getNextManifestUri() - Method in interface com.google.android.exoplayer.util.ManifestFetcher.RedirectingManifest
-
-
Returns the URI from which subsequent manifests should be requested, or null to continue - using the current URI.
-
-
getOutput() - Method in class com.google.android.exoplayer.chunk.BaseMediaChunk
-
-
Returns the output most recently passed to BaseMediaChunk.init(DefaultTrackOutput).
-
-
getPassthroughDecoderInfo() - Method in interface com.google.android.exoplayer.MediaCodecSelector
-
-
Selects a decoder to instantiate for audio passthrough.
-
-
getPassthroughDecoderInfo() - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
-
Gets information about a decoder suitable for audio passthrough.
-
-
getPcmEncoding(int) - Static method in class com.google.android.exoplayer.util.Util
-
-
Converts a sample bit depth to a corresponding PCM encoding constant.
-
-
getPeekPosition() - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
getPeekPosition() - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Returns the current peek position (byte offset) in the stream.
-
-
getPercentile(float) - Method in class com.google.android.exoplayer.util.SlidingPercentile
-
-
Compute the percentile by integration.
-
-
getPeriod(int) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
getPeriodCount() - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
getPeriodDuration(int) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
getPhysicalDisplaySize(Context) - Static method in class com.google.android.exoplayer.util.Util
-
-
Gets the physical size of the default display, in pixels.
-
-
getPlaybackLooper() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Gets the Looper associated with the playback thread.
-
-
getPlaybackState() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Returns the current state of the player.
-
-
getPlayWhenReady() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Whether playback will proceed when ExoPlayer.getPlaybackState() == ExoPlayer.STATE_READY.
-
-
getPosition(long) - Method in class com.google.android.exoplayer.extractor.ChunkIndex
-
 
-
getPosition() - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
getPosition() - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Returns the current read position (byte offset) in the stream.
-
-
getPosition(long) - Method in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
getPosition(long) - Method in class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
getPosition(long) - Method in interface com.google.android.exoplayer.extractor.SeekMap
-
-
Maps a seek position in microseconds to a corresponding position (byte offset) in the stream - from which data can be provided to the extractor.
-
-
getPosition(long) - Method in class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
getPosition() - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Gets the current bit offset.
-
-
getPosition() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Returns the current offset in the array, in bytes.
-
-
getPositionUs() - Method in interface com.google.android.exoplayer.MediaClock
-
 
-
getPositionUs() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
getPresentationTimeOffsetUs() - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase
-
-
Gets the presentation time offset, in microseconds.
-
-
getPropertyByteArray(String) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
getPropertyByteArray(String) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
getPropertyByteArray(String) - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
- -
-
getPropertyString(String) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
getPropertyString(String) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
getPropertyString(String) - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
- -
-
getProvisionRequest() - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
getProvisionRequest() - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
getReadIndex() - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Returns the current absolute read index.
-
-
getRemainderDataSpec(DataSpec, int) - Static method in class com.google.android.exoplayer.util.Util
-
-
Given a DataSpec and a number of bytes already loaded, returns a DataSpec - that represents the remainder of the data.
-
-
getResponseHeaders() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
getResponseHeaders() - Method in interface com.google.android.exoplayer.upstream.HttpDataSource
-
-
Gets the headers provided in the response.
-
-
getResult() - Method in class com.google.android.exoplayer.upstream.UriLoadable
-
-
Returns the loaded object, or null if an object has not been loaded.
-
-
getSample(SampleHolder) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Removes the next sample from the head of the queue, writing it into the provided holder.
-
-
getSample(int, SampleHolder) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Gets the next sample for the specified track.
-
-
getSeekMap() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
-
Returns a SeekMap parsed from the chunk, or null.
-
-
getSegmentDurationUs(int, long) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
 
-
getSegmentEndTimeUs(int) - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
getSegmentNum(long) - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
getSegmentNum(long, long) - Method in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
-
Returns the segment number of the segment containing a given media time.
-
-
getSegmentNum(long, long) - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getSegmentNum(long, long) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
 
-
getSegmentStartTimeUs(int) - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
getSegmentTimeUs(int) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
 
-
getSegmentUrl(int) - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
getSegmentUrl(int) - Method in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
-
Returns a RangedUri defining the location of a segment.
-
-
getSegmentUrl(int) - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getSegmentUrl(Representation, int) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
-
Returns a RangedUri defining the location of a segment for the given index in the - given representation.
-
-
getSegmentUrl(Representation, int) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList
-
 
-
getSegmentUrl(Representation, int) - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate
-
 
-
getSelectedTrack(int) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Returns the index of the currently selected track for the specified renderer.
-
-
getSelectedTrackIndex() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Returns the currently selected track index.
-
-
getSourceState() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Gets the source state.
-
-
getStartTimeUs(int) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
-
Gets the start time of the specified chunk.
-
-
getState() - Method in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
Gets the current state of the session.
-
-
getState() - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
getState() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Returns the current state of the renderer.
-
-
getTimeUs(int) - Method in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
-
Returns the start time of a segment.
-
-
getTimeUs(int) - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
getTopInt(long) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns the top 32 bits of a long as an integer.
-
-
getTotalBytesAllocated() - Method in interface com.google.android.exoplayer.upstream.Allocator
-
-
Returns the total number of bytes currently allocated.
-
-
getTotalBytesAllocated() - Method in class com.google.android.exoplayer.upstream.DefaultAllocator
-
 
-
getTrackCount() - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
getTrackCount() - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Returns the number of tracks exposed by the source.
-
-
getTrackCount() - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
getTrackCount() - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
getTrackCount(int) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Returns the number of tracks exposed by the specified renderer.
-
-
getTrackCount() - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
getTrackCount() - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
getTrackCount() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Returns the number of tracks exposed by the source.
-
-
getTrackCount() - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Gets the number of available tracks.
-
-
getTrackCount() - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
getTrackCount() - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Returns the number of tracks exposed by the source.
-
-
getTrackCount() - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
getTrackCount() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
getTrackCount() - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
getTrackCount() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Returns the number of tracks exposed by the renderer.
-
-
getTrackFormat(int, int) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Returns the format of a track.
-
-
getUri() - Method in class com.google.android.exoplayer.upstream.AssetDataSource
-
 
-
getUri() - Method in class com.google.android.exoplayer.upstream.ContentDataSource
-
 
-
getUri() - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
getUri() - Method in class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
 
-
getUri() - Method in class com.google.android.exoplayer.upstream.FileDataSource
-
 
-
getUri() - Method in class com.google.android.exoplayer.upstream.UdpDataSource
-
 
-
getUri() - Method in interface com.google.android.exoplayer.upstream.UriDataSource
-
-
When the source is open, returns the URI from which data is being read.
-
-
getUserAgent(Context, String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns a user agent string based on the given application name and the library version.
-
-
getVideoMediaMimeType(String) - Static method in class com.google.android.exoplayer.util.MimeTypes
-
-
Returns the video mimeType type of codecs.
-
-
getWrappedMediaCrypto() - Method in class com.google.android.exoplayer.drm.FrameworkMediaCrypto
-
 
-
getWriteIndex() - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Returns the current absolute write index.
-
-
- - - -

H

-
-
handleAudioTrackDiscontinuity() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
handleBuffer(ByteBuffer, int, int, long) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Attempts to write size bytes from buffer at offset to the audio track.
-
-
handleDiscontinuity() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Signals to the audio track that the next buffer is discontinuous with the previous buffer.
-
-
handleEndOfStream() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Ensures that the last data passed to AudioTrack.handleBuffer(ByteBuffer, int, int, long) is - played out in full.
-
-
handleMessage(int, Object) - Method in interface com.google.android.exoplayer.ExoPlayer.ExoPlayerComponent
-
-
Handles a message delivered to the component.
-
-
handleMessage(int, Object) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
handleMessage(int, Object) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
handleMessage(Message) - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
handleMessage(Message) - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
handleMessage(Message) - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
handleMessage(int, Object) - Method in class com.google.android.exoplayer.TrackRenderer
-
 
-
handlesTrack(MediaCodecSelector, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
handlesTrack(MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
handlesTrack(MediaCodecSelector, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Returns whether this renderer is capable of handling the provided track.
-
-
handlesTrack(MediaCodecSelector, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
handlesTrack(MediaFormat) - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
handlesTrack(MediaFormat) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
-
Returns whether this renderer is capable of handling the provided track.
-
-
handlesTrack(MediaFormat) - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
handlesTrack(MediaFormat) - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
hasContentProtection() - Method in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
hasDrmInitData() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
-
True if a DrmInitData was parsed from the chunk.
-
-
hasFormat() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
-
True if a MediaFormat was parsed from the chunk.
-
-
hasFormat() - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
True if the output has received a format.
-
-
hashCode() - Method in class com.google.android.exoplayer.audio.AudioCapabilities
-
 
-
hashCode() - Method in class com.google.android.exoplayer.chunk.Format
-
 
-
hashCode() - Method in class com.google.android.exoplayer.dash.mpd.ContentProtection
-
 
-
hashCode() - Method in class com.google.android.exoplayer.dash.mpd.RangedUri
-
 
-
hashCode() - Method in class com.google.android.exoplayer.drm.DrmInitData.Mapped
-
 
-
hashCode() - Method in class com.google.android.exoplayer.drm.DrmInitData.SchemeInitData
-
 
-
hashCode() - Method in class com.google.android.exoplayer.drm.DrmInitData.Universal
-
 
-
hashCode() - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
hashCode() - Method in class com.google.android.exoplayer.TimeRange.DynamicTimeRange
-
 
-
hashCode() - Method in class com.google.android.exoplayer.TimeRange.StaticTimeRange
-
 
-
hasPendingData() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returns whether the audio track has more data pending that will be played back.
-
-
hasSamples(int) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Whether samples are available for reading from HlsExtractorWrapper.getSample(int, SampleHolder) for the - specified track.
-
-
hasSeekMap() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
-
True if a SeekMap was parsed from the chunk.
-
-
haveFormat() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
haveRenderedFirstFrame() - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
headerFields - Variable in exception com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException
-
-
An unmodifiable map of the response header fields and values.
-
-
height - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The height of the video in pixels, or -1 if unknown or not applicable.
-
-
height - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The height of the video in pixels, or MediaFormat.NO_VALUE if unknown or not applicable.
-
-
height - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
HlsChunkSource - Class in com.google.android.exoplayer.hls
-
-
A temporary test source of HLS chunks.
-
-
HlsChunkSource(boolean, DataSource, HlsPlaylist, HlsTrackSelector, BandwidthMeter, PtsTimestampAdjusterProvider) - Constructor for class com.google.android.exoplayer.hls.HlsChunkSource
-
 
-
HlsChunkSource(boolean, DataSource, HlsPlaylist, HlsTrackSelector, BandwidthMeter, PtsTimestampAdjusterProvider, long, long) - Constructor for class com.google.android.exoplayer.hls.HlsChunkSource
-
 
-
HlsChunkSource(boolean, DataSource, HlsPlaylist, HlsTrackSelector, BandwidthMeter, PtsTimestampAdjusterProvider, long, long, Handler, HlsChunkSource.EventListener) - Constructor for class com.google.android.exoplayer.hls.HlsChunkSource
-
 
-
HlsChunkSource.EventListener - Interface in com.google.android.exoplayer.hls
-
-
Interface definition for a callback to be notified of HlsChunkSource events.
-
-
HlsExtractorWrapper - Class in com.google.android.exoplayer.hls
-
-
Wraps a Extractor, adding functionality to enable reading of the extracted samples.
-
-
HlsExtractorWrapper(int, Format, long, Extractor, boolean, int, int) - Constructor for class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
HlsMasterPlaylist - Class in com.google.android.exoplayer.hls
-
-
Represents an HLS master playlist.
-
-
HlsMasterPlaylist(String, List<Variant>, List<Variant>, List<Variant>, String, String) - Constructor for class com.google.android.exoplayer.hls.HlsMasterPlaylist
-
 
-
HlsMediaPlaylist - Class in com.google.android.exoplayer.hls
-
-
Represents an HLS media playlist.
-
-
HlsMediaPlaylist(String, int, int, int, boolean, List<HlsMediaPlaylist.Segment>) - Constructor for class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
HlsMediaPlaylist.Segment - Class in com.google.android.exoplayer.hls
-
-
Media segment reference.
-
-
HlsPlaylist - Class in com.google.android.exoplayer.hls
-
-
Represents an HLS playlist.
-
-
HlsPlaylist(String, int) - Constructor for class com.google.android.exoplayer.hls.HlsPlaylist
-
 
-
HlsPlaylistParser - Class in com.google.android.exoplayer.hls
-
-
HLS playlists parsing logic.
-
-
HlsPlaylistParser() - Constructor for class com.google.android.exoplayer.hls.HlsPlaylistParser
-
 
-
HlsSampleSource - Class in com.google.android.exoplayer.hls
-
-
A SampleSource for HLS streams.
-
-
HlsSampleSource(HlsChunkSource, LoadControl, int) - Constructor for class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
HlsSampleSource(HlsChunkSource, LoadControl, int, Handler, HlsSampleSource.EventListener, int) - Constructor for class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
HlsSampleSource(HlsChunkSource, LoadControl, int, Handler, HlsSampleSource.EventListener, int, int) - Constructor for class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
HlsSampleSource.EventListener - Interface in com.google.android.exoplayer.hls
-
-
Interface definition for a callback to be notified of HlsSampleSource events.
-
-
HlsTrackSelector - Interface in com.google.android.exoplayer.hls
-
-
Specifies a track selection from an HlsMasterPlaylist.
-
-
HlsTrackSelector.Output - Interface in com.google.android.exoplayer.hls
-
-
Defines a selector output.
-
-
HttpDataSource - Interface in com.google.android.exoplayer.upstream
-
-
An HTTP specific extension to UriDataSource.
-
-
HttpDataSource.HttpDataSourceException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when an error is encountered when trying to read from a HttpDataSource.
-
-
HttpDataSource.InvalidContentTypeException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when the content type is invalid.
-
-
HttpDataSource.InvalidResponseCodeException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when an attempt to open a connection results in a response code not in the 2xx range.
-
-
HttpDataSourceException(DataSpec, int) - Constructor for exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
HttpDataSourceException(String, DataSpec, int) - Constructor for exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
HttpDataSourceException(IOException, DataSpec, int) - Constructor for exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
HttpDataSourceException(String, IOException, DataSpec, int) - Constructor for exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
- - - -

I

-
-
id - Variable in class com.google.android.exoplayer.chunk.Format
-
-
An identifier for the format.
-
-
id - Variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
id - Variable in class com.google.android.exoplayer.dash.mpd.Period
-
-
The period identifier, if one exists.
-
-
id - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
The track identifier.
-
-
ID - Static variable in class com.google.android.exoplayer.metadata.id3.ApicFrame
-
 
-
ID - Static variable in class com.google.android.exoplayer.metadata.id3.GeobFrame
-
 
-
id - Variable in class com.google.android.exoplayer.metadata.id3.Id3Frame
-
-
The frame ID.
-
-
ID - Static variable in class com.google.android.exoplayer.metadata.id3.PrivFrame
-
 
-
ID - Static variable in class com.google.android.exoplayer.metadata.id3.TxxxFrame
-
 
-
Id3Frame - Class in com.google.android.exoplayer.metadata.id3
-
-
Base class for ID3 frames.
-
-
Id3Frame(String) - Constructor for class com.google.android.exoplayer.metadata.id3.Id3Frame
-
 
-
Id3Parser - Class in com.google.android.exoplayer.metadata.id3
-
-
Extracts individual TXXX text frames from raw ID3 data.
-
-
Id3Parser() - Constructor for class com.google.android.exoplayer.metadata.id3.Id3Parser
-
 
-
INDEX_UNBOUNDED - Static variable in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
 
-
inferContentType(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Makes a best guess to infer the type from a file name.
-
-
init(DefaultTrackOutput) - Method in class com.google.android.exoplayer.chunk.BaseMediaChunk
-
-
Initializes the chunk for loading, setting the DefaultTrackOutput that will receive - samples as they are loaded.
-
-
init(ChunkExtractorWrapper.SingleTrackOutput) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
-
Initializes the extractor to output to the provided ChunkExtractorWrapper.SingleTrackOutput, and configures - it to receive data from a new chunk.
-
-
init(ExtractorOutput) - Method in interface com.google.android.exoplayer.extractor.Extractor
-
-
Initializes the extractor with an ExtractorOutput.
-
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.mp3.Mp3Extractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.ogg.OggExtractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.ts.AdtsExtractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
init(ExtractorOutput) - Method in class com.google.android.exoplayer.extractor.webm.WebmExtractor
-
 
-
init(Allocator) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Initializes the wrapper for use.
-
-
InitializationChunk - Class in com.google.android.exoplayer.chunk
-
-
A Chunk that uses an Extractor to parse initialization data for single track.
-
-
InitializationChunk(DataSource, DataSpec, int, Format, ChunkExtractorWrapper) - Constructor for class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
InitializationChunk(DataSource, DataSpec, int, Format, ChunkExtractorWrapper, int) - Constructor for class com.google.android.exoplayer.chunk.InitializationChunk
-
-
Constructor for a chunk of media samples.
-
-
initializationData - Variable in class com.google.android.exoplayer.MediaFormat
-
-
Initialization data that must be provided to the decoder.
-
-
InitializationException(int, int, int, int) - Constructor for exception com.google.android.exoplayer.audio.AudioTrack.InitializationException
-
 
-
initializationVectorSize - Variable in class com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox
-
-
The initialization vector size in bytes for the samples in the corresponding sample group.
-
-
initialize() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Initializes the audio track for writing new buffers using AudioTrack.handleBuffer(java.nio.ByteBuffer, int, int, long).
-
-
initialize(int) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Initializes the audio track for writing new buffers using AudioTrack.handleBuffer(java.nio.ByteBuffer, int, int, long).
-
-
InputBuffer - Class in com.google.android.exoplayer.util.extensions
-
-
Input buffer to be decoded by a Decoder.
-
-
InputBuffer() - Constructor for class com.google.android.exoplayer.util.extensions.InputBuffer
-
 
-
inputBufferCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
instance - Static variable in class com.google.android.exoplayer.upstream.NetworkLock
-
 
-
InvalidAudioTrackTimestampException(String) - Constructor for exception com.google.android.exoplayer.audio.AudioTrack.InvalidAudioTrackTimestampException
-
 
-
InvalidContentTypeException(String, DataSpec) - Constructor for exception com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException
-
 
-
InvalidResponseCodeException(int, Map<String, List<String>>, DataSpec) - Constructor for exception com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException
-
 
-
isAdaptive() - Method in class com.google.android.exoplayer.dash.DashChunkSource.ExposedTrack
-
 
-
isAndroidTv(Context) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns whether the device is an AndroidTV.
-
-
isApplication(String) - Static method in class com.google.android.exoplayer.util.MimeTypes
-
-
Whether the top-level type of mimeType is application.
-
-
isAudio(String) - Static method in class com.google.android.exoplayer.util.MimeTypes
-
-
Whether the top-level type of mimeType is audio.
-
-
isBeyondLastSegment(int) - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
isCached(String, long, long) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Queries if a range is entirely available in the cache.
-
-
isCached - Variable in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
Whether the CacheSpan is cached.
-
-
isCached(String, long, long) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
isDecodeOnly() - Method in class com.google.android.exoplayer.SampleHolder
-
- -
-
isEmpty() - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
True if at least one sample can be read from the queue.
-
-
isEncrypted - Variable in class com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox
-
-
Indicates the encryption state of the samples in the sample group.
-
-
isEncrypted - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
isEncrypted() - Method in class com.google.android.exoplayer.SampleHolder
-
- -
-
isEnded() - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
isEnded() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
isEnded() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
isEnded() - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
isEnded() - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
isEnded() - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
isEnded() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Whether the renderer is ready for the ExoPlayer instance to transition to - ExoPlayer.STATE_ENDED.
-
-
isEndTag(XmlPullParser, String) - Static method in class com.google.android.exoplayer.util.ParserUtil
-
 
-
isEndTag(XmlPullParser) - Static method in class com.google.android.exoplayer.util.ParserUtil
-
 
-
isExplicit() - Method in interface com.google.android.exoplayer.dash.DashSegmentIndex
-
-
Returns true if segments are defined explicitly by the index.
-
-
isExplicit() - Method in class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
isExplicit() - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
 
-
isExplicit() - Method in class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList
-
 
-
isH264ProfileSupported(int, int) - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
-
Deprecated. - -
-
-
isHoleSpan() - Method in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
Returns whether this is a hole CacheSpan.
-
-
isIndexExplicit() - Method in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
isIndexUnbounded() - Method in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
isInitialized() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returns whether the audio track has been successfully initialized via AudioTrack.initialize() and - not yet AudioTrack.reset().
-
-
isInitialized() - Method in class com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster
-
-
Whether this adjuster has been initialized with a first MPEG-2 TS presentation timestamp.
-
-
isLive() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Returns whether this is a live playback.
-
-
isLive - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
True if the manifest describes a live presentation still in progress.
-
-
isLoadCanceled() - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
isLoadCanceled() - Method in class com.google.android.exoplayer.chunk.DataChunk
-
 
-
isLoadCanceled() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
isLoadCanceled() - Method in class com.google.android.exoplayer.chunk.SingleSampleMediaChunk
-
 
-
isLoadCanceled() - Method in class com.google.android.exoplayer.hls.TsChunk
-
 
-
isLoadCanceled() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
isLoadCanceled() - Method in interface com.google.android.exoplayer.upstream.Loader.Loadable
-
-
Whether the load has been canceled.
-
-
isLoadCanceled() - Method in class com.google.android.exoplayer.upstream.UriLoadable
-
 
-
isLoading() - Method in class com.google.android.exoplayer.upstream.Loader
-
-
Whether the Loader is currently loading a Loader.Loadable.
-
-
isLocalFileUri(Uri) - Static method in class com.google.android.exoplayer.util.Util
-
-
Returns true if the URI is a path to a local file or a reference to a local file.
-
-
isMediaFormatFinal - Variable in class com.google.android.exoplayer.chunk.BaseMediaChunk
-
-
Whether BaseMediaChunk.getMediaFormat() and BaseMediaChunk.getDrmInitData() can be called at any time to - obtain the chunk's media format and drm initialization data.
-
-
isOpenEnded() - Method in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
Returns whether this is an open-ended CacheSpan.
-
-
isPassthroughSupported(String) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returns whether it is possible to play back input audio in the specified format using encoded - audio passthrough.
-
-
isPlaying() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
isPlayWhenReadyCommitted() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Whether the current value of ExoPlayer.getPlayWhenReady() has been reflected by the - internal playback thread.
-
-
isPrepared() - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Whether the extractor is prepared.
-
-
isReady() - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
isReady() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
isReady() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
isReady() - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
isReady() - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
isReady() - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
isReady() - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
isReady() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Whether the renderer is able to immediately render media from the current position.
-
-
isSeekable() - Method in class com.google.android.exoplayer.extractor.ChunkIndex
-
 
-
isSeekable() - Method in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
isSeekable() - Method in class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
isSeekable() - Method in interface com.google.android.exoplayer.extractor.SeekMap
-
-
Whether or not the seeking is supported.
-
-
isSeekable() - Method in class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
isSeiMessageEia608(int, int, ParsableByteArray) - Static method in class com.google.android.exoplayer.text.eia608.Eia608Parser
-
-
Inspects an sei message to determine whether it contains EIA-608.
-
-
isSizeAndRateSupportedV21(String, boolean, int, int, double) - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
-
Tests whether the device advertises it can decode video of a given type at a specified - width, height, and frame rate.
-
-
isSizeSupportedV21(String, boolean, int, int) - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
-
Tests whether the device advertises it can decode video of a given type at a specified width - and height.
-
-
isStartTag(XmlPullParser, String) - Static method in class com.google.android.exoplayer.util.ParserUtil
-
 
-
isStartTag(XmlPullParser) - Static method in class com.google.android.exoplayer.util.ParserUtil
-
 
-
isStatic() - Method in class com.google.android.exoplayer.TimeRange.DynamicTimeRange
-
 
-
isStatic() - Method in interface com.google.android.exoplayer.TimeRange
-
-
Whether the range is static, meaning repeated calls to TimeRange.getCurrentBoundsMs(long[]) - or TimeRange.getCurrentBoundsUs(long[]) will return identical results.
-
-
isStatic() - Method in class com.google.android.exoplayer.TimeRange.StaticTimeRange
-
 
-
isSyncFrame() - Method in class com.google.android.exoplayer.SampleHolder
-
-
Returns whether SampleHolder.flags has C.SAMPLE_FLAG_SYNC set.
-
-
isTagEnabled(String) - Static method in class com.google.android.exoplayer.util.VerboseLogUtil
-
-
Checks whether verbose logging should be output for a given tag.
-
-
isText(String) - Static method in class com.google.android.exoplayer.util.MimeTypes
-
-
Whether the top-level type of mimeType is text.
-
-
isVideo(String) - Static method in class com.google.android.exoplayer.util.MimeTypes
-
-
Whether the top-level type of mimeType is video.
-
-
iv - Variable in class com.google.android.exoplayer.CryptoInfo
-
 
-
- - - -

K

-
-
key - Variable in class com.google.android.exoplayer.CryptoInfo
-
 
-
key - Variable in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
The cache key that uniquely identifies the original stream.
-
-
key - Variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
A key that uniquely identifies the original stream.
-
-
keyId - Variable in class com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox
-
-
The key identifier for the samples in the corresponding sample group.
-
-
KeysExpiredException - Exception in com.google.android.exoplayer.drm
-
-
Thrown when the drm keys loaded into an open session expire.
-
-
KeysExpiredException() - Constructor for exception com.google.android.exoplayer.drm.KeysExpiredException
-
 
-
- - - -

L

-
-
language - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The language of the format.
-
-
language - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The language of the track, or null if unknown or not applicable.
-
-
language - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
lastAccessTimestamp - Variable in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
The last access timestamp, or C.UNKNOWN_TIME_US if CacheSpan.isCached is false.
-
-
LeastRecentlyUsedCacheEvictor - Class in com.google.android.exoplayer.upstream.cache
-
-
Evicts least recently used cache files first.
-
-
LeastRecentlyUsedCacheEvictor(long) - Constructor for class com.google.android.exoplayer.upstream.cache.LeastRecentlyUsedCacheEvictor
-
 
-
length - Variable in class com.google.android.exoplayer.dash.mpd.RangedUri
-
-
The length of the range, or -1 to indicate that the range is unbounded.
-
-
length - Variable in class com.google.android.exoplayer.extractor.ChunkIndex
-
-
The number of chunks.
-
-
length - Variable in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
The length of the CacheSpan, or C.LENGTH_UNBOUNDED if this is an open-ended - hole.
-
-
length - Variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
The length of the data.
-
-
LENGTH_UNBOUNDED - Static variable in class com.google.android.exoplayer.C
-
-
Represents an unbounded length of data.
-
-
limit() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Returns the limit.
-
-
line - Variable in class com.google.android.exoplayer.text.Cue
-
-
The position of the Cue.lineAnchor of the cue box within the viewport in the direction - orthogonal to the writing direction, or Cue.DIMEN_UNSET.
-
-
LINE_TYPE_FRACTION - Static variable in class com.google.android.exoplayer.text.Cue
-
-
Value for Cue.lineType when Cue.line is a fractional position.
-
-
LINE_TYPE_NUMBER - Static variable in class com.google.android.exoplayer.text.Cue
-
-
Value for Cue.lineType when Cue.line is a line number.
-
-
lineAnchor - Variable in class com.google.android.exoplayer.text.Cue
-
-
The cue box anchor positioned by Cue.line.
-
-
lineType - Variable in class com.google.android.exoplayer.text.Cue
-
-
The type of the Cue.line value.
-
-
live - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
load() - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
load() - Method in class com.google.android.exoplayer.chunk.DataChunk
-
 
-
load() - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
load() - Method in class com.google.android.exoplayer.chunk.SingleSampleMediaChunk
-
 
-
load() - Method in class com.google.android.exoplayer.hls.TsChunk
-
 
-
load() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
load() - Method in interface com.google.android.exoplayer.upstream.Loader.Loadable
-
-
Performs the load, returning on completion or cancelation.
-
-
load() - Method in class com.google.android.exoplayer.upstream.UriLoadable
-
 
-
LoadControl - Interface in com.google.android.exoplayer
-
-
Coordinates multiple loaders of time series data.
-
-
Loader - Class in com.google.android.exoplayer.upstream
-
-
Manages the background loading of Loader.Loadables.
-
-
Loader(String) - Constructor for class com.google.android.exoplayer.upstream.Loader
-
 
-
Loader.Callback - Interface in com.google.android.exoplayer.upstream
-
-
Interface definition for a callback to be notified of Loader events.
-
-
Loader.Loadable - Interface in com.google.android.exoplayer.upstream
-
-
Interface definition of an object that can be loaded using a Loader.
-
-
Loader.UnexpectedLoaderException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when an unexpected exception is encountered during loading.
-
-
localIndex - Variable in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
location - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
LongArray - Class in com.google.android.exoplayer.util
-
-
An append-only, auto-growing long[].
-
-
LongArray() - Constructor for class com.google.android.exoplayer.util.LongArray
-
 
-
LongArray(int) - Constructor for class com.google.android.exoplayer.util.LongArray
-
 
-
lookAheadCount - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
The number of fragments in a lookahead, or -1 if the lookahead is unspecified.
-
-
- - - -

M

-
-
majorVersion - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
The client manifest major version.
-
-
ManifestFetcher<T> - Class in com.google.android.exoplayer.util
-
-
Performs both single and repeated loads of media manifests.
-
-
ManifestFetcher(String, UriDataSource, UriLoadable.Parser<T>) - Constructor for class com.google.android.exoplayer.util.ManifestFetcher
-
 
-
ManifestFetcher(String, UriDataSource, UriLoadable.Parser<T>, Handler, ManifestFetcher.EventListener) - Constructor for class com.google.android.exoplayer.util.ManifestFetcher
-
 
-
ManifestFetcher.EventListener - Interface in com.google.android.exoplayer.util
-
-
Interface definition for a callback to be notified of ManifestFetcher events.
-
-
ManifestFetcher.ManifestCallback<T> - Interface in com.google.android.exoplayer.util
-
-
Callback for the result of a single load.
-
-
ManifestFetcher.ManifestIOException - Exception in com.google.android.exoplayer.util
-
-
Thrown when an error occurs trying to fetch a manifest.
-
-
ManifestFetcher.RedirectingManifest - Interface in com.google.android.exoplayer.util
-
-
Interface for manifests that are able to specify that subsequent loads should use a different - URI.
-
-
ManifestIOException(Throwable) - Constructor for exception com.google.android.exoplayer.util.ManifestFetcher.ManifestIOException
-
 
-
MANUFACTURER - Static variable in class com.google.android.exoplayer.util.Util
-
-
Like Build.MANUFACTURER, but in a place where it can be conveniently overridden for - local testing.
-
-
Mapped() - Constructor for class com.google.android.exoplayer.drm.DrmInitData.Mapped
-
 
-
MATCH_LONGEST_US - Static variable in class com.google.android.exoplayer.C
-
-
Represents a microsecond duration whose exact value is unknown, but which should match the - longest of some other known durations.
-
-
MATCH_LONGEST_US - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
Represents a time or duration that should match the duration of the longest track whose - duration is known.
-
-
MAX_FRAME_SIZE_BYTES - Static variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2 - MPEG 2.5 audio stream at 16 kb/s (with padding).
-
-
maxBlockSize - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
maxConsecutiveDroppedOutputBufferCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
maxDecodedFrameSize() - Method in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
maxFrameSize - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
maxH264DecodableFrameSize() - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
 
-
maxHeight - Variable in class com.google.android.exoplayer.MediaFormat
-
-
For formats that belong to an adaptive video track (either describing the track, or describing - a specific format within it), this is the maximum height of the video in pixels that will be - encountered in the stream.
-
-
maxHeight - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
maxInputSize - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The maximum size of a buffer of data (typically one sample) in the format, or MediaFormat.NO_VALUE - if unknown or not applicable.
-
-
maxWidth - Variable in class com.google.android.exoplayer.MediaFormat
-
-
For formats that belong to an adaptive video track (either describing the track, or describing - a specific format within it), this is the maximum width of the video in pixels that will be - encountered in the stream.
-
-
maxWidth - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
maybeInitCodec() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
maybeTerminateInputStream(HttpURLConnection, long) - Static method in class com.google.android.exoplayer.util.Util
-
-
On platform API levels 19 and 20, okhttp's implementation of InputStream.close() can - block for a long time if the stream has a lot of data remaining.
-
-
maybeThrowError() - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
maybeThrowError() - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
If the source is currently having difficulty preparing or providing chunks, then this method - throws the underlying error.
-
-
maybeThrowError() - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
maybeThrowError() - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
maybeThrowError() - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
maybeThrowError() - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
maybeThrowError() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
If the source is currently having difficulty providing chunks, then this method throws the - underlying error.
-
-
maybeThrowError() - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
maybeThrowError() - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
If the source is currently having difficulty preparing or loading samples, then this method - throws the underlying error.
-
-
maybeThrowError() - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
maybeThrowError() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
maybeThrowError() - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
maybeThrowError() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Throws an error that's preventing the renderer from making progress or buffering more data at - this point in time.
-
-
maybeThrowError() - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Throws the error that affected the most recent attempt to load the manifest.
-
-
MediaChunk - Class in com.google.android.exoplayer.chunk
-
-
An abstract base class for Chunks that contain media samples.
-
-
MediaChunk(DataSource, DataSpec, int, Format, long, long, int) - Constructor for class com.google.android.exoplayer.chunk.MediaChunk
-
 
-
MediaChunk(DataSource, DataSpec, int, Format, long, long, int, int) - Constructor for class com.google.android.exoplayer.chunk.MediaChunk
-
 
-
MediaClock - Interface in com.google.android.exoplayer
-
-
Tracks the progression of media time.
-
-
MediaCodecAudioTrackRenderer - Class in com.google.android.exoplayer
-
-
Decodes and renders audio using MediaCodec and AudioTrack.
-
-
MediaCodecAudioTrackRenderer(SampleSource, MediaCodecSelector) - Constructor for class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
MediaCodecAudioTrackRenderer(SampleSource, MediaCodecSelector, DrmSessionManager, boolean) - Constructor for class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
MediaCodecAudioTrackRenderer(SampleSource, MediaCodecSelector, Handler, MediaCodecAudioTrackRenderer.EventListener) - Constructor for class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
MediaCodecAudioTrackRenderer(SampleSource, MediaCodecSelector, DrmSessionManager, boolean, Handler, MediaCodecAudioTrackRenderer.EventListener) - Constructor for class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
MediaCodecAudioTrackRenderer(SampleSource, MediaCodecSelector, DrmSessionManager, boolean, Handler, MediaCodecAudioTrackRenderer.EventListener, AudioCapabilities, int) - Constructor for class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
MediaCodecAudioTrackRenderer(SampleSource[], MediaCodecSelector, DrmSessionManager, boolean, Handler, MediaCodecAudioTrackRenderer.EventListener, AudioCapabilities, int) - Constructor for class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
MediaCodecAudioTrackRenderer.EventListener - Interface in com.google.android.exoplayer
-
-
Interface definition for a callback to be notified of MediaCodecAudioTrackRenderer - events.
-
-
MediaCodecSelector - Interface in com.google.android.exoplayer
-
-
Selector of MediaCodec instances.
-
-
MediaCodecTrackRenderer - Class in com.google.android.exoplayer
-
-
An abstract TrackRenderer that uses MediaCodec to decode samples for rendering.
-
-
MediaCodecTrackRenderer(SampleSource, MediaCodecSelector, DrmSessionManager<FrameworkMediaCrypto>, boolean, Handler, MediaCodecTrackRenderer.EventListener) - Constructor for class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
MediaCodecTrackRenderer(SampleSource[], MediaCodecSelector, DrmSessionManager<FrameworkMediaCrypto>, boolean, Handler, MediaCodecTrackRenderer.EventListener) - Constructor for class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
MediaCodecTrackRenderer.DecoderInitializationException - Exception in com.google.android.exoplayer
-
-
Thrown when a failure occurs instantiating a decoder.
-
-
MediaCodecTrackRenderer.EventListener - Interface in com.google.android.exoplayer
-
-
Interface definition for a callback to be notified of MediaCodecTrackRenderer events.
-
-
MediaCodecUtil - Class in com.google.android.exoplayer
-
-
A utility class for querying the available codecs.
-
-
MediaCodecUtil.DecoderQueryException - Exception in com.google.android.exoplayer
-
-
Thrown when an error occurs querying the device for its underlying media capabilities.
-
-
MediaCodecVideoTrackRenderer - Class in com.google.android.exoplayer
-
-
Decodes and renders video using MediaCodec.
-
-
MediaCodecVideoTrackRenderer(Context, SampleSource, MediaCodecSelector, int) - Constructor for class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
MediaCodecVideoTrackRenderer(Context, SampleSource, MediaCodecSelector, int, long) - Constructor for class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
MediaCodecVideoTrackRenderer(Context, SampleSource, MediaCodecSelector, int, long, Handler, MediaCodecVideoTrackRenderer.EventListener, int) - Constructor for class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
MediaCodecVideoTrackRenderer(Context, SampleSource, MediaCodecSelector, int, long, DrmSessionManager<FrameworkMediaCrypto>, boolean, Handler, MediaCodecVideoTrackRenderer.EventListener, int) - Constructor for class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
MediaCodecVideoTrackRenderer.EventListener - Interface in com.google.android.exoplayer
-
-
Interface definition for a callback to be notified of MediaCodecVideoTrackRenderer - events.
-
-
MediaDrmCallback - Interface in com.google.android.exoplayer.drm
-
-
Performs ExoMediaDrm key and provisioning requests.
-
-
mediaFormat - Variable in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
mediaFormat - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
The media format.
-
-
MediaFormat - Class in com.google.android.exoplayer
-
-
Defines the format of an elementary media stream.
-
-
MediaFormatHolder - Class in com.google.android.exoplayer
-
-
Holds a MediaFormat and corresponding drm scheme initialization data.
-
-
MediaFormatHolder() - Constructor for class com.google.android.exoplayer.MediaFormatHolder
-
 
-
MediaPresentationDescription - Class in com.google.android.exoplayer.dash.mpd
-
-
Represents a DASH media presentation description (mpd).
-
-
MediaPresentationDescription(long, long, long, boolean, long, long, UtcTimingElement, String, List<Period>) - Constructor for class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
MediaPresentationDescriptionParser - Class in com.google.android.exoplayer.dash.mpd
-
-
A parser of media presentation description files.
-
-
MediaPresentationDescriptionParser() - Constructor for class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
-
Equivalent to calling new MediaPresentationDescriptionParser(null).
-
-
MediaPresentationDescriptionParser(String) - Constructor for class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
MediaPresentationDescriptionParser.ContentProtectionsBuilder - Class in com.google.android.exoplayer.dash.mpd
-
-
Builds a list of ContentProtection elements for an AdaptationSet.
-
-
mediaSequence - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
MetadataParser<T> - Interface in com.google.android.exoplayer.metadata
-
-
Parses metadata from binary data.
-
-
MetadataTrackRenderer<T> - Class in com.google.android.exoplayer.metadata
-
-
A TrackRenderer for metadata embedded in a media stream.
-
-
MetadataTrackRenderer(SampleSource, MetadataParser<T>, MetadataTrackRenderer.MetadataRenderer<T>, Looper) - Constructor for class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
MetadataTrackRenderer.MetadataRenderer<T> - Interface in com.google.android.exoplayer.metadata
-
-
An interface for components that process metadata.
-
-
MICROS_PER_SECOND - Static variable in class com.google.android.exoplayer.C
-
-
The number of microseconds in one second.
-
-
mimeType - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The mime type of the format.
-
-
mimeType - Variable in class com.google.android.exoplayer.drm.DrmInitData.SchemeInitData
-
- -
-
mimeType - Variable in exception com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
-
-
The mime type for which a decoder was being initialized.
-
-
mimeType - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The mime type of the format.
-
-
mimeType - Variable in class com.google.android.exoplayer.metadata.id3.ApicFrame
-
 
-
mimeType - Variable in class com.google.android.exoplayer.metadata.id3.GeobFrame
-
 
-
mimeType - Variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
The mime type.
-
-
mimeTypeIsRawText - Variable in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
MimeTypes - Class in com.google.android.exoplayer.util
-
-
Defines common MIME types and helper methods.
-
-
minBlockSize - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
minBufferTime - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
minFrameSize - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
minorVersion - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
The client manifest minor version.
-
-
minUpdatePeriod - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
MissingFieldException(String) - Constructor for exception com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser.MissingFieldException
-
 
-
mode - Variable in class com.google.android.exoplayer.CryptoInfo
-
 
-
MODEL - Static variable in class com.google.android.exoplayer.util.Util
-
-
Like Build.MODEL, but in a place where it can be conveniently overridden for local - testing.
-
-
movieTimescale - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
The movie timescale.
-
-
Mp3Extractor - Class in com.google.android.exoplayer.extractor.mp3
-
-
Extracts data from an MP3 file.
-
-
Mp3Extractor() - Constructor for class com.google.android.exoplayer.extractor.mp3.Mp3Extractor
-
-
Constructs a new Mp3Extractor.
-
-
Mp3Extractor(long) - Constructor for class com.google.android.exoplayer.extractor.mp3.Mp3Extractor
-
-
Constructs a new Mp3Extractor.
-
-
Mp4Extractor - Class in com.google.android.exoplayer.extractor.mp4
-
-
Extracts data from an unfragmented MP4 file.
-
-
Mp4Extractor() - Constructor for class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
Mp4WebvttParser - Class in com.google.android.exoplayer.text.webvtt
-
-
A SubtitleParser for Webvtt embedded in a Mp4 container file.
-
-
Mp4WebvttParser() - Constructor for class com.google.android.exoplayer.text.webvtt.Mp4WebvttParser
-
 
-
MpegAudioHeader - Class in com.google.android.exoplayer.util
-
-
Representation of an MPEG audio frame header.
-
-
MpegAudioHeader() - Constructor for class com.google.android.exoplayer.util.MpegAudioHeader
-
 
-
MSG_SET_PLAYBACK_PARAMS - Static variable in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
- -
-
MSG_SET_STREAM_TYPE - Static variable in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
- -
-
MSG_SET_SURFACE - Static variable in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
- -
-
MSG_SET_VOLUME - Static variable in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
- -
-
MultiSegmentBase(RangedUri, long, long, int, long, List<SegmentBase.SegmentTimelineElement>) - Constructor for class com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
-
 
-
MultiSegmentRepresentation(String, long, Format, SegmentBase.MultiSegmentBase, String, String) - Constructor for class com.google.android.exoplayer.dash.mpd.Representation.MultiSegmentRepresentation
-
 
-
muxedAudioLanguage - Variable in class com.google.android.exoplayer.hls.HlsMasterPlaylist
-
 
-
muxedCaptionLanguage - Variable in class com.google.android.exoplayer.hls.HlsMasterPlaylist
-
 
-
- - - -

N

-
-
NAL_START_CODE - Static variable in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Four initial bytes that must prefix NAL units for decoding.
-
-
nalUnitLengthFieldLength - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
For H264 video tracks, the length in bytes of the NALUnitLength field in each sample.
-
-
NalUnitUtil - Class in com.google.android.exoplayer.util
-
-
Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
-
-
NalUnitUtil.PpsData - Class in com.google.android.exoplayer.util
-
-
Holds data parsed from a picture parameter set NAL unit.
-
-
NalUnitUtil.SpsData - Class in com.google.android.exoplayer.util
-
-
Holds data parsed from a sequence parameter set NAL unit.
-
-
name - Variable in class com.google.android.exoplayer.DecoderInfo
-
-
The name of the decoder.
-
-
name - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
NetworkLock - Class in com.google.android.exoplayer.upstream
-
-
A network task prioritization mechanism.
-
-
NetworkLock.PriorityTooLowException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when a task is attempts to proceed when it does not have the highest priority.
-
-
newAudioInstance() - Static method in class com.google.android.exoplayer.dash.DefaultDashTrackSelector
-
 
-
newAudioInstance() - Static method in class com.google.android.exoplayer.hls.DefaultHlsTrackSelector
-
-
Creates a DefaultHlsTrackSelector that selects alternate audio renditions.
-
-
newAudioInstance() - Static method in class com.google.android.exoplayer.smoothstreaming.DefaultSmoothStreamingTrackSelector
-
 
-
newDefaultInstance(Context) - Static method in class com.google.android.exoplayer.hls.DefaultHlsTrackSelector
-
-
Creates a DefaultHlsTrackSelector that selects the streams defined in the playlist.
-
-
newFrameworkInstance(UUID, Looper, MediaDrmCallback, HashMap<String, String>, Handler, StreamingDrmSessionManager.EventListener) - Static method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
newInstance(String, long, Format, String, SegmentBase) - Static method in class com.google.android.exoplayer.dash.mpd.Representation
-
-
Constructs a new instance.
-
-
newInstance(String, long, Format, String, SegmentBase, String) - Static method in class com.google.android.exoplayer.dash.mpd.Representation
-
-
Constructs a new instance.
-
-
newInstance(String, long, Format, String, long, long, long, long, String, long) - Static method in class com.google.android.exoplayer.dash.mpd.Representation.SingleSegmentRepresentation
-
 
-
newInstance(UUID, Looper, MediaDrmCallback, HashMap<String, String>, Handler, StreamingDrmSessionManager.EventListener, ExoMediaDrm<T>) - Static method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
newInstance(int, int, int) - Static method in class com.google.android.exoplayer.ExoPlayer.Factory
-
-
Obtains an ExoPlayer instance.
-
-
newInstance(int) - Static method in class com.google.android.exoplayer.ExoPlayer.Factory
-
-
Obtains an ExoPlayer instance.
-
-
newMediaChunk(DashChunkSource.PeriodHolder, DashChunkSource.RepresentationHolder, DataSource, MediaFormat, DashChunkSource.ExposedTrack, int, int, boolean) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
newPlayReadyInstance(Looper, MediaDrmCallback, String, Handler, StreamingDrmSessionManager.EventListener) - Static method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
-
Instantiates a new instance using the PlayReady scheme.
-
-
newSingleThreadExecutor(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Instantiates a new single threaded executor whose thread has the specified name.
-
-
newSingleThreadScheduledExecutor(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Instantiates a new single threaded scheduled executor whose thread has the specified name.
-
-
newSubtitleInstance() - Static method in class com.google.android.exoplayer.hls.DefaultHlsTrackSelector
-
-
Creates a DefaultHlsTrackSelector that selects subtitle renditions.
-
-
newTextInstance() - Static method in class com.google.android.exoplayer.dash.DefaultDashTrackSelector
-
 
-
newTextInstance() - Static method in class com.google.android.exoplayer.smoothstreaming.DefaultSmoothStreamingTrackSelector
-
 
-
newVideoInstance(Context, boolean, boolean) - Static method in class com.google.android.exoplayer.dash.DefaultDashTrackSelector
-
 
-
newVideoInstance(Context, boolean, boolean) - Static method in class com.google.android.exoplayer.smoothstreaming.DefaultSmoothStreamingTrackSelector
-
 
-
newWidevineInstance(Looper, MediaDrmCallback, HashMap<String, String>, Handler, StreamingDrmSessionManager.EventListener) - Static method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
-
Instantiates a new instance using the Widevine scheme.
-
-
NO_DISCONTINUITY - Static variable in interface com.google.android.exoplayer.SampleSource
-
-
Returned from SampleSource.SampleSourceReader.readDiscontinuity(int) to indicate no discontinuity.
-
-
NO_ESTIMATE - Static variable in interface com.google.android.exoplayer.upstream.BandwidthMeter
-
-
Indicates no bandwidth estimate is available.
-
-
NO_PARENT_ID - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.parentId if no parent id need be specified.
-
-
NO_VALUE - Static variable in class com.google.android.exoplayer.MediaFormat
-
 
-
NoAdaptationSetException(String) - Constructor for exception com.google.android.exoplayer.dash.DashChunkSource.NoAdaptationSetException
-
 
-
NoOpCacheEvictor - Class in com.google.android.exoplayer.upstream.cache
-
-
Evictor that doesn't ever evict cache files.
-
-
NoOpCacheEvictor() - Constructor for class com.google.android.exoplayer.upstream.cache.NoOpCacheEvictor
-
 
-
NOTHING_READ - Static variable in interface com.google.android.exoplayer.SampleSource
-
-
Neither a sample nor a format was read in full.
-
-
numBytesOfClearData - Variable in class com.google.android.exoplayer.CryptoInfo
-
 
-
numBytesOfEncryptedData - Variable in class com.google.android.exoplayer.CryptoInfo
-
 
-
numSubSamples - Variable in class com.google.android.exoplayer.CryptoInfo
-
 
-
- - - -

O

-
-
OFFSET_SAMPLE_RELATIVE - Static variable in class com.google.android.exoplayer.MediaFormat
-
-
A value for MediaFormat.subsampleOffsetUs to indicate that subsample timestamps are relative to - the timestamps of their parent samples.
-
-
offsets - Variable in class com.google.android.exoplayer.extractor.ChunkIndex
-
-
The chunk byte offsets.
-
-
OggExtractor - Class in com.google.android.exoplayer.extractor.ogg
-
- -
-
OggExtractor() - Constructor for class com.google.android.exoplayer.extractor.ogg.OggExtractor
-
 
-
onAudioCapabilitiesChanged(AudioCapabilities) - Method in interface com.google.android.exoplayer.audio.AudioCapabilitiesReceiver.Listener
-
-
Called when the audio capabilities change.
-
-
onAudioSessionId(int) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
-
Invoked when the audio session id becomes known.
-
-
onAudioTrackInitializationError(AudioTrack.InitializationException) - Method in interface com.google.android.exoplayer.MediaCodecAudioTrackRenderer.EventListener
-
-
Invoked when an AudioTrack fails to initialize.
-
-
onAudioTrackUnderrun(int, long, long) - Method in interface com.google.android.exoplayer.MediaCodecAudioTrackRenderer.EventListener
-
-
Invoked when an AudioTrack underrun occurs.
-
-
onAudioTrackWriteError(AudioTrack.WriteException) - Method in interface com.google.android.exoplayer.MediaCodecAudioTrackRenderer.EventListener
-
-
Invoked when an AudioTrack write fails.
-
-
onAvailableRangeChanged(int, TimeRange) - Method in interface com.google.android.exoplayer.dash.DashChunkSource.EventListener
-
-
Invoked when the available seek range of the stream has changed.
-
-
onBandwidthSample(int, long, long) - Method in interface com.google.android.exoplayer.upstream.BandwidthMeter.EventListener
-
-
Invoked periodically to indicate that bytes have been transferred.
-
-
onBytesTransferred(int) - Method in class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
onBytesTransferred(int) - Method in interface com.google.android.exoplayer.upstream.TransferListener
-
-
Called incrementally during a transfer.
-
-
onCachedBytesRead(long, long) - Method in interface com.google.android.exoplayer.upstream.cache.CacheDataSource.EventListener
-
-
Invoked when bytes have been read from the cache.
-
-
onCacheInitialized() - Method in interface com.google.android.exoplayer.upstream.cache.CacheEvictor
-
-
Invoked when cache has been initialized.
-
-
onCacheInitialized() - Method in class com.google.android.exoplayer.upstream.cache.LeastRecentlyUsedCacheEvictor
-
 
-
onCacheInitialized() - Method in class com.google.android.exoplayer.upstream.cache.NoOpCacheEvictor
-
 
-
onChunkLoadCompleted(Chunk) - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Invoked when the ChunkSampleSource has finished loading a chunk obtained from this - source.
-
-
onChunkLoadCompleted(Chunk) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
onChunkLoadCompleted(Chunk) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Invoked when the HlsSampleSource has finished loading a chunk obtained from this - source.
-
-
onChunkLoadCompleted(Chunk) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
onChunkLoadError(Chunk, Exception) - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Invoked when the ChunkSampleSource encounters an error loading a chunk obtained from - this source.
-
-
onChunkLoadError(Chunk, Exception) - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
onChunkLoadError(Chunk, IOException) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Invoked when the HlsSampleSource encounters an error loading a chunk obtained from - this source.
-
-
onChunkLoadError(Chunk, Exception) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
onCryptoError(MediaCodec.CryptoException) - Method in interface com.google.android.exoplayer.MediaCodecTrackRenderer.EventListener
-
-
Invoked when a decoder operation raises a MediaCodec.CryptoException.
-
-
onCues(List<Cue>) - Method in interface com.google.android.exoplayer.text.TextRenderer
-
-
Invoked each time there is a change in the Cues to be rendered.
-
-
onDecoderError(E) - Method in interface com.google.android.exoplayer.util.extensions.SimpleDecoder.EventListener
-
-
Invoked when the decoder encounters an error.
-
-
onDecoderInitializationError(MediaCodecTrackRenderer.DecoderInitializationException) - Method in interface com.google.android.exoplayer.MediaCodecTrackRenderer.EventListener
-
-
Invoked when a decoder fails to initialize.
-
-
onDecoderInitialized(String, long, long) - Method in interface com.google.android.exoplayer.MediaCodecTrackRenderer.EventListener
-
-
Invoked when a decoder is successfully created.
-
-
onDisabled() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
onDisabled() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
onDisabled() - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
onDisabled() - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
onDisabled() - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
onDisabled() - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
onDisabled() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Called when the renderer is disabled.
-
-
onDiscontinuity(long) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
onDiscontinuity(long) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
onDiscontinuity(long) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
onDiscontinuity(long) - Method in class com.google.android.exoplayer.metadata.MetadataTrackRenderer
-
 
-
onDiscontinuity(long) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
-
Invoked when a discontinuity is encountered.
-
-
onDiscontinuity(long) - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
onDiscontinuity(long) - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
onDownstreamFormatChanged(int, Format, int, long) - Method in interface com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener
-
-
Invoked when the downstream format changes (i.e.
-
-
onDrawnToSurface(Surface) - Method in interface com.google.android.exoplayer.MediaCodecVideoTrackRenderer.EventListener
-
-
Invoked when a frame is rendered to a surface for the first time following that surface - having been set as the target for the renderer.
-
-
onDrmKeysLoaded() - Method in interface com.google.android.exoplayer.drm.StreamingDrmSessionManager.EventListener
-
-
Invoked each time keys are loaded.
-
-
onDrmSessionManagerError(Exception) - Method in interface com.google.android.exoplayer.drm.StreamingDrmSessionManager.EventListener
-
-
Invoked when a drm error occurs.
-
-
onDroppedFrames(int, long) - Method in interface com.google.android.exoplayer.MediaCodecVideoTrackRenderer.EventListener
-
-
Invoked to report the number of frames dropped by the renderer.
-
-
onEnabled(int, long, boolean) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
onEnabled(int, long, boolean) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
onEnabled(int, long, boolean) - Method in class com.google.android.exoplayer.text.eia608.Eia608TrackRenderer
-
 
-
onEnabled(int, long, boolean) - Method in class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
onEnabled(int, long, boolean) - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Called when the renderer is enabled.
-
-
onEvent(ExoMediaDrm<? extends T>, byte[], int, int, byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm.OnEventListener
-
-
Called when an event occurs that requires the app to be notified
-
-
onInputFormatChanged(MediaFormatHolder) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
onInputFormatChanged(MediaFormatHolder) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Invoked when a new format is read from the upstream SampleSource.
-
-
onInputFormatChanged(MediaFormatHolder) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
onLoadCanceled(int, long) - Method in interface com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener
-
-
Invoked when the current upstream load operation is canceled.
-
-
onLoadCanceled(Loader.Loadable) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
onLoadCanceled(Loader.Loadable) - Method in class com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver
-
 
-
onLoadCanceled(Loader.Loadable) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
onLoadCanceled(Loader.Loadable) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
onLoadCanceled(Loader.Loadable) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
onLoadCanceled(Loader.Loadable) - Method in interface com.google.android.exoplayer.upstream.Loader.Callback
-
-
Invoked when loading has been canceled.
-
-
onLoadCanceled(Loader.Loadable) - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
 
-
onLoadCompleted(int, long, int, int, Format, long, long, long, long) - Method in interface com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener
-
-
Invoked when the current load operation completes.
-
-
onLoadCompleted(Loader.Loadable) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
onLoadCompleted(Loader.Loadable) - Method in class com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver
-
 
-
onLoadCompleted(Loader.Loadable) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
onLoadCompleted(Loader.Loadable) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
onLoadCompleted(Loader.Loadable) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
onLoadCompleted(Loader.Loadable) - Method in interface com.google.android.exoplayer.upstream.Loader.Callback
-
-
Invoked when the data source has been fully loaded.
-
-
onLoadCompleted(Loader.Loadable) - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
 
-
onLoadError(int, IOException) - Method in interface com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener
-
-
Invoked when an error occurs loading media data.
-
-
onLoadError(Loader.Loadable, IOException) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
onLoadError(Loader.Loadable, IOException) - Method in class com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver
-
 
-
onLoadError(int, IOException) - Method in interface com.google.android.exoplayer.extractor.ExtractorSampleSource.EventListener
-
-
Invoked when an error occurs loading media data.
-
-
onLoadError(Loader.Loadable, IOException) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
onLoadError(Loader.Loadable, IOException) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
onLoadError(int, IOException) - Method in interface com.google.android.exoplayer.SingleSampleSource.EventListener
-
-
Invoked when an error occurs loading media data.
-
-
onLoadError(Loader.Loadable, IOException) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
onLoadError(Loader.Loadable, IOException) - Method in interface com.google.android.exoplayer.upstream.Loader.Callback
-
-
Invoked when the data source is stopped due to an error.
-
-
onLoadError(Loader.Loadable, IOException) - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
 
-
onLoadingChanged(boolean) - Method in interface com.google.android.exoplayer.DefaultLoadControl.EventListener
-
-
Invoked when the control transitions from a loading to a draining state, or vice versa.
-
-
onLoadStarted(int, long, int, int, Format, long, long) - Method in interface com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener
-
-
Invoked when an upstream load is started.
-
-
onManifestError(IOException) - Method in interface com.google.android.exoplayer.util.ManifestFetcher.EventListener
-
 
-
onManifestRefreshed() - Method in interface com.google.android.exoplayer.util.ManifestFetcher.EventListener
-
 
-
onManifestRefreshStarted() - Method in interface com.google.android.exoplayer.util.ManifestFetcher.EventListener
-
 
-
onMeasure(int, int) - Method in class com.google.android.exoplayer.AspectRatioFrameLayout
-
 
-
onMediaPlaylistLoadCompleted(byte[]) - Method in interface com.google.android.exoplayer.hls.HlsChunkSource.EventListener
-
-
Invoked when a media playlist has been loaded.
-
-
onMetadata(T) - Method in interface com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer
-
-
Invoked each time there is a metadata associated with current playback time.
-
-
onOutputFormatChanged(MediaCodec, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
onOutputFormatChanged(MediaCodec, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Invoked when the output format of the MediaCodec changes.
-
-
onOutputFormatChanged(MediaCodec, MediaFormat) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
onOutputStreamEnded() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
onOutputStreamEnded() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Invoked when the output stream ends, meaning that the last output buffer has been processed - and the MediaCodec.BUFFER_FLAG_END_OF_STREAM flag has been propagated through the - decoder.
-
-
onPlayerError(ExoPlaybackException) - Method in interface com.google.android.exoplayer.ExoPlayer.Listener
-
-
Invoked when an error occurs.
-
-
onPlayerStateChanged(boolean, int) - Method in interface com.google.android.exoplayer.ExoPlayer.Listener
-
-
Invoked when the value returned from either ExoPlayer.getPlayWhenReady() or - ExoPlayer.getPlaybackState() changes.
-
-
onPlayWhenReadyCommitted() - Method in interface com.google.android.exoplayer.ExoPlayer.Listener
-
-
Invoked when the current value of ExoPlayer.getPlayWhenReady() has been reflected - by the internal playback thread.
-
-
onProcessedOutputBuffer(long) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Invoked when an output buffer is successfully processed.
-
-
onQueuedInputBuffer(long, ByteBuffer, int, boolean) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Invoked immediately before an input buffer is queued into the codec.
-
-
onReleased() - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
onReleased() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Called when the renderer is released.
-
-
onSampleRead(MediaChunk, SampleHolder) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
-
Called when a sample has been read.
-
-
onSingleManifest(T) - Method in interface com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
-
-
Invoked when the load has successfully completed.
-
-
onSingleManifestError(IOException) - Method in interface com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
-
-
Invoked when the load has failed.
-
-
onSpanAdded(Cache, CacheSpan) - Method in interface com.google.android.exoplayer.upstream.cache.Cache.Listener
-
-
Invoked when a CacheSpan is added to the cache.
-
-
onSpanAdded(Cache, CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.LeastRecentlyUsedCacheEvictor
-
 
-
onSpanAdded(Cache, CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.NoOpCacheEvictor
-
 
-
onSpanRemoved(Cache, CacheSpan) - Method in interface com.google.android.exoplayer.upstream.cache.Cache.Listener
-
-
Invoked when a CacheSpan is removed from the cache.
-
-
onSpanRemoved(Cache, CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.LeastRecentlyUsedCacheEvictor
-
 
-
onSpanRemoved(Cache, CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.NoOpCacheEvictor
-
 
-
onSpanTouched(Cache, CacheSpan, CacheSpan) - Method in interface com.google.android.exoplayer.upstream.cache.Cache.Listener
-
-
Invoked when an existing CacheSpan is accessed, causing it to be replaced.
-
-
onSpanTouched(Cache, CacheSpan, CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.LeastRecentlyUsedCacheEvictor
-
 
-
onSpanTouched(Cache, CacheSpan, CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.NoOpCacheEvictor
-
 
-
onStarted() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
onStarted() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
onStarted() - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
onStarted() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Called when the renderer is started.
-
-
onStartFile(Cache, String, long, long) - Method in interface com.google.android.exoplayer.upstream.cache.CacheEvictor
-
-
Invoked when a writer starts writing to the cache.
-
-
onStartFile(Cache, String, long, long) - Method in class com.google.android.exoplayer.upstream.cache.LeastRecentlyUsedCacheEvictor
-
 
-
onStartFile(Cache, String, long, long) - Method in class com.google.android.exoplayer.upstream.cache.NoOpCacheEvictor
-
 
-
onStopped() - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
onStopped() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
onStopped() - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
onStopped() - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Called when the renderer is stopped.
-
-
onSynced() - Method in class com.google.android.exoplayer.VideoFrameReleaseTimeHelper
-
 
-
onTimestampError(UtcTimingElement, IOException) - Method in interface com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback
-
-
Invoked when the element was not successfully resolved.
-
-
onTimestampResolved(UtcTimingElement, long) - Method in interface com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback
-
-
Invoked when the element has been resolved.
-
-
onTransferEnd() - Method in class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
onTransferEnd() - Method in interface com.google.android.exoplayer.upstream.TransferListener
-
-
Invoked when a transfer ends.
-
-
onTransferStart() - Method in class com.google.android.exoplayer.upstream.DefaultBandwidthMeter
-
 
-
onTransferStart() - Method in interface com.google.android.exoplayer.upstream.TransferListener
-
-
Invoked when a transfer starts.
-
-
onUpstreamDiscarded(int, long, long) - Method in interface com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener
-
-
Invoked when data is removed from the back of the buffer, typically so that it can be - re-buffered using a different representation.
-
-
onVideoSizeChanged(int, int, int, float) - Method in interface com.google.android.exoplayer.MediaCodecVideoTrackRenderer.EventListener
-
-
Invoked each time there's a change in the size of the video being rendered.
-
-
open(DrmInitData) - Method in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
Opens the session, possibly asynchronously.
-
-
open(DrmInitData) - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.AssetDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.ByteArrayDataSink
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.ByteArrayDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.cache.CacheDataSink
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.cache.CacheDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.ContentDataSource
-
 
-
open(DataSpec) - Method in interface com.google.android.exoplayer.upstream.DataSink
-
-
Opens the DataSink to consume the specified data.
-
-
open(DataSpec) - Method in interface com.google.android.exoplayer.upstream.DataSource
-
-
Opens the DataSource to read the specified data.
-
-
open() - Method in class com.google.android.exoplayer.upstream.DataSourceInputStream
-
-
Optional call to open the underlying DataSource.
-
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.FileDataSource
-
 
-
open(DataSpec) - Method in interface com.google.android.exoplayer.upstream.HttpDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.PriorityDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.TeeDataSource
-
 
-
open(DataSpec) - Method in class com.google.android.exoplayer.upstream.UdpDataSource
-
 
-
openRead() - Method in class com.google.android.exoplayer.util.AtomicFile
-
-
Open the atomic file for reading.
-
-
openSession() - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
openSession() - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
OutputBuffer - Class in com.google.android.exoplayer.util.extensions
-
-
Output buffer decoded by a Decoder.
-
-
OutputBuffer() - Constructor for class com.google.android.exoplayer.util.extensions.OutputBuffer
-
 
-
outputBuffersChangedCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
outputFormatChangedCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
owner - Variable in class com.google.android.exoplayer.metadata.id3.PrivFrame
-
 
-
- - - -

P

-
-
parentId - Variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Optional identifier for a parent from which this chunk originates.
-
-
ParsableBitArray - Class in com.google.android.exoplayer.util
-
-
Wraps a byte array, providing methods that allow it to be read as a bitstream.
-
-
ParsableBitArray() - Constructor for class com.google.android.exoplayer.util.ParsableBitArray
-
-
Creates a new instance that initially has no backing data.
-
-
ParsableBitArray(byte[]) - Constructor for class com.google.android.exoplayer.util.ParsableBitArray
-
-
Creates a new instance that wraps an existing array.
-
-
ParsableBitArray(byte[], int) - Constructor for class com.google.android.exoplayer.util.ParsableBitArray
-
-
Creates a new instance that wraps an existing array.
-
-
ParsableByteArray - Class in com.google.android.exoplayer.util
-
-
Wraps a byte array, providing a set of methods for parsing data from it.
-
-
ParsableByteArray() - Constructor for class com.google.android.exoplayer.util.ParsableByteArray
-
-
Creates a new instance that initially has no backing data.
-
-
ParsableByteArray(int) - Constructor for class com.google.android.exoplayer.util.ParsableByteArray
-
-
Creates a new instance with length bytes.
-
-
ParsableByteArray(byte[]) - Constructor for class com.google.android.exoplayer.util.ParsableByteArray
-
-
Creates a new instance wrapping data.
-
-
ParsableByteArray(byte[], int) - Constructor for class com.google.android.exoplayer.util.ParsableByteArray
-
-
Creates a new instance that wraps an existing array.
-
-
parse(String, InputStream) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parse(String, InputStream) - Method in class com.google.android.exoplayer.hls.HlsPlaylistParser
-
 
-
parse(byte[], int) - Method in class com.google.android.exoplayer.metadata.id3.Id3Parser
-
 
-
parse(byte[], int) - Method in interface com.google.android.exoplayer.metadata.MetadataParser
-
-
Parses a metadata object from the provided binary data.
-
-
parse(String, InputStream) - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser
-
 
-
parse(byte[], int, int) - Method in class com.google.android.exoplayer.text.subrip.SubripParser
-
 
-
parse(byte[], int, int) - Method in interface com.google.android.exoplayer.text.SubtitleParser
-
-
Parses a Subtitle from the provided byte[].
-
-
parse(byte[], int, int) - Method in class com.google.android.exoplayer.text.ttml.TtmlParser
-
 
-
parse(byte[], int, int) - Method in class com.google.android.exoplayer.text.tx3g.Tx3gParser
-
 
-
parse(byte[], int, int) - Method in class com.google.android.exoplayer.text.webvtt.Mp4WebvttParser
-
 
-
parse(byte[], int, int) - Method in class com.google.android.exoplayer.text.webvtt.WebvttParser
-
 
-
parse(String, InputStream) - Method in interface com.google.android.exoplayer.upstream.UriLoadable.Parser
-
-
Parses an object from a response.
-
-
parseAacAudioSpecificConfig(byte[]) - Static method in class com.google.android.exoplayer.util.CodecSpecificDataUtil
-
-
Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
-
-
parseAc3AnnexFFormat(ParsableByteArray, String, long, String) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the AC-3 format given data containing the AC3SpecificBox according to - ETSI TS 102 366 Annex F.
-
-
parseAc3SyncframeFormat(ParsableBitArray, String, long, String) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the AC-3 format given data containing a syncframe.
-
-
parseAc3SyncframeSize(byte[]) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the size in bytes of the given AC-3 syncframe.
-
-
parseAdaptationSet(XmlPullParser, String, SegmentBase) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseAdaptationSetChild(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
-
Parses children of AdaptationSet elements not specifically parsed elsewhere.
-
-
parseAudioChannelConfiguration(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseBaseUrl(XmlPullParser, String) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseChildNalUnit(ParsableByteArray) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Constructs and returns a NAL unit with a start code followed by the data in atom.
-
-
parseContentProtection(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
-
Parses a ContentProtection element.
-
-
parseContentType(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseDateTime(XmlPullParser, String, long) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseDtsAudioSampleCount(byte[]) - Static method in class com.google.android.exoplayer.util.DtsUtil
-
-
Returns the number of audio samples represented by the given DTS frame.
-
-
parseDtsAudioSampleCount(ByteBuffer) - Static method in class com.google.android.exoplayer.util.DtsUtil
-
-
Like DtsUtil.parseDtsAudioSampleCount(byte[]) but reads from a byte buffer.
-
-
parseDtsFormat(byte[], String, long, String) - Static method in class com.google.android.exoplayer.util.DtsUtil
-
-
Returns the DTS format given data containing the DTS frame according to ETSI TS 102 114 - subsections 5.3/5.4.
-
-
parseDuration(XmlPullParser, String, long) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseEAc3AnnexFFormat(ParsableByteArray, String, long, String) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the E-AC-3 format given data containing the EC3SpecificBox according to - ETSI TS 102 366 Annex F.
-
-
parseEAc3SyncframeAudioSampleCount(byte[]) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the number of audio samples represented by the given E-AC-3 syncframe.
-
-
parseEAc3SyncframeAudioSampleCount(ByteBuffer) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Like Ac3Util.parseEAc3SyncframeAudioSampleCount(byte[]) but reads from a byte buffer.
-
-
parseEac3SyncframeFormat(ParsableBitArray, String, long, String) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the E-AC-3 format given data containing a syncframe.
-
-
parseEAc3SyncframeSize(byte[]) - Static method in class com.google.android.exoplayer.util.Ac3Util
-
-
Returns the size in bytes of the given E-AC-3 syncframe.
-
-
parseEmsg(ParsableByteArray, long) - Method in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
parseFrameRate(XmlPullParser, float) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseInitialization(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseInt(XmlPullParser, String) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseInt(XmlPullParser, String, int) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseLong(XmlPullParser, String) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseLong(XmlPullParser, String, long) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseMediaPresentationDescription(XmlPullParser, String) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parsePercentage(String) - Static method in class com.google.android.exoplayer.text.webvtt.WebvttParserUtil
-
-
Parses a percentage and returns a scaled float.
-
-
parsePeriod(XmlPullParser, String, long) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parsePpsNalUnit(ParsableBitArray) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection - 7.3.2.2.
-
-
parseRangedUrl(XmlPullParser, String, String) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseRepresentation(XmlPullParser, String, String, String, int, int, float, int, int, String, SegmentBase, MediaPresentationDescriptionParser.ContentProtectionsBuilder) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
ParserException - Exception in com.google.android.exoplayer
-
-
Thrown when an error occurs parsing media data.
-
-
ParserException() - Constructor for exception com.google.android.exoplayer.ParserException
-
 
-
ParserException(String) - Constructor for exception com.google.android.exoplayer.ParserException
-
 
-
ParserException(Throwable) - Constructor for exception com.google.android.exoplayer.ParserException
-
 
-
ParserException(String, Throwable) - Constructor for exception com.google.android.exoplayer.ParserException
-
 
-
ParserUtil - Class in com.google.android.exoplayer.util
-
-
Parser utility functions.
-
-
parseSchemeSpecificData(byte[], UUID) - Static method in class com.google.android.exoplayer.extractor.mp4.PsshAtomUtil
-
-
Parses the scheme specific data from a PSSH atom.
-
-
parseSeekTable(ParsableByteArray) - Static method in class com.google.android.exoplayer.util.FlacSeekTable
-
-
Parses a FLAC file seek table metadata structure and creates a FlacSeekTable instance.
-
-
parseSegmentBase(XmlPullParser, SegmentBase.SingleSegmentBase) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseSegmentList(XmlPullParser, SegmentBase.SegmentList) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseSegmentTemplate(XmlPullParser, SegmentBase.SegmentTemplate) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseSegmentTimeline(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseSegmentUrl(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseSpsNalUnit(ParsableBitArray) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Parses an SPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection - 7.3.2.1.1.
-
-
parseString(XmlPullParser, String, String) - Static method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseTimestampUs(String) - Static method in class com.google.android.exoplayer.text.webvtt.WebvttParserUtil
-
-
Parses a WebVTT timestamp.
-
-
parseUrlTemplate(XmlPullParser, String, UrlTemplate) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseUtcTiming(XmlPullParser) - Method in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
-
 
-
parseUuid(byte[]) - Static method in class com.google.android.exoplayer.extractor.mp4.PsshAtomUtil
-
-
Parses the UUID from a PSSH atom.
-
-
parseXsDateTime(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Parses an xs:dateTime attribute value, returning the parsed timestamp in milliseconds since - the epoch.
-
-
parseXsDuration(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Parses an xs:duration attribute value, returning the parsed duration in milliseconds.
-
-
pause() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Pauses playback.
-
-
pause() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
pcmEncoding - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The encoding for PCM audio streams.
-
-
peekFully(byte[], int, int, boolean) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
peekFully(byte[], int, int) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
peekFully(byte[], int, int, boolean) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Peeks length bytes from the peek position, writing them into target at index - offset.
-
-
peekFully(byte[], int, int) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Peeks length bytes from the peek position, writing them into target at index - offset.
-
-
Period - Class in com.google.android.exoplayer.dash.mpd
-
-
Encapsulates media content components over a contiguous period of time.
-
-
Period(String, long, List<AdaptationSet>) - Constructor for class com.google.android.exoplayer.dash.mpd.Period
-
 
-
PeriodHolder(int, MediaPresentationDescription, int, DashChunkSource.ExposedTrack) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
picOrderCntLsbLength - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
picOrderCountType - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
picParameterSetId - Variable in class com.google.android.exoplayer.util.NalUnitUtil.PpsData
-
 
-
pictureData - Variable in class com.google.android.exoplayer.metadata.id3.ApicFrame
-
 
-
pictureType - Variable in class com.google.android.exoplayer.metadata.id3.ApicFrame
-
 
-
pixelWidthAspectRatio - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
pixelWidthHeightRatio - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The width to height ratio of pixels in the video, or MediaFormat.NO_VALUE if unknown or not - applicable.
-
-
play() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Starts or resumes playing audio if the audio track has been initialized.
-
-
PlayerControl - Class in com.google.android.exoplayer.util
-
-
An implementation of MediaController.MediaPlayerControl for controlling an ExoPlayer instance.
-
-
PlayerControl(ExoPlayer) - Constructor for class com.google.android.exoplayer.util.PlayerControl
-
 
-
PLAYREADY_CUSTOM_DATA_KEY - Static variable in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
-
The key to use when passing CustomData to a PlayReady instance in an optional parameter map.
-
-
PLAYREADY_UUID - Static variable in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
-
UUID for the PlayReady DRM scheme.
-
-
populateHeader(int, MpegAudioHeader) - Static method in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Parses headerData, populating header with the parsed data.
-
-
position - Variable in class com.google.android.exoplayer.extractor.PositionHolder
-
-
The held position.
-
-
position - Variable in class com.google.android.exoplayer.text.Cue
-
-
The fractional position of the Cue.positionAnchor of the cue box within the viewport in - the direction orthogonal to Cue.line, or Cue.DIMEN_UNSET.
-
-
position - Variable in class com.google.android.exoplayer.upstream.cache.CacheSpan
-
-
The position of the CacheSpan in the original stream.
-
-
position - Variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
The position of the data when read from DataSpec.uri.
-
-
positionAnchor - Variable in class com.google.android.exoplayer.text.Cue
-
-
The cue box anchor positioned by Cue.position.
-
-
PositionHolder - Class in com.google.android.exoplayer.extractor
-
-
Holds a position in the stream.
-
-
PositionHolder() - Constructor for class com.google.android.exoplayer.extractor.PositionHolder
-
 
-
postBody - Variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
Body for a POST request, null otherwise.
-
-
PpsData(int, int, boolean) - Constructor for class com.google.android.exoplayer.util.NalUnitUtil.PpsData
-
 
-
Predicate<T> - Interface in com.google.android.exoplayer.util
-
-
Determines a true of false value for a given input.
-
-
prepare(long) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
prepare() - Method in interface com.google.android.exoplayer.chunk.ChunkSource
-
-
Prepares the source.
-
-
prepare() - Method in class com.google.android.exoplayer.dash.DashChunkSource
-
 
-
prepare(TrackRenderer...) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Prepares the player for playback.
-
-
prepare(long) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
prepare(long) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
prepare() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Prepares the source.
-
-
prepare(long) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
prepare(long) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Prepares the source.
-
-
prepare(long) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
prepare() - Method in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
 
-
presentationTimeOffsetUs - Variable in class com.google.android.exoplayer.dash.mpd.Representation
-
-
The offset of the presentation timestamps in the media stream relative to media time.
-
-
PriorityDataSource - Class in com.google.android.exoplayer.upstream
-
-
Allows PriorityDataSource.open(DataSpec) and PriorityDataSource.read(byte[], int, int) calls only if the specified - priority is the highest priority of any task.
-
-
PriorityDataSource(int, DataSource) - Constructor for class com.google.android.exoplayer.upstream.PriorityDataSource
-
 
-
PriorityHandlerThread - Class in com.google.android.exoplayer.util
-
-
A HandlerThread with a specified process priority.
-
-
PriorityHandlerThread(String, int) - Constructor for class com.google.android.exoplayer.util.PriorityHandlerThread
-
 
-
PriorityTooLowException(int, int) - Constructor for exception com.google.android.exoplayer.upstream.NetworkLock.PriorityTooLowException
-
 
-
PRIVATE_STREAM_1 - Static variable in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
privateData - Variable in class com.google.android.exoplayer.metadata.id3.PrivFrame
-
 
-
PrivFrame - Class in com.google.android.exoplayer.metadata.id3
-
-
PRIV (Private) ID3 frame.
-
-
PrivFrame(String, byte[]) - Constructor for class com.google.android.exoplayer.metadata.id3.PrivFrame
-
 
-
proceed(int) - Method in class com.google.android.exoplayer.upstream.NetworkLock
-
-
Blocks until the passed priority is the lowest one (i.e.
-
-
proceedNonBlocking(int) - Method in class com.google.android.exoplayer.upstream.NetworkLock
-
-
A non-blocking variant of NetworkLock.proceed(int).
-
-
proceedOrThrow(int) - Method in class com.google.android.exoplayer.upstream.NetworkLock
-
-
A throwing variant of NetworkLock.proceed(int).
-
-
processOutputBuffer(long, long, MediaCodec, ByteBuffer, MediaCodec.BufferInfo, int, boolean) - Method in class com.google.android.exoplayer.MediaCodecAudioTrackRenderer
-
 
-
processOutputBuffer(long, long, MediaCodec, ByteBuffer, MediaCodec.BufferInfo, int, boolean) - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Processes the provided output buffer.
-
-
processOutputBuffer(long, long, MediaCodec, ByteBuffer, MediaCodec.BufferInfo, int, boolean) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
projectionData - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The projection data for 360/VR video, or null if not applicable.
-
-
protectionElement - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
Content protection information, or null if the content is not protected.
-
-
ProtectionElement(UUID, byte[]) - Constructor for class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement
-
 
-
provideKeyResponse(byte[], byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
provideKeyResponse(byte[], byte[]) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
provideProvisionResponse(byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
provideProvisionResponse(byte[]) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
PsExtractor - Class in com.google.android.exoplayer.extractor.ts
-
-
Facilitates the extraction of data from the MPEG-2 TS container format.
-
-
PsExtractor() - Constructor for class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
PsExtractor(PtsTimestampAdjuster) - Constructor for class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
PsshAtomUtil - Class in com.google.android.exoplayer.extractor.mp4
-
-
Utility methods for handling PSSH atoms.
-
-
PtsTimestampAdjuster - Class in com.google.android.exoplayer.extractor.ts
-
-
Scales and adjusts MPEG-2 TS presentation timestamps, taking into account an initial offset and - timestamp rollover.
-
-
PtsTimestampAdjuster(long) - Constructor for class com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster
-
 
-
PtsTimestampAdjusterProvider - Class in com.google.android.exoplayer.hls
-
-
Provides PtsTimestampAdjuster instances for use during HLS playbacks.
-
-
PtsTimestampAdjusterProvider() - Constructor for class com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider
-
 
-
ptsToUs(long) - Static method in class com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster
-
-
Converts a value in MPEG-2 timestamp units to the corresponding value in microseconds.
-
-
put(UUID, DrmInitData.SchemeInitData) - Method in class com.google.android.exoplayer.drm.DrmInitData.Mapped
-
-
Inserts scheme specific initialization data.
-
-
- - - -

Q

-
-
qualityLevels - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
queryKeyStatus(byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
queryKeyStatus(byte[]) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
queueInputBuffer(I) - Method in interface com.google.android.exoplayer.util.extensions.Decoder
-
-
Queues an input buffer to the decoder.
-
-
queueInputBuffer(I) - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
 
-
queueSize - Variable in class com.google.android.exoplayer.chunk.ChunkOperationHolder
-
-
The number of MediaChunks to retain in a queue.
-
-
queueSize - Variable in class com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
-
-
The desired size of the queue.
-
-
- - - -

R

-
-
RandomEvaluator() - Constructor for class com.google.android.exoplayer.chunk.FormatEvaluator.RandomEvaluator
-
 
-
RandomEvaluator(int) - Constructor for class com.google.android.exoplayer.chunk.FormatEvaluator.RandomEvaluator
-
 
-
RangedUri - Class in com.google.android.exoplayer.dash.mpd
-
-
Defines a range of data located at a reference uri.
-
-
RangedUri(String, long, long) - Constructor for class com.google.android.exoplayer.dash.mpd.RangedUri
-
-
Constructs an ranged uri.
-
-
read(ExtractorInput) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
-
Reads from the provided ExtractorInput.
-
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
read(ExtractorInput, PositionHolder) - Method in interface com.google.android.exoplayer.extractor.Extractor
-
-
Extracts data read from a provided ExtractorInput.
-
-
read(byte[], int, int) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Reads up to length bytes from the input and resets the peek position.
-
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.mp3.Mp3Extractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.ogg.OggExtractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.ts.AdtsExtractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
read(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer.extractor.webm.WebmExtractor
-
 
-
read(ExtractorInput) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
-
Reads from the provided ExtractorInput.
-
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.AssetDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.ByteArrayDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.cache.CacheDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.ContentDataSource
-
 
-
read(byte[], int, int) - Method in interface com.google.android.exoplayer.upstream.DataSource
-
-
Reads up to length bytes of data and stores them into buffer, starting at - index offset.
-
-
read() - Method in class com.google.android.exoplayer.upstream.DataSourceInputStream
-
 
-
read(byte[]) - Method in class com.google.android.exoplayer.upstream.DataSourceInputStream
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.DataSourceInputStream
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.DefaultUriDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.FileDataSource
-
 
-
read(byte[], int, int) - Method in interface com.google.android.exoplayer.upstream.HttpDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.PriorityDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.TeeDataSource
-
 
-
read(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.UdpDataSource
-
 
-
readBit() - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Reads a single bit.
-
-
readBits(int) - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Reads up to 32 bits.
-
-
readBytes(ParsableBitArray, int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next length bytes into bitArray, and resets the position of - bitArray to zero.
-
-
readBytes(byte[], int, int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next length bytes into buffer at offset.
-
-
readBytes(ByteBuffer, int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next length bytes into buffer.
-
-
readData(int, long, MediaFormatHolder, SampleHolder) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
readData(int, long, MediaFormatHolder, SampleHolder) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
readData(int, long, MediaFormatHolder, SampleHolder) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
readData(int, long, MediaFormatHolder, SampleHolder) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
readData(int, long, MediaFormatHolder, SampleHolder) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Attempts to read a sample or a new format from the source.
-
-
readData(int, long, MediaFormatHolder, SampleHolder) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
readDiscontinuity(int) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
readDiscontinuity(int) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
readDiscontinuity(int) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
readDiscontinuity(int) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
readDiscontinuity(int) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Attempts to read a pending discontinuity from the source.
-
-
readDiscontinuity(int) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
readDouble() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next eight bytes as a 64-bit floating point value.
-
-
readFloat() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes as a 32-bit floating point value.
-
-
readFully(byte[], int, int, boolean) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
readFully(byte[], int, int) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
readFully(byte[], int, int, boolean) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Like ExtractorInput.read(byte[], int, int), but reads the requested length in full.
-
-
readFully(byte[], int, int) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Equivalent to readFully(target, offset, length, false).
-
-
readInt() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes as a signed value.
-
-
readLine() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads a line of text.
-
-
readLittleEndianInt() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes as an signed value in little endian order.
-
-
readLittleEndianInt24() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next three bytes as a signed value in little endian order.
-
-
readLittleEndianLong() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next eight bytes as a signed value in little endian order.
-
-
readLittleEndianShort() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next two bytes as a signed value.
-
-
readLittleEndianUnsignedInt() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes as an unsigned value in little endian order.
-
-
readLittleEndianUnsignedInt24() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next three bytes as an unsigned value in little endian order.
-
-
readLittleEndianUnsignedIntToInt() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes as a little endian unsigned integer into an integer, if the top bit - is a zero.
-
-
readLittleEndianUnsignedShort() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next two bytes as an unsigned value.
-
-
readLong() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next eight bytes as a signed value.
-
-
readNullTerminatedString() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads a null-terminated string using the default character set.
-
-
readShort() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next two bytes as an signed value.
-
-
readSignedExpGolombCodedInt() - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Reads an signed Exp-Golomb-coded format integer.
-
-
readSource(long, MediaFormatHolder, SampleHolder) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
-
Reads from the enabled upstream source.
-
-
readString(int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next length bytes as UTF-8 characters.
-
-
readString(int, Charset) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next length bytes as characters in the specified Charset.
-
-
readSynchSafeInt() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads a Synchsafe integer.
-
-
readUnsignedByte() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next byte as an unsigned value.
-
-
readUnsignedExpGolombCodedInt() - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Reads an unsigned Exp-Golomb-coded format integer.
-
-
readUnsignedFixedPoint1616() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes, returning the integer portion of the fixed point 16.16 integer.
-
-
readUnsignedInt() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes as an unsigned value.
-
-
readUnsignedInt24() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next three bytes as an unsigned value.
-
-
readUnsignedIntToInt() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero.
-
-
readUnsignedLongToLong() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next eight bytes as an unsigned long into a long, if the top bit is a zero.
-
-
readUnsignedShort() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads the next two bytes as an unsigned value.
-
-
readUTF8EncodedLong() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Reads a long value encoded by UTF-8 encoding
-
-
reason - Variable in exception com.google.android.exoplayer.drm.UnsupportedDrmException
-
 
-
REASON_INSTANTIATION_ERROR - Static variable in exception com.google.android.exoplayer.drm.UnsupportedDrmException
-
-
There device advertises support for the requested DRM scheme, but there was an error - instantiating it.
-
-
REASON_UNSUPPORTED_SCHEME - Static variable in exception com.google.android.exoplayer.drm.UnsupportedDrmException
-
-
The requested DRM scheme is unsupported by the device.
-
-
register() - Method in class com.google.android.exoplayer.audio.AudioCapabilitiesReceiver
-
-
Registers to notify the listener when audio capabilities change.
-
-
register() - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
register(Object, int) - Method in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
register() - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
register() - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
register() - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
register(Object, int) - Method in interface com.google.android.exoplayer.LoadControl
-
-
Registers a loader.
-
-
register() - Method in interface com.google.android.exoplayer.SampleSource
-
-
A consumer of samples should call this method to register themselves and gain access to the - source through the returned SampleSource.SampleSourceReader.
-
-
register() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
REJECT_PAYWALL_TYPES - Static variable in interface com.google.android.exoplayer.upstream.HttpDataSource
-
-
A Predicate that rejects content types often used for pay-walls.
-
-
release() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Releases all resources associated with this instance.
-
-
release() - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
release() - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
release() - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
release() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Releases the player.
-
-
release() - Method in interface com.google.android.exoplayer.extractor.Extractor
-
-
Releases all kept resources.
-
-
release() - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.mp3.Mp3Extractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.ogg.OggExtractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.ts.AdtsExtractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
release() - Method in class com.google.android.exoplayer.extractor.webm.WebmExtractor
-
 
-
release() - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
release() - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
release() - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
- -
-
release() - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
release(Allocation) - Method in interface com.google.android.exoplayer.upstream.Allocator
-
-
Return an Allocation.
-
-
release(Allocation[]) - Method in interface com.google.android.exoplayer.upstream.Allocator
-
-
Return an array of Allocations.
-
-
release(Allocation) - Method in class com.google.android.exoplayer.upstream.DefaultAllocator
-
 
-
release(Allocation[]) - Method in class com.google.android.exoplayer.upstream.DefaultAllocator
-
 
-
release() - Method in class com.google.android.exoplayer.upstream.Loader
-
-
Releases the Loader.
-
-
release(Runnable) - Method in class com.google.android.exoplayer.upstream.Loader
-
-
Releases the Loader, running postLoadAction on its thread.
-
-
release() - Method in interface com.google.android.exoplayer.util.extensions.Decoder
-
-
Releases the decoder.
-
-
release() - Method in class com.google.android.exoplayer.util.extensions.OutputBuffer
-
-
Releases the output buffer for reuse.
-
-
release() - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
 
-
releaseCodec() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
releaseHoleSpan(CacheSpan) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Releases a CacheSpan obtained from Cache.startReadWrite(String, long) which - corresponded to a hole in the cache.
-
-
releaseHoleSpan(CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
releaseOutputBuffer(O) - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
-
Releases an output buffer back to the decoder.
-
-
remove(int) - Method in class com.google.android.exoplayer.upstream.NetworkLock
-
-
Unregister a task.
-
-
removeListener(ExoPlayer.Listener) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Unregister a listener.
-
-
removeListener(String, Cache.Listener) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Unregisters a listener.
-
-
removeListener(String, Cache.Listener) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
removeSpan(CacheSpan) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Removes a cached CacheSpan from the cache, deleting the underlying file.
-
-
removeSpan(CacheSpan) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
renderedOutputBufferCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
renderOutputBuffer(MediaCodec, int) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
renderOutputBufferV21(MediaCodec, int, long) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
representation - Variable in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
Representation - Class in com.google.android.exoplayer.dash.mpd
-
-
A DASH representation.
-
-
Representation.MultiSegmentRepresentation - Class in com.google.android.exoplayer.dash.mpd
-
-
A DASH representation consisting of multiple segments.
-
-
Representation.SingleSegmentRepresentation - Class in com.google.android.exoplayer.dash.mpd
-
-
A DASH representation consisting of a single segment.
-
-
RepresentationHolder(long, long, Representation) - Constructor for class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
representationHolders - Variable in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
representations - Variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
requestRefresh() - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Should be invoked repeatedly by callers who require an updated manifest.
-
-
requiresSecureDecoderComponent(String) - Method in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
Whether the session requires a secure decoder for the specified mime type.
-
-
requiresSecureDecoderComponent(String) - Method in interface com.google.android.exoplayer.drm.ExoMediaCrypto
-
 
-
requiresSecureDecoderComponent(String) - Method in class com.google.android.exoplayer.drm.FrameworkMediaCrypto
-
 
-
requiresSecureDecoderComponent(String) - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
 
-
reset() - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Releases the underlying audio track asynchronously.
-
-
reset() - Method in class com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster
-
-
Resets the instance to its initial state.
-
-
reset() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Resets the source.
-
-
reset() - Method in class com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider
-
-
Resets the provider.
-
-
reset() - Method in class com.google.android.exoplayer.util.extensions.Buffer
-
 
-
reset() - Method in class com.google.android.exoplayer.util.extensions.InputBuffer
-
 
-
reset(byte[]) - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Updates the instance to wrap data, and resets the position to zero.
-
-
reset(byte[], int) - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Updates the instance to wrap data, and resets the position to zero.
-
-
reset(int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Resets the position to zero and the limit to the specified value.
-
-
reset(byte[], int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Updates the instance to wrap data, and resets the position to zero.
-
-
reset() - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Sets the position and limit to zero.
-
-
reset(OutputStream) - Method in class com.google.android.exoplayer.util.ReusableBufferedOutputStream
-
-
Resets this stream and uses the given output stream for writing.
-
-
resetPeekPosition() - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
resetPeekPosition() - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Resets the peek position to equal the current read position.
-
-
resolve(String, String) - Static method in class com.google.android.exoplayer.util.UriUtil
-
-
Performs relative resolution of a referenceUri with respect to a baseUri.
-
-
resolveTimingElement(UriDataSource, UtcTimingElement, long, UtcTimingElementResolver.UtcTimingCallback) - Static method in class com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver
-
-
Resolves a UtcTimingElement.
-
-
resolveToUri(String, String) - Static method in class com.google.android.exoplayer.util.UriUtil
-
-
Like UriUtil.resolve(String, String), but returns a Uri instead of a String.
-
-
resolveUri(String) - Method in class com.google.android.exoplayer.dash.mpd.RangedUri
-
-
Returns the resolved Uri represented by the instance.
-
-
resolveUriString(String) - Method in class com.google.android.exoplayer.dash.mpd.RangedUri
-
-
Returns the resolve uri represented by the instance as a string.
-
-
responseCode - Variable in exception com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException
-
-
The response code that was outside of the 2xx range.
-
-
restoreKeys(byte[], byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
restoreKeys(byte[], byte[]) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
RESULT_BUFFER_CONSUMED - Static variable in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returned in the result of AudioTrack.handleBuffer(java.nio.ByteBuffer, int, int, long) if the buffer can be released.
-
-
RESULT_CONTINUE - Static variable in interface com.google.android.exoplayer.extractor.Extractor
-
-
Returned by Extractor.read(ExtractorInput, PositionHolder) if the ExtractorInput passed - to the next Extractor.read(ExtractorInput, PositionHolder) is required to provide data - continuing from the position in the stream reached by the returning call.
-
-
RESULT_END_OF_INPUT - Static variable in class com.google.android.exoplayer.C
-
-
A return value for methods where the end of an input was encountered.
-
-
RESULT_END_OF_INPUT - Static variable in interface com.google.android.exoplayer.extractor.Extractor
-
-
Returned by Extractor.read(ExtractorInput, PositionHolder) if the end of the - ExtractorInput was reached.
-
-
RESULT_MAX_LENGTH_EXCEEDED - Static variable in class com.google.android.exoplayer.C
-
-
A return value for methods where the length of parsed data exceeds the maximum length allowed.
-
-
RESULT_POSITION_DISCONTINUITY - Static variable in class com.google.android.exoplayer.audio.AudioTrack
-
-
Returned in the result of AudioTrack.handleBuffer(java.nio.ByteBuffer, int, int, long) if the buffer was discontinuous.
-
-
RESULT_SEEK - Static variable in interface com.google.android.exoplayer.extractor.Extractor
-
-
Returned by Extractor.read(ExtractorInput, PositionHolder) if the ExtractorInput passed - to the next Extractor.read(ExtractorInput, PositionHolder) is required to provide data starting - from a specified position in the stream.
-
-
ReusableBufferedOutputStream - Class in com.google.android.exoplayer.util
-
-
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.
-
-
ReusableBufferedOutputStream(OutputStream) - Constructor for class com.google.android.exoplayer.util.ReusableBufferedOutputStream
-
 
-
ReusableBufferedOutputStream(OutputStream, int) - Constructor for class com.google.android.exoplayer.util.ReusableBufferedOutputStream
-
 
-
revisionId - Variable in class com.google.android.exoplayer.dash.mpd.Representation
-
-
Identifies the revision of the content.
-
-
rotationDegrees - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The clockwise rotation that should be applied to the video for it to be rendered in the correct - orientation, or MediaFormat.NO_VALUE if unknown or not applicable.
-
-
run() - Method in class com.google.android.exoplayer.util.DebugTextViewHelper
-
 
-
run() - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
 
-
run() - Method in class com.google.android.exoplayer.util.PriorityHandlerThread
-
 
-
- - - -

S

-
-
SAMPLE_FLAG_DECODE_ONLY - Static variable in class com.google.android.exoplayer.C
-
-
Indicates that a sample should be decoded but not rendered.
-
-
SAMPLE_FLAG_ENCRYPTED - Static variable in class com.google.android.exoplayer.C
-
 
-
SAMPLE_FLAG_SYNC - Static variable in class com.google.android.exoplayer.C
-
 
-
SAMPLE_READ - Static variable in interface com.google.android.exoplayer.SampleSource
-
-
A sample was read.
-
-
sampleData(ExtractorInput, int, boolean) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
sampleData(ParsableByteArray, int) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
sampleData(ExtractorInput, int, boolean) - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
sampleData(ParsableByteArray, int) - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
sampleData(ExtractorInput, int, boolean) - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
sampleData(ParsableByteArray, int) - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
sampleData(DataSource, int, boolean) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Invoked to write sample data to the output.
-
-
sampleData(ExtractorInput, int, boolean) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
 
-
sampleData(ParsableByteArray, int) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
 
-
sampleData(ExtractorInput, int, boolean) - Method in class com.google.android.exoplayer.extractor.DummyTrackOutput
-
 
-
sampleData(ParsableByteArray, int) - Method in class com.google.android.exoplayer.extractor.DummyTrackOutput
-
 
-
sampleData(ExtractorInput, int, boolean) - Method in interface com.google.android.exoplayer.extractor.TrackOutput
-
-
Invoked to write sample data to the output.
-
-
sampleData(ParsableByteArray, int) - Method in interface com.google.android.exoplayer.extractor.TrackOutput
-
-
Invoked to write sample data to the output.
-
-
sampleDescriptionEncryptionBoxes - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
Track encryption boxes for the different track sample descriptions.
-
-
SampleHolder - Class in com.google.android.exoplayer
-
-
Holds sample data and corresponding metadata.
-
-
SampleHolder(int) - Constructor for class com.google.android.exoplayer.SampleHolder
-
 
-
sampleHolder - Variable in class com.google.android.exoplayer.util.extensions.InputBuffer
-
 
-
sampleMetadata(long, int, int, int, byte[]) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
sampleMetadata(long, int, int, int, byte[]) - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
sampleMetadata(long, int, int, int, byte[]) - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
sampleMetadata(long, int, int, int, byte[]) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
 
-
sampleMetadata(long, int, int, int, byte[]) - Method in class com.google.android.exoplayer.extractor.DummyTrackOutput
-
 
-
sampleMetadata(long, int, int, int, byte[]) - Method in interface com.google.android.exoplayer.extractor.TrackOutput
-
-
Invoked when metadata associated with a sample has been extracted from the stream.
-
-
sampleQueue - Variable in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
sampleRate - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The audio sampling rate in Hz, or MediaFormat.NO_VALUE if unknown or not applicable.
-
-
sampleRate - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
sampleRate - Variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Sample rate in samples per second.
-
-
SampleSource - Interface in com.google.android.exoplayer
-
-
A source of media samples.
-
-
SampleSource.SampleSourceReader - Interface in com.google.android.exoplayer
-
-
An interface providing read access to a SampleSource.
-
-
SampleSourceTrackRenderer - Class in com.google.android.exoplayer
-
-
Base class for TrackRenderer implementations that render samples obtained from a - SampleSource.
-
-
SampleSourceTrackRenderer(SampleSource...) - Constructor for class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
samplesPerFrame - Variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
Number of samples stored in the frame.
-
-
scaleLargeTimestamp(long, long, long) - Static method in class com.google.android.exoplayer.util.Util
-
-
Scales a large timestamp.
-
-
scaleLargeTimestamps(List<Long>, long, long) - Static method in class com.google.android.exoplayer.util.Util
-
-
Applies Util.scaleLargeTimestamp(long, long, long) to a list of unscaled timestamps.
-
-
scaleLargeTimestampsInPlace(long[], long, long) - Static method in class com.google.android.exoplayer.util.Util
-
-
Applies Util.scaleLargeTimestamp(long, long, long) to an array of unscaled timestamps.
-
-
schemeIdUri - Variable in class com.google.android.exoplayer.dash.mpd.UtcTimingElement
-
 
-
SchemeInitData(String, byte[]) - Constructor for class com.google.android.exoplayer.drm.DrmInitData.SchemeInitData
-
 
-
schemeUriId - Variable in class com.google.android.exoplayer.dash.mpd.ContentProtection
-
-
Identifies the content protection scheme.
-
-
SDK_INT - Static variable in class com.google.android.exoplayer.util.Util
-
-
Like Build.VERSION.SDK_INT, but in a place where it can be conveniently - overridden for local testing.
-
-
secureDecoderRequired - Variable in exception com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
-
-
Whether it was required that the decoder support a secure output path.
-
-
seek() - Method in interface com.google.android.exoplayer.extractor.Extractor
-
-
Notifies the extractor that a seek has occurred.
-
-
seek() - Method in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.mp3.Mp3Extractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.ogg.OggExtractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.ts.AdtsExtractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
seek() - Method in class com.google.android.exoplayer.extractor.webm.WebmExtractor
-
 
-
seek() - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Notifies the source that a seek has occurred.
-
-
seekMap(SeekMap) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
seekMap(SeekMap) - Method in interface com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput
-
 
-
seekMap(SeekMap) - Method in class com.google.android.exoplayer.chunk.ContainerMediaChunk
-
 
-
seekMap(SeekMap) - Method in class com.google.android.exoplayer.chunk.InitializationChunk
-
 
-
seekMap(SeekMap) - Method in interface com.google.android.exoplayer.extractor.ExtractorOutput
-
-
Invoked when a SeekMap has been extracted from the stream.
-
-
seekMap(SeekMap) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
SeekMap - Interface in com.google.android.exoplayer.extractor
-
-
Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream.
-
-
seekMap(SeekMap) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
seekTo(long) - Method in class com.google.android.exoplayer.DummyTrackRenderer
-
 
-
seekTo(long) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Seeks to a position specified in milliseconds.
-
-
seekTo(long) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
 
-
seekTo(long) - Method in class com.google.android.exoplayer.TrackRenderer
-
-
Seeks to a specified time in the track.
-
-
seekTo(int) - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
seekToUs(long) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
seekToUs(long) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
seekToUs(long) - Method in class com.google.android.exoplayer.FrameworkSampleSource
-
-
Deprecated.
-
seekToUs(long) - Method in class com.google.android.exoplayer.hls.HlsSampleSource
-
 
-
seekToUs(long) - Method in interface com.google.android.exoplayer.SampleSource.SampleSourceReader
-
-
Seeks to the specified time in microseconds.
-
-
seekToUs(long) - Method in class com.google.android.exoplayer.SingleSampleSource
-
 
-
Segment(String, double, int, long, boolean, String, String, long, long) - Constructor for class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
SegmentBase - Class in com.google.android.exoplayer.dash.mpd
-
-
An approximate representation of a SegmentBase manifest element.
-
-
SegmentBase(RangedUri, long, long) - Constructor for class com.google.android.exoplayer.dash.mpd.SegmentBase
-
 
-
SegmentBase.MultiSegmentBase - Class in com.google.android.exoplayer.dash.mpd
-
-
A SegmentBase that consists of multiple segments.
-
-
SegmentBase.SegmentList - Class in com.google.android.exoplayer.dash.mpd
-
-
A SegmentBase.MultiSegmentBase that uses a SegmentList to define its segments.
-
-
SegmentBase.SegmentTemplate - Class in com.google.android.exoplayer.dash.mpd
-
-
A SegmentBase.MultiSegmentBase that uses a SegmentTemplate to define its segments.
-
-
SegmentBase.SegmentTimelineElement - Class in com.google.android.exoplayer.dash.mpd
-
-
Represents a timeline segment from the MPD's SegmentTimeline list.
-
-
SegmentBase.SingleSegmentBase - Class in com.google.android.exoplayer.dash.mpd
-
-
A SegmentBase that defines a single segment.
-
-
segmentIndex - Variable in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
SegmentList(RangedUri, long, long, int, long, List<SegmentBase.SegmentTimelineElement>, List<RangedUri>) - Constructor for class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList
-
 
-
segments - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
SegmentTemplate(RangedUri, long, long, int, long, List<SegmentBase.SegmentTimelineElement>, UrlTemplate, UrlTemplate) - Constructor for class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate
-
 
-
SegmentTimelineElement(long, long) - Constructor for class com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement
-
 
-
selectTrack(int) - Method in class com.google.android.exoplayer.hls.HlsChunkSource
-
-
Selects a track for use.
-
-
selectTracks(MediaPresentationDescription, int, DashTrackSelector.Output) - Method in interface com.google.android.exoplayer.dash.DashTrackSelector
-
-
Outputs a track selection for a given period.
-
-
selectTracks(MediaPresentationDescription, int, DashTrackSelector.Output) - Method in class com.google.android.exoplayer.dash.DefaultDashTrackSelector
-
 
-
selectTracks(HlsMasterPlaylist, HlsTrackSelector.Output) - Method in class com.google.android.exoplayer.hls.DefaultHlsTrackSelector
-
 
-
selectTracks(HlsMasterPlaylist, HlsTrackSelector.Output) - Method in interface com.google.android.exoplayer.hls.HlsTrackSelector
-
-
Outputs a track selection for a given period.
-
-
selectTracks(SmoothStreamingManifest, SmoothStreamingTrackSelector.Output) - Method in class com.google.android.exoplayer.smoothstreaming.DefaultSmoothStreamingTrackSelector
-
 
-
selectTracks(SmoothStreamingManifest, SmoothStreamingTrackSelector.Output) - Method in interface com.google.android.exoplayer.smoothstreaming.SmoothStreamingTrackSelector
-
-
Outputs a track selection for a given manifest.
-
-
selectVideoFormats(List<? extends FormatWrapper>, String[], boolean, boolean, boolean, int, int) - Static method in class com.google.android.exoplayer.chunk.VideoFormatSelectorUtil
-
-
Chooses a suitable subset from a number of video formats.
-
-
selectVideoFormatsForDefaultDisplay(Context, List<? extends FormatWrapper>, String[], boolean) - Static method in class com.google.android.exoplayer.chunk.VideoFormatSelectorUtil
-
-
Chooses a suitable subset from a number of video formats, to be rendered on the device's - default display.
-
-
sendMessage(ExoPlayer.ExoPlayerComponent, int, Object) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Sends a message to a specified component.
-
-
separateColorPlaneFlag - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
seqParameterSetId - Variable in class com.google.android.exoplayer.util.NalUnitUtil.PpsData
-
 
-
seqParameterSetId - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
SESSION_ID_NOT_SET - Static variable in class com.google.android.exoplayer.audio.AudioTrack
-
-
Represents an unset AudioTrack session identifier.
-
-
set(int, int[], int[], byte[], byte[], int) - Method in class com.google.android.exoplayer.CryptoInfo
-
 
-
setApplyEmbeddedStyles(boolean) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
-
Sets whether styling embedded within the cues should be applied.
-
-
setAspectRatio(float) - Method in class com.google.android.exoplayer.AspectRatioFrameLayout
-
-
Set the aspect ratio that this view should satisfy.
-
-
setBottomPaddingFraction(float) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
-
Sets the bottom padding fraction to apply when Cue.line is Cue.DIMEN_UNSET, - as a fraction of the view's remaining height after its top and bottom padding have been - subtracted.
-
-
setCues(List<Cue>) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
-
Sets the cues to be displayed by the view.
-
-
setEnableAllTags(boolean) - Static method in class com.google.android.exoplayer.util.VerboseLogUtil
-
-
Specifies whether or not all logging should be enabled.
-
-
setEnabledTags(String...) - Static method in class com.google.android.exoplayer.util.VerboseLogUtil
-
-
Sets the tags for which verbose logging should be enabled.
-
-
setFixedTextSize(int, float) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
-
Set the text size to a given unit and value.
-
-
setFlag(int) - Method in class com.google.android.exoplayer.util.extensions.Buffer
-
 
-
setFractionalTextSize(float) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
-
Sets the text size to be a fraction of the view's remaining height after its top and bottom - padding have been subtracted.
-
-
setFractionalTextSize(float, boolean) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
-
Sets the text size to be a fraction of the height of this view.
-
-
setFromExtractorV16(MediaExtractor) - Method in class com.google.android.exoplayer.CryptoInfo
-
- -
-
setInitialInputBufferSize(int) - Method in class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
-
Sets the initial size of each input buffer.
-
-
setLimit(int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Sets the limit.
-
-
setOnEventListener(ExoMediaDrm.OnEventListener<? super T>) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
setOnEventListener(ExoMediaDrm.OnEventListener<? super FrameworkMediaCrypto>) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
setPlaybackParams(PlaybackParams) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Sets the playback parameters.
-
-
setPlayWhenReady(boolean) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Sets whether playback should proceed when ExoPlayer.getPlaybackState() == ExoPlayer.STATE_READY.
-
-
setPosition(int) - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Sets the current bit offset.
-
-
setPosition(int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Sets the reading offset in the array.
-
-
setPropertyByteArray(String, byte[]) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
setPropertyByteArray(String, byte[]) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
setPropertyByteArray(String, byte[]) - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
- -
-
setPropertyString(String, String) - Method in interface com.google.android.exoplayer.drm.ExoMediaDrm
-
 
-
setPropertyString(String, String) - Method in class com.google.android.exoplayer.drm.FrameworkMediaDrm
-
 
-
setPropertyString(String, String) - Method in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
- -
-
setRequestProperty(String, String) - Method in class com.google.android.exoplayer.upstream.DefaultHttpDataSource
-
 
-
setRequestProperty(String, String) - Method in interface com.google.android.exoplayer.upstream.HttpDataSource
-
-
Sets the value of a request header field.
-
-
setSelectedTrack(int, int) - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Selects a track for the specified renderer.
-
-
setStreamType(int) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Sets the stream type for audio track.
-
-
setStyle(CaptionStyleCompat) - Method in class com.google.android.exoplayer.text.SubtitleLayout
-
-
Sets the caption style.
-
-
setVolume(float) - Method in class com.google.android.exoplayer.audio.AudioTrack
-
-
Sets the playback volume.
-
-
shiftInputPosition(long) - Method in class com.google.android.exoplayer.SampleSourceTrackRenderer
-
- -
-
shouldInitCodec() - Method in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
 
-
shouldInitCodec() - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
SimpleCache - Class in com.google.android.exoplayer.upstream.cache
-
-
A Cache implementation that maintains an in-memory representation.
-
-
SimpleCache(File, CacheEvictor) - Constructor for class com.google.android.exoplayer.upstream.cache.SimpleCache
-
-
Constructs the cache.
-
-
SimpleCache(File, CacheEvictor, byte[]) - Constructor for class com.google.android.exoplayer.upstream.cache.SimpleCache
-
-
Constructs the cache.
-
-
SimpleDecoder<I extends InputBuffer,O extends OutputBuffer,E extends Exception> - Class in com.google.android.exoplayer.util.extensions
-
-
Base class for Decoders that use their own decode thread.
-
-
SimpleDecoder(I[], O[]) - Constructor for class com.google.android.exoplayer.util.extensions.SimpleDecoder
-
 
-
SimpleDecoder.EventListener<E> - Interface in com.google.android.exoplayer.util.extensions
-
-
Listener for SimpleDecoder events.
-
-
singleLoad(Looper, ManifestFetcher.ManifestCallback<T>) - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Performs a single manifest load.
-
-
SingleSampleMediaChunk - Class in com.google.android.exoplayer.chunk
-
-
A BaseMediaChunk for chunks consisting of a single raw sample.
-
-
SingleSampleMediaChunk(DataSource, DataSpec, int, Format, long, long, int, MediaFormat, DrmInitData, int) - Constructor for class com.google.android.exoplayer.chunk.SingleSampleMediaChunk
-
 
-
SingleSampleSource - Class in com.google.android.exoplayer
-
-
A SampleSource that loads the data at a given Uri as a single sample.
-
-
SingleSampleSource(Uri, DataSource, MediaFormat) - Constructor for class com.google.android.exoplayer.SingleSampleSource
-
 
-
SingleSampleSource(Uri, DataSource, MediaFormat, int) - Constructor for class com.google.android.exoplayer.SingleSampleSource
-
 
-
SingleSampleSource(Uri, DataSource, MediaFormat, int, Handler, SingleSampleSource.EventListener, int) - Constructor for class com.google.android.exoplayer.SingleSampleSource
-
 
-
SingleSampleSource.EventListener - Interface in com.google.android.exoplayer
-
-
Interface definition for a callback to be notified of SingleSampleSource events.
-
-
SingleSegmentBase(RangedUri, long, long, long, long) - Constructor for class com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase
-
 
-
SingleSegmentBase() - Constructor for class com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase
-
 
-
SingleSegmentRepresentation(String, long, Format, SegmentBase.SingleSegmentBase, String, long, String) - Constructor for class com.google.android.exoplayer.dash.mpd.Representation.SingleSegmentRepresentation
-
 
-
size - Variable in class com.google.android.exoplayer.SampleHolder
-
-
The size of the sample in bytes.
-
-
size - Variable in class com.google.android.exoplayer.text.Cue
-
-
The size of the cue box in the writing direction specified as a fraction of the viewport size - in that direction, or Cue.DIMEN_UNSET.
-
-
size() - Method in class com.google.android.exoplayer.util.LongArray
-
-
Gets the current size of the array.
-
-
sizes - Variable in class com.google.android.exoplayer.extractor.ChunkIndex
-
-
The chunk sizes, in bytes.
-
-
skip(int) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
skip(int) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Like ExtractorInput.read(byte[], int, int), except the data is skipped instead of read.
-
-
skip(long) - Method in class com.google.android.exoplayer.upstream.DataSourceInputStream
-
 
-
skipBits(int) - Method in class com.google.android.exoplayer.util.ParsableBitArray
-
-
Skips bits and moves current reading position forward.
-
-
skipBytes(int) - Method in class com.google.android.exoplayer.util.ParsableByteArray
-
-
Moves the reading offset by bytes.
-
-
skipFully(int, boolean) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
skipFully(int) - Method in class com.google.android.exoplayer.extractor.DefaultExtractorInput
-
 
-
skipFully(int, boolean) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Like ExtractorInput.readFully(byte[], int, int, boolean), except the data is skipped instead of read.
-
-
skipFully(int) - Method in interface com.google.android.exoplayer.extractor.ExtractorInput
-
-
Like ExtractorInput.readFully(byte[], int, int), except the data is skipped instead of read.
-
-
skipOutputBuffer(MediaCodec, int) - Method in class com.google.android.exoplayer.MediaCodecVideoTrackRenderer
-
 
-
skippedOutputBufferCount - Variable in class com.google.android.exoplayer.CodecCounters
-
 
-
skipToKeyframeBefore(long) - Method in class com.google.android.exoplayer.extractor.DefaultTrackOutput
-
-
Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
-
-
SlidingPercentile - Class in com.google.android.exoplayer.util
-
-
Calculate any percentile over a sliding window of weighted values.
-
-
SlidingPercentile(int) - Constructor for class com.google.android.exoplayer.util.SlidingPercentile
-
 
-
SmoothStreamingChunkSource - Class in com.google.android.exoplayer.smoothstreaming
-
-
An ChunkSource for SmoothStreaming.
-
-
SmoothStreamingChunkSource(ManifestFetcher<SmoothStreamingManifest>, SmoothStreamingTrackSelector, DataSource, FormatEvaluator, long) - Constructor for class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
-
Constructor to use for live streaming.
-
-
SmoothStreamingChunkSource(SmoothStreamingManifest, SmoothStreamingTrackSelector, DataSource, FormatEvaluator) - Constructor for class com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource
-
-
Constructor to use for fixed duration content.
-
-
SmoothStreamingManifest - Class in com.google.android.exoplayer.smoothstreaming
-
-
Represents a SmoothStreaming manifest.
-
-
SmoothStreamingManifest(int, int, long, long, long, int, boolean, SmoothStreamingManifest.ProtectionElement, SmoothStreamingManifest.StreamElement[]) - Constructor for class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
 
-
SmoothStreamingManifest.ProtectionElement - Class in com.google.android.exoplayer.smoothstreaming
-
-
Represents a protection element containing a single header.
-
-
SmoothStreamingManifest.StreamElement - Class in com.google.android.exoplayer.smoothstreaming
-
-
Represents a StreamIndex element.
-
-
SmoothStreamingManifest.TrackElement - Class in com.google.android.exoplayer.smoothstreaming
-
-
Represents a QualityLevel element.
-
-
SmoothStreamingManifestParser - Class in com.google.android.exoplayer.smoothstreaming
-
-
Parses SmoothStreaming client manifests.
-
-
SmoothStreamingManifestParser() - Constructor for class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser
-
 
-
SmoothStreamingManifestParser.MissingFieldException - Exception in com.google.android.exoplayer.smoothstreaming
-
-
Thrown if a required field is missing.
-
-
SmoothStreamingTrackSelector - Interface in com.google.android.exoplayer.smoothstreaming
-
-
Specifies a track selection from a SmoothStreamingManifest.
-
-
SmoothStreamingTrackSelector.Output - Interface in com.google.android.exoplayer.smoothstreaming
-
-
Defines a selector output.
-
-
sneakyThrow(Throwable) - Static method in class com.google.android.exoplayer.util.Util
-
-
A hacky method that always throws t even if t is a checked exception, - and is not declared to be thrown.
-
-
sniff(ExtractorInput) - Method in interface com.google.android.exoplayer.extractor.Extractor
-
-
Returns whether this extractor can extract samples from the ExtractorInput, which must - provide data from the start of the stream.
-
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.mp3.Mp3Extractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.mp4.Mp4Extractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.ogg.OggExtractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.ts.AdtsExtractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
sniff(ExtractorInput) - Method in class com.google.android.exoplayer.extractor.webm.WebmExtractor
-
 
-
SOURCE_STATE_NOT_READY - Static variable in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Value returned by MediaCodecTrackRenderer.getSourceState() when the source is not ready.
-
-
SOURCE_STATE_READY - Static variable in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Value returned by MediaCodecTrackRenderer.getSourceState() when the source is ready and we're able to read - from it.
-
-
SOURCE_STATE_READY_READ_MAY_FAIL - Static variable in class com.google.android.exoplayer.MediaCodecTrackRenderer
-
-
Value returned by MediaCodecTrackRenderer.getSourceState() when the source is ready but we might not be able - to read from it.
-
-
splitNalUnits(byte[]) - Static method in class com.google.android.exoplayer.util.CodecSpecificDataUtil
-
-
Splits an array of NAL units.
-
-
SpsData(int, int, int, float, boolean, boolean, int, int, int, boolean) - Constructor for class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
start - Variable in class com.google.android.exoplayer.dash.mpd.RangedUri
-
-
The (zero based) index of the first byte of the range.
-
-
start() - Method in class com.google.android.exoplayer.util.DebugTextViewHelper
-
-
Starts periodic updates of the TextView.
-
-
start() - Method in class com.google.android.exoplayer.util.PlayerControl
-
 
-
startFile(String, long, long) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
Obtains a cache file into which data can be written.
-
-
startFile(String, long, long) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
startLoading(Loader.Loadable, Loader.Callback) - Method in class com.google.android.exoplayer.upstream.Loader
-
-
Invokes Loader.startLoading(Looper, Loadable, Callback), using the Looper - associated with the calling thread.
-
-
startLoading(Looper, Loader.Loadable, Loader.Callback) - Method in class com.google.android.exoplayer.upstream.Loader
-
-
Start loading a Loader.Loadable.
-
-
startMs - Variable in class com.google.android.exoplayer.dash.mpd.Period
-
-
The start time of the period in milliseconds.
-
-
startReadWrite(String, long) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
-
A caller should invoke this method when they require data from a given position for a given - key.
-
-
startReadWrite(String, long) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
startReadWriteNonBlocking(String, long) - Method in interface com.google.android.exoplayer.upstream.cache.Cache
-
- -
-
startReadWriteNonBlocking(String, long) - Method in class com.google.android.exoplayer.upstream.cache.SimpleCache
-
 
-
startTimeUs - Variable in class com.google.android.exoplayer.chunk.MediaChunk
-
-
The start time of the media contained by the chunk.
-
-
startTimeUs - Variable in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
startTimeUs - Variable in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
startTimeUs - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
startWrite() - Method in class com.google.android.exoplayer.util.AtomicFile
-
-
Start a new write operation on the file.
-
-
STATE_BUFFERING - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
The player is prepared but not able to immediately play from the current position.
-
-
STATE_CLOSED - Static variable in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
The session is closed.
-
-
STATE_ENABLED - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
The renderer is enabled.
-
-
STATE_ENDED - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
The player has finished playing the media.
-
-
STATE_ERROR - Static variable in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
The error state.
-
-
STATE_IDLE - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
The player is neither prepared or being prepared.
-
-
STATE_OPENED - Static variable in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
The session is open, but does not yet have the keys required for decryption.
-
-
STATE_OPENED_WITH_KEYS - Static variable in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
The session is open and has the keys required for decryption.
-
-
STATE_OPENING - Static variable in interface com.google.android.exoplayer.drm.DrmSessionManager
-
-
The session is being opened (i.e.
-
-
STATE_PREPARED - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
The renderer has completed necessary preparation.
-
-
STATE_PREPARING - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
The player is being prepared.
-
-
STATE_READY - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
The player is prepared and able to immediately play from the current position.
-
-
STATE_RELEASED - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
The renderer has been released and should not be used.
-
-
STATE_STARTED - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
The renderer is started.
-
-
STATE_UNPREPARED - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
The renderer has not yet been prepared.
-
-
StaticTimeRange(long, long) - Constructor for class com.google.android.exoplayer.TimeRange.StaticTimeRange
-
 
-
STEREO_MODE_LEFT_RIGHT - Static variable in class com.google.android.exoplayer.C
-
-
Indicates Left-Right stereo layout, used with 360/3D/VR videos.
-
-
STEREO_MODE_MONO - Static variable in class com.google.android.exoplayer.C
-
-
Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
-
-
STEREO_MODE_TOP_BOTTOM - Static variable in class com.google.android.exoplayer.C
-
-
Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
-
-
stereoMode - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The stereo layout for 360/3D/VR video, or MediaFormat.NO_VALUE if not applicable.
-
-
stop() - Method in interface com.google.android.exoplayer.ExoPlayer
-
-
Stops playback.
-
-
stop() - Method in class com.google.android.exoplayer.util.DebugTextViewHelper
-
-
Stops periodic updates of the TextView.
-
-
StreamElement(String, String, int, String, long, String, int, int, int, int, int, String, SmoothStreamingManifest.TrackElement[], List<Long>, long) - Constructor for class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
streamElements - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest
-
-
The contained stream elements.
-
-
STREAMING_PRIORITY - Static variable in class com.google.android.exoplayer.upstream.NetworkLock
-
-
Priority for network tasks associated with media streaming.
-
-
StreamingDrmSessionManager<T extends ExoMediaCrypto> - Class in com.google.android.exoplayer.drm
-
-
A base class for DrmSessionManager implementations that support streaming playbacks - using ExoMediaDrm.
-
-
StreamingDrmSessionManager.EventListener - Interface in com.google.android.exoplayer.drm
-
-
Interface definition for a callback to be notified of StreamingDrmSessionManager - events.
-
-
SubripParser - Class in com.google.android.exoplayer.text.subrip
-
-
A simple SubRip parser.
-
-
SubripParser() - Constructor for class com.google.android.exoplayer.text.subrip.SubripParser
-
 
-
subsampleOffsetUs - Variable in class com.google.android.exoplayer.MediaFormat
-
-
For samples that contain subsamples, this is an offset that should be added to subsample - timestamps.
-
-
Subtitle - Interface in com.google.android.exoplayer.text
-
-
A subtitle that contains textual data associated with time indices.
-
-
SubtitleLayout - Class in com.google.android.exoplayer.text
-
-
A view for rendering rich-formatted captions.
-
-
SubtitleLayout(Context) - Constructor for class com.google.android.exoplayer.text.SubtitleLayout
-
 
-
SubtitleLayout(Context, AttributeSet) - Constructor for class com.google.android.exoplayer.text.SubtitleLayout
-
 
-
SubtitleParser - Interface in com.google.android.exoplayer.text
-
-
Parses Subtitles from a byte array.
-
-
subtitles - Variable in class com.google.android.exoplayer.hls.HlsMasterPlaylist
-
 
-
subType - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
supportsEncoding(int) - Method in class com.google.android.exoplayer.audio.AudioCapabilities
-
-
Returns whether this device supports playback of the specified audio encoding.
-
-
SystemClock - Class in com.google.android.exoplayer.util
-
-
The standard implementation of Clock.
-
-
SystemClock() - Constructor for class com.google.android.exoplayer.util.SystemClock
-
 
-
- - - -

T

-
-
tagDataSize - Variable in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
tagTimestampUs - Variable in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
tagType - Variable in class com.google.android.exoplayer.extractor.flv.FlvExtractor
-
 
-
targetDurationSecs - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
TeeDataSource - Class in com.google.android.exoplayer.upstream
-
-
Tees data into a DataSink as the data is read.
-
-
TeeDataSource(DataSource, DataSink) - Constructor for class com.google.android.exoplayer.upstream.TeeDataSource
-
 
-
text - Variable in class com.google.android.exoplayer.text.Cue
-
-
The cue text.
-
-
TEXT_UNKNOWN - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
TEXT_VTT - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
textAlignment - Variable in class com.google.android.exoplayer.text.Cue
-
-
The alignment of the cue text within the cue box.
-
-
TextInformationFrame - Class in com.google.android.exoplayer.metadata.id3
-
-
Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame.
-
-
TextInformationFrame(String, String) - Constructor for class com.google.android.exoplayer.metadata.id3.TextInformationFrame
-
 
-
TextRenderer - Interface in com.google.android.exoplayer.text
-
-
An interface for components that render text.
-
-
TextTrackRenderer - Class in com.google.android.exoplayer.text
-
-
A TrackRenderer for subtitles.
-
-
TextTrackRenderer(SampleSource, TextRenderer, Looper, SubtitleParser...) - Constructor for class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
TextTrackRenderer(SampleSource[], TextRenderer, Looper, SubtitleParser...) - Constructor for class com.google.android.exoplayer.text.TextTrackRenderer
-
 
-
TimeRange - Interface in com.google.android.exoplayer
-
-
A container to store a start and end time in microseconds.
-
-
TimeRange.DynamicTimeRange - Class in com.google.android.exoplayer
-
-
A dynamic TimeRange.
-
-
TimeRange.StaticTimeRange - Class in com.google.android.exoplayer
-
-
A static TimeRange.
-
-
timescale - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
-
The track timescale, defined as the number of time units that pass in one second.
-
-
timescale - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
timeShiftBufferDepth - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
timestampUs - Variable in class com.google.android.exoplayer.util.extensions.OutputBuffer
-
-
The presentation timestamp for the buffer, in microseconds.
-
-
timesUs - Variable in class com.google.android.exoplayer.extractor.ChunkIndex
-
-
The start time of each chunk, in microseconds.
-
-
timeUs - Variable in class com.google.android.exoplayer.SampleHolder
-
-
The time at which the sample should be presented.
-
-
toArray() - Method in class com.google.android.exoplayer.util.LongArray
-
-
Copies the current values into a newly allocated primitive array.
-
-
toArray(List<Integer>) - Static method in class com.google.android.exoplayer.util.Util
-
-
Converts a list of integers to a primitive array.
-
-
toByteArray(InputStream) - Static method in class com.google.android.exoplayer.util.Util
-
-
Converts the entirety of an InputStream to a byte array.
-
-
toLowerInvariant(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Converts text to lower case using Locale.US.
-
-
toString() - Method in class com.google.android.exoplayer.audio.AudioCapabilities
-
 
-
toString() - Method in class com.google.android.exoplayer.dash.mpd.UtcTimingElement
-
 
-
toString() - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
toString() - Method in class com.google.android.exoplayer.upstream.DataSpec
-
 
-
totalSamples - Variable in class com.google.android.exoplayer.util.FlacStreamInfo
-
 
-
TRACE_ENABLED - Static variable in class com.google.android.exoplayer.ExoPlayerLibraryInfo
-
-
Whether the library was compiled with TraceUtil - trace enabled.
-
-
TraceUtil - Class in com.google.android.exoplayer.util
-
-
Calls through to Trace methods on supported API levels.
-
-
track(int) - Method in class com.google.android.exoplayer.chunk.ChunkExtractorWrapper
-
 
-
track(int) - Method in interface com.google.android.exoplayer.extractor.ExtractorOutput
-
-
Called by the Extractor to get the TrackOutput for a specific track.
-
-
track(int) - Method in class com.google.android.exoplayer.extractor.ExtractorSampleSource
-
 
-
Track - Class in com.google.android.exoplayer.extractor.mp4
-
-
Encapsulates information describing an MP4 track.
-
-
Track(int, int, long, long, long, MediaFormat, TrackEncryptionBox[], int, long[], long[]) - Constructor for class com.google.android.exoplayer.extractor.mp4.Track
-
 
-
track(int) - Method in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
TRACK_DEFAULT - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
A value that can be passed as the second argument to ExoPlayer.setSelectedTrack(int, int) to - select the default track.
-
-
TRACK_DISABLED - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
A value that can be passed as the second argument to ExoPlayer.setSelectedTrack(int, int) to - disable the renderer.
-
-
TrackElement(int, int, String, byte[][], int, int, int, int, String) - Constructor for class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
-
 
-
TrackEncryptionBox - Class in com.google.android.exoplayer.extractor.mp4
-
-
Encapsulates information parsed from a track encryption (tenc) box or sample group description - (sgpd) box in an MP4 stream.
-
-
TrackEncryptionBox(boolean, int, byte[]) - Constructor for class com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox
-
 
-
trackFormat - Variable in class com.google.android.exoplayer.dash.DashChunkSource.ExposedTrack
-
 
-
trackId - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The identifier for the track represented by the format, or null if unknown or not applicable.
-
-
TrackOutput - Interface in com.google.android.exoplayer.extractor
-
-
Receives track level data extracted by an Extractor.
-
-
TrackRenderer - Class in com.google.android.exoplayer
-
-
Renders a single component of media.
-
-
TrackRenderer() - Constructor for class com.google.android.exoplayer.TrackRenderer
-
 
-
tracks - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
TransferListener - Interface in com.google.android.exoplayer.upstream
-
-
Interface definition for a callback to be notified of data transfer events.
-
-
translateOffset(int) - Method in class com.google.android.exoplayer.upstream.Allocation
-
-
Translates a zero-based offset into the allocation to the corresponding Allocation.data offset.
-
-
trigger - Variable in class com.google.android.exoplayer.chunk.Chunk
-
-
The reason why the chunk was generated.
-
-
trigger - Variable in class com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
-
-
The sticky reason for the format selection.
-
-
trigger - Variable in class com.google.android.exoplayer.hls.HlsExtractorWrapper
-
 
-
TRIGGER_ADAPTIVE - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.trigger for a load triggered by an adaptive format selection.
-
-
TRIGGER_CUSTOM_BASE - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Implementations may define custom Chunk.trigger codes greater than or equal to this value.
-
-
TRIGGER_INITIAL - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.trigger for a load triggered by an initial format selection.
-
-
TRIGGER_MANUAL - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.trigger for a load triggered by a user initiated format selection.
-
-
TRIGGER_TRICK_PLAY - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.trigger for a load triggered whilst in a trick play mode.
-
-
TRIGGER_UNSPECIFIED - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.trigger for a load whose reason is unspecified.
-
-
trim(int) - Method in interface com.google.android.exoplayer.upstream.Allocator
-
-
Hints to the Allocator that it should make a best effort to release any memory that it - has allocated, beyond the specified target number of bytes.
-
-
trim(int) - Method in class com.google.android.exoplayer.upstream.DefaultAllocator
-
 
-
trimAllocator() - Method in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
trimAllocator() - Method in interface com.google.android.exoplayer.LoadControl
-
-
Hints to the control that it should consider trimming any unused memory being held in order - to satisfy allocation requests.
-
-
TsChunk - Class in com.google.android.exoplayer.hls
-
-
An MPEG2TS chunk.
-
-
TsChunk(DataSource, DataSpec, int, Format, long, long, int, int, HlsExtractorWrapper, byte[], byte[]) - Constructor for class com.google.android.exoplayer.hls.TsChunk
-
 
-
TsExtractor - Class in com.google.android.exoplayer.extractor.ts
-
-
Facilitates the extraction of data from the MPEG-2 TS container format.
-
-
TsExtractor() - Constructor for class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
TsExtractor(PtsTimestampAdjuster) - Constructor for class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
TsExtractor(PtsTimestampAdjuster, int) - Constructor for class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
TtmlParser - Class in com.google.android.exoplayer.text.ttml
-
-
A simple TTML parser that supports DFXP presentation profile.
-
-
TtmlParser() - Constructor for class com.google.android.exoplayer.text.ttml.TtmlParser
-
 
-
TtmlSubtitle - Class in com.google.android.exoplayer.text.ttml
-
-
A representation of a TTML subtitle.
-
-
TtmlSubtitle(TtmlNode, Map<String, TtmlStyle>, Map<String, TtmlRegion>) - Constructor for class com.google.android.exoplayer.text.ttml.TtmlSubtitle
-
 
-
Tx3gParser - Class in com.google.android.exoplayer.text.tx3g
-
-
A SubtitleParser for tx3g.
-
-
Tx3gParser() - Constructor for class com.google.android.exoplayer.text.tx3g.Tx3gParser
-
 
-
TxxxFrame - Class in com.google.android.exoplayer.metadata.id3
-
-
TXXX (User defined text information) ID3 frame.
-
-
TxxxFrame(String, String) - Constructor for class com.google.android.exoplayer.metadata.id3.TxxxFrame
-
 
-
type - Variable in class com.google.android.exoplayer.chunk.Chunk
-
-
The type of the chunk.
-
-
type - Variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
type - Variable in class com.google.android.exoplayer.extractor.mp4.Track
-
- -
-
type - Variable in class com.google.android.exoplayer.hls.HlsPlaylist
-
 
-
type - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
type - Variable in exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
TYPE_AUDIO - Static variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
TYPE_AUDIO - Static variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
TYPE_CLOSE - Static variable in exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
TYPE_CUSTOM_BASE - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Implementations may define custom Chunk.type codes greater than or equal to this value.
-
-
TYPE_DASH - Static variable in class com.google.android.exoplayer.util.Util
-
-
Value returned by Util.inferContentType(String) for DASH manifests.
-
-
TYPE_DRM - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.type for chunks containing drm related data.
-
-
TYPE_HLS - Static variable in class com.google.android.exoplayer.util.Util
-
-
Value returned by Util.inferContentType(String) for HLS manifests.
-
-
TYPE_MANIFEST - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.type for chunks containing manifest or playlist data.
-
-
TYPE_MASTER - Static variable in class com.google.android.exoplayer.hls.HlsPlaylist
-
 
-
TYPE_MEDIA - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.type for chunks containing media data.
-
-
TYPE_MEDIA - Static variable in class com.google.android.exoplayer.hls.HlsPlaylist
-
 
-
TYPE_MEDIA_INITIALIZATION - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.type for chunks containing media initialization data.
-
-
TYPE_meta - Static variable in class com.google.android.exoplayer.extractor.mp4.Track
-
 
-
TYPE_OPEN - Static variable in exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
TYPE_OTHER - Static variable in class com.google.android.exoplayer.util.Util
-
-
Value returned by Util.inferContentType(String) for files other than DASH, HLS or Smooth - Streaming manifests.
-
-
TYPE_READ - Static variable in exception com.google.android.exoplayer.upstream.HttpDataSource.HttpDataSourceException
-
 
-
TYPE_sbtl - Static variable in class com.google.android.exoplayer.extractor.mp4.Track
-
 
-
TYPE_soun - Static variable in class com.google.android.exoplayer.extractor.mp4.Track
-
 
-
TYPE_SS - Static variable in class com.google.android.exoplayer.util.Util
-
-
Value returned by Util.inferContentType(String) for Smooth Streaming manifests.
-
-
TYPE_subt - Static variable in class com.google.android.exoplayer.extractor.mp4.Track
-
 
-
TYPE_TEXT - Static variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
TYPE_text - Static variable in class com.google.android.exoplayer.extractor.mp4.Track
-
 
-
TYPE_TEXT - Static variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
TYPE_UNKNOWN - Static variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
TYPE_UNKNOWN - Static variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
TYPE_UNSET - Static variable in class com.google.android.exoplayer.text.Cue
-
-
An unset anchor or line type value.
-
-
TYPE_UNSPECIFIED - Static variable in class com.google.android.exoplayer.chunk.Chunk
-
-
Value of Chunk.type for chunks containing unspecified data.
-
-
TYPE_vide - Static variable in class com.google.android.exoplayer.extractor.mp4.Track
-
 
-
TYPE_VIDEO - Static variable in class com.google.android.exoplayer.dash.mpd.AdaptationSet
-
 
-
TYPE_VIDEO - Static variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
-
 
-
typeface - Variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
The preferred typeface.
-
-
- - - -

U

-
-
UdpDataSource - Class in com.google.android.exoplayer.upstream
-
-
A UDP DataSource.
-
-
UdpDataSource(TransferListener) - Constructor for class com.google.android.exoplayer.upstream.UdpDataSource
-
 
-
UdpDataSource(TransferListener, int) - Constructor for class com.google.android.exoplayer.upstream.UdpDataSource
-
 
-
UdpDataSource(TransferListener, int, int) - Constructor for class com.google.android.exoplayer.upstream.UdpDataSource
-
 
-
UdpDataSource.UdpDataSourceException - Exception in com.google.android.exoplayer.upstream
-
-
Thrown when an error is encountered when trying to read from a UdpDataSource.
-
-
UdpDataSourceException(String) - Constructor for exception com.google.android.exoplayer.upstream.UdpDataSource.UdpDataSourceException
-
 
-
UdpDataSourceException(IOException) - Constructor for exception com.google.android.exoplayer.upstream.UdpDataSource.UdpDataSourceException
-
 
-
unescapeFileName(String) - Static method in class com.google.android.exoplayer.util.Util
-
-
Unescapes an escaped file or directory name back to its original value.
-
-
unescapeStream(byte[], int) - Static method in class com.google.android.exoplayer.util.NalUnitUtil
-
-
Unescapes data up to the specified limit, replacing occurrences of [0, 0, 3] with - [0, 0].
-
-
UnexpectedLoaderException(Exception) - Constructor for exception com.google.android.exoplayer.upstream.Loader.UnexpectedLoaderException
-
 
-
Universal(DrmInitData.SchemeInitData) - Constructor for class com.google.android.exoplayer.drm.DrmInitData.Universal
-
 
-
UNKNOWN_TIME - Static variable in interface com.google.android.exoplayer.ExoPlayer
-
-
Represents an unknown time or duration.
-
-
UNKNOWN_TIME_US - Static variable in class com.google.android.exoplayer.C
-
-
Represents an unknown microsecond time or duration.
-
-
UNKNOWN_TIME_US - Static variable in class com.google.android.exoplayer.TrackRenderer
-
-
Represents an unknown time or duration.
-
-
UnrecognizedInputFormatException(Extractor[]) - Constructor for exception com.google.android.exoplayer.extractor.ExtractorSampleSource.UnrecognizedInputFormatException
-
 
-
unregister() - Method in class com.google.android.exoplayer.audio.AudioCapabilitiesReceiver
-
-
Unregisters to stop notifying the listener when audio capabilities change.
-
-
unregister(Object) - Method in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
unregister(Object) - Method in interface com.google.android.exoplayer.LoadControl
-
-
Unregisters a loader.
-
-
UNSEEKABLE - Static variable in interface com.google.android.exoplayer.extractor.SeekMap
-
-
A SeekMap that does not support seeking.
-
-
UnsupportedDrmException - Exception in com.google.android.exoplayer.drm
-
-
Thrown when the requested DRM scheme is not supported.
-
-
UnsupportedDrmException(int) - Constructor for exception com.google.android.exoplayer.drm.UnsupportedDrmException
-
 
-
UnsupportedDrmException(int, Exception) - Constructor for exception com.google.android.exoplayer.drm.UnsupportedDrmException
-
 
-
update(Object, long, long, boolean) - Method in class com.google.android.exoplayer.DefaultLoadControl
-
 
-
update(Object, long, long, boolean) - Method in interface com.google.android.exoplayer.LoadControl
-
-
Invoked by a loader to update the control with its current state.
-
-
updateManifestUri(String) - Method in class com.google.android.exoplayer.util.ManifestFetcher
-
-
Updates the manifest location.
-
-
updatePeriod(MediaPresentationDescription, int, DashChunkSource.ExposedTrack) - Method in class com.google.android.exoplayer.dash.DashChunkSource.PeriodHolder
-
 
-
updateRepresentation(long, Representation) - Method in class com.google.android.exoplayer.dash.DashChunkSource.RepresentationHolder
-
 
-
uri - Variable in class com.google.android.exoplayer.dash.mpd.Representation.SingleSegmentRepresentation
-
-
The uri of the single segment.
-
-
uri - Variable in class com.google.android.exoplayer.upstream.DataSpec
-
-
Identifies the source from which data should be read.
-
-
UriDataSource - Interface in com.google.android.exoplayer.upstream
-
-
A component that provides media data from a URI.
-
-
UriLoadable<T> - Class in com.google.android.exoplayer.upstream
-
-
A Loader.Loadable for loading an object from a URI.
-
-
UriLoadable(String, UriDataSource, UriLoadable.Parser<T>) - Constructor for class com.google.android.exoplayer.upstream.UriLoadable
-
 
-
UriLoadable.Parser<T> - Interface in com.google.android.exoplayer.upstream
-
-
Parses an object from loaded data.
-
-
UriUtil - Class in com.google.android.exoplayer.util
-
-
Utility methods for manipulating URIs.
-
-
url - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
-
 
-
url - Variable in class com.google.android.exoplayer.hls.Variant
-
 
-
UrlTemplate - Class in com.google.android.exoplayer.dash.mpd
-
-
A template from which URLs can be built.
-
-
USE_TRACK_COLOR_SETTINGS - Static variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
Use color setting specified by the track and fallback to default caption style.
-
-
usToMs(long) - Method in class com.google.android.exoplayer.chunk.ChunkSampleSource
-
 
-
usToPts(long) - Static method in class com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster
-
-
Converts a value in microseconds to the corresponding values in MPEG-2 timestamp units.
-
-
utcTiming - Variable in class com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
-
 
-
UtcTimingElement - Class in com.google.android.exoplayer.dash.mpd
-
-
Represents a UTCTiming element.
-
-
UtcTimingElement(String, String) - Constructor for class com.google.android.exoplayer.dash.mpd.UtcTimingElement
-
 
-
UtcTimingElementResolver - Class in com.google.android.exoplayer.dash.mpd
-
-
Resolves a UtcTimingElement.
-
-
UtcTimingElementResolver.UtcTimingCallback - Interface in com.google.android.exoplayer.dash.mpd
-
-
Callback for timing element resolution.
-
-
UTF8_NAME - Static variable in class com.google.android.exoplayer.C
-
-
The name of the UTF-8 charset.
-
-
Util - Class in com.google.android.exoplayer.util
-
-
Miscellaneous utility functions.
-
-
uuid - Variable in class com.google.android.exoplayer.dash.mpd.ContentProtection
-
-
The UUID of the protection scheme.
-
-
uuid - Variable in class com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement
-
 
-
- - - -

V

-
-
validateWebvttHeaderLine(ParsableByteArray) - Static method in class com.google.android.exoplayer.text.webvtt.WebvttParserUtil
-
-
Reads and validates the first line of a WebVTT file.
-
-
value - Variable in class com.google.android.exoplayer.dash.mpd.UtcTimingElement
-
 
-
value - Variable in class com.google.android.exoplayer.metadata.id3.TxxxFrame
-
 
-
Variant - Class in com.google.android.exoplayer.hls
-
-
Variant stream reference.
-
-
Variant(String, Format) - Constructor for class com.google.android.exoplayer.hls.Variant
-
 
-
variants - Variable in class com.google.android.exoplayer.hls.HlsMasterPlaylist
-
 
-
VerboseLogUtil - Class in com.google.android.exoplayer.util
-
-
Utility class for managing a set of tags for which verbose logging should be enabled.
-
-
VERSION - Static variable in class com.google.android.exoplayer.ExoPlayerLibraryInfo
-
-
The version of the library, expressed as a string.
-
-
version - Variable in class com.google.android.exoplayer.hls.HlsMediaPlaylist
-
 
-
version - Variable in class com.google.android.exoplayer.util.MpegAudioHeader
-
-
MPEG audio header version.
-
-
VERSION_INT - Static variable in class com.google.android.exoplayer.ExoPlayerLibraryInfo
-
-
The version of the library, expressed as an integer.
-
-
VIDEO_H263 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_H264 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_H265 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_MP4 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_MP4V - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_MPEG2 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_STREAM - Static variable in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
VIDEO_STREAM_MASK - Static variable in class com.google.android.exoplayer.extractor.ts.PsExtractor
-
 
-
VIDEO_UNKNOWN - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_VC1 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_VP8 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_VP9 - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VIDEO_WEBM - Static variable in class com.google.android.exoplayer.util.MimeTypes
-
 
-
VideoFormatSelectorUtil - Class in com.google.android.exoplayer.chunk
-
-
Selects from possible video formats.
-
-
VideoFrameReleaseTimeHelper - Class in com.google.android.exoplayer
-
-
Makes a best effort to adjust frame release timestamps for a smoother visual result.
-
-
VideoFrameReleaseTimeHelper() - Constructor for class com.google.android.exoplayer.VideoFrameReleaseTimeHelper
-
-
Constructs an instance that smoothes frame release but does not snap release to the default - display's vsync signal.
-
-
VideoFrameReleaseTimeHelper(Context) - Constructor for class com.google.android.exoplayer.VideoFrameReleaseTimeHelper
-
-
Constructs an instance that smoothes frame release and snaps release to the default display's - vsync signal.
-
-
- - - -

W

-
-
warmCodec(String, boolean) - Static method in class com.google.android.exoplayer.MediaCodecUtil
-
-
Optional call to warm the codec cache for a given mime type.
-
-
WavExtractor - Class in com.google.android.exoplayer.extractor.wav
-
-
Extractor to extract samples from a WAV byte stream.
-
-
WavExtractor() - Constructor for class com.google.android.exoplayer.extractor.wav.WavExtractor
-
 
-
WebmExtractor - Class in com.google.android.exoplayer.extractor.webm
-
-
An extractor to facilitate data retrieval from the WebM container format.
-
-
WebmExtractor() - Constructor for class com.google.android.exoplayer.extractor.webm.WebmExtractor
-
 
-
WebvttCueParser - Class in com.google.android.exoplayer.text.webvtt
-
-
Parser for WebVTT cues.
-
-
WebvttCueParser() - Constructor for class com.google.android.exoplayer.text.webvtt.WebvttCueParser
-
 
-
WebvttParser - Class in com.google.android.exoplayer.text.webvtt
-
-
A simple WebVTT parser.
-
-
WebvttParser() - Constructor for class com.google.android.exoplayer.text.webvtt.WebvttParser
-
 
-
WebvttParserUtil - Class in com.google.android.exoplayer.text.webvtt
-
-
Utility methods for parsing WebVTT data.
-
-
WebvttSubtitle - Class in com.google.android.exoplayer.text.webvtt
-
-
A representation of a WebVTT subtitle.
-
-
WebvttSubtitle(List<WebvttCue>) - Constructor for class com.google.android.exoplayer.text.webvtt.WebvttSubtitle
-
 
-
WIDEVINE_UUID - Static variable in class com.google.android.exoplayer.drm.StreamingDrmSessionManager
-
-
UUID for the Widevine DRM scheme.
-
-
width - Variable in class com.google.android.exoplayer.chunk.Format
-
-
The width of the video in pixels, or -1 if unknown or not applicable.
-
-
width - Variable in class com.google.android.exoplayer.MediaFormat
-
-
The width of the video in pixels, or MediaFormat.NO_VALUE if unknown or not applicable.
-
-
width - Variable in class com.google.android.exoplayer.util.NalUnitUtil.SpsData
-
 
-
windowColor - Variable in class com.google.android.exoplayer.text.CaptionStyleCompat
-
-
The preferred window color.
-
-
WORKAROUND_ALLOW_NON_IDR_KEYFRAMES - Static variable in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
WORKAROUND_DETECT_ACCESS_UNITS - Static variable in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
WORKAROUND_HLS_MODE - Static variable in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
WORKAROUND_IGNORE_AAC_STREAM - Static variable in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
WORKAROUND_IGNORE_H264_STREAM - Static variable in class com.google.android.exoplayer.extractor.ts.TsExtractor
-
 
-
write(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.ByteArrayDataSink
-
 
-
write(byte[], int, int) - Method in class com.google.android.exoplayer.upstream.cache.CacheDataSink
-
 
-
write(byte[], int, int) - Method in interface com.google.android.exoplayer.upstream.DataSink
-
-
Consumes the provided data.
-
-
WriteException(int) - Constructor for exception com.google.android.exoplayer.audio.AudioTrack.WriteException
-
 
-
writeToParcel(Parcel, int) - Method in class com.google.android.exoplayer.MediaFormat
-
 
-
-A B C D E F G H I K L M N O P Q R S T U V W 
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/index.html b/docs/doc/reference-v1/index.html deleted file mode 100644 index 4c0aebb45b..0000000000 --- a/docs/doc/reference-v1/index.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - -ExoPlayer library - - - - - - - - - -<noscript> -<div>JavaScript is disabled on your browser.</div> -</noscript> -<h2>Frame Alert</h2> -<p>This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. Link to <a href="overview-summary.html">Non-frame version</a>.</p> - - - diff --git a/docs/doc/reference-v1/overview-frame.html b/docs/doc/reference-v1/overview-frame.html deleted file mode 100644 index 7ea5740f16..0000000000 --- a/docs/doc/reference-v1/overview-frame.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - -Overview List (ExoPlayer library) - - - - - -
All Classes
-
-

Packages

- -
-

 

- - diff --git a/docs/doc/reference-v1/overview-summary.html b/docs/doc/reference-v1/overview-summary.html deleted file mode 100644 index 48f1835d17..0000000000 --- a/docs/doc/reference-v1/overview-summary.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - -Overview (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

ExoPlayer library

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Packages 
PackageDescription
com.google.android.exoplayer 
com.google.android.exoplayer.audio 
com.google.android.exoplayer.chunk 
com.google.android.exoplayer.dash 
com.google.android.exoplayer.dash.mpd 
com.google.android.exoplayer.drm 
com.google.android.exoplayer.extractor 
com.google.android.exoplayer.extractor.flv 
com.google.android.exoplayer.extractor.mp3 
com.google.android.exoplayer.extractor.mp4 
com.google.android.exoplayer.extractor.ogg 
com.google.android.exoplayer.extractor.ts 
com.google.android.exoplayer.extractor.wav 
com.google.android.exoplayer.extractor.webm 
com.google.android.exoplayer.hls 
com.google.android.exoplayer.metadata 
com.google.android.exoplayer.metadata.id3 
com.google.android.exoplayer.smoothstreaming 
com.google.android.exoplayer.text 
com.google.android.exoplayer.text.eia608 
com.google.android.exoplayer.text.subrip 
com.google.android.exoplayer.text.ttml 
com.google.android.exoplayer.text.tx3g 
com.google.android.exoplayer.text.webvtt 
com.google.android.exoplayer.upstream 
com.google.android.exoplayer.upstream.cache 
com.google.android.exoplayer.util 
com.google.android.exoplayer.util.extensions 
-
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/overview-tree.html b/docs/doc/reference-v1/overview-tree.html deleted file mode 100644 index 96cd7af77d..0000000000 --- a/docs/doc/reference-v1/overview-tree.html +++ /dev/null @@ -1,580 +0,0 @@ - - - - - -Class Hierarchy (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Hierarchy For All Packages

-Package Hierarchies: - -
-
-

Class Hierarchy

- -

Interface Hierarchy

- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/package-list b/docs/doc/reference-v1/package-list deleted file mode 100644 index 211335f1b1..0000000000 --- a/docs/doc/reference-v1/package-list +++ /dev/null @@ -1,28 +0,0 @@ -com.google.android.exoplayer -com.google.android.exoplayer.audio -com.google.android.exoplayer.chunk -com.google.android.exoplayer.dash -com.google.android.exoplayer.dash.mpd -com.google.android.exoplayer.drm -com.google.android.exoplayer.extractor -com.google.android.exoplayer.extractor.flv -com.google.android.exoplayer.extractor.mp3 -com.google.android.exoplayer.extractor.mp4 -com.google.android.exoplayer.extractor.ogg -com.google.android.exoplayer.extractor.ts -com.google.android.exoplayer.extractor.wav -com.google.android.exoplayer.extractor.webm -com.google.android.exoplayer.hls -com.google.android.exoplayer.metadata -com.google.android.exoplayer.metadata.id3 -com.google.android.exoplayer.smoothstreaming -com.google.android.exoplayer.text -com.google.android.exoplayer.text.eia608 -com.google.android.exoplayer.text.subrip -com.google.android.exoplayer.text.ttml -com.google.android.exoplayer.text.tx3g -com.google.android.exoplayer.text.webvtt -com.google.android.exoplayer.upstream -com.google.android.exoplayer.upstream.cache -com.google.android.exoplayer.util -com.google.android.exoplayer.util.extensions diff --git a/docs/doc/reference-v1/script.js b/docs/doc/reference-v1/script.js deleted file mode 100644 index b346356931..0000000000 --- a/docs/doc/reference-v1/script.js +++ /dev/null @@ -1,30 +0,0 @@ -function show(type) -{ - count = 0; - for (var key in methods) { - var row = document.getElementById(key); - if ((methods[key] & type) != 0) { - row.style.display = ''; - row.className = (count++ % 2) ? rowColor : altColor; - } - else - row.style.display = 'none'; - } - updateTabs(type); -} - -function updateTabs(type) -{ - for (var value in tabs) { - var sNode = document.getElementById(tabs[value][0]); - var spanNode = sNode.firstChild; - if (value == type) { - sNode.className = activeTableTab; - spanNode.innerHTML = tabs[value][1]; - } - else { - sNode.className = tableTab; - spanNode.innerHTML = "" + tabs[value][1] + ""; - } - } -} diff --git a/docs/doc/reference-v1/serialized-form.html b/docs/doc/reference-v1/serialized-form.html deleted file mode 100644 index 1e9197ffe3..0000000000 --- a/docs/doc/reference-v1/serialized-form.html +++ /dev/null @@ -1,424 +0,0 @@ - - - - - -Serialized Form (ExoPlayer library) - - - - - - - - -
- - -
Skip navigation links
- - - - -
- - -
-

Serialized Form

-
-
- -
- -
- - -
Skip navigation links
- - - - -
- - - - diff --git a/docs/doc/reference-v1/stylesheet.css b/docs/doc/reference-v1/stylesheet.css deleted file mode 100644 index 98055b22d6..0000000000 --- a/docs/doc/reference-v1/stylesheet.css +++ /dev/null @@ -1,574 +0,0 @@ -/* Javadoc style sheet */ -/* -Overall document style -*/ - -@import url('resources/fonts/dejavu.css'); - -body { - background-color:#ffffff; - color:#353833; - font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; - font-size:14px; - margin:0; -} -a:link, a:visited { - text-decoration:none; - color:#4A6782; -} -a:hover, a:focus { - text-decoration:none; - color:#bb7a2a; -} -a:active { - text-decoration:none; - color:#4A6782; -} -a[name] { - color:#353833; -} -a[name]:hover { - text-decoration:none; - color:#353833; -} -pre { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; -} -h1 { - font-size:20px; -} -h2 { - font-size:18px; -} -h3 { - font-size:16px; - font-style:italic; -} -h4 { - font-size:13px; -} -h5 { - font-size:12px; -} -h6 { - font-size:11px; -} -ul { - list-style-type:disc; -} -code, tt { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - padding-top:4px; - margin-top:8px; - line-height:1.4em; -} -dt code { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - padding-top:4px; -} -table tr td dt code { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - vertical-align:top; - padding-top:4px; -} -sup { - font-size:8px; -} -/* -Document title and Copyright styles -*/ -.clear { - clear:both; - height:0px; - overflow:hidden; -} -.aboutLanguage { - float:right; - padding:0px 21px; - font-size:11px; - z-index:200; - margin-top:-9px; -} -.legalCopy { - margin-left:.5em; -} -.bar a, .bar a:link, .bar a:visited, .bar a:active { - color:#FFFFFF; - text-decoration:none; -} -.bar a:hover, .bar a:focus { - color:#bb7a2a; -} -.tab { - background-color:#0066FF; - color:#ffffff; - padding:8px; - width:5em; - font-weight:bold; -} -/* -Navigation bar styles -*/ -.bar { - background-color:#4D7A97; - color:#FFFFFF; - padding:.8em .5em .4em .8em; - height:auto;/*height:1.8em;*/ - font-size:11px; - margin:0; -} -.topNav { - background-color:#4D7A97; - color:#FFFFFF; - float:left; - padding:0; - width:100%; - clear:right; - height:2.8em; - padding-top:10px; - overflow:hidden; - font-size:12px; -} -.bottomNav { - margin-top:10px; - background-color:#4D7A97; - color:#FFFFFF; - float:left; - padding:0; - width:100%; - clear:right; - height:2.8em; - padding-top:10px; - overflow:hidden; - font-size:12px; -} -.subNav { - background-color:#dee3e9; - float:left; - width:100%; - overflow:hidden; - font-size:12px; -} -.subNav div { - clear:left; - float:left; - padding:0 0 5px 6px; - text-transform:uppercase; -} -ul.navList, ul.subNavList { - float:left; - margin:0 25px 0 0; - padding:0; -} -ul.navList li{ - list-style:none; - float:left; - padding: 5px 6px; - text-transform:uppercase; -} -ul.subNavList li{ - list-style:none; - float:left; -} -.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { - color:#FFFFFF; - text-decoration:none; - text-transform:uppercase; -} -.topNav a:hover, .bottomNav a:hover { - text-decoration:none; - color:#bb7a2a; - text-transform:uppercase; -} -.navBarCell1Rev { - background-color:#F8981D; - color:#253441; - margin: auto 5px; -} -.skipNav { - position:absolute; - top:auto; - left:-9999px; - overflow:hidden; -} -/* -Page header and footer styles -*/ -.header, .footer { - clear:both; - margin:0 20px; - padding:5px 0 0 0; -} -.indexHeader { - margin:10px; - position:relative; -} -.indexHeader span{ - margin-right:15px; -} -.indexHeader h1 { - font-size:13px; -} -.title { - color:#2c4557; - margin:10px 0; -} -.subTitle { - margin:5px 0 0 0; -} -.header ul { - margin:0 0 15px 0; - padding:0; -} -.footer ul { - margin:20px 0 5px 0; -} -.header ul li, .footer ul li { - list-style:none; - font-size:13px; -} -/* -Heading styles -*/ -div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { - background-color:#dee3e9; - border:1px solid #d0d9e0; - margin:0 0 6px -8px; - padding:7px 5px; -} -ul.blockList ul.blockList ul.blockList li.blockList h3 { - background-color:#dee3e9; - border:1px solid #d0d9e0; - margin:0 0 6px -8px; - padding:7px 5px; -} -ul.blockList ul.blockList li.blockList h3 { - padding:0; - margin:15px 0; -} -ul.blockList li.blockList h2 { - padding:0px 0 20px 0; -} -/* -Page layout container styles -*/ -.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { - clear:both; - padding:10px 20px; - position:relative; -} -.indexContainer { - margin:10px; - position:relative; - font-size:12px; -} -.indexContainer h2 { - font-size:13px; - padding:0 0 3px 0; -} -.indexContainer ul { - margin:0; - padding:0; -} -.indexContainer ul li { - list-style:none; - padding-top:2px; -} -.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { - font-size:12px; - font-weight:bold; - margin:10px 0 0 0; - color:#4E4E4E; -} -.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { - margin:5px 0 10px 0px; - font-size:14px; - font-family:'DejaVu Sans Mono',monospace; -} -.serializedFormContainer dl.nameValue dt { - margin-left:1px; - font-size:1.1em; - display:inline; - font-weight:bold; -} -.serializedFormContainer dl.nameValue dd { - margin:0 0 0 1px; - font-size:1.1em; - display:inline; -} -/* -List styles -*/ -ul.horizontal li { - display:inline; - font-size:0.9em; -} -ul.inheritance { - margin:0; - padding:0; -} -ul.inheritance li { - display:inline; - list-style:none; -} -ul.inheritance li ul.inheritance { - margin-left:15px; - padding-left:15px; - padding-top:1px; -} -ul.blockList, ul.blockListLast { - margin:10px 0 10px 0; - padding:0; -} -ul.blockList li.blockList, ul.blockListLast li.blockList { - list-style:none; - margin-bottom:15px; - line-height:1.4; -} -ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { - padding:0px 20px 5px 10px; - border:1px solid #ededed; - background-color:#f8f8f8; -} -ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { - padding:0 0 5px 8px; - background-color:#ffffff; - border:none; -} -ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { - margin-left:0; - padding-left:0; - padding-bottom:15px; - border:none; -} -ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { - list-style:none; - border-bottom:none; - padding-bottom:0; -} -table tr td dl, table tr td dl dt, table tr td dl dd { - margin-top:0; - margin-bottom:1px; -} -/* -Table styles -*/ -.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { - width:100%; - border-left:1px solid #EEE; - border-right:1px solid #EEE; - border-bottom:1px solid #EEE; -} -.overviewSummary, .memberSummary { - padding:0px; -} -.overviewSummary caption, .memberSummary caption, .typeSummary caption, -.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { - position:relative; - text-align:left; - background-repeat:no-repeat; - color:#253441; - font-weight:bold; - clear:none; - overflow:hidden; - padding:0px; - padding-top:10px; - padding-left:1px; - margin:0px; - white-space:pre; -} -.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, -.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, -.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, -.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, -.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, -.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, -.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, -.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { - color:#FFFFFF; -} -.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, -.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { - white-space:nowrap; - padding-top:5px; - padding-left:12px; - padding-right:12px; - padding-bottom:7px; - display:inline-block; - float:left; - background-color:#F8981D; - border: none; - height:16px; -} -.memberSummary caption span.activeTableTab span { - white-space:nowrap; - padding-top:5px; - padding-left:12px; - padding-right:12px; - margin-right:3px; - display:inline-block; - float:left; - background-color:#F8981D; - height:16px; -} -.memberSummary caption span.tableTab span { - white-space:nowrap; - padding-top:5px; - padding-left:12px; - padding-right:12px; - margin-right:3px; - display:inline-block; - float:left; - background-color:#4D7A97; - height:16px; -} -.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { - padding-top:0px; - padding-left:0px; - padding-right:0px; - background-image:none; - float:none; - display:inline; -} -.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, -.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { - display:none; - width:5px; - position:relative; - float:left; - background-color:#F8981D; -} -.memberSummary .activeTableTab .tabEnd { - display:none; - width:5px; - margin-right:3px; - position:relative; - float:left; - background-color:#F8981D; -} -.memberSummary .tableTab .tabEnd { - display:none; - width:5px; - margin-right:3px; - position:relative; - background-color:#4D7A97; - float:left; - -} -.overviewSummary td, .memberSummary td, .typeSummary td, -.useSummary td, .constantsSummary td, .deprecatedSummary td { - text-align:left; - padding:0px 0px 12px 10px; -} -th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, -td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ - vertical-align:top; - padding-right:0px; - padding-top:8px; - padding-bottom:3px; -} -th.colFirst, th.colLast, th.colOne, .constantsSummary th { - background:#dee3e9; - text-align:left; - padding:8px 3px 3px 7px; -} -td.colFirst, th.colFirst { - white-space:nowrap; - font-size:13px; -} -td.colLast, th.colLast { - font-size:13px; -} -td.colOne, th.colOne { - font-size:13px; -} -.overviewSummary td.colFirst, .overviewSummary th.colFirst, -.useSummary td.colFirst, .useSummary th.colFirst, -.overviewSummary td.colOne, .overviewSummary th.colOne, -.memberSummary td.colFirst, .memberSummary th.colFirst, -.memberSummary td.colOne, .memberSummary th.colOne, -.typeSummary td.colFirst{ - width:25%; - vertical-align:top; -} -td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { - font-weight:bold; -} -.tableSubHeadingColor { - background-color:#EEEEFF; -} -.altColor { - background-color:#FFFFFF; -} -.rowColor { - background-color:#EEEEEF; -} -/* -Content styles -*/ -.description pre { - margin-top:0; -} -.deprecatedContent { - margin:0; - padding:10px 0; -} -.docSummary { - padding:0; -} - -ul.blockList ul.blockList ul.blockList li.blockList h3 { - font-style:normal; -} - -div.block { - font-size:14px; - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; -} - -td.colLast div { - padding-top:0px; -} - - -td.colLast a { - padding-bottom:3px; -} -/* -Formatting effect styles -*/ -.sourceLineNo { - color:green; - padding:0 30px 0 0; -} -h1.hidden { - visibility:hidden; - overflow:hidden; - font-size:10px; -} -.block { - display:block; - margin:3px 10px 2px 0px; - color:#474747; -} -.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, -.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, -.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { - font-weight:bold; -} -.deprecationComment, .emphasizedPhrase, .interfaceName { - font-style:italic; -} - -div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, -div.block div.block span.interfaceName { - font-style:normal; -} - -div.contentContainer ul.blockList li.blockList h2{ - padding-bottom:0px; -} diff --git a/docs/drm.md b/docs/drm.md index 181f5c2c7c..dab0edae89 100644 --- a/docs/drm.md +++ b/docs/drm.md @@ -26,12 +26,6 @@ outlined in the sections below. To play streams with rotating keys, pass `true` to `MediaItem.Builder.setDrmMultiSession` when building the media item. -{% include known-issue-box.html issue-id="4133" description="There may be a -slight pause in playback when key rotation occurs." %} - -{% include known-issue-box.html issue-id="3561" description="On API level 22 -and below, the output surface may flicker when key rotation occurs." %} - ### Multi-key content ### Multi-key content consists of multiple streams, where some streams use different diff --git a/docs/hello-world.md b/docs/hello-world.md index 048bb5b24d..01db16036c 100644 --- a/docs/hello-world.md +++ b/docs/hello-world.md @@ -70,6 +70,7 @@ modules individually. * `exoplayer-core`: Core functionality (required). * `exoplayer-dash`: Support for DASH content. * `exoplayer-hls`: Support for HLS content. +* `exoplayer-rtsp`: Support for RTSP content. * `exoplayer-smoothstreaming`: Support for SmoothStreaming content. * `exoplayer-transformer`: Media transformation functionality. * `exoplayer-ui`: UI components and resources for use with ExoPlayer. @@ -98,12 +99,9 @@ to prevent build errors. ## Creating the player ## -You can create an `ExoPlayer` instance using `SimpleExoPlayer.Builder` or -`ExoPlayer.Builder`. The builders provide a range of customization options for -creating `ExoPlayer` instances. For the vast majority of use cases -`SimpleExoPlayer.Builder` should be used. This builder returns -`SimpleExoPlayer`, which extends `ExoPlayer` to add additional high level player -functionality. The code below is an example of creating a `SimpleExoPlayer`. +You can create an `ExoPlayer` instance using `SimpleExoPlayer.Builder`, which +provides a range of customization options. The code below is the simplest +example of creating an instance. ~~~ SimpleExoPlayer player = new SimpleExoPlayer.Builder(context).build(); @@ -132,7 +130,7 @@ shows you where). You can temporarily opt out from these exceptions being thrown by calling `SimpleExoPlayer.setThrowsWhenUsingWrongThread(false)`, in which case the issue will be logged as a warning instead. Using this opt out is not safe and may result in unexpected or obscure errors. It will be removed in ExoPlayer -2.14. +2.16. {:.info} For more information about ExoPlayer's treading model, see the @@ -224,9 +222,9 @@ on the player. Some of the most commonly used methods are listed below. * `setShuffleModeEnabled` controls playlist shuffling. * `setPlaybackParameters` adjusts playback speed and audio pitch. -If the player is bound to a `PlayerView` or `PlayerControlView`, then user -interaction with these components will cause corresponding methods on the player -to be invoked. +If the player is bound to a `StyledPlayerView` or `StyledPlayerControlView`, +then user interaction with these components will cause corresponding methods on +the player to be invoked. ## Releasing the player ## diff --git a/docs/hls.md b/docs/hls.md index 4bc432d947..9718306ac7 100644 --- a/docs/hls.md +++ b/docs/hls.md @@ -60,14 +60,14 @@ player.prepare(); You can retrieve the current manifest by calling `Player.getCurrentManifest`. For HLS you should cast the returned object to `HlsManifest`. The -`onTimelineChanged` callback of `Player.EventListener` is also called whenever +`onTimelineChanged` callback of `Player.Listener` is also called whenever the manifest is loaded. This will happen once for a on-demand content, and possibly many times for live content. The code snippet below shows how an app can do something whenever the manifest is loaded. ~~~ player.addListener( - new Player.EventListener() { + new Player.Listener() { @Override public void onTimelineChanged( Timeline timeline, @Player.TimelineChangeReason int reason) { @@ -110,7 +110,7 @@ follow to improve your HLS content. Read our [Medium post about HLS playback in ExoPlayer][] for a full explanation. The main points are: * Use precise segment durations. -* Use a continues media stream; avoid changes in the media structure across +* Use a continuous media stream; avoid changes in the media structure across segments. * Use the `#EXT-X-INDEPENDENT-SEGMENTS` tag. * Prefer demuxed streams, as opposed to files that include both video and audio. diff --git a/docs/listening-to-player-events.md b/docs/listening-to-player-events.md index 37e1954140..58dbb7fe75 100644 --- a/docs/listening-to-player-events.md +++ b/docs/listening-to-player-events.md @@ -5,16 +5,16 @@ title: Player events ## Listening to playback events ## Events such as changes in state and playback errors are reported to registered -[`Player.EventListener`][] instances. Registering a listener to receive such +[`Player.Listener`][] instances. Registering a listener to receive such events is easy: ~~~ // Add a listener to receive events from the player. -player.addListener(eventListener); +player.addListener(listener); ~~~ {: .language-java} -`Player.EventListener` has empty default methods, so you only need to implement +`Player.Listener` has empty default methods, so you only need to implement the methods you're interested in. See the [Javadoc][] for a full description of the methods and when they're called. Some of the most important methods are described in more detail below. @@ -28,7 +28,7 @@ should be preferred for different use cases. Changes in player state can be received by implementing `onPlaybackStateChanged(@State int state)` in a registered -`Player.EventListener`. The player can be in one of four playback states: +`Player.Listener`. The player can be in one of four playback states: * `Player.STATE_IDLE`: This is the initial state, the state when the player is stopped, and when playback failed. @@ -68,7 +68,7 @@ public void onIsPlayingChanged(boolean isPlaying) { Errors that cause playback to fail can be received by implementing `onPlayerError(ExoPlaybackException error)` in a registered -`Player.EventListener`. When a failure occurs, this method will be called +`Player.Listener`. When a failure occurs, this method will be called immediately before the playback state transitions to `Player.STATE_IDLE`. Failed or stopped playbacks can be retried by calling `ExoPlayer.retry`. @@ -107,7 +107,7 @@ public void onPlayerError(ExoPlaybackException error) { Whenever the player changes to a new media item in the playlist `onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason)` is called on registered -`Player.EventListener`s. The reason indicates whether this was an automatic +`Player.Listener`s. The reason indicates whether this was an automatic transition, a seek (for example after calling `player.next()`), a repetition of the same item, or caused by a playlist change (e.g., if the currently playing item is removed). @@ -115,7 +115,7 @@ item is removed). ### Seeking ### Calling `Player.seekTo` methods results in a series of callbacks to registered -`Player.EventListener` instances: +`Player.Listener` instances: 1. `onPositionDiscontinuity` with `reason=DISCONTINUITY_REASON_SEEK`. This is the direct result of calling `Player.seekTo`. @@ -177,28 +177,12 @@ generic `onEvents` callback, for example to record media item change reasons with `onMediaItemTransition`, but only act once all state changes can be used together in `onEvents`. -## Additional SimpleExoPlayer listeners ## +## Using AnalyticsListener ## -When using `SimpleExoPlayer`, additional listeners can be registered with the -player. - -* `addAnalyticsListener`: Listen to detailed events that may be useful for - analytics and logging purposes. Please refer to the [analytics page][] for - more details. -* `addTextOutput`: Listen to changes in the subtitle or caption cues. -* `addMetadataOutput`: Listen to timed metadata events, such as timed ID3 and - EMSG data. -* `addVideoListener`: Listen to events related to video rendering that may be - useful for adjusting the UI (e.g., the aspect ratio of the `Surface` onto - which video is being rendered). -* `addAudioListener`: Listen to events related to audio, such as when an audio - session ID changes, and when the player volume is changed. -* `addDeviceListener`: Listen to events related to the state of the device. - -ExoPlayer's UI components, such as `StyledPlayerView`, will register themselves -as listeners to events that they are interested in. Hence manual registration -using the methods above is only useful for applications that implement their own -player UI, or need to listen to events for some other purpose. +When using `SimpleExoPlayer`, an `AnalyticsListener` can be registered with the +player by calling `addAnalyticsListener`. `AnalyticsListener` implementations +are able to listen to detailed events that may be useful for analytics and +logging purposes. Please refer to the [analytics page][] for more details. ### Using EventLogger ### @@ -241,8 +225,8 @@ player ~~~ {: .language-java } -[`Player.EventListener`]: {{ site.exo_sdk }}/Player.EventListener.html -[Javadoc]: {{ site.exo_sdk }}/Player.EventListener.html +[`Player.Listener`]: {{ site.exo_sdk }}/Player.Listener.html +[Javadoc]: {{ site.exo_sdk }}/Player.Listener.html [`Individual callbacks vs onEvents`]: #individual-callbacks-vs-onevents [`ExoPlaybackException`]: {{ site.exo_sdk }}/ExoPlaybackException.html [log output]: event-logger.html diff --git a/docs/live-streaming.md b/docs/live-streaming.md index 302e804137..86ead16d51 100644 --- a/docs/live-streaming.md +++ b/docs/live-streaming.md @@ -25,7 +25,7 @@ closer to the live edge again. ## Detecting and monitoring live playbacks ## -Every time a live window is updated, registered `Player.EventListener` instances +Every time a live window is updated, registered `Player.Listener` instances will receive an `onTimelineChanged` event. You can retrieve details about the current live playback by querying various `Player` and `Timeline.Window` methods, as listed below and shown in the following figure. @@ -135,7 +135,7 @@ setting `minPlaybackSpeed` and `maxPlaybackSpeed` to `1.0f`. The playback position may fall behind the live window, for example if the player is paused or buffering for a long enough period of time. If this happens then playback will fail and a `BehindLiveWindowException` will be reported via -`Player.EventListener.onPlayerError`. Application code may wish to handle such +`Player.Listener.onPlayerError`. Application code may wish to handle such errors by resuming playback at the default position. The [PlayerActivity][] of the demo app exemplifies this approach. diff --git a/docs/media-sources.md b/docs/media-sources.md index f257dce5c5..3084cf8d17 100644 --- a/docs/media-sources.md +++ b/docs/media-sources.md @@ -15,6 +15,7 @@ instances of the following content `MediaSource` implementations: * `SsMediaSource` for [SmoothStreaming][]. * `HlsMediaSource` for [HLS][]. * `ProgressiveMediaSource` for [regular media files][]. +* `RtspMediaSource` for [RTSP][]. `DefaultMediaSourceFactory` can also create more complex media sources depending on the properties of the corresponding media items. This is described in more diff --git a/docs/playlists.md b/docs/playlists.md index a3ca0c68bd..bc06164d39 100644 --- a/docs/playlists.md +++ b/docs/playlists.md @@ -101,7 +101,7 @@ MediaItem mediaItem = ## Detecting when playback transitions to another media item ## When playback transitions to another media item, or starts repeating the same -media item, `EventListener.onMediaItemTransition(MediaItem, +media item, `Listener.onMediaItemTransition(MediaItem, @MediaItemTransitionReason)` is called. This callback receives the new media item, along with a `@MediaItemTransitionReason` indicating why the transition occurred. A common use case for `onMediaItemTransition` is to update the @@ -135,7 +135,7 @@ public void onMediaItemTransition( ## Detecting when the playlist changes ## When a media item is added, removed or moved, -`EventListener.onTimelineChanged(Timeline, @TimelineChangeReason)` is called +`Listener.onTimelineChanged(Timeline, @TimelineChangeReason)` is called immediately with `TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED`. This callback is called even when the player has not yet been prepared. diff --git a/docs/retrieving-metadata.md b/docs/retrieving-metadata.md new file mode 100644 index 0000000000..73ad82e614 --- /dev/null +++ b/docs/retrieving-metadata.md @@ -0,0 +1,90 @@ +--- +title: Retrieving metadata +--- + +## During playback ## + +The metadata of the media can be retrieved during playback in multiple ways. The +most straightforward is to listen for the +`Player.EventListener#onMediaMetadataChanged` event; this will provide a +[`MediaMetadata`][] object for use, which has fields such as `title` and +`albumArtist`. Alternatively, calling `Player#getMediaMetadata` returns the same +object. + +~~~ +public void onMediaMetadataChanged(MediaMetadata mediaMetadata) { + if (mediaMetadata.title != null) { + handleTitle(mediaMetadata.title); + } +} + +~~~ +{: .language-java} + +If an application needs access to specific [`Metadata.Entry`][] objects, then it +should listen for `Player#onStaticMetadataChanged` (for static metadata from the +`Format`s) and/or add a `MetadataOutput` (for dynamic metadata delivered during +playback) to the player. The return values of these callbacks are used to +populate the `MediaMetadata`. + +## Without playback ## + +If playback is not needed, it is more efficient to use the +[`MetadataRetriever`][] to extract the metadata because it avoids having to +create and prepare a player. + +~~~ +ListenableFuture trackGroupsFuture = + MetadataRetriever.retrieveMetadata(context, mediaItem); +Futures.addCallback( + trackGroupsFuture, + new FutureCallback() { + @Override + public void onSuccess(TrackGroupArray trackGroups) { + handleMetadata(trackGroups); + } + + @Override + public void onFailure(Throwable t) { + handleFailure(t); + } + }, + executor); +~~~ +{: .language-java} + +## Motion photos ## + +It is also possible to extract the metadata of a motion photo, containing the +image and video offset and length for example. The supported formats are: + +* JPEG motion photos recorded by Google Pixel and Samsung camera apps. This + format is playable by ExoPlayer and the associated metadata can therefore be + retrieved with a player or using the `MetadataRetriever`. +* HEIC motion photos recorded by Google Pixel and Samsung camera apps. This + format is currently not playable by ExoPlayer and the associated metadata + should therefore be retrieved using the `MetadataRetriever`. + +For motion photos, the `TrackGroupArray` obtained with the `MetadataRetriever` +contains a `TrackGroup` with a single `Format` enclosing a +[`MotionPhotoMetadata`][] metadata entry. + +~~~ +for (int i = 0; i < trackGroups.length; i++) { + TrackGroup trackGroup = trackGroups.get(i); + Metadata metadata = trackGroup.getFormat(0).metadata; + if (metadata != null && metadata.length() == 1) { + Metadata.Entry metadataEntry = metadata.get(0); + if (metadataEntry instanceof MotionPhotoMetadata) { + MotionPhotoMetadata motionPhotoMetadata = (MotionPhotoMetadata) metadataEntry; + handleMotionPhotoMetadata(motionPhotoMetadata); + } + } +} +~~~ +{: .language-java} + +[`MediaMetadata`]: {{ site.exo_sdk }}/MediaMetadata.html +[`Metadata.Entry`][]: {{ site.exo_sdk}}/metadata/Metadata.Entry.html +[`MetadataRetriever`]: {{ site.exo_sdk }}/MetadataRetriever.html +[`MotionPhotoMetadata`]: {{ site.exo_sdk }}/metadata/mp4/MotionPhotoMetadata.html diff --git a/docs/rtsp.md b/docs/rtsp.md new file mode 100644 index 0000000000..17c11048d0 --- /dev/null +++ b/docs/rtsp.md @@ -0,0 +1,57 @@ +--- +title: RTSP +--- + +{% include_relative _page_fragments/supported-formats-rtsp.md %} + +## Using MediaItem ## + +To play an RTSP stream, you need to depend on the RTSP module. + +~~~ +implementation 'com.google.android.exoplayer:exoplayer-rtsp:2.X.X' +~~~ +{: .language-gradle} + +You can then create a `MediaItem` for an RTSP URI and pass it to the player. + +~~~ +// Create a player instance. +SimpleExoPlayer player = new SimpleExoPlayer.Builder(context).build(); +// Set the media item to be played. +player.setMediaItem(MediaItem.fromUri(rtspUri)); +// Prepare the player. +player.prepare(); +~~~ +{: .language-java} + + +## Using RtspMediaSource ## + +For more customization options, you can create an `RtspMediaSource` and pass it +directly to the player instead of a `MediaItem`. + +~~~ +// Create an RTSP media source pointing to an RTSP uri. +MediaSource mediaSource = + new RtspMediaSource.Factory() + .createMediaSource(MediaItem.fromUri(rtspUri)); +// Create a player instance. +SimpleExoPlayer player = new SimpleExoPlayer.Builder(context).build(); +// Set the media source to be played. +player.setMediaSource(mediaSource); +// Prepare the player. +player.prepare(); +~~~ +{: .language-java} + +## Using RTSP behind a NAT ## + +ExoPlayer uses UDP as the default protocol for RTP transport. + +When streaming RTSP behind a NAT layer, the NAT might not be able to forward the +incoming RTP/UDP packets to the device. This occurs if the NAT lacks the +necessary UDP port mapping. If ExoPlayer detects there have not been incoming +RTP packets for a while and the playback has not started yet, ExoPlayer tears +down the current RTSP playback session, and retries playback using RTP-over-RTSP +(transmitting RTP packets using the TCP connection opened for RTSP). diff --git a/docs/smoothstreaming.md b/docs/smoothstreaming.md index 3e686e4ad7..fb6824cde4 100644 --- a/docs/smoothstreaming.md +++ b/docs/smoothstreaming.md @@ -59,14 +59,14 @@ player.prepare(); You can retrieve the current manifest by calling `Player.getCurrentManifest`. For SmoothStreaming you should cast the returned object to `SsManifest`. The -`onTimelineChanged` callback of `Player.EventListener` is also called whenever +`onTimelineChanged` callback of `Player.Listener` is also called whenever the manifest is loaded. This will happen once for a on-demand content, and possibly many times for live content. The code snippet below shows how an app can do something whenever the manifest is loaded. ~~~ player.addListener( - new Player.EventListener() { + new Player.Listener() { @Override public void onTimelineChanged( Timeline timeline, @Player.TimelineChangeReason int reason) { diff --git a/docs/supported-formats.md b/docs/supported-formats.md index 10282d78ed..8270866bcd 100644 --- a/docs/supported-formats.md +++ b/docs/supported-formats.md @@ -41,6 +41,10 @@ and HDR video playback. {% include_relative _page_fragments/supported-formats-progressive.md %} +## RTSP ## + +{% include_relative _page_fragments/supported-formats-rtsp.md %} + ## Sample formats ## By default ExoPlayer uses Android's platform decoders. Hence the supported diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d0910ee04e..3c73e49b9b 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -14,7 +14,7 @@ redirect_from: * [Why do some streams fail with HTTP response code 301 or 302?][] * [Why do some streams fail with UnrecognizedInputFormatException?][] * [Why doesn't setPlaybackParameters work properly on some devices?][] -* [What do "Player is accessed on the wrong thread" warnings mean?][] +* [What do "Player is accessed on the wrong thread" errors mean?][] * [How can I fix "Unexpected status line: ICY 200 OK"?][] * [How can I query whether the stream being played is a live stream?][] * [How do I keep audio playing when my app is backgrounded?][] @@ -204,17 +204,7 @@ releases you provide to end users should not be affected by this issue. #### What do "Player is accessed on the wrong thread" errors mean? #### -If you are seeing `IllegalStateException` being thrown with the message "Player -is accessed on the wrong thread", then some code in your app is accessing a -`SimpleExoPlayer` instance on the wrong thread (the exception's stack trace -shows you where). ExoPlayer instances need to be accessed from a single thread -only. In most cases, this should be the application's main thread. For details, -please read through the ["Threading model" section of the ExoPlayer Javadoc][]. -You can temporarily opt out from these exceptions being thrown by calling -`SimpleExoPlayer.setThrowsWhenUsingWrongThread(false)`, in which case the issue -will be logged as a warning instead. Using this opt out is not safe and may -result in unexpected or obscure errors. The opt out will be removed in ExoPlayer -2.14. +See [A note on threading][] on the getting started page. #### How can I fix "Unexpected status line: ICY 200 OK"? #### @@ -313,7 +303,7 @@ is the official way to play YouTube videos on Android. [Why do some streams fail with HTTP response code 301 or 302?]: #why-do-some-streams-fail-with-http-response-code-301-or-302 [Why do some streams fail with UnrecognizedInputFormatException?]: #why-do-some-streams-fail-with-unrecognizedinputformatexception [Why doesn't setPlaybackParameters work properly on some devices?]: #why-doesnt-setplaybackparameters-work-properly-on-some-devices -[What do "Player is accessed on the wrong thread" warnings mean?]: #what-do-player-is-accessed-on-the-wrong-thread-warnings-mean +[What do "Player is accessed on the wrong thread" errors mean?]: #what-do-player-is-accessed-on-the-wrong-thread-errors-mean [How can I fix "Unexpected status line: ICY 200 OK"?]: #how-can-i-fix-unexpected-status-line-icy-200-ok [How can I query whether the stream being played is a live stream?]: #how-can-i-query-whether-the-stream-being-played-is-a-live-stream [How do I keep audio playing when my app is backgrounded?]: #how-do-i-keep-audio-playing-when-my-app-is-backgrounded @@ -347,7 +337,7 @@ is the official way to play YouTube videos on Android. [`WakeLock`]: {{ site.android_sdk }}/android/os/PowerManager.WakeLock.html [`SimpleExoPlayer`]: {{ site.exo_sdk }}/SimpleExoPlayer.html [`setWakeMode`]: {{ site.exo_sdk }}/SimpleExoPlayer.html#setWakeMode-int- -["Threading model" section of the ExoPlayer Javadoc]: {{ site.exo_sdk }}/ExoPlayer.html +[A note on threading]: {{ site.base_url }}/hello-world.html#a-note-on-threading [OkHttp extension]: {{ site.release_v2 }}/extensions/okhttp [CORS enabled]: https://www.w3.org/wiki/CORS_Enabled [Cast framework]: {{ site.google_sdk }}/cast/docs/chrome_sender/advanced#cors_requirements diff --git a/extensions/cast/build.gradle b/extensions/cast/build.gradle index d0cc501fcb..0efda30b93 100644 --- a/extensions/cast/build.gradle +++ b/extensions/cast/build.gradle @@ -14,10 +14,9 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { - api 'com.google.android.gms:play-services-cast-framework:18.1.0' + api 'com.google.android.gms:play-services-cast-framework:19.0.0' implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation project(modulePrefix + 'library-common') - implementation project(modulePrefix + 'library-ui') compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion 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 4f75ba9d6e..60ba2e4a36 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 @@ -15,21 +15,31 @@ */ package com.google.android.exoplayer2.ext.cast; +import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.min; 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 androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.BasePlayer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; 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.Timeline; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.device.DeviceInfo; import com.google.android.exoplayer2.metadata.Metadata; 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.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Assertions; @@ -37,6 +47,8 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; 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.VideoSize; import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaQueueItem; @@ -50,7 +62,7 @@ import com.google.android.gms.cast.framework.media.RemoteMediaClient; import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; -import java.util.Collections; +import com.google.common.collect.ImmutableList; import java.util.List; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -73,6 +85,21 @@ public final class CastPlayer extends BasePlayer { ExoPlayerLibraryInfo.registerModule("goog.exo.cast"); } + @VisibleForTesting + /* package */ static final Commands PERMANENT_AVAILABLE_COMMANDS = + new Commands.Builder() + .addAll( + COMMAND_PLAY_PAUSE, + COMMAND_PREPARE_STOP, + COMMAND_SEEK_TO_DEFAULT_POSITION, + COMMAND_SEEK_TO_MEDIA_ITEM, + COMMAND_SET_REPEAT_MODE, + COMMAND_GET_CURRENT_MEDIA_ITEM, + COMMAND_GET_MEDIA_ITEMS, + COMMAND_GET_MEDIA_ITEMS_METADATA, + COMMAND_CHANGE_MEDIA_ITEMS) + .build(); + private static final String TAG = "CastPlayer"; private static final int RENDERER_COUNT = 3; @@ -95,7 +122,7 @@ public final class CastPlayer extends BasePlayer { private final SeekResultCallback seekResultCallback; // Listeners and notification. - private final ListenerSet listeners; + private final ListenerSet listeners; @Nullable private SessionAvailabilityListener sessionAvailabilityListener; // Internal state. @@ -105,12 +132,14 @@ public final class CastPlayer extends BasePlayer { private CastTimeline currentTimeline; private TrackGroupArray currentTrackGroups; private TrackSelectionArray currentTrackSelection; + private Commands availableCommands; @Player.State private int playbackState; private int currentWindowIndex; private long lastReportedPositionMs; private int pendingSeekCount; private int pendingSeekWindowIndex; private long pendingSeekPositionMs; + @Nullable private PositionInfo pendingMediaItemRemovalPosition; /** * Creates a new cast player that uses a {@link DefaultMediaItemConverter}. @@ -138,15 +167,14 @@ public final class CastPlayer extends BasePlayer { new ListenerSet<>( Looper.getMainLooper(), Clock.DEFAULT, - Player.Events::new, - (listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); - + (listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags))); playWhenReady = new StateHolder<>(false); repeatMode = new StateHolder<>(REPEAT_MODE_OFF); playbackState = STATE_IDLE; currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE; currentTrackGroups = TrackGroupArray.EMPTY; currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY; + availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build(); pendingSeekWindowIndex = C.INDEX_UNSET; pendingSeekPositionMs = C.TIME_UNSET; @@ -231,14 +259,13 @@ public final class CastPlayer extends BasePlayer { public MediaQueueItem getItem(int periodId) { MediaStatus mediaStatus = getMediaStatus(); return mediaStatus != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET - ? mediaStatus.getItemById(periodId) : null; + ? mediaStatus.getItemById(periodId) + : null; } // CastSession methods. - /** - * Returns whether a cast session is available. - */ + /** Returns whether a cast session is available. */ public boolean isCastSessionAvailable() { return remoteMediaClient != null; } @@ -254,47 +281,28 @@ public final class CastPlayer extends BasePlayer { // Player implementation. - @Override - @Nullable - public AudioComponent getAudioComponent() { - return null; - } - - @Override - @Nullable - public VideoComponent getVideoComponent() { - return null; - } - - @Override - @Nullable - public TextComponent getTextComponent() { - return null; - } - - @Override - @Nullable - public MetadataComponent getMetadataComponent() { - return null; - } - - @Override - @Nullable - public DeviceComponent getDeviceComponent() { - // TODO(b/151792305): Implement the component. - return null; - } - @Override public Looper getApplicationLooper() { return Looper.getMainLooper(); } + @Override + public void addListener(Listener listener) { + EventListener eventListener = listener; + addListener(eventListener); + } + @Override public void addListener(EventListener listener) { listeners.add(listener); } + @Override + public void removeListener(Listener listener) { + EventListener eventListener = listener; + removeListener(eventListener); + } + @Override public void removeListener(EventListener listener) { listeners.remove(listener); @@ -314,11 +322,6 @@ public final class CastPlayer extends BasePlayer { toMediaQueueItems(mediaItems), startWindowIndex, startPositionMs, repeatMode.value); } - @Override - public void addMediaItems(List mediaItems) { - addMediaItemsInternal(toMediaQueueItems(mediaItems), MediaQueueItem.INVALID_ITEM_ID); - } - @Override public void addMediaItems(int index, List mediaItems) { Assertions.checkArgument(index >= 0); @@ -351,8 +354,8 @@ public final class CastPlayer extends BasePlayer { @Override public void removeMediaItems(int fromIndex, int toIndex) { - Assertions.checkArgument( - fromIndex >= 0 && toIndex >= fromIndex && toIndex <= currentTimeline.getWindowCount()); + Assertions.checkArgument(fromIndex >= 0 && toIndex >= fromIndex); + toIndex = min(toIndex, currentTimeline.getWindowCount()); if (fromIndex == toIndex) { // Do nothing. return; @@ -365,8 +368,8 @@ public final class CastPlayer extends BasePlayer { } @Override - public void clearMediaItems() { - removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ currentTimeline.getWindowCount()); + public Commands getAvailableCommands() { + return availableCommands; } @Override @@ -386,13 +389,6 @@ public final class CastPlayer extends BasePlayer { return Player.PLAYBACK_SUPPRESSION_REASON_NONE; } - @Deprecated - @Override - @Nullable - public ExoPlaybackException getPlaybackError() { - return getPlayerError(); - } - @Override @Nullable public ExoPlaybackException getPlayerError() { @@ -430,7 +426,7 @@ public final class CastPlayer extends BasePlayer { return playWhenReady.value; } - // We still call EventListener#onSeekProcessed() for backwards compatibility with listeners that + // We still call Listener#onSeekProcessed() for backwards compatibility with listeners that // don't implement onPositionDiscontinuity(). @SuppressWarnings("deprecation") @Override @@ -441,17 +437,34 @@ public final class CastPlayer extends BasePlayer { positionMs = positionMs != C.TIME_UNSET ? positionMs : 0; if (mediaStatus != null) { if (getCurrentWindowIndex() != windowIndex) { - remoteMediaClient.queueJumpToItem((int) currentTimeline.getPeriod(windowIndex, period).uid, - positionMs, null).setResultCallback(seekResultCallback); + remoteMediaClient + .queueJumpToItem( + (int) currentTimeline.getPeriod(windowIndex, period).uid, positionMs, null) + .setResultCallback(seekResultCallback); } else { remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback); } + PositionInfo oldPosition = getCurrentPositionInfo(); pendingSeekCount++; pendingSeekWindowIndex = windowIndex; pendingSeekPositionMs = positionMs; + PositionInfo newPosition = getCurrentPositionInfo(); listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, - listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK)); + listener -> { + listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK); + listener.onPositionDiscontinuity(oldPosition, newPosition, DISCONTINUITY_REASON_SEEK); + }); + if (oldPosition.windowIndex != newPosition.windowIndex) { + // TODO(internal b/182261884): queue `onMediaItemTransition` event when the media item is + // repeated. + MediaItem mediaItem = getCurrentTimeline().getWindow(windowIndex, window).mediaItem; + listeners.queueEvent( + Player.EVENT_MEDIA_ITEM_TRANSITION, + listener -> + listener.onMediaItemTransition(mediaItem, MEDIA_ITEM_TRANSITION_REASON_SEEK)); + } + updateAvailableCommandsAndNotifyIfChanged(); } else if (pendingSeekCount == 0) { listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); } @@ -459,7 +472,7 @@ public final class CastPlayer extends BasePlayer { } @Override - public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { + public void setPlaybackParameters(PlaybackParameters playbackParameters) { // Unsupported by the RemoteMediaClient API. Do nothing. } @@ -484,26 +497,6 @@ public final class CastPlayer extends BasePlayer { sessionManager.endCurrentSession(false); } - @Override - public int getRendererCount() { - // We assume there are three renderers: video, audio, and text. - return RENDERER_COUNT; - } - - @Override - public int getRendererType(int index) { - switch (index) { - case RENDERER_INDEX_VIDEO: - return C.TRACK_TYPE_VIDEO; - case RENDERER_INDEX_AUDIO: - return C.TRACK_TYPE_AUDIO; - case RENDERER_INDEX_TEXT: - return C.TRACK_TYPE_TEXT; - default: - throw new IndexOutOfBoundsException(); - } - } - @Override public void setRepeatMode(@RepeatMode int repeatMode) { if (remoteMediaClient == null) { @@ -530,7 +523,8 @@ public final class CastPlayer extends BasePlayer { } @Override - @RepeatMode public int getRepeatMode() { + @RepeatMode + public int getRepeatMode() { return repeatMode.value; } @@ -556,9 +550,15 @@ public final class CastPlayer extends BasePlayer { } @Override - public List getCurrentStaticMetadata() { + public ImmutableList getCurrentStaticMetadata() { // CastPlayer does not currently support metadata. - return Collections.emptyList(); + return ImmutableList.of(); + } + + @Override + public MediaMetadata getMediaMetadata() { + // CastPlayer does not currently support metadata. + return MediaMetadata.EMPTY; } @Override @@ -636,13 +636,118 @@ public final class CastPlayer extends BasePlayer { return getBufferedPosition(); } + /** This method is not supported and returns {@link AudioAttributes#DEFAULT}. */ + @Override + public AudioAttributes getAudioAttributes() { + return AudioAttributes.DEFAULT; + } + + /** This method is not supported and does nothing. */ + @Override + public void setVolume(float audioVolume) {} + + /** This method is not supported and returns 1. */ + @Override + public float getVolume() { + return 1; + } + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurface() {} + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurface(@Nullable Surface surface) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoSurface(@Nullable Surface surface) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {} + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {} + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoTextureView(@Nullable TextureView textureView) {} + /** This method is not supported and does nothing. */ + @Override + public void clearVideoTextureView(@Nullable TextureView textureView) {} + + /** This method is not supported and returns {@link VideoSize#UNKNOWN}. */ + @Override + public VideoSize getVideoSize() { + return VideoSize.UNKNOWN; + } + + /** This method is not supported and returns an empty list. */ + @Override + public ImmutableList getCurrentCues() { + return ImmutableList.of(); + } + + /** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */ + @Override + public DeviceInfo getDeviceInfo() { + return DeviceInfo.UNKNOWN; + } + + /** This method is not supported and always returns {@code 0}. */ + @Override + public int getDeviceVolume() { + return 0; + } + + /** This method is not supported and always returns {@code false}. */ + @Override + public boolean isDeviceMuted() { + return false; + } + + /** This method is not supported and does nothing. */ + @Override + public void setDeviceVolume(int volume) {} + + /** This method is not supported and does nothing. */ + @Override + public void increaseDeviceVolume() {} + + /** This method is not supported and does nothing. */ + @Override + public void decreaseDeviceVolume() {} + + /** This method is not supported and does nothing. */ + @Override + public void setDeviceMuted(boolean muted) {} + // Internal methods. + // Call deprecated callbacks. + @SuppressWarnings("deprecation") private void updateInternalStateAndNotifyIfChanged() { if (remoteMediaClient == null) { // There is no session. We leave the state of the player as it is now. return; } + int oldWindowIndex = this.currentWindowIndex; + @Nullable + Object oldPeriodUid = + !getCurrentTimeline().isEmpty() + ? getCurrentTimeline().getPeriod(oldWindowIndex, period, /* setIds= */ true).uid + : null; boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady.value; updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null); boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady.value; @@ -651,20 +756,62 @@ public final class CastPlayer extends BasePlayer { Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying)); } updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null); - updateTimelineAndNotifyIfChanged(); - - int currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline); - if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) { - this.currentWindowIndex = currentWindowIndex; + boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged(); + Timeline currentTimeline = getCurrentTimeline(); + currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline); + @Nullable + Object currentPeriodUid = + !currentTimeline.isEmpty() + ? currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */ true).uid + : null; + if (!playingPeriodChangedByTimelineChange + && !Util.areEqual(oldPeriodUid, currentPeriodUid) + && pendingSeekCount == 0) { + // Report discontinuity and media item auto transition. + currentTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true); + currentTimeline.getWindow(oldWindowIndex, window); + long windowDurationMs = window.getDurationMs(); + PositionInfo oldPosition = + new PositionInfo( + window.uid, + period.windowIndex, + period.uid, + period.windowIndex, + /* positionMs= */ windowDurationMs, + /* contentPositionMs= */ windowDurationMs, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */ true); + currentTimeline.getWindow(currentWindowIndex, window); + PositionInfo newPosition = + new PositionInfo( + window.uid, + period.windowIndex, + period.uid, + period.windowIndex, + /* positionMs= */ window.getDefaultPositionMs(), + /* contentPositionMs= */ window.getDefaultPositionMs(), + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, - listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION)); + listener -> { + listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AUTO_TRANSITION); + listener.onPositionDiscontinuity( + oldPosition, newPosition, DISCONTINUITY_REASON_AUTO_TRANSITION); + }); + listeners.queueEvent( + Player.EVENT_MEDIA_ITEM_TRANSITION, + listener -> + listener.onMediaItemTransition( + getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_AUTO)); } if (updateTracksAndSelectionsAndNotifyIfChanged()) { listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection)); } + updateAvailableCommandsAndNotifyIfChanged(); listeners.flushEvents(); } @@ -700,16 +847,81 @@ public final class CastPlayer extends BasePlayer { } } - private void updateTimelineAndNotifyIfChanged() { + /** + * Updates the timeline and notifies {@link Player.Listener event listeners} if required. + * + * @return Whether the timeline change has caused a change of the period currently being played. + */ + @SuppressWarnings("deprecation") // Calling deprecated listener method. + private boolean updateTimelineAndNotifyIfChanged() { + Timeline oldTimeline = currentTimeline; + int oldWindowIndex = currentWindowIndex; + boolean playingPeriodChanged = false; if (updateTimeline()) { // TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and // TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553]. + Timeline timeline = currentTimeline; + // Call onTimelineChanged. listeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, - listener -> - listener.onTimelineChanged( - currentTimeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); + listener -> { + listener.onTimelineChanged( + timeline, /* manifest= */ null, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + listener.onTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + }); + + // Call onPositionDiscontinuity if required. + Timeline currentTimeline = getCurrentTimeline(); + boolean playingPeriodRemoved = false; + if (!oldTimeline.isEmpty()) { + Object oldPeriodUid = + castNonNull(oldTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true).uid); + playingPeriodRemoved = currentTimeline.getIndexOfPeriod(oldPeriodUid) == C.INDEX_UNSET; + } + if (playingPeriodRemoved) { + PositionInfo oldPosition; + if (pendingMediaItemRemovalPosition != null) { + oldPosition = pendingMediaItemRemovalPosition; + pendingMediaItemRemovalPosition = null; + } else { + // If the media item has been removed by another client, we don't know the removal + // position. We use the current position as a fallback. + oldTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true); + oldTimeline.getWindow(period.windowIndex, window); + oldPosition = + new PositionInfo( + window.uid, + period.windowIndex, + period.uid, + period.windowIndex, + getCurrentPosition(), + getContentPosition(), + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + } + PositionInfo newPosition = getCurrentPositionInfo(); + listeners.queueEvent( + Player.EVENT_POSITION_DISCONTINUITY, + listener -> { + listener.onPositionDiscontinuity(DISCONTINUITY_REASON_REMOVE); + listener.onPositionDiscontinuity( + oldPosition, newPosition, DISCONTINUITY_REASON_REMOVE); + }); + } + + // Call onMediaItemTransition if required. + playingPeriodChanged = + currentTimeline.isEmpty() != oldTimeline.isEmpty() || playingPeriodRemoved; + if (playingPeriodChanged) { + listeners.queueEvent( + Player.EVENT_MEDIA_ITEM_TRANSITION, + listener -> + listener.onMediaItemTransition( + getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + } + updateAvailableCommandsAndNotifyIfChanged(); } + return playingPeriodChanged; } /** @@ -761,7 +973,8 @@ public final class CastPlayer extends BasePlayer { long id = mediaTrack.getId(); int trackType = MimeTypes.getTrackType(mediaTrack.getContentType()); int rendererIndex = getRendererIndexForTrackType(trackType); - if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET + if (isTrackActive(id, activeTrackIds) + && rendererIndex != C.INDEX_UNSET && trackSelections[rendererIndex] == null) { trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]); } @@ -778,6 +991,16 @@ public final class CastPlayer extends BasePlayer { return false; } + private void updateAvailableCommandsAndNotifyIfChanged() { + Commands previousAvailableCommands = availableCommands; + availableCommands = getAvailableCommands(PERMANENT_AVAILABLE_COMMANDS); + if (!availableCommands.equals(previousAvailableCommands)) { + listeners.queueEvent( + Player.EVENT_AVAILABLE_COMMANDS_CHANGED, + listener -> listener.onAvailableCommandsChanged(availableCommands)); + } + } + @Nullable private PendingResult setMediaItemsInternal( MediaQueueItem[] mediaQueueItems, @@ -792,6 +1015,10 @@ public final class CastPlayer extends BasePlayer { startWindowIndex = getCurrentWindowIndex(); startPositionMs = getCurrentPosition(); } + Timeline currentTimeline = getCurrentTimeline(); + if (!currentTimeline.isEmpty()) { + pendingMediaItemRemovalPosition = getCurrentPositionInfo(); + } return remoteMediaClient.queueLoad( mediaQueueItems, min(startWindowIndex, mediaQueueItems.length - 1), @@ -827,14 +1054,47 @@ public final class CastPlayer extends BasePlayer { if (remoteMediaClient == null || getMediaStatus() == null) { return null; } + Timeline timeline = getCurrentTimeline(); + if (!timeline.isEmpty()) { + Object periodUid = + castNonNull(timeline.getPeriod(getCurrentPeriodIndex(), period, /* setIds= */ true).uid); + for (int uid : uids) { + if (periodUid.equals(uid)) { + pendingMediaItemRemovalPosition = getCurrentPositionInfo(); + break; + } + } + } return remoteMediaClient.queueRemoveItems(uids, /* customData= */ null); } + private PositionInfo getCurrentPositionInfo() { + Timeline currentTimeline = getCurrentTimeline(); + @Nullable + Object newPeriodUid = + !currentTimeline.isEmpty() + ? currentTimeline.getPeriod(getCurrentPeriodIndex(), period, /* setIds= */ true).uid + : null; + @Nullable + Object newWindowUid = + newPeriodUid != null ? currentTimeline.getWindow(period.windowIndex, window).uid : null; + return new PositionInfo( + newWindowUid, + getCurrentWindowIndex(), + newPeriodUid, + getCurrentPeriodIndex(), + getCurrentPosition(), + getContentPosition(), + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + } + private void setRepeatModeAndNotifyIfChanged(@Player.RepeatMode int repeatMode) { if (this.repeatMode.value != repeatMode) { this.repeatMode.value = repeatMode; listeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode)); + updateAvailableCommandsAndNotifyIfChanged(); } } @@ -914,8 +1174,8 @@ public final class CastPlayer extends BasePlayer { } /** - * Retrieves the repeat mode from {@code remoteMediaClient} and maps it into a - * {@link Player.RepeatMode}. + * Retrieves the repeat mode from {@code remoteMediaClient} and maps it into a {@link + * Player.RepeatMode}. */ @RepeatMode private static int fetchRepeatMode(RemoteMediaClient remoteMediaClient) { @@ -1019,6 +1279,7 @@ public final class CastPlayer extends BasePlayer { @Override public void onQueueStatusUpdated() { updateTimelineAndNotifyIfChanged(); + listeners.flushEvents(); } @Override @@ -1054,8 +1315,12 @@ public final class CastPlayer extends BasePlayer { @Override public void onSessionResumeFailed(CastSession castSession, int statusCode) { - Log.e(TAG, "Session resume failed. Error code " + statusCode + ": " - + CastUtils.getLogString(statusCode)); + Log.e( + TAG, + "Session resume failed. Error code " + + statusCode + + ": " + + CastUtils.getLogString(statusCode)); } @Override @@ -1065,8 +1330,12 @@ public final class CastPlayer extends BasePlayer { @Override public void onSessionStartFailed(CastSession castSession, int statusCode) { - Log.e(TAG, "Session start failed. Error code " + statusCode + ": " - + CastUtils.getLogString(statusCode)); + Log.e( + TAG, + "Session start failed. Error code " + + statusCode + + ": " + + CastUtils.getLogString(statusCode)); } @Override @@ -1078,20 +1347,20 @@ public final class CastPlayer extends BasePlayer { public void onSessionResuming(CastSession castSession, String s) { // Do nothing. } - } private final class SeekResultCallback implements ResultCallback { - // We still call EventListener#onSeekProcessed() for backwards compatibility with listeners that + // We still call Listener#onSeekProcessed() for backwards compatibility with listeners that // don't implement onPositionDiscontinuity(). @SuppressWarnings("deprecation") @Override public void onResult(MediaChannelResult result) { int statusCode = result.getStatus().getStatusCode(); if (statusCode != CastStatusCodes.SUCCESS && statusCode != CastStatusCodes.REPLACED) { - Log.e(TAG, "Seek failed. Error code " + statusCode + ": " - + CastUtils.getLogString(statusCode)); + Log.e( + TAG, + "Seek failed. Error code " + statusCode + ": " + CastUtils.getLogString(statusCode)); } if (--pendingSeekCount == 0) { currentWindowIndex = pendingSeekWindowIndex; diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTrackSelection.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTrackSelection.java index 22fe86d9e4..9d5ea06b17 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTrackSelection.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTrackSelection.java @@ -36,6 +36,11 @@ import com.google.android.exoplayer2.util.Assertions; this.trackGroup = trackGroup; } + @Override + public int getType() { + return TYPE_UNSET; + } + @Override public TrackGroup getTrackGroup() { return trackGroup; diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java index d6644e6bb3..69702ea286 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java @@ -22,9 +22,7 @@ import com.google.android.gms.cast.framework.SessionProvider; import java.util.Collections; import java.util.List; -/** - * A convenience {@link OptionsProvider} to target the default cast receiver app. - */ +/** A convenience {@link OptionsProvider} to target the default cast receiver app. */ public final class DefaultCastOptionsProvider implements OptionsProvider { /** diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java index c72a1fb316..5fbabb807e 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java @@ -58,7 +58,7 @@ public final class DefaultMediaItemConverter implements MediaItemConverter { } MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); if (item.mediaMetadata.title != null) { - metadata.putString(MediaMetadata.KEY_TITLE, item.mediaMetadata.title); + metadata.putString(MediaMetadata.KEY_TITLE, item.mediaMetadata.title.toString()); } MediaInfo mediaInfo = new MediaInfo.Builder(item.playbackProperties.uri.toString()) diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java index 049bc89b72..0a687f50b4 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java @@ -15,16 +15,44 @@ */ package com.google.android.exoplayer2.ext.cast; +import static com.google.android.exoplayer2.Player.COMMAND_ADJUST_DEVICE_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS; +import static com.google.android.exoplayer2.Player.COMMAND_GET_AUDIO_ATTRIBUTES; +import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_GET_DEVICE_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS; +import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; +import static com.google.android.exoplayer2.Player.COMMAND_GET_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE; +import static com.google.android.exoplayer2.Player.COMMAND_PREPARE_STOP; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_SET_REPEAT_MODE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_SHUFFLE_MODE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_SPEED_AND_PITCH; +import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_VOLUME; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_REMOVE; +import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; +import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; @@ -42,7 +70,9 @@ import com.google.android.gms.cast.framework.media.MediaQueue; import com.google.android.gms.cast.framework.media.RemoteMediaClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Before; @@ -50,6 +80,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; @@ -58,17 +89,15 @@ import org.mockito.Mockito; public class CastPlayerTest { private CastPlayer castPlayer; - private RemoteMediaClient.Callback remoteMediaClientCallback; @Mock private RemoteMediaClient mockRemoteMediaClient; @Mock private MediaStatus mockMediaStatus; - @Mock private MediaInfo mockMediaInfo; @Mock private MediaQueue mockMediaQueue; @Mock private CastContext mockCastContext; @Mock private SessionManager mockSessionManager; @Mock private CastSession mockCastSession; - @Mock private Player.EventListener mockListener; + @Mock private Player.Listener mockListener; @Mock private PendingResult mockPendingResult; @Captor @@ -76,8 +105,8 @@ public class CastPlayerTest { setResultCallbackArgumentCaptor; @Captor private ArgumentCaptor callbackArgumentCaptor; - @Captor private ArgumentCaptor queueItemsArgumentCaptor; + @Captor private ArgumentCaptor mediaItemCaptor; @SuppressWarnings("deprecation") @Before @@ -119,7 +148,7 @@ public class CastPlayerTest { when(mockRemoteMediaClient.isPaused()).thenReturn(false); setResultCallbackArgumentCaptor .getValue() - .onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class)); + .onResult(mock(RemoteMediaClient.MediaChannelResult.class)); verifyNoMoreInteractions(mockListener); } @@ -139,7 +168,7 @@ public class CastPlayerTest { // Upon result, the remote media client is still paused. The state should reflect that. setResultCallbackArgumentCaptor .getValue() - .onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class)); + .onResult(mock(RemoteMediaClient.MediaChannelResult.class)); verify(mockListener).onPlayerStateChanged(false, Player.STATE_IDLE); verify(mockListener).onPlayWhenReadyChanged(false, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE); assertThat(castPlayer.getPlayWhenReady()).isFalse(); @@ -193,7 +222,7 @@ public class CastPlayerTest { when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_SINGLE); setResultCallbackArgumentCaptor .getValue() - .onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class)); + .onResult(mock(RemoteMediaClient.MediaChannelResult.class)); verifyNoMoreInteractions(mockListener); } @@ -214,7 +243,7 @@ public class CastPlayerTest { // Upon result, the repeat mode is ALL. The state should reflect that. setResultCallbackArgumentCaptor .getValue() - .onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class)); + .onResult(mock(RemoteMediaClient.MediaChannelResult.class)); verify(mockListener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); assertThat(castPlayer.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL); } @@ -268,6 +297,109 @@ public class CastPlayerTest { assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(uri2); } + @SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly. + @Test + public void setMediaItems_replaceExistingPlaylist_notifiesMediaItemTransition() { + List firstPlaylist = new ArrayList<>(); + String uri1 = "http://www.google.com/video1"; + String uri2 = "http://www.google.com/video2"; + firstPlaylist.add( + new MediaItem.Builder().setUri(uri1).setMimeType(MimeTypes.APPLICATION_MPD).build()); + firstPlaylist.add( + new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build()); + ImmutableList secondPlaylist = + ImmutableList.of( + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setMimeType(MimeTypes.APPLICATION_MPD) + .build()); + + castPlayer.setMediaItems( + firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L); + updateTimeLine( + firstPlaylist, /* mediaQueueItemIds= */ new int[] {1, 2}, /* currentItemId= */ 2); + // Replacing existing playlist. + castPlayer.setMediaItems( + secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L); + updateTimeLine(secondPlaylist, /* mediaQueueItemIds= */ new int[] {3}, /* currentItemId= */ 3); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener, times(2)) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + assertThat(mediaItemCaptor.getAllValues().get(1).playbackProperties.tag).isEqualTo(3); + } + + @SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly. + @Test + public void setMediaItems_replaceExistingPlaylist_notifiesPositionDiscontinuity() { + List firstPlaylist = new ArrayList<>(); + String uri1 = "http://www.google.com/video1"; + String uri2 = "http://www.google.com/video2"; + firstPlaylist.add( + new MediaItem.Builder().setUri(uri1).setMimeType(MimeTypes.APPLICATION_MPD).build()); + firstPlaylist.add( + new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build()); + ImmutableList secondPlaylist = + ImmutableList.of( + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setMimeType(MimeTypes.APPLICATION_MPD) + .build()); + + castPlayer.setMediaItems( + firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L); + updateTimeLine( + firstPlaylist, + /* mediaQueueItemIds= */ new int[] {1, 2}, + /* currentItemId= */ 2, + /* streamTypes= */ new int[] { + MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED + }, + /* durationsMs= */ new long[] {20_000, 20_000}, + /* positionMs= */ 2000L); + // Replacing existing playlist. + castPlayer.setMediaItems( + secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L); + updateTimeLine( + secondPlaylist, + /* mediaQueueItemIds= */ new int[] {3}, + /* currentItemId= */ 3, + /* streamTypes= */ new int[] {MediaInfo.STREAM_TYPE_BUFFERED}, + /* durationsMs= */ new long[] {20_000}, + /* positionMs= */ 1000L); + + Player.PositionInfo oldPosition = + new Player.PositionInfo( + /* windowUid= */ 2, + /* windowIndex= */ 1, + /* periodUid= */ 2, + /* periodIndex= */ 1, + /* positionMs= */ 2000, + /* contentPositionMs= */ 2000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + Player.PositionInfo newPosition = + new Player.PositionInfo( + /* windowUid= */ 3, + /* windowIndex= */ 0, + /* periodUid= */ 3, + /* periodIndex= */ 0, + /* positionMs= */ 1000, + /* contentPositionMs= */ 1000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder.verify(mockListener).onPositionDiscontinuity(eq(DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(mockListener) + .onPositionDiscontinuity(eq(oldPosition), eq(newPosition), eq(DISCONTINUITY_REASON_REMOVE)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + @Test public void addMediaItems_callsRemoteMediaClient() { MediaItem.Builder builder = new MediaItem.Builder(); @@ -293,7 +425,7 @@ public class CastPlayerTest { public void addMediaItems_insertAtIndex_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 2); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); String uri = "http://www.google.com/video3"; MediaItem anotherMediaItem = new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build(); @@ -316,7 +448,7 @@ public class CastPlayerTest { public void moveMediaItem_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 2); @@ -328,7 +460,7 @@ public class CastPlayerTest { public void moveMediaItem_toBegin_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 0); @@ -340,7 +472,7 @@ public class CastPlayerTest { public void moveMediaItem_toEnd_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 4); @@ -355,7 +487,7 @@ public class CastPlayerTest { public void moveMediaItems_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 3, /* newIndex= */ 1); @@ -368,7 +500,7 @@ public class CastPlayerTest { public void moveMediaItems_toBeginning_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 4, /* newIndex= */ 0); @@ -381,7 +513,7 @@ public class CastPlayerTest { public void moveMediaItems_toEnd_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 3); @@ -396,7 +528,7 @@ public class CastPlayerTest { public void moveMediaItems_noItems_doesNotCallRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 1, /* newIndex= */ 0); @@ -407,7 +539,7 @@ public class CastPlayerTest { public void moveMediaItems_noMove_doesNotCallRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 1); @@ -418,7 +550,7 @@ public class CastPlayerTest { public void removeMediaItems_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 4); @@ -429,7 +561,7 @@ public class CastPlayerTest { public void clearMediaItems_callsRemoteMediaClient() { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.clearMediaItems(); @@ -444,7 +576,7 @@ public class CastPlayerTest { int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5); List mediaItems = createMediaItems(mediaQueueItemIds); - fillTimeline(mediaItems, mediaQueueItemIds); + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); Timeline currentTimeline = castPlayer.getCurrentTimeline(); for (int i = 0; i < mediaItems.size(); i++) { @@ -453,6 +585,899 @@ public class CastPlayerTest { } } + @Test + public void addMediaItems_notifiesMediaItemTransition() { + MediaItem mediaItem = createMediaItem(/* mediaQueueItemId= */ 1); + List mediaItems = ImmutableList.of(mediaItem); + int[] mediaQueueItemIds = new int[] {1}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + assertThat(mediaItemCaptor.getValue().playbackProperties.tag) + .isEqualTo(mediaItem.playbackProperties.tag); + } + + @Test + public void clearMediaItems_notifiesMediaItemTransition() { + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.clearMediaItems(); + updateTimeLine( + /* mediaItems= */ ImmutableList.of(), + /* mediaQueueItemIds= */ new int[0], + /* currentItemId= */ C.INDEX_UNSET); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder + .verify(mockListener) + .onMediaItemTransition( + /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void clearMediaItems_notifiesPositionDiscontinuity() { + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine( + mediaItems, + mediaQueueItemIds, + /* currentItemId= */ 1, + new int[] {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED}, + /* durationsMs= */ new long[] {20_000L, 30_000L}, + /* positionMs= */ 1234); + castPlayer.clearMediaItems(); + updateTimeLine( + /* mediaItems= */ ImmutableList.of(), + /* mediaQueueItemIds= */ new int[0], + /* currentItemId= */ C.INDEX_UNSET, + new int[] {MediaInfo.STREAM_TYPE_BUFFERED}, + /* durationsMs= */ new long[] {20_000L}, + /* positionMs= */ 0); + + Player.PositionInfo oldPosition = + new Player.PositionInfo( + /* windowUid= */ 1, + /* windowIndex= */ 0, + /* periodUid= */ 1, + /* periodIndex= */ 0, + /* positionMs= */ 1234, + /* contentPositionMs= */ 1234, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + Player.PositionInfo newPosition = + new Player.PositionInfo( + /* windowUid= */ null, + /* windowIndex= */ 0, + /* periodUid= */ null, + /* periodIndex= */ 0, + /* positionMs= */ 0, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(mockListener) + .onPositionDiscontinuity( + eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + + @Test + public void removeCurrentMediaItem_notifiesMediaItemTransition() { + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.removeMediaItem(/* index= */ 0); + // Update with the new timeline after removal. + updateTimeLine( + ImmutableList.of(mediaItem2), + /* mediaQueueItemIds= */ new int[] {2}, + /* currentItemId= */ 2); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener, times(2)) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + assertThat(mediaItemCaptor.getAllValues().get(0).playbackProperties.tag) + .isEqualTo(mediaItem1.playbackProperties.tag); + assertThat(mediaItemCaptor.getAllValues().get(1).playbackProperties.tag) + .isEqualTo(mediaItem2.playbackProperties.tag); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void removeCurrentMediaItem_notifiesPositionDiscontinuity() { + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine( + mediaItems, + mediaQueueItemIds, + /* currentItemId= */ 1, + new int[] {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED}, + /* durationsMs= */ new long[] {20_000L, 30_000L}, + /* positionMs= */ 1234); + castPlayer.removeMediaItem(/* index= */ 0); + // Update with the new timeline after removal. + updateTimeLine( + ImmutableList.of(mediaItem2), + /* mediaQueueItemIds= */ new int[] {2}, + /* currentItemId= */ 2, + new int[] {MediaInfo.STREAM_TYPE_BUFFERED}, + /* durationsMs= */ new long[] {20_000L}, + /* positionMs= */ 0); + + Player.PositionInfo oldPosition = + new Player.PositionInfo( + /* windowUid= */ 1, + /* windowIndex= */ 0, + /* periodUid= */ 1, + /* periodIndex= */ 0, + /* positionMs= */ 1234, + /* contentPositionMs= */ 1234, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + Player.PositionInfo newPosition = + new Player.PositionInfo( + /* windowUid= */ 2, + /* windowIndex= */ 0, + /* periodUid= */ 2, + /* periodIndex= */ 0, + /* positionMs= */ 0, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(mockListener) + .onPositionDiscontinuity( + eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + + @Test + public void removeCurrentMediaItem_byRemoteClient_notifiesMediaItemTransition() { + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, new int[] {1, 2}, /* currentItemId= */ 1); + // Update with the new timeline after removal on the device. + updateTimeLine( + ImmutableList.of(mediaItem2), + /* mediaQueueItemIds= */ new int[] {2}, + /* currentItemId= */ 2); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener, times(2)) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + List capturedMediaItems = mediaItemCaptor.getAllValues(); + assertThat(capturedMediaItems.get(0).playbackProperties.tag) + .isEqualTo(mediaItem1.playbackProperties.tag); + assertThat(capturedMediaItems.get(1).playbackProperties.tag) + .isEqualTo(mediaItem2.playbackProperties.tag); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void removeCurrentMediaItem_byRemoteClient_notifiesPositionDiscontinuity() { + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine( + mediaItems, + new int[] {1, 2}, + /* currentItemId= */ 1, + new int[] {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED}, + /* durationsMs= */ new long[] {20_000L, 30_000L}, + /* positionMs= */ 1234); + // Update with the new timeline after removal on the device. + updateTimeLine( + ImmutableList.of(mediaItem2), + /* mediaQueueItemIds= */ new int[] {2}, + /* currentItemId= */ 2, + new int[] {MediaInfo.STREAM_TYPE_BUFFERED}, + /* durationsMs= */ new long[] {30_000L}, + /* positionMs= */ 0); + + Player.PositionInfo oldPosition = + new Player.PositionInfo( + /* windowUid= */ 1, + /* windowIndex= */ 0, + /* periodUid= */ 1, + /* periodIndex= */ 0, + /* positionMs= */ 0, // position at which we receive the timeline change + /* contentPositionMs= */ 0, // position at which we receive the timeline change + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + Player.PositionInfo newPosition = + new Player.PositionInfo( + /* windowUid= */ 2, + /* windowIndex= */ 0, + /* periodUid= */ 2, + /* periodIndex= */ 0, + /* positionMs= */ 0, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(mockListener) + .onPositionDiscontinuity( + eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + + @Test + public void removeNonCurrentMediaItem_doesNotNotifyMediaItemTransition() { + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.removeMediaItem(/* index= */ 1); + updateTimeLine( + ImmutableList.of(mediaItem1), + /* mediaQueueItemIds= */ new int[] {1}, + /* currentItemId= */ 1); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void removeNonCurrentMediaItem_doesNotNotifyPositionDiscontinuity() { + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.removeMediaItem(/* index= */ 1); + updateTimeLine( + ImmutableList.of(mediaItem1), + /* mediaQueueItemIds= */ new int[] {1}, + /* currentItemId= */ 1); + + verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + + @Test + public void seekTo_otherWindow_notifiesMediaItemTransition() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder + .verify(mockListener) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + assertThat(mediaItemCaptor.getValue().playbackProperties.tag) + .isEqualTo(mediaItem2.playbackProperties.tag); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void seekTo_otherWindow_notifiesPositionDiscontinuity() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234); + + Player.PositionInfo oldPosition = + new Player.PositionInfo( + /* windowUid= */ 1, + /* windowIndex= */ 0, + /* periodUid= */ 1, + /* periodIndex= */ 0, + /* positionMs= */ 0, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + Player.PositionInfo newPosition = + new Player.PositionInfo( + /* windowUid= */ 2, + /* windowIndex= */ 1, + /* periodUid= */ 2, + /* periodIndex= */ 1, + /* positionMs= */ 1234, + /* contentPositionMs= */ 1234, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_SEEK)); + inOrder + .verify(mockListener) + .onPositionDiscontinuity( + eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_SEEK)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void seekTo_sameWindow_doesNotNotifyMediaItemTransition() { + when(mockRemoteMediaClient.seek(anyLong())).thenReturn(mockPendingResult); + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void seekTo_sameWindow_notifiesPositionDiscontinuity() { + when(mockRemoteMediaClient.seek(anyLong())).thenReturn(mockPendingResult); + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234); + + Player.PositionInfo oldPosition = + new Player.PositionInfo( + /* windowUid= */ 1, + /* windowIndex= */ 0, + /* periodUid= */ 1, + /* periodIndex= */ 0, + /* positionMs= */ 0, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + Player.PositionInfo newPosition = + new Player.PositionInfo( + /* windowUid= */ 1, + /* windowIndex= */ 0, + /* periodUid= */ 1, + /* periodIndex= */ 0, + /* positionMs= */ 1234, + /* contentPositionMs= */ 1234, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_SEEK)); + inOrder + .verify(mockListener) + .onPositionDiscontinuity( + eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_SEEK)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + + @Test + public void autoTransition_notifiesMediaItemTransition() { + int[] mediaQueueItemIds = new int[] {1, 2}; + // When the remote Cast player transitions to an item that wasn't played before, the media state + // delivers the duration for that media item which updates the timeline accordingly. + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 2); + + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + inOrder + .verify(mockListener) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)); + inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt()); + assertThat(mediaItemCaptor.getValue().playbackProperties.tag).isEqualTo(2); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void autoTransition_notifiesPositionDiscontinuity() { + int[] mediaQueueItemIds = new int[] {1, 2}; + int[] streamTypes = {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED}; + long[] durationsFirstMs = {12500, C.TIME_UNSET}; + // When the remote Cast player transitions to an item that wasn't played before, the media state + // delivers the duration for that media item which updates the timeline accordingly. + long[] durationsSecondMs = {12500, 22000}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine( + mediaItems, + mediaQueueItemIds, + /* currentItemId= */ 1, + /* streamTypes= */ streamTypes, + /* durationsMs= */ durationsFirstMs, + /* positionMs= */ C.TIME_UNSET); + updateTimeLine( + mediaItems, + mediaQueueItemIds, + /* currentItemId= */ 2, + /* streamTypes= */ streamTypes, + /* durationsMs= */ durationsSecondMs, + /* positionMs= */ C.TIME_UNSET); + + Player.PositionInfo oldPosition = + new Player.PositionInfo( + /* windowUid= */ 1, + /* windowIndex= */ 0, + /* periodUid= */ 1, + /* periodIndex= */ 0, + /* positionMs= */ 12500, + /* contentPositionMs= */ 12500, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + Player.PositionInfo newPosition = + new Player.PositionInfo( + /* windowUid= */ 2, + /* windowIndex= */ 1, + /* periodUid= */ 2, + /* periodIndex= */ 1, + /* positionMs= */ 0, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET); + InOrder inOrder = Mockito.inOrder(mockListener); + inOrder + .verify(mockListener) + .onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(mockListener) + .onPositionDiscontinuity( + eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + } + + @Test + public void isCommandAvailable_isTrueForAvailableCommands() { + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + + assertThat(castPlayer.isCommandAvailable(COMMAND_PLAY_PAUSE)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_PREPARE_STOP)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_DEFAULT_POSITION)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SET_REPEAT_MODE)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)).isTrue(); + assertThat(castPlayer.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_GET_VOLUME)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SET_VOLUME)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_GET_TEXT)).isFalse(); + } + + @Test + public void isCommandAvailable_duringUnseekableItem_isFalseForSeekInCurrent() { + MediaItem mediaItem = createMediaItem(/* mediaQueueItemId= */ 1); + List mediaItems = ImmutableList.of(mediaItem); + int[] mediaQueueItemIds = new int[] {1}; + int[] streamTypes = new int[] {MediaInfo.STREAM_TYPE_LIVE}; + long[] durationsMs = new long[] {C.TIME_UNSET}; + + castPlayer.addMediaItem(mediaItem); + updateTimeLine( + mediaItems, + mediaQueueItemIds, + /* currentItemId= */ 1, + streamTypes, + durationsMs, + /* positionMs= */ C.TIME_UNSET); + + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)).isFalse(); + } + + @Test + public void seekTo_nextWindow_notifiesAvailableCommandsChanged() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + Player.Commands commandsWithSeekInCurrentAndToNext = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToPrevious = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Commands commandsWithSeekAnywhere = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + int[] mediaQueueItemIds = new int[] {1, 2, 3, 4}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + } + + @Test + public void seekTo_previousWindow_notifiesAvailableCommandsChanged() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + Player.Commands commandsWithSeekInCurrentAndToNext = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToPrevious = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Commands commandsWithSeekAnywhere = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + int[] mediaQueueItemIds = new int[] {1, 2, 3, 4}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 4); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + } + + @Test + @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. + public void seekTo_sameWindow_doesNotNotifyAvailableCommandsChanged() { + when(mockRemoteMediaClient.seek(anyLong())).thenReturn(mockPendingResult); + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + int[] mediaQueueItemIds = new int[] {1}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + + castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200); + castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + } + + @Test + public void addMediaItem_atTheEnd_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToNext = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); + + castPlayer.addMediaItem(mediaItem1); + updateTimeLine( + ImmutableList.of(mediaItem1), + /* mediaQueueItemIds= */ new int[] {1}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.addMediaItem(mediaItem2); + updateTimeLine( + ImmutableList.of(mediaItem1, mediaItem2), + /* mediaQueueItemIds= */ new int[] {1, 2}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.addMediaItem(mediaItem3); + updateTimeLine( + ImmutableList.of(mediaItem1, mediaItem2, mediaItem3), + /* mediaQueueItemIds= */ new int[] {1, 2, 3}, + /* currentItemId= */ 1); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + + @Test + public void addMediaItem_atTheStart_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToPrevious = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); + + castPlayer.addMediaItem(mediaItem1); + updateTimeLine( + ImmutableList.of(mediaItem1), + /* mediaQueueItemIds= */ new int[] {1}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.addMediaItem(/* index= */ 0, mediaItem2); + updateTimeLine( + ImmutableList.of(mediaItem2, mediaItem1), + /* mediaQueueItemIds= */ new int[] {2, 1}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.addMediaItem(/* index= */ 0, mediaItem3); + updateTimeLine( + ImmutableList.of(mediaItem3, mediaItem2, mediaItem1), + /* mediaQueueItemIds= */ new int[] {3, 2, 1}, + /* currentItemId= */ 1); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + + @Test + public void removeMediaItem_atTheEnd_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithoutSeek = createWithPermanentCommands(); + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToNext = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); + + castPlayer.addMediaItems(ImmutableList.of(mediaItem1, mediaItem2, mediaItem3)); + updateTimeLine( + ImmutableList.of(mediaItem1, mediaItem2, mediaItem3), + /* mediaQueueItemIds= */ new int[] {1, 2, 3}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.removeMediaItem(/* index= */ 2); + updateTimeLine( + ImmutableList.of(mediaItem1, mediaItem2), + /* mediaQueueItemIds= */ new int[] {1, 2}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.removeMediaItem(/* index= */ 1); + updateTimeLine( + ImmutableList.of(mediaItem1), + /* mediaQueueItemIds= */ new int[] {1}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.removeMediaItem(/* index= */ 0); + updateTimeLine( + ImmutableList.of(), + /* mediaQueueItemIds= */ new int[0], + /* currentItemId= */ C.INDEX_UNSET); + verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + } + + @Test + public void removeMediaItem_atTheStart_notifiesAvailableCommandsChanged() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + Player.Commands commandsWithoutSeek = createWithPermanentCommands(); + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToPrevious = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); + + castPlayer.addMediaItems(ImmutableList.of(mediaItem1, mediaItem2, mediaItem3)); + updateTimeLine( + ImmutableList.of(mediaItem1, mediaItem2, mediaItem3), + /* mediaQueueItemIds= */ new int[] {1, 2, 3}, + /* currentItemId= */ 3); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.removeMediaItem(/* index= */ 0); + updateTimeLine( + ImmutableList.of(mediaItem2, mediaItem3), + /* mediaQueueItemIds= */ new int[] {2, 3}, + /* currentItemId= */ 3); + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.removeMediaItem(/* index= */ 0); + updateTimeLine( + ImmutableList.of(mediaItem3), + /* mediaQueueItemIds= */ new int[] {3}, + /* currentItemId= */ 3); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + castPlayer.removeMediaItem(/* index= */ 0); + updateTimeLine( + ImmutableList.of(), + /* mediaQueueItemIds= */ new int[0], + /* currentItemId= */ C.INDEX_UNSET); + verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + } + + @Test + public void removeMediaItem_current_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToNext = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + + castPlayer.addMediaItems(ImmutableList.of(mediaItem1, mediaItem2)); + updateTimeLine( + ImmutableList.of(mediaItem1, mediaItem2), + /* mediaQueueItemIds= */ new int[] {1, 2}, + /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.removeMediaItem(/* index= */ 0); + updateTimeLine( + ImmutableList.of(mediaItem2), + /* mediaQueueItemIds= */ new int[] {2}, + /* currentItemId= */ 2); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + + @Test + public void setRepeatMode_all_notifiesAvailableCommandsChanged() { + when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null))) + .thenReturn(mockPendingResult); + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + Player.Commands commandsWithSeekAnywhere = + createWithPermanentCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + int[] mediaQueueItemIds = new int[] {1}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.setRepeatMode(Player.REPEAT_MODE_ALL); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + + @Test + public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() { + when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null))) + .thenReturn(mockPendingResult); + Player.Commands commandsWithSeekInCurrent = + createWithPermanentCommands(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + int[] mediaQueueItemIds = new int[] {1}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + castPlayer.setRepeatMode(Player.REPEAT_MODE_ONE); + verify(mockListener).onAvailableCommandsChanged(any()); + } + private int[] createMediaQueueItemIds(int numberOfIds) { int[] mediaQueueItemIds = new int[numberOfIds]; for (int i = 0; i < numberOfIds; i++) { @@ -462,37 +1487,89 @@ public class CastPlayerTest { } private List createMediaItems(int[] mediaQueueItemIds) { - MediaItem.Builder builder = new MediaItem.Builder(); List mediaItems = new ArrayList<>(); for (int mediaQueueItemId : mediaQueueItemIds) { - MediaItem mediaItem = - builder - .setUri("http://www.google.com/video" + mediaQueueItemId) - .setMimeType(MimeTypes.APPLICATION_MPD) - .setTag(mediaQueueItemId) - .build(); - mediaItems.add(mediaItem); + mediaItems.add(createMediaItem(mediaQueueItemId)); } return mediaItems; } - private void fillTimeline(List mediaItems, int[] mediaQueueItemIds) { + private MediaItem createMediaItem(int mediaQueueItemId) { + return new MediaItem.Builder() + .setUri("http://www.google.com/video" + mediaQueueItemId) + .setMimeType(MimeTypes.APPLICATION_MPD) + .setTag(mediaQueueItemId) + .build(); + } + + private void addMediaItemsAndUpdateTimeline(List mediaItems, int[] mediaQueueItemIds) { Assertions.checkState(mediaItems.size() == mediaQueueItemIds.length); - List queueItems = new ArrayList<>(); - DefaultMediaItemConverter converter = new DefaultMediaItemConverter(); - for (MediaItem mediaItem : mediaItems) { - queueItems.add(converter.toMediaQueueItem(mediaItem)); - } - - // Set up mocks to allow the player to update the timeline. - when(mockMediaQueue.getItemIds()).thenReturn(mediaQueueItemIds); - when(mockMediaStatus.getCurrentItemId()).thenReturn(1); - when(mockMediaStatus.getMediaInfo()).thenReturn(mockMediaInfo); - when(mockMediaInfo.getStreamType()).thenReturn(MediaInfo.STREAM_TYPE_NONE); - when(mockMediaStatus.getQueueItems()).thenReturn(queueItems); - castPlayer.addMediaItems(mediaItems); + updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + } + + private void updateTimeLine( + List mediaItems, int[] mediaQueueItemIds, int currentItemId) { + int[] streamTypes = new int[mediaItems.size()]; + Arrays.fill(streamTypes, MediaInfo.STREAM_TYPE_BUFFERED); + long[] durationsMs = new long[mediaItems.size()]; + updateTimeLine( + mediaItems, + mediaQueueItemIds, + currentItemId, + streamTypes, + durationsMs, + /* positionMs= */ C.TIME_UNSET); + } + + private void updateTimeLine( + List mediaItems, + int[] mediaQueueItemIds, + int currentItemId, + int[] streamTypes, + long[] durationsMs, + long positionMs) { + // Set up mocks to allow the player to update the timeline. + List queueItems = new ArrayList<>(); + for (int i = 0; i < mediaQueueItemIds.length; i++) { + MediaItem mediaItem = mediaItems.get(i); + int mediaQueueItemId = mediaQueueItemIds[i]; + int streamType = streamTypes[i]; + long durationMs = durationsMs[i]; + MediaInfo.Builder mediaInfoBuilder = + new MediaInfo.Builder(mediaItem.playbackProperties.uri.toString()) + .setStreamType(streamType) + .setContentType(mediaItem.playbackProperties.mimeType); + if (durationMs != C.TIME_UNSET) { + mediaInfoBuilder.setStreamDuration(durationMs); + } + MediaInfo mediaInfo = mediaInfoBuilder.build(); + MediaQueueItem mediaQueueItem = mock(MediaQueueItem.class); + when(mediaQueueItem.getItemId()).thenReturn(mediaQueueItemId); + when(mediaQueueItem.getMedia()).thenReturn(mediaInfo); + queueItems.add(mediaQueueItem); + if (mediaQueueItemId == currentItemId) { + when(mockRemoteMediaClient.getCurrentItem()).thenReturn(mediaQueueItem); + when(mockMediaStatus.getMediaInfo()).thenReturn(mediaInfo); + } + } + if (positionMs != C.TIME_UNSET) { + when(mockRemoteMediaClient.getApproximateStreamPosition()).thenReturn(positionMs); + } + when(mockMediaQueue.getItemIds()).thenReturn(mediaQueueItemIds); + when(mockMediaStatus.getQueueItems()).thenReturn(queueItems); + when(mockMediaStatus.getCurrentItemId()) + .thenReturn(currentItemId == C.INDEX_UNSET ? 0 : currentItemId); + // Call listener to update the timeline of the player. - remoteMediaClientCallback.onQueueStatusUpdated(); + remoteMediaClientCallback.onStatusUpdated(); + } + + private static Player.Commands createWithPermanentCommands( + @Player.Command int... additionalCommands) { + Player.Commands.Builder builder = new Player.Commands.Builder(); + builder.addAll(CastPlayer.PERMANENT_AVAILABLE_COMMANDS); + builder.addAll(additionalCommands); + return builder.build(); } } diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java index 9d65bada16..9d80725c56 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java @@ -51,7 +51,7 @@ public class DefaultMediaItemConverterTest { MediaItem item = builder .setUri(Uri.parse("http://example.com")) - .setMediaMetadata(new MediaMetadata.Builder().build()) + .setMediaMetadata(MediaMetadata.EMPTY) .setMimeType(MimeTypes.APPLICATION_MPD) .setDrmUuid(C.WIDEVINE_UUID) .setDrmLicenseUri("http://license.com") diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index f50304fb94..e12f2f050a 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -28,6 +28,7 @@ dependencies { androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion + androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion // Instrumentation tests assume that an app-packaged version of cronet is // available. androidTestImplementation 'org.chromium.net:cronet-embedded:72.3626.96' diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 2726b00c73..9b73387fc1 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -15,8 +15,8 @@ */ package com.google.android.exoplayer2.ext.cronet; +import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader; import static com.google.android.exoplayer2.util.Util.castNonNull; -import static java.lang.Math.max; import android.net.Uri; import android.text.TextUtils; @@ -29,14 +29,16 @@ import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.upstream.HttpUtil; 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.ConditionVariable; -import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.base.Predicate; -import com.google.common.primitives.Ints; +import com.google.common.net.HttpHeaders; +import com.google.common.primitives.Longs; import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketTimeoutException; @@ -49,9 +51,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executor; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.chromium.net.CronetEngine; import org.chromium.net.CronetException; import org.chromium.net.NetworkException; @@ -295,13 +294,6 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { /* package */ final UrlRequest.Callback urlRequestCallback; - private static final String TAG = "CronetDataSource"; - private static final String CONTENT_TYPE = "Content-Type"; - private static final String SET_COOKIE = "Set-Cookie"; - private static final String COOKIE = "Cookie"; - - private static final Pattern CONTENT_RANGE_HEADER_PATTERN = - Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$"); // The size of read buffer passed to cronet UrlRequest.read(). private static final int READ_BUFFER_SIZE_BYTES = 32 * 1024; @@ -321,7 +313,6 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // Accessed by the calling thread only. private boolean opened; - private long bytesToSkip; private long bytesRemaining; // Written from the calling thread only. currentUrlRequest.start() calls ensure writes are visible @@ -556,8 +547,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { @Nullable IOException connectionOpenException = exception; if (connectionOpenException != null) { @Nullable String message = connectionOpenException.getMessage(); - if (message != null - && Util.toLowerInvariant(message).contains("err_cleartext_not_permitted")) { + if (message != null && Ascii.toLowerCase(message).contains("err_cleartext_not_permitted")) { throw new CleartextNotPermittedException(connectionOpenException, dataSpec); } throw new OpenException(connectionOpenException, dataSpec, getStatus(urlRequest)); @@ -573,11 +563,22 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // Check for a valid response code. UrlResponseInfo responseInfo = Assertions.checkNotNull(this.responseInfo); int responseCode = responseInfo.getHttpStatusCode(); + Map> responseHeaders = responseInfo.getAllHeaders(); if (responseCode < 200 || responseCode > 299) { + if (responseCode == 416) { + long documentSize = + HttpUtil.getDocumentSize(getFirstHeader(responseHeaders, HttpHeaders.CONTENT_RANGE)); + if (dataSpec.position == documentSize) { + opened = true; + transferStarted(dataSpec); + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0; + } + } + byte[] responseBody; try { responseBody = readResponseBody(); - } catch (HttpDataSourceException e) { + } catch (IOException e) { responseBody = Util.EMPTY_BYTE_ARRAY; } @@ -585,7 +586,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { new InvalidResponseCodeException( responseCode, responseInfo.getHttpStatusText(), - responseInfo.getAllHeaders(), + responseHeaders, dataSpec, responseBody); if (responseCode == 416) { @@ -597,8 +598,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // Check for a valid content type. Predicate contentTypePredicate = this.contentTypePredicate; if (contentTypePredicate != null) { - List contentTypeHeaders = responseInfo.getAllHeaders().get(CONTENT_TYPE); - String contentType = isEmpty(contentTypeHeaders) ? null : contentTypeHeaders.get(0); + @Nullable String contentType = getFirstHeader(responseHeaders, HttpHeaders.CONTENT_TYPE); if (contentType != null && !contentTypePredicate.apply(contentType)) { throw new InvalidContentTypeException(contentType, dataSpec); } @@ -607,14 +607,17 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // If we requested a range starting from a non-zero position and received a 200 rather than a // 206, then the server does not support partial requests. We'll need to manually skip to the // requested position. - bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; + long bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; // Calculate the content length. if (!isCompressed(responseInfo)) { if (dataSpec.length != C.LENGTH_UNSET) { bytesRemaining = dataSpec.length; } else { - long contentLength = getContentLength(responseInfo); + long contentLength = + HttpUtil.getContentLength( + getFirstHeader(responseHeaders, HttpHeaders.CONTENT_LENGTH), + getFirstHeader(responseHeaders, HttpHeaders.CONTENT_RANGE)); bytesRemaining = contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) : C.LENGTH_UNSET; } @@ -627,6 +630,14 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { opened = true; transferStarted(dataSpec); + try { + if (!skipFully(bytesToSkip)) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + } catch (IOException e) { + throw new OpenException(e, dataSpec, Status.READING_RESPONSE); + } + return bytesRemaining; } @@ -641,34 +652,35 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { } ByteBuffer readBuffer = getOrCreateReadBuffer(); - while (!readBuffer.hasRemaining()) { + if (!readBuffer.hasRemaining()) { // Fill readBuffer with more data from Cronet. operation.close(); readBuffer.clear(); - readInternal(readBuffer); + try { + readInternal(readBuffer); + } catch (IOException e) { + throw new HttpDataSourceException( + e, castNonNull(currentDataSpec), HttpDataSourceException.TYPE_READ); + } if (finished) { bytesRemaining = 0; return C.RESULT_END_OF_INPUT; - } else { - // The operation didn't time out, fail or finish, and therefore data must have been read. - readBuffer.flip(); - Assertions.checkState(readBuffer.hasRemaining()); - if (bytesToSkip > 0) { - int bytesSkipped = (int) Math.min(readBuffer.remaining(), bytesToSkip); - readBuffer.position(readBuffer.position() + bytesSkipped); - bytesToSkip -= bytesSkipped; - } } + + // The operation didn't time out, fail or finish, and therefore data must have been read. + readBuffer.flip(); + Assertions.checkState(readBuffer.hasRemaining()); } // Ensure we read up to bytesRemaining, in case this was a Range request with finite end, but // the server does not support Range requests and transmitted the entire resource. int bytesRead = - Ints.min( - bytesRemaining != C.LENGTH_UNSET ? (int) bytesRemaining : Integer.MAX_VALUE, - readBuffer.remaining(), - readLength); + (int) + Longs.min( + bytesRemaining != C.LENGTH_UNSET ? bytesRemaining : Long.MAX_VALUE, + readBuffer.remaining(), + readLength); readBuffer.get(buffer, offset, bytesRead); @@ -718,17 +730,6 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { int readLength = buffer.remaining(); if (readBuffer != null) { - // Skip all the bytes we can from readBuffer if there are still bytes to skip. - if (bytesToSkip != 0) { - if (bytesToSkip >= readBuffer.remaining()) { - bytesToSkip -= readBuffer.remaining(); - readBuffer.position(readBuffer.limit()); - } else { - readBuffer.position(readBuffer.position() + (int) bytesToSkip); - bytesToSkip = 0; - } - } - // If there is existing data in the readBuffer, read as much as possible. Return if any read. int copyBytes = copyByteBuffer(/* src= */ readBuffer, /* dst= */ buffer); if (copyBytes != 0) { @@ -740,44 +741,23 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { } } - boolean readMore = true; - while (readMore) { - // If bytesToSkip > 0, read into intermediate buffer that we can discard instead of caller's - // buffer. If we do not need to skip bytes, we may write to buffer directly. - final boolean useCallerBuffer = bytesToSkip == 0; - - operation.close(); - - if (!useCallerBuffer) { - ByteBuffer readBuffer = getOrCreateReadBuffer(); - readBuffer.clear(); - if (bytesToSkip < READ_BUFFER_SIZE_BYTES) { - readBuffer.limit((int) bytesToSkip); - } - } - - // Fill buffer with more data from Cronet. - readInternal(useCallerBuffer ? buffer : castNonNull(readBuffer)); - - if (finished) { - bytesRemaining = 0; - return C.RESULT_END_OF_INPUT; - } else { - // The operation didn't time out, fail or finish, and therefore data must have been read. - Assertions.checkState( - useCallerBuffer - ? readLength > buffer.remaining() - : castNonNull(readBuffer).position() > 0); - // If we meant to skip bytes, subtract what was left and repeat, otherwise, continue. - if (useCallerBuffer) { - readMore = false; - } else { - bytesToSkip -= castNonNull(readBuffer).position(); - } - } + // Fill buffer with more data from Cronet. + operation.close(); + try { + readInternal(buffer); + } catch (IOException e) { + throw new HttpDataSourceException( + e, castNonNull(currentDataSpec), HttpDataSourceException.TYPE_READ); } - final int bytesRead = readLength - buffer.remaining(); + if (finished) { + bytesRemaining = 0; + return C.RESULT_END_OF_INPUT; + } + + // The operation didn't time out, fail or finish, and therefore data must have been read. + Assertions.checkState(readLength > buffer.remaining()); + int bytesRead = readLength - buffer.remaining(); if (bytesRemaining != C.LENGTH_UNSET) { bytesRemaining -= bytesRead; } @@ -836,23 +816,16 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { requestBuilder.addHeader(key, value); } - if (dataSpec.httpBody != null && !requestHeaders.containsKey(CONTENT_TYPE)) { + if (dataSpec.httpBody != null && !requestHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) { throw new IOException("HTTP request with non-empty body must set Content-Type"); } - // Set the Range header. - if (dataSpec.position != 0 || dataSpec.length != C.LENGTH_UNSET) { - StringBuilder rangeValue = new StringBuilder(); - rangeValue.append("bytes="); - rangeValue.append(dataSpec.position); - rangeValue.append("-"); - if (dataSpec.length != C.LENGTH_UNSET) { - rangeValue.append(dataSpec.position + dataSpec.length - 1); - } - requestBuilder.addHeader("Range", rangeValue.toString()); + @Nullable String rangeHeader = buildRangeRequestHeader(dataSpec.position, dataSpec.length); + if (rangeHeader != null) { + requestBuilder.addHeader(HttpHeaders.RANGE, rangeHeader); } if (userAgent != null) { - requestBuilder.addHeader("User-Agent", userAgent); + requestBuilder.addHeader(HttpHeaders.USER_AGENT, userAgent); } // TODO: Uncomment when https://bugs.chromium.org/p/chromium/issues/detail?id=711810 is fixed // (adjusting the code as necessary). @@ -885,13 +858,49 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { currentConnectTimeoutMs = clock.elapsedRealtime() + connectTimeoutMs; } + /** + * Attempts to skip the specified number of bytes in full. + * + * @param bytesToSkip The number of bytes to skip. + * @throws InterruptedIOException If the thread is interrupted during the operation. + * @throws IOException If an error occurs reading from the source. + * @return Whether the bytes were skipped in full. If {@code false} then the data ended before the + * specified number of bytes were skipped. Always {@code true} if {@code bytesToSkip == 0}. + */ + private boolean skipFully(long bytesToSkip) throws IOException { + if (bytesToSkip == 0) { + return true; + } + ByteBuffer readBuffer = getOrCreateReadBuffer(); + while (bytesToSkip > 0) { + // Fill readBuffer with more data from Cronet. + operation.close(); + readBuffer.clear(); + readInternal(readBuffer); + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedIOException(); + } + if (finished) { + return false; + } else { + // The operation didn't time out, fail or finish, and therefore data must have been read. + readBuffer.flip(); + Assertions.checkState(readBuffer.hasRemaining()); + int bytesSkipped = (int) Math.min(readBuffer.remaining(), bytesToSkip); + readBuffer.position(readBuffer.position() + bytesSkipped); + bytesToSkip -= bytesSkipped; + } + } + return true; + } + /** * Reads the whole response body. * * @return The response body. - * @throws HttpDataSourceException If an error occurs reading from the source. + * @throws IOException If an error occurs reading from the source. */ - private byte[] readResponseBody() throws HttpDataSourceException { + private byte[] readResponseBody() throws IOException { byte[] responseBody = Util.EMPTY_BYTE_ARRAY; ByteBuffer readBuffer = getOrCreateReadBuffer(); while (!finished) { @@ -914,10 +923,10 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { * the current {@code readBuffer} object so that it is not reused in the future. * * @param buffer The ByteBuffer into which the read data is stored. Must be a direct ByteBuffer. - * @throws HttpDataSourceException If an error occurs reading from the source. + * @throws IOException If an error occurs reading from the source. */ @SuppressWarnings("ReferenceEquality") - private void readInternal(ByteBuffer buffer) throws HttpDataSourceException { + private void readInternal(ByteBuffer buffer) throws IOException { castNonNull(currentUrlRequest).read(buffer); try { if (!operation.block(readTimeoutMs)) { @@ -930,23 +939,18 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { readBuffer = null; } Thread.currentThread().interrupt(); - throw new HttpDataSourceException( - new InterruptedIOException(), - castNonNull(currentDataSpec), - HttpDataSourceException.TYPE_READ); + throw new InterruptedIOException(); } catch (SocketTimeoutException e) { // The operation is ongoing so replace buffer to avoid it being written to by this // operation during a subsequent request. if (buffer == readBuffer) { readBuffer = null; } - throw new HttpDataSourceException( - e, castNonNull(currentDataSpec), HttpDataSourceException.TYPE_READ); + throw e; } if (exception != null) { - throw new HttpDataSourceException( - exception, castNonNull(currentDataSpec), HttpDataSourceException.TYPE_READ); + throw exception; } } @@ -967,52 +971,6 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { return false; } - private static long getContentLength(UrlResponseInfo info) { - long contentLength = C.LENGTH_UNSET; - Map> headers = info.getAllHeaders(); - List contentLengthHeaders = headers.get("Content-Length"); - String contentLengthHeader = null; - if (!isEmpty(contentLengthHeaders)) { - contentLengthHeader = contentLengthHeaders.get(0); - if (!TextUtils.isEmpty(contentLengthHeader)) { - try { - contentLength = Long.parseLong(contentLengthHeader); - } catch (NumberFormatException e) { - Log.e(TAG, "Unexpected Content-Length [" + contentLengthHeader + "]"); - } - } - } - List contentRangeHeaders = headers.get("Content-Range"); - if (!isEmpty(contentRangeHeaders)) { - String contentRangeHeader = contentRangeHeaders.get(0); - Matcher matcher = CONTENT_RANGE_HEADER_PATTERN.matcher(contentRangeHeader); - if (matcher.find()) { - try { - long contentLengthFromRange = - Long.parseLong(Assertions.checkNotNull(matcher.group(2))) - - Long.parseLong(Assertions.checkNotNull(matcher.group(1))) - + 1; - if (contentLength < 0) { - // Some proxy servers strip the Content-Length header. Fall back to the length - // calculated here in this case. - contentLength = contentLengthFromRange; - } else if (contentLength != contentLengthFromRange) { - // If there is a discrepancy between the Content-Length and Content-Range headers, - // assume the one with the larger value is correct. We have seen cases where carrier - // change one of them to reduce the size of a request, but it is unlikely anybody - // would increase it. - Log.w(TAG, "Inconsistent headers [" + contentLengthHeader + "] [" + contentRangeHeader - + "]"); - contentLength = max(contentLength, contentLengthFromRange); - } - } catch (NumberFormatException e) { - Log.e(TAG, "Unexpected Content-Range [" + contentRangeHeader + "]"); - } - } - } - return contentLength; - } - private static String parseCookies(List setCookieHeaders) { return TextUtils.join(";", setCookieHeaders); } @@ -1021,7 +979,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { if (TextUtils.isEmpty(cookies)) { return; } - requestBuilder.addHeader(COOKIE, cookies); + requestBuilder.addHeader(HttpHeaders.COOKIE, cookies); } private static int getStatus(UrlRequest request) throws InterruptedException { @@ -1038,9 +996,10 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { return statusHolder[0]; } - @EnsuresNonNullIf(result = false, expression = "#1") - private static boolean isEmpty(@Nullable List list) { - return list == null || list.isEmpty(); + @Nullable + private static String getFirstHeader(Map> allHeaders, String headerName) { + @Nullable List headers = allHeaders.get(headerName); + return headers != null && !headers.isEmpty() ? headers.get(0) : null; } // Copy as much as possible from the src buffer into dst buffer. @@ -1088,8 +1047,8 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { return; } - List setCookieHeaders = info.getAllHeaders().get(SET_COOKIE); - if (isEmpty(setCookieHeaders)) { + @Nullable List setCookieHeaders = info.getAllHeaders().get(HttpHeaders.SET_COOKIE); + if (setCookieHeaders == null || setCookieHeaders.isEmpty()) { request.followRedirect(); return; } diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java index df3e9549e5..a446fcc299 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.ext.cronet; - import androidx.annotation.Nullable; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource; diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java index d9332342e3..de292006ec 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java @@ -33,9 +33,7 @@ import java.util.List; import org.chromium.net.CronetEngine; import org.chromium.net.CronetProvider; -/** - * A wrapper class for a {@link CronetEngine}. - */ +/** A wrapper class for a {@link CronetEngine}. */ public final class CronetEngineWrapper { private static final String TAG = "CronetEngineWrapper"; diff --git a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index 631e1300d6..5255163f8e 100644 --- a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -56,6 +56,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -66,6 +67,7 @@ import org.chromium.net.CronetEngine; import org.chromium.net.NetworkException; import org.chromium.net.UrlRequest; import org.chromium.net.UrlResponseInfo; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -96,10 +98,10 @@ public final class CronetDataSourceTest { @Mock private UrlRequest.Builder mockUrlRequestBuilder; @Mock private UrlRequest mockUrlRequest; @Mock private TransferListener mockTransferListener; - @Mock private Executor mockExecutor; @Mock private NetworkException mockNetworkException; @Mock private CronetEngine mockCronetEngine; + private ExecutorService executorService; private CronetDataSource dataSourceUnderTest; private boolean redirectCalled; @@ -111,9 +113,10 @@ public final class CronetDataSourceTest { defaultRequestProperties.put("defaultHeader1", "defaultValue1"); defaultRequestProperties.put("defaultHeader2", "defaultValue2"); + executorService = Executors.newSingleThreadExecutor(); dataSourceUnderTest = (CronetDataSource) - new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), mockExecutor) + new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), executorService) .setConnectionTimeoutMs(TEST_CONNECT_TIMEOUT_MS) .setReadTimeoutMs(TEST_READ_TIMEOUT_MS) .setResetTimeoutOnRedirects(true) @@ -143,6 +146,11 @@ public final class CronetDataSourceTest { testUrlResponseInfo = createUrlResponseInfo(200); // statusCode } + @After + public void tearDown() { + executorService.shutdown(); + } + private UrlResponseInfo createUrlResponseInfo(int statusCode) { return createUrlResponseInfoWithUrl(TEST_URL, statusCode); } @@ -256,6 +264,7 @@ public final class CronetDataSourceTest { public void requestSetsRangeHeader() throws HttpDataSourceException { testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); mockResponseStartSuccess(); + mockReadSuccess(0, 1000); dataSourceUnderTest.open(testDataSpec); // The header value to add is current position to current position + length - 1. @@ -287,8 +296,6 @@ public final class CronetDataSourceTest { testDataSpec = new DataSpec.Builder() .setUri(TEST_URL) - .setPosition(1000) - .setLength(5000) .setHttpRequestHeaders(dataSpecRequestProperties) .build(); mockResponseStartSuccess(); @@ -1160,7 +1167,7 @@ public final class CronetDataSourceTest { throws HttpDataSourceException { dataSourceUnderTest = (CronetDataSource) - new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), mockExecutor) + new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), executorService) .setConnectionTimeoutMs(TEST_CONNECT_TIMEOUT_MS) .setReadTimeoutMs(TEST_READ_TIMEOUT_MS) .setResetTimeoutOnRedirects(true) @@ -1188,7 +1195,7 @@ public final class CronetDataSourceTest { testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); dataSourceUnderTest = (CronetDataSource) - new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), mockExecutor) + new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), executorService) .setConnectionTimeoutMs(TEST_CONNECT_TIMEOUT_MS) .setReadTimeoutMs(TEST_READ_TIMEOUT_MS) .setResetTimeoutOnRedirects(true) @@ -1198,6 +1205,7 @@ public final class CronetDataSourceTest { dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); mockSingleRedirectSuccess(); + mockReadSuccess(0, 1000); testResponseHeader.put("Set-Cookie", "testcookie=testcookie; Path=/video"); @@ -1224,7 +1232,7 @@ public final class CronetDataSourceTest { throws HttpDataSourceException { dataSourceUnderTest = (CronetDataSource) - new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), mockExecutor) + new CronetDataSource.Factory(new CronetEngineWrapper(mockCronetEngine), executorService) .setConnectionTimeoutMs(TEST_CONNECT_TIMEOUT_MS) .setReadTimeoutMs(TEST_READ_TIMEOUT_MS) .setResetTimeoutOnRedirects(true) @@ -1368,7 +1376,7 @@ public final class CronetDataSourceTest { @Test public void allowDirectExecutor() throws HttpDataSourceException { - testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); + testDataSpec = new DataSpec(Uri.parse(TEST_URL)); mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); @@ -1384,7 +1392,7 @@ public final class CronetDataSourceTest { DefaultHttpDataSource.Factory fallbackFactory = new DefaultHttpDataSource.Factory().setUserAgent("customFallbackFactoryUserAgent"); HttpDataSource dataSourceUnderTest = - new CronetDataSource.Factory(cronetEngineWrapper, Executors.newSingleThreadExecutor()) + new CronetDataSource.Factory(cronetEngineWrapper, executorService) .setFallbackFactory(fallbackFactory) .createDataSource(); @@ -1403,7 +1411,7 @@ public final class CronetDataSourceTest { mockWebServer.enqueue(new MockResponse()); CronetEngineWrapper cronetEngineWrapper = new CronetEngineWrapper((CronetEngine) null); HttpDataSource dataSourceUnderTest = - new CronetDataSource.Factory(cronetEngineWrapper, Executors.newSingleThreadExecutor()) + new CronetDataSource.Factory(cronetEngineWrapper, executorService) .setTransferListener(mockTransferListener) .createDataSource(); DataSpec dataSpec = @@ -1428,7 +1436,7 @@ public final class CronetDataSourceTest { Map defaultRequestProperties = new HashMap<>(); defaultRequestProperties.put("0", "defaultRequestProperty0"); HttpDataSource dataSourceUnderTest = - new CronetDataSource.Factory(cronetEngineWrapper, Executors.newSingleThreadExecutor()) + new CronetDataSource.Factory(cronetEngineWrapper, executorService) .setDefaultRequestProperties(defaultRequestProperties) .createDataSource(); @@ -1450,7 +1458,7 @@ public final class CronetDataSourceTest { Map defaultRequestProperties = new HashMap<>(); defaultRequestProperties.put("0", "defaultRequestProperty0"); CronetDataSource.Factory factory = - new CronetDataSource.Factory(cronetEngineWrapper, Executors.newSingleThreadExecutor()); + new CronetDataSource.Factory(cronetEngineWrapper, executorService); HttpDataSource dataSourceUnderTest = factory.setDefaultRequestProperties(defaultRequestProperties).createDataSource(); defaultRequestProperties.clear(); diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java index 0600254be5..eac96a9a31 100644 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java @@ -43,7 +43,7 @@ import java.util.List; private final String codecName; @Nullable private final byte[] extraData; - private final @C.Encoding int encoding; + @C.Encoding private final int encoding; private final int outputBufferSize; private long nativeContext; // May be reassigned on resetting the codec. @@ -98,7 +98,8 @@ import java.util.List; } @Override - protected @Nullable FfmpegDecoderException decode( + @Nullable + protected FfmpegDecoderException decode( DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) { if (reset) { nativeContext = ffmpegReset(nativeContext, extraData); @@ -159,7 +160,8 @@ import java.util.List; } /** Returns the encoding of output audio. */ - public @C.Encoding int getEncoding() { + @C.Encoding + public int getEncoding() { return encoding; } @@ -167,7 +169,8 @@ import java.util.List; * Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if * not required. */ - private static @Nullable byte[] getExtraData(String mimeType, List initializationData) { + @Nullable + private static byte[] getExtraData(String mimeType, List initializationData) { switch (mimeType) { case MimeTypes.AUDIO_AAC: case MimeTypes.AUDIO_OPUS: 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 6fb47e962b..798c936e2e 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 @@ -23,9 +23,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * Configures and queries the underlying native library. - */ +/** Configures and queries the underlying native library. */ public final class FfmpegLibrary { static { diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index bbcc26fb64..a52f8088b3 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -24,9 +24,11 @@ import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.RenderersFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.audio.AudioProcessor; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.DefaultAudioSink; @@ -87,13 +89,13 @@ public class FlacPlaybackTest { "audiosinkdumps/" + fileName + ".audiosink.dump"); } - private static class TestPlaybackRunnable implements Player.EventListener, Runnable { + private static class TestPlaybackRunnable implements Player.Listener, Runnable { private final Context context; private final Uri uri; private final AudioSink audioSink; - @Nullable private ExoPlayer player; + @Nullable private SimpleExoPlayer player; @Nullable private ExoPlaybackException playbackException; public TestPlaybackRunnable(Uri uri, Context context, AudioSink audioSink) { @@ -105,9 +107,16 @@ public class FlacPlaybackTest { @Override public void run() { Looper.prepare(); - LibflacAudioRenderer audioRenderer = - new LibflacAudioRenderer(/* eventHandler= */ null, /* eventListener= */ null, audioSink); - player = new ExoPlayer.Builder(context, audioRenderer).build(); + RenderersFactory renderersFactory = + (eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput) -> + new Renderer[] { + new LibflacAudioRenderer(eventHandler, audioRendererEventListener, audioSink) + }; + player = new SimpleExoPlayer.Builder(context, renderersFactory).build(); player.addListener(this); MediaSource mediaSource = new ProgressiveMediaSource.Factory( diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java index 0ac4dbeffa..99e74d64bb 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java @@ -45,9 +45,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Facilitates the extraction of data from the FLAC container format. - */ +/** Facilitates the extraction of data from the FLAC container format. */ public final class FlacExtractor implements Extractor { /** Factory that returns one extractor which is a {@link FlacExtractor}. */ @@ -75,7 +73,7 @@ public final class FlacExtractor implements Extractor { */ public static final int FLAG_DISABLE_ID3_METADATA = com.google.android.exoplayer2.extractor.flac.FlacExtractor.FLAG_DISABLE_ID3_METADATA; - // LINT.ThenChange(../../../../../../../../../../../library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java) + // LINT.ThenChange(../../../../../../../../../../extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java) private final ParsableByteArray outputBuffer; private final boolean id3MetadataDisabled; diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacLibrary.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacLibrary.java index d8b9b808a6..8a2b14d366 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacLibrary.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacLibrary.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.ext.flac; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.util.LibraryLoader; -/** - * Configures and queries the underlying native library. - */ +/** Configures and queries the underlying native library. */ public final class FlacLibrary { static { diff --git a/extensions/flac/src/main/jni/flac_jni.cc b/extensions/flac/src/main/jni/flac_jni.cc index 850f6883bf..50db06f436 100644 --- a/extensions/flac/src/main/jni/flac_jni.cc +++ b/extensions/flac/src/main/jni/flac_jni.cc @@ -47,6 +47,7 @@ class JavaDataSource : public DataSource { if (mid == NULL) { jclass cls = env->GetObjectClass(flacDecoderJni); mid = env->GetMethodID(cls, "read", "(Ljava/nio/ByteBuffer;)I"); + env->DeleteLocalRef(cls); } } @@ -57,6 +58,7 @@ class JavaDataSource : public DataSource { // Exception is thrown in Java when returning from the native call. result = -1; } + env->DeleteLocalRef(byteBuffer); return result; } diff --git a/extensions/gvr/build.gradle b/extensions/gvr/build.gradle index 891888a0d2..db508e5137 100644 --- a/extensions/gvr/build.gradle +++ b/extensions/gvr/build.gradle @@ -17,7 +17,6 @@ android.defaultConfig.minSdkVersion 19 dependencies { implementation project(modulePrefix + 'library-core') - implementation project(modulePrefix + 'library-ui') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion api 'com.google.vr:sdk-base:1.190.0' compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion diff --git a/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java b/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java index 839c832951..6ddc317274 100644 --- a/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java +++ b/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.net.Uri; import android.view.Surface; -import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -29,7 +28,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player.DiscontinuityReason; -import com.google.android.exoplayer2.Player.EventListener; import com.google.android.exoplayer2.Player.TimelineChangeReason; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline.Window; @@ -38,8 +36,6 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.ads.AdsLoader; -import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ExoHostedTest; @@ -51,7 +47,6 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; -import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -190,7 +185,7 @@ public final class ImaPlaybackTest { } } - private static final class ImaHostedTest extends ExoHostedTest implements EventListener { + private static final class ImaHostedTest extends ExoHostedTest implements Player.Listener { private final Uri contentUri; private final DataSpec adTagDataSpec; @@ -251,18 +246,7 @@ public final class ImaPlaybackTest { /* adsId= */ adTagDataSpec.uri, new DefaultMediaSourceFactory(dataSourceFactory), Assertions.checkNotNull(imaAdsLoader), - new AdViewProvider() { - - @Override - public ViewGroup getAdViewGroup() { - return overlayFrameLayout; - } - - @Override - public ImmutableList getAdOverlayInfos() { - return ImmutableList.of(); - } - }); + () -> overlayFrameLayout); } @Override 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 7275da7230..6690fa95ec 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.ima; +import static com.google.android.exoplayer2.Player.COMMAND_GET_VOLUME; import static com.google.android.exoplayer2.ext.ima.ImaUtil.BITRATE_UNSET; import static com.google.android.exoplayer2.ext.ima.ImaUtil.TIMEOUT_UNSET; import static com.google.android.exoplayer2.ext.ima.ImaUtil.getAdGroupTimesUsForCuePoints; @@ -55,11 +56,12 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ads.AdPlaybackState; -import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider; import com.google.android.exoplayer2.source.ads.AdsLoader.EventListener; -import com.google.android.exoplayer2.source.ads.AdsLoader.OverlayInfo; import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionUtil; +import com.google.android.exoplayer2.ui.AdOverlayInfo; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -74,7 +76,7 @@ import java.util.List; import java.util.Map; /** Handles loading and playback of a single ad tag. */ -/* package */ final class AdTagLoader implements Player.EventListener { +/* package */ final class AdTagLoader implements Player.Listener { private static final String TAG = "AdTagLoader"; @@ -317,7 +319,7 @@ import java.util.Map; new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints())); updateAdPlaybackState(); } - for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) { + for (AdOverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) { adDisplayContainer.registerFriendlyObstruction( imaFactory.createFriendlyObstruction( overlayInfo.view, @@ -467,7 +469,10 @@ import java.util.Map; } @Override - public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { + public void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @Player.DiscontinuityReason int reason) { handleTimelineOrPositionChanged(); } @@ -696,19 +701,13 @@ import java.util.Map; return lastVolumePercent; } - @Nullable Player.AudioComponent audioComponent = player.getAudioComponent(); - if (audioComponent != null) { - return (int) (audioComponent.getVolume() * 100); + if (player.isCommandAvailable(COMMAND_GET_VOLUME)) { + return (int) (player.getVolume() * 100); } // Check for a selected track using an audio renderer. TrackSelectionArray trackSelections = player.getCurrentTrackSelections(); - for (int i = 0; i < player.getRendererCount() && i < trackSelections.length; i++) { - if (player.getRendererType(i) == C.TRACK_TYPE_AUDIO && trackSelections.get(i) != null) { - return 100; - } - } - return 0; + return TrackSelectionUtil.hasTrackOfType(trackSelections, C.TRACK_TYPE_AUDIO) ? 100 : 0; } private void handleAdEvent(AdEvent adEvent) { 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 336a560042..72cae676af 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 @@ -48,6 +48,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -84,7 +85,7 @@ import java.util.Set; * href="https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/omsdk">IMA * SDK Open Measurement documentation for more information. */ -public final class ImaAdsLoader implements Player.EventListener, AdsLoader { +public final class ImaAdsLoader implements Player.Listener, AdsLoader { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); @@ -600,7 +601,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { .handlePrepareError(adGroupIndex, adIndexInAdGroup, exception); } - // Player.EventListener implementation. + // Player.Listener implementation. @Override public void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) { @@ -613,7 +614,10 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { } @Override - public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { + public void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @Player.DiscontinuityReason int reason) { maybeUpdateCurrentAdTagLoader(); maybePreloadNextPeriodAds(); } 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 0324e93713..377a9c4db8 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 @@ -36,7 +36,7 @@ import com.google.ads.interactivemedia.v3.api.UiElement; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.source.ads.AdsLoader.OverlayInfo; +import com.google.android.exoplayer2.ui.AdOverlayInfo; import com.google.android.exoplayer2.upstream.DataSchemeDataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; @@ -138,18 +138,18 @@ import java.util.Set; /** * Returns the IMA {@link FriendlyObstructionPurpose} corresponding to the given {@link - * OverlayInfo#purpose}. + * AdOverlayInfo#purpose}. */ public static FriendlyObstructionPurpose getFriendlyObstructionPurpose( - @OverlayInfo.Purpose int purpose) { + @AdOverlayInfo.Purpose int purpose) { switch (purpose) { - case OverlayInfo.PURPOSE_CONTROLS: + case AdOverlayInfo.PURPOSE_CONTROLS: return FriendlyObstructionPurpose.VIDEO_CONTROLS; - case OverlayInfo.PURPOSE_CLOSE_AD: + case AdOverlayInfo.PURPOSE_CLOSE_AD: return FriendlyObstructionPurpose.CLOSE_AD; - case OverlayInfo.PURPOSE_NOT_VISIBLE: + case AdOverlayInfo.PURPOSE_NOT_VISIBLE: return FriendlyObstructionPurpose.NOT_VISIBLE; - case OverlayInfo.PURPOSE_OTHER: + case AdOverlayInfo.PURPOSE_OTHER: default: return FriendlyObstructionPurpose.OTHER; } 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 6b62af93f3..0582423a91 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,8 +27,10 @@ import com.google.android.exoplayer2.util.ListenerSet; /** A fake player for testing content/ad playback. */ /* package */ final class FakePlayer extends StubExoPlayer { - private final ListenerSet listeners; + private final ListenerSet listeners; private final Timeline.Period period; + private final Object windowUid = new Object(); + private final Object periodUid = new Object(); private Timeline timeline; @Player.State private int state; @@ -45,8 +47,7 @@ import com.google.android.exoplayer2.util.ListenerSet; new ListenerSet<>( Looper.getMainLooper(), Clock.DEFAULT, - Player.Events::new, - (listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); + (listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags))); period = new Timeline.Period(); state = Player.STATE_IDLE; playWhenReady = true; @@ -66,6 +67,16 @@ import com.google.android.exoplayer2.util.ListenerSet; */ public void setPlayingContentPosition(int periodIndex, long positionMs) { boolean notify = isPlayingAd; + PositionInfo oldPosition = + new PositionInfo( + windowUid, + /* windowIndex= */ 0, + periodUid, + /* periodIndex= */ 0, + this.positionMs, + this.contentPositionMs, + this.adGroupIndex, + this.adIndexInAdGroup); isPlayingAd = false; adGroupIndex = C.INDEX_UNSET; adIndexInAdGroup = C.INDEX_UNSET; @@ -73,9 +84,21 @@ import com.google.android.exoplayer2.util.ListenerSet; this.positionMs = positionMs; contentPositionMs = positionMs; if (notify) { + PositionInfo newPosition = + new PositionInfo( + windowUid, + /* windowIndex= */ 0, + periodUid, + /* periodIndex= */ 0, + positionMs, + this.contentPositionMs, + this.adGroupIndex, + this.adIndexInAdGroup); listeners.sendEvent( Player.EVENT_POSITION_DISCONTINUITY, - listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION)); + listener -> + listener.onPositionDiscontinuity( + oldPosition, newPosition, DISCONTINUITY_REASON_AUTO_TRANSITION)); } } @@ -91,6 +114,16 @@ import com.google.android.exoplayer2.util.ListenerSet; long positionMs, long contentPositionMs) { boolean notify = !isPlayingAd || this.adIndexInAdGroup != adIndexInAdGroup; + PositionInfo oldPosition = + new PositionInfo( + windowUid, + /* windowIndex= */ 0, + periodUid, + /* periodIndex= */ 0, + this.positionMs, + this.contentPositionMs, + this.adGroupIndex, + this.adIndexInAdGroup); isPlayingAd = true; this.periodIndex = periodIndex; this.adGroupIndex = adGroupIndex; @@ -98,9 +131,21 @@ import com.google.android.exoplayer2.util.ListenerSet; this.positionMs = positionMs; this.contentPositionMs = contentPositionMs; if (notify) { + PositionInfo newPosition = + new PositionInfo( + windowUid, + /* windowIndex= */ 0, + periodUid, + /* periodIndex= */ 0, + positionMs, + contentPositionMs, + adGroupIndex, + adIndexInAdGroup); listeners.sendEvent( EVENT_POSITION_DISCONTINUITY, - listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION)); + listener -> + listener.onPositionDiscontinuity( + oldPosition, newPosition, DISCONTINUITY_REASON_AUTO_TRANSITION)); } } @@ -140,15 +185,20 @@ import com.google.android.exoplayer2.util.ListenerSet; } @Override - public void addListener(Player.EventListener listener) { + public void addListener(Player.Listener listener) { listeners.add(listener); } @Override - public void removeListener(Player.EventListener listener) { + public void removeListener(Player.Listener listener) { listeners.remove(listener); } + @Override + public Commands getAvailableCommands() { + return Commands.EMPTY; + } + @Override @Player.State public int getPlaybackState() { 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 e7b6603694..6b62b38e4d 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 @@ -68,6 +68,8 @@ import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; +import com.google.android.exoplayer2.ui.AdOverlayInfo; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -129,8 +131,8 @@ public final class ImaAdsLoaderTest { private TimelineWindowDefinition[] timelineWindowDefinitions; private AdsMediaSource adsMediaSource; private ViewGroup adViewGroup; - private AdsLoader.AdViewProvider adViewProvider; - private AdsLoader.AdViewProvider audioAdsAdViewProvider; + private AdViewProvider adViewProvider; + private AdViewProvider audioAdsAdViewProvider; private AdEvent.AdEventListener adEventListener; private ContentProgressProvider contentProgressProvider; private VideoAdPlayer videoAdPlayer; @@ -145,30 +147,19 @@ public final class ImaAdsLoaderTest { adViewGroup = new FrameLayout(getApplicationContext()); View adOverlayView = new View(getApplicationContext()); adViewProvider = - new AdsLoader.AdViewProvider() { + new AdViewProvider() { @Override public ViewGroup getAdViewGroup() { return adViewGroup; } @Override - public ImmutableList getAdOverlayInfos() { + public ImmutableList getAdOverlayInfos() { return ImmutableList.of( - new AdsLoader.OverlayInfo(adOverlayView, AdsLoader.OverlayInfo.PURPOSE_CLOSE_AD)); - } - }; - audioAdsAdViewProvider = - new AdsLoader.AdViewProvider() { - @Override - public ViewGroup getAdViewGroup() { - return null; - } - - @Override - public ImmutableList getAdOverlayInfos() { - return ImmutableList.of(); + new AdOverlayInfo(adOverlayView, AdOverlayInfo.PURPOSE_CLOSE_AD)); } }; + audioAdsAdViewProvider = () -> null; imaAdsLoader = new ImaAdsLoader.Builder(getApplicationContext()) .setImaFactory(mockImaFactory) @@ -281,7 +272,26 @@ public final class ImaAdsLoaderTest { videoAdPlayer.pauseAd(TEST_AD_MEDIA_INFO); videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO); imaAdsLoader.onPlayerError(ExoPlaybackException.createForSource(new IOException())); - imaAdsLoader.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + imaAdsLoader.onPositionDiscontinuity( + new Player.PositionInfo( + /* windowUid= */ new Object(), + /* windowIndex= */ 0, + /* periodUid= */ new Object(), + /* periodIndex= */ 0, + /* positionMs= */ 10_000, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ -1, + /* adIndexInAdGroup= */ -1), + new Player.PositionInfo( + /* windowUid= */ new Object(), + /* windowIndex= */ 1, + /* periodUid= */ new Object(), + /* periodIndex= */ 0, + /* positionMs= */ 20_000, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ -1, + /* adIndexInAdGroup= */ -1), + Player.DISCONTINUITY_REASON_SEEK); adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null)); imaAdsLoader.handlePrepareError( adsMediaSource, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, new IOException()); diff --git a/extensions/jobdispatcher/README.md b/extensions/jobdispatcher/README.md deleted file mode 100644 index 9e26c07c5d..0000000000 --- a/extensions/jobdispatcher/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# ExoPlayer Firebase JobDispatcher extension # - -**This extension is deprecated. Use the [WorkManager extension][] instead.** - -This extension provides a Scheduler implementation which uses [Firebase JobDispatcher][]. - -[WorkManager extension]: https://github.com/google/ExoPlayer/blob/release-v2/extensions/workmanager/README.md -[Firebase JobDispatcher]: https://github.com/firebase/firebase-jobdispatcher-android - -## Getting the extension ## - -The easiest way to use the extension is to add it as a gradle dependency: - -```gradle -implementation 'com.google.android.exoplayer:extension-jobdispatcher:2.X.X' -``` - -where `2.X.X` is the version, which must match the version of the ExoPlayer -library being used. - -Alternatively, you can clone the ExoPlayer repository and depend on the module -locally. Instructions for doing this can be found in ExoPlayer's -[top level README][]. - -[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md diff --git a/extensions/jobdispatcher/build.gradle b/extensions/jobdispatcher/build.gradle deleted file mode 100644 index df50cde8f9..0000000000 --- a/extensions/jobdispatcher/build.gradle +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2018 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" - -dependencies { - implementation project(modulePrefix + 'library-core') - implementation 'com.firebase:firebase-jobdispatcher:0.8.5' - compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion -} - -ext { - javadocTitle = 'Firebase JobDispatcher extension' -} -apply from: '../../javadoc_library.gradle' - -ext { - releaseArtifact = 'extension-jobdispatcher' - releaseDescription = 'Firebase JobDispatcher extension for ExoPlayer.' -} -apply from: '../../publish.gradle' diff --git a/extensions/jobdispatcher/src/main/AndroidManifest.xml b/extensions/jobdispatcher/src/main/AndroidManifest.xml deleted file mode 100644 index 306a087e6c..0000000000 --- a/extensions/jobdispatcher/src/main/AndroidManifest.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - diff --git a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java deleted file mode 100644 index b65988a5e2..0000000000 --- a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2018 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.jobdispatcher; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import com.firebase.jobdispatcher.Constraint; -import com.firebase.jobdispatcher.FirebaseJobDispatcher; -import com.firebase.jobdispatcher.GooglePlayDriver; -import com.firebase.jobdispatcher.Job; -import com.firebase.jobdispatcher.JobParameters; -import com.firebase.jobdispatcher.JobService; -import com.firebase.jobdispatcher.Lifetime; -import com.google.android.exoplayer2.scheduler.Requirements; -import com.google.android.exoplayer2.scheduler.Scheduler; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Log; -import com.google.android.exoplayer2.util.Util; - -/** - * A {@link Scheduler} that uses {@link FirebaseJobDispatcher}. To use this scheduler, you must add - * {@link JobDispatcherSchedulerService} to your manifest: - * - *
{@literal
- * 
- * 
- *
- * 
- *   
- *     
- *   
- * 
- * }
- * - *

This Scheduler uses Google Play services but does not do any availability checks. Any uses - * should be guarded with a call to {@code - * GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)} - * - * @see GoogleApiAvailability - * @deprecated Use com.google.android.exoplayer2.ext.workmanager.WorkManagerScheduler or {@link - * com.google.android.exoplayer2.scheduler.PlatformScheduler}. - */ -@Deprecated -public final class JobDispatcherScheduler implements Scheduler { - - private static final String TAG = "JobDispatcherScheduler"; - private static final String KEY_SERVICE_ACTION = "service_action"; - private static final String KEY_SERVICE_PACKAGE = "service_package"; - private static final String KEY_REQUIREMENTS = "requirements"; - private static final int SUPPORTED_REQUIREMENTS = - Requirements.NETWORK - | Requirements.NETWORK_UNMETERED - | Requirements.DEVICE_IDLE - | Requirements.DEVICE_CHARGING; - - private final String jobTag; - private final FirebaseJobDispatcher jobDispatcher; - - /** - * @param context A context. - * @param jobTag A tag for jobs scheduled by this instance. If the same tag was used by a previous - * instance, anything scheduled by the previous instance will be canceled by this instance if - * {@link #schedule(Requirements, String, String)} or {@link #cancel()} are called. - */ - public JobDispatcherScheduler(Context context, String jobTag) { - context = context.getApplicationContext(); - this.jobDispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); - this.jobTag = jobTag; - } - - @Override - public boolean schedule(Requirements requirements, String servicePackage, String serviceAction) { - Job job = buildJob(jobDispatcher, requirements, jobTag, servicePackage, serviceAction); - int result = jobDispatcher.schedule(job); - return result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS; - } - - @Override - public boolean cancel() { - int result = jobDispatcher.cancel(jobTag); - return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS; - } - - @Override - public Requirements getSupportedRequirements(Requirements requirements) { - return requirements.filterRequirements(SUPPORTED_REQUIREMENTS); - } - - private static Job buildJob( - FirebaseJobDispatcher dispatcher, - Requirements requirements, - String tag, - String servicePackage, - String serviceAction) { - Requirements filteredRequirements = requirements.filterRequirements(SUPPORTED_REQUIREMENTS); - if (!filteredRequirements.equals(requirements)) { - Log.w( - TAG, - "Ignoring unsupported requirements: " - + (filteredRequirements.getRequirements() ^ requirements.getRequirements())); - } - - Job.Builder builder = - dispatcher - .newJobBuilder() - .setService(JobDispatcherSchedulerService.class) // the JobService that will be called - .setTag(tag); - if (requirements.isUnmeteredNetworkRequired()) { - builder.addConstraint(Constraint.ON_UNMETERED_NETWORK); - } else if (requirements.isNetworkRequired()) { - builder.addConstraint(Constraint.ON_ANY_NETWORK); - } - if (requirements.isIdleRequired()) { - builder.addConstraint(Constraint.DEVICE_IDLE); - } - if (requirements.isChargingRequired()) { - builder.addConstraint(Constraint.DEVICE_CHARGING); - } - builder.setLifetime(Lifetime.FOREVER).setReplaceCurrent(true); - - Bundle extras = new Bundle(); - extras.putString(KEY_SERVICE_ACTION, serviceAction); - extras.putString(KEY_SERVICE_PACKAGE, servicePackage); - extras.putInt(KEY_REQUIREMENTS, requirements.getRequirements()); - builder.setExtras(extras); - - return builder.build(); - } - - /** A {@link JobService} that starts the target service if the requirements are met. */ - public static final class JobDispatcherSchedulerService extends JobService { - @Override - public boolean onStartJob(JobParameters params) { - Bundle extras = Assertions.checkNotNull(params.getExtras()); - Requirements requirements = new Requirements(extras.getInt(KEY_REQUIREMENTS)); - int notMetRequirements = requirements.getNotMetRequirements(this); - if (notMetRequirements == 0) { - String serviceAction = Assertions.checkNotNull(extras.getString(KEY_SERVICE_ACTION)); - String servicePackage = Assertions.checkNotNull(extras.getString(KEY_SERVICE_PACKAGE)); - Intent intent = new Intent(serviceAction).setPackage(servicePackage); - Util.startForegroundService(this, intent); - } else { - Log.w(TAG, "Requirements not met: " + notMetRequirements); - jobFinished(params, /* needsReschedule */ true); - } - return false; - } - - @Override - public boolean onStopJob(JobParameters params) { - return false; - } - } -} diff --git a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/package-info.java b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/package-info.java deleted file mode 100644 index a66904b505..0000000000 --- a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@NonNullApi -package com.google.android.exoplayer2.ext.jobdispatcher; - -import com.google.android.exoplayer2.util.NonNullApi; diff --git a/extensions/leanback/build.gradle b/extensions/leanback/build.gradle index 14ced09f12..a09e6d2f7c 100644 --- a/extensions/leanback/build.gradle +++ b/extensions/leanback/build.gradle @@ -16,7 +16,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" android.defaultConfig.minSdkVersion 17 dependencies { - implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-common') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.leanback:leanback:1.0.0' compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion 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 6da02bb324..f2fc3279b8 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 @@ -37,7 +37,6 @@ import com.google.android.exoplayer2.Player.TimelineChangeReason; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoListener; /** Leanback {@code PlayerAdapter} implementation for {@link Player}. */ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnable { @@ -49,7 +48,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab private final Context context; private final Player player; private final Handler handler; - private final ComponentListener componentListener; + private final PlayerListener playerListener; private final int updatePeriodMs; @Nullable private PlaybackPreparer playbackPreparer; @@ -73,7 +72,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab this.player = player; this.updatePeriodMs = updatePeriodMs; handler = Util.createHandlerForCurrentOrMainLooper(); - componentListener = new ComponentListener(); + playerListener = new PlayerListener(); controlDispatcher = new DefaultControlDispatcher(); } @@ -118,23 +117,15 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab public void onAttachedToHost(PlaybackGlueHost host) { if (host instanceof SurfaceHolderGlueHost) { surfaceHolderGlueHost = ((SurfaceHolderGlueHost) host); - surfaceHolderGlueHost.setSurfaceHolderCallback(componentListener); + surfaceHolderGlueHost.setSurfaceHolderCallback(playerListener); } notifyStateChanged(); - player.addListener(componentListener); - Player.VideoComponent videoComponent = player.getVideoComponent(); - if (videoComponent != null) { - videoComponent.addVideoListener(componentListener); - } + player.addListener(playerListener); } @Override public void onDetachedFromHost() { - player.removeListener(componentListener); - Player.VideoComponent videoComponent = player.getVideoComponent(); - if (videoComponent != null) { - videoComponent.removeVideoListener(componentListener); - } + player.removeListener(playerListener); if (surfaceHolderGlueHost != null) { removeSurfaceHolderCallback(surfaceHolderGlueHost); surfaceHolderGlueHost = null; @@ -227,10 +218,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab /* package */ void setVideoSurface(@Nullable Surface surface) { hasSurface = surface != null; - Player.VideoComponent videoComponent = player.getVideoComponent(); - if (videoComponent != null) { - videoComponent.setVideoSurface(surface); - } + player.setVideoSurface(surface); maybeNotifyPreparedStateChanged(getCallback()); } @@ -258,8 +246,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab surfaceHolderGlueHost.setSurfaceHolderCallback(null); } - private final class ComponentListener - implements Player.EventListener, SurfaceHolder.Callback, VideoListener { + private final class PlayerListener implements Player.Listener, SurfaceHolder.Callback { // SurfaceHolder.Callback implementation. @@ -306,7 +293,10 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab } @Override - public void onPositionDiscontinuity(@DiscontinuityReason int reason) { + public void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @DiscontinuityReason int reason) { Callback callback = getCallback(); callback.onCurrentPositionChanged(LeanbackPlayerAdapter.this); callback.onBufferedPositionChanged(LeanbackPlayerAdapter.this); diff --git a/extensions/media2/build.gradle b/extensions/media2/build.gradle index a89354d7b3..da70210bd6 100644 --- a/extensions/media2/build.gradle +++ b/extensions/media2/build.gradle @@ -13,16 +13,15 @@ // limitations under the License. apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" -android.defaultConfig.minSdkVersion 19 - dependencies { - implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-common') implementation 'androidx.collection:collection:' + androidxCollectionVersion implementation 'androidx.concurrent:concurrent-futures:' + androidxFuturesVersion api 'androidx.media2:media2-session:' + androidxMedia2Version compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion + androidTestImplementation project(modulePrefix + 'library-core') androidTestImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion androidTestImplementation 'androidx.test:core:' + androidxTestCoreVersion androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion diff --git a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java b/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java index edabd55812..d4e996caa2 100644 --- a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java +++ b/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java @@ -28,11 +28,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.content.Context; import android.media.AudioManager; -import android.os.Build; -import android.os.Build.VERSION_CODES; import android.os.Looper; import androidx.annotation.Nullable; -import androidx.core.util.ObjectsCompat; import androidx.media.AudioAttributesCompat; import androidx.media2.common.MediaItem; import androidx.media2.common.MediaMetadata; @@ -43,7 +40,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.filters.MediumTest; -import androidx.test.filters.SdkSuppress; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import com.google.android.exoplayer2.ControlDispatcher; @@ -52,6 +48,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.ext.media2.test.R; import com.google.android.exoplayer2.upstream.RawResourceDataSource; +import com.google.android.exoplayer2.util.Util; import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; import java.util.Arrays; @@ -93,7 +90,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void play_onceWithAudioResource_changesPlayerStateToPlaying() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); @@ -120,7 +116,6 @@ public class SessionPlayerConnectorTest { @Test @MediumTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void play_onceWithAudioResourceOnMainThread_notifiesOnPlayerStateChanged() throws Exception { CountDownLatch onPlayerStatePlayingLatch = new CountDownLatch(1); @@ -158,7 +153,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void play_withCustomControlDispatcher_isSkipped() throws Exception { if (Looper.myLooper() == null) { Looper.prepare(); @@ -194,7 +188,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setMediaItem_withAudioResource_notifiesOnPlaybackCompleted() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); @@ -219,7 +212,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setMediaItem_withVideoResource_notifiesOnPlaybackCompleted() throws Exception { TestUtils.loadResource(R.raw.video_desks, sessionPlayerConnector); CountDownLatch onPlaybackCompletedLatch = new CountDownLatch(1); @@ -243,7 +235,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void getDuration_whenIdleState_returnsUnknownTime() { assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE); assertThat(sessionPlayerConnector.getDuration()).isEqualTo(SessionPlayer.UNKNOWN_TIME); @@ -251,7 +242,6 @@ public class SessionPlayerConnectorTest { @Test @MediumTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void getDuration_afterPrepared_returnsDuration() throws Exception { TestUtils.loadResource(R.raw.video_desks, sessionPlayerConnector); @@ -263,7 +253,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void getCurrentPosition_whenIdleState_returnsDefaultPosition() { assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE); assertThat(sessionPlayerConnector.getCurrentPosition()).isEqualTo(0); @@ -271,7 +260,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void getBufferedPosition_whenIdleState_returnsDefaultPosition() { assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE); assertThat(sessionPlayerConnector.getBufferedPosition()).isEqualTo(0); @@ -279,7 +267,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void getPlaybackSpeed_whenIdleState_throwsNoException() { assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE); try { @@ -291,7 +278,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void play_withDataSourceCallback_changesPlayerState() throws Exception { sessionPlayerConnector.setMediaItem(TestUtils.createMediaItem(R.raw.video_big_buck_bunny)); sessionPlayerConnector.prepare(); @@ -308,7 +294,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setMediaItem_withNullMediaItem_throwsException() { try { sessionPlayerConnector.setMediaItem(null); @@ -320,7 +305,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaybackSpeed_afterPlayback_remainsSame() throws Exception { int resId1 = R.raw.video_big_buck_bunny; MediaItem mediaItem1 = @@ -363,7 +347,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void seekTo_withSeriesOfSeek_succeeds() throws Exception { TestUtils.loadResource(R.raw.video_big_buck_bunny, sessionPlayerConnector); @@ -378,7 +361,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void seekTo_skipsUnnecessarySeek() throws Exception { CountDownLatch readAllowedLatch = new CountDownLatch(1); playerTestRule.setDataSourceInstrumentation( @@ -435,7 +417,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void seekTo_whenUnderlyingPlayerAlsoSeeks_throwsNoException() throws Exception { TestUtils.loadResource(R.raw.video_big_buck_bunny, sessionPlayerConnector); assertPlayerResultSuccess(sessionPlayerConnector.prepare()); @@ -456,7 +437,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void seekTo_byUnderlyingPlayer_notifiesOnSeekCompleted() throws Exception { TestUtils.loadResource(R.raw.video_big_buck_bunny, sessionPlayerConnector); assertPlayerResultSuccess(sessionPlayerConnector.prepare()); @@ -484,7 +464,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void getPlayerState_withCallingPrepareAndPlayAndPause_reflectsPlayerState() throws Throwable { TestUtils.loadResource(R.raw.video_desks, sessionPlayerConnector); @@ -521,7 +500,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = VERSION_CODES.KITKAT) public void prepare_twice_finishes() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); assertPlayerResultSuccess(sessionPlayerConnector.prepare()); @@ -530,7 +508,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void prepare_notifiesOnPlayerStateChanged() throws Throwable { TestUtils.loadResource(R.raw.video_big_buck_bunny, sessionPlayerConnector); @@ -552,7 +529,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void prepare_notifiesBufferingCompletedOnce() throws Throwable { TestUtils.loadResource(R.raw.video_big_buck_bunny, sessionPlayerConnector); @@ -587,7 +563,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void seekTo_whenPrepared_notifiesOnSeekCompleted() throws Throwable { long mp4DurationMs = 8_484L; TestUtils.loadResource(R.raw.video_big_buck_bunny, sessionPlayerConnector); @@ -611,7 +586,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaybackSpeed_whenPrepared_notifiesOnPlaybackSpeedChanged() throws Throwable { TestUtils.loadResource(R.raw.video_big_buck_bunny, sessionPlayerConnector); @@ -636,7 +610,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaybackSpeed_withZeroSpeed_throwsException() { try { sessionPlayerConnector.setPlaybackSpeed(0.0f); @@ -648,7 +621,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaybackSpeed_withNegativeSpeed_throwsException() { try { sessionPlayerConnector.setPlaybackSpeed(-1.0f); @@ -660,7 +632,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void close_throwsNoExceptionAndDoesNotCrash() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); AudioAttributesCompat attributes = @@ -679,7 +650,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void cancelReturnedFuture_withSeekTo_cancelsPendingCommand() throws Exception { CountDownLatch readRequestedLatch = new CountDownLatch(1); CountDownLatch readAllowedLatch = new CountDownLatch(1); @@ -719,7 +689,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylist_withNullPlaylist_throwsException() throws Exception { try { sessionPlayerConnector.setPlaylist(null, null); @@ -731,7 +700,6 @@ public class SessionPlayerConnectorTest { @Test @SmallTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylist_withPlaylistContainingNullItem_throwsException() { try { List list = new ArrayList<>(); @@ -745,7 +713,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylist_setsPlaylistAndCurrentMediaItem() throws Exception { List playlist = TestUtils.createPlaylist(10); PlayerCallbackForPlaylist callback = new PlayerCallbackForPlaylist(playlist, 1); @@ -760,7 +727,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylistAndRemoveAllPlaylistItem_playerStateBecomesIdle() throws Exception { List playlist = new ArrayList<>(); playlist.add(TestUtils.createMediaItem(R.raw.video_1)); @@ -786,7 +752,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylist_calledOnlyOnce_notifiesPlaylistChangeOnlyOnce() throws Exception { List playlist = TestUtils.createPlaylist(10); CountDownLatch onPlaylistChangedLatch = new CountDownLatch(2); @@ -811,7 +776,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylist_byUnderlyingPlayerBeforePrepare_notifiesOnPlaylistChanged() throws Exception { List playlistToExoPlayer = TestUtils.createPlaylist(4); @@ -830,7 +794,7 @@ public class SessionPlayerConnectorTest { SessionPlayer player, @Nullable List list, @Nullable MediaMetadata metadata) { - if (ObjectsCompat.equals(list, playlistToExoPlayer)) { + if (Util.areEqual(list, playlistToExoPlayer)) { onPlaylistChangedLatch.countDown(); } } @@ -842,7 +806,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylist_byUnderlyingPlayerAfterPrepare_notifiesOnPlaylistChanged() throws Exception { List playlistToSessionPlayer = TestUtils.createPlaylist(2); @@ -862,7 +825,7 @@ public class SessionPlayerConnectorTest { SessionPlayer player, @Nullable List list, @Nullable MediaMetadata metadata) { - if (ObjectsCompat.equals(list, playlistToExoPlayer)) { + if (Util.areEqual(list, playlistToExoPlayer)) { onPlaylistChangedLatch.countDown(); } } @@ -876,7 +839,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void addPlaylistItem_calledOnlyOnce_notifiesPlaylistChangeOnlyOnce() throws Exception { List playlist = TestUtils.createPlaylist(10); assertPlayerResultSuccess(sessionPlayerConnector.setPlaylist(playlist, /* metadata= */ null)); @@ -905,7 +867,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void removePlaylistItem_calledOnlyOnce_notifiesPlaylistChangeOnlyOnce() throws Exception { List playlist = TestUtils.createPlaylist(10); assertPlayerResultSuccess(sessionPlayerConnector.setPlaylist(playlist, /* metadata= */ null)); @@ -933,7 +894,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void movePlaylistItem_calledOnlyOnce_notifiesPlaylistChangeOnlyOnce() throws Exception { List playlist = new ArrayList<>(); playlist.add(TestUtils.createMediaItem(R.raw.video_1)); @@ -967,7 +927,6 @@ public class SessionPlayerConnectorTest { @Ignore @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void replacePlaylistItem_calledOnlyOnce_notifiesPlaylistChangeOnlyOnce() throws Exception { List playlist = TestUtils.createPlaylist(10); assertPlayerResultSuccess(sessionPlayerConnector.setPlaylist(playlist, /* metadata= */ null)); @@ -996,7 +955,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setPlaylist_withPlaylist_notifiesOnCurrentMediaItemChanged() throws Exception { int listSize = 2; List playlist = TestUtils.createPlaylist(listSize); @@ -1011,7 +969,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void play_twice_finishes() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); assertPlayerResultSuccess(sessionPlayerConnector.prepare()); @@ -1021,7 +978,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void play_withPlaylist_notifiesOnCurrentMediaItemChangedAndOnPlaybackCompleted() throws Exception { List playlist = new ArrayList<>(); @@ -1060,7 +1016,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void play_byUnderlyingPlayer_notifiesOnPlayerStateChanges() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); SimpleExoPlayer simpleExoPlayer = playerTestRule.getSimpleExoPlayer(); @@ -1086,7 +1041,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void pause_twice_finishes() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); assertPlayerResultSuccess(sessionPlayerConnector.prepare()); @@ -1097,7 +1051,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void pause_byUnderlyingPlayer_notifiesOnPlayerStateChanges() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); SimpleExoPlayer simpleExoPlayer = playerTestRule.getSimpleExoPlayer(); @@ -1124,7 +1077,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void pause_byUnderlyingPlayerInListener_changesToPlayerStatePaused() throws Exception { TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); SimpleExoPlayer simpleExoPlayer = playerTestRule.getSimpleExoPlayer(); @@ -1169,7 +1121,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void skipToNextAndPrevious_calledInARow_notifiesOnCurrentMediaItemChanged() throws Exception { List playlist = new ArrayList<>(); @@ -1221,7 +1172,6 @@ public class SessionPlayerConnectorTest { @Test @LargeTest - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) public void setRepeatMode_withRepeatAll_continuesToPlayPlaylistWithoutBeingCompleted() throws Exception { List playlist = new ArrayList<>(); diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java index e6d4550d88..9ff1f3dd24 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java @@ -84,7 +84,7 @@ public class DefaultMediaItemConverter implements MediaItemConverter { return new MediaItem.Builder() .setUri(uri) - .setMediaId(mediaId) + .setMediaId(mediaId != null ? mediaId : MediaItem.DEFAULT_MEDIA_ID) .setMediaMetadata( new com.google.android.exoplayer2.MediaMetadata.Builder().setTitle(title).build()) .setTag(media2MediaItem) @@ -123,14 +123,14 @@ public class DefaultMediaItemConverter implements MediaItemConverter { * MediaItem ExoPlayer MediaItem}. */ protected androidx.media2.common.MediaMetadata getMetadata(MediaItem exoPlayerMediaItem) { - @Nullable String title = exoPlayerMediaItem.mediaMetadata.title; + @Nullable CharSequence title = exoPlayerMediaItem.mediaMetadata.title; androidx.media2.common.MediaMetadata.Builder metadataBuilder = new androidx.media2.common.MediaMetadata.Builder() .putString(METADATA_KEY_MEDIA_ID, exoPlayerMediaItem.mediaId); if (title != null) { - metadataBuilder.putString(METADATA_KEY_TITLE, title); - metadataBuilder.putString(METADATA_KEY_DISPLAY_TITLE, title); + metadataBuilder.putString(METADATA_KEY_TITLE, title.toString()); + metadataBuilder.putString(METADATA_KEY_DISPLAY_TITLE, title.toString()); } return metadataBuilder.build(); } diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java index a1d4941f50..c65431a857 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java @@ -39,7 +39,7 @@ import java.util.List; import java.util.concurrent.Callable; /** Manages the queue of player actions and handles running them one by one. */ -/* package */ class PlayerCommandQueue implements AutoCloseable { +/* package */ class PlayerCommandQueue { private static final String TAG = "PlayerCommandQueue"; private static final boolean DEBUG = false; @@ -141,9 +141,6 @@ import java.util.concurrent.Callable; @GuardedBy("lock") private final Deque pendingPlayerCommandQueue; - @GuardedBy("lock") - private boolean closed; - // Should be only used on the handler. @Nullable private AsyncPlayerCommandResult pendingAsyncPlayerCommandResult; @@ -154,17 +151,6 @@ import java.util.concurrent.Callable; pendingPlayerCommandQueue = new ArrayDeque<>(); } - @Override - public void close() { - synchronized (lock) { - if (closed) { - return; - } - closed = true; - } - reset(); - } - public void reset() { handler.removeCallbacksAndMessages(/* token= */ null); List queue; @@ -187,11 +173,6 @@ import java.util.concurrent.Callable; @CommandCode int commandCode, Callable command, @Nullable Object tag) { SettableFuture result = SettableFuture.create(); synchronized (lock) { - if (closed) { - // OK to set result with lock hold because developers cannot add listener here. - result.set(new PlayerResult(PlayerResult.RESULT_ERROR_INVALID_STATE, /* item= */ null)); - return result; - } PlayerCommand playerCommand = new PlayerCommand(commandCode, command, result, tag); result.addListener( () -> { 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 74d7bd110e..f4d934bcf9 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 @@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.util.Util.postOrRun; import android.os.Handler; import androidx.annotation.IntRange; import androidx.annotation.Nullable; -import androidx.core.util.ObjectsCompat; import androidx.media.AudioAttributesCompat; import androidx.media2.common.CallbackMediaItem; import androidx.media2.common.MediaMetadata; @@ -34,7 +33,6 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioAttributes; -import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -139,10 +137,6 @@ import java.util.List; controlDispatcher = new DefaultControlDispatcher(); componentListener = new ComponentListener(); player.addListener(componentListener); - @Nullable Player.AudioComponent audioComponent = player.getAudioComponent(); - if (audioComponent != null) { - audioComponent.addAudioListener(componentListener); - } handler = new Handler(player.getApplicationLooper()); pollBufferRunnable = new PollBufferRunnable(); @@ -438,7 +432,7 @@ import java.util.List; case Player.STATE_READY: if (!prepared) { prepared = true; - handlePositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + handlePositionDiscontinuity(Player.DISCONTINUITY_REASON_AUTO_TRANSITION); listener.onPrepared( Assertions.checkNotNull(getCurrentMediaItem()), player.getBufferedPercentage()); } @@ -456,15 +450,15 @@ import java.util.List; } public void setAudioAttributes(AudioAttributesCompat audioAttributes) { - Player.AudioComponent audioComponent = Assertions.checkStateNotNull(player.getAudioComponent()); - audioComponent.setAudioAttributes( - Utils.getAudioAttributes(audioAttributes), /* handleAudioFocus= */ true); + // Player interface doesn't support setting audio attributes. } public AudioAttributesCompat getAudioAttributes() { - @Nullable Player.AudioComponent audioComponent = player.getAudioComponent(); - return Utils.getAudioAttributesCompat( - audioComponent != null ? audioComponent.getAudioAttributes() : AudioAttributes.DEFAULT); + AudioAttributes audioAttributes = AudioAttributes.DEFAULT; + if (player.isCommandAvailable(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) { + audioAttributes = player.getAudioAttributes(); + } + return Utils.getAudioAttributesCompat(audioAttributes); } public void setPlaybackSpeed(float playbackSpeed) { @@ -484,11 +478,6 @@ import java.util.List; public void close() { handler.removeCallbacks(pollBufferRunnable); player.removeListener(componentListener); - - @Nullable Player.AudioComponent audioComponent = player.getAudioComponent(); - if (audioComponent != null) { - audioComponent.removeAudioListener(componentListener); - } } public boolean isCurrentMediaItemSeekable() { @@ -518,9 +507,11 @@ import java.util.List; int currentWindowIndex = getCurrentMediaItemIndex(); if (this.currentWindowIndex != currentWindowIndex) { this.currentWindowIndex = currentWindowIndex; - androidx.media2.common.MediaItem currentMediaItem = - Assertions.checkNotNull(getCurrentMediaItem()); - listener.onCurrentMediaItemChanged(currentMediaItem); + if (currentWindowIndex != C.INDEX_UNSET) { + androidx.media2.common.MediaItem currentMediaItem = + Assertions.checkNotNull(getCurrentMediaItem()); + listener.onCurrentMediaItemChanged(currentMediaItem); + } } else { listener.onSeekCompleted(); } @@ -535,7 +526,7 @@ import java.util.List; int windowCount = timeline.getWindowCount(); for (int i = 0; i < windowCount; i++) { timeline.getWindow(i, window); - if (!ObjectsCompat.equals(exoPlayerPlaylist.get(i), window.mediaItem)) { + if (!Util.areEqual(exoPlayerPlaylist.get(i), window.mediaItem)) { return true; } } @@ -583,7 +574,7 @@ import java.util.List; } } - private final class ComponentListener implements Player.EventListener, AudioListener { + private final class ComponentListener implements Player.Listener { // Player.EventListener implementation. @@ -598,7 +589,10 @@ import java.util.List; } @Override - public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { + public void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @Player.DiscontinuityReason int reason) { handlePositionDiscontinuity(reason); } diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java index 9a3dc09b07..e51964b648 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java @@ -22,7 +22,6 @@ import androidx.annotation.FloatRange; import androidx.annotation.GuardedBy; import androidx.annotation.IntRange; import androidx.annotation.Nullable; -import androidx.core.util.ObjectsCompat; import androidx.core.util.Pair; import androidx.media.AudioAttributesCompat; import androidx.media2.common.CallbackMediaItem; @@ -36,6 +35,7 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.Util; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.HashMap; @@ -578,7 +578,7 @@ public final class SessionPlayerConnector extends SessionPlayer { MediaItem currentMediaItem = player.getCurrentMediaItem(); boolean notifyCurrentMediaItem = - !ObjectsCompat.equals(this.currentMediaItem, currentMediaItem) && currentMediaItem != null; + !Util.areEqual(this.currentMediaItem, currentMediaItem) && currentMediaItem != null; this.currentMediaItem = currentMediaItem; long currentPosition = getCurrentPosition(); @@ -594,7 +594,7 @@ public final class SessionPlayerConnector extends SessionPlayer { private void notifySkipToCompletedOnHandler() { MediaItem currentMediaItem = Assertions.checkNotNull(player.getCurrentMediaItem()); - if (ObjectsCompat.equals(this.currentMediaItem, currentMediaItem)) { + if (Util.areEqual(this.currentMediaItem, currentMediaItem)) { return; } this.currentMediaItem = currentMediaItem; @@ -714,7 +714,7 @@ public final class SessionPlayerConnector extends SessionPlayer { @Override public void onCurrentMediaItemChanged(MediaItem mediaItem) { - if (ObjectsCompat.equals(currentMediaItem, mediaItem)) { + if (Util.areEqual(currentMediaItem, mediaItem)) { return; } currentMediaItem = mediaItem; diff --git a/extensions/mediasession/build.gradle b/extensions/mediasession/build.gradle index 5c827084da..9b812911ab 100644 --- a/extensions/mediasession/build.gradle +++ b/extensions/mediasession/build.gradle @@ -14,7 +14,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { - implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-common') api 'androidx.media:media:' + androidxMediaVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion 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 179a8a3f11..f806a579be 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.mediasession; +import static androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID; import static com.google.android.exoplayer2.Player.EVENT_IS_PLAYING_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_PLAYBACK_STATE_CHANGED; @@ -45,11 +46,11 @@ import com.google.android.exoplayer2.ControlDispatcher; import com.google.android.exoplayer2.DefaultControlDispatcher; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; +import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ErrorMessageProvider; -import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -784,6 +785,10 @@ public final class MediaSessionConnector { float playbackSpeed = player.getPlaybackParameters().speed; extras.putFloat(EXTRAS_SPEED, playbackSpeed); float sessionPlaybackSpeed = player.isPlaying() ? playbackSpeed : 0f; + @Nullable MediaItem currentMediaItem = player.getCurrentMediaItem(); + if (currentMediaItem != null && !MediaItem.DEFAULT_MEDIA_ID.equals(currentMediaItem.mediaId)) { + extras.putString(PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, currentMediaItem.mediaId); + } builder .setActions(buildPrepareActions() | buildPlaybackActions(player)) .setActiveQueueItemId(activeQueueItemId) @@ -1080,13 +1085,12 @@ public final class MediaSessionConnector { } } - private class ComponentListener extends MediaSessionCompat.Callback - implements Player.EventListener { + private class ComponentListener extends MediaSessionCompat.Callback implements Player.Listener { private int currentWindowIndex; private int currentWindowCount; - // Player.EventListener implementation. + // Player.Listener implementation. @Override public void onEvents(Player player, Player.Events events) { @@ -1220,7 +1224,7 @@ public final class MediaSessionConnector { @Override public void onSetRepeatMode(@PlaybackStateCompat.RepeatMode int mediaSessionRepeatMode) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_SET_REPEAT_MODE)) { - @RepeatModeUtil.RepeatToggleModes int repeatMode; + @Player.RepeatMode int repeatMode; switch (mediaSessionRepeatMode) { case PlaybackStateCompat.REPEAT_MODE_ALL: case PlaybackStateCompat.REPEAT_MODE_GROUP: diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java index 7f60d5e715..cab16744b9 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java @@ -23,15 +23,13 @@ import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ControlDispatcher; +import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Util; import java.util.List; /** - * A {@link MediaSessionConnector.QueueEditor} implementation based on the {@link - * ConcatenatingMediaSource}. + * A {@link MediaSessionConnector.QueueEditor} implementation. * *

This class implements the {@link MediaSessionConnector.CommandReceiver} interface and handles * the {@link #COMMAND_MOVE_QUEUE_ITEM} to move a queue item instead of removing and inserting it. @@ -44,18 +42,17 @@ public final class TimelineQueueEditor public static final String EXTRA_FROM_INDEX = "from_index"; public static final String EXTRA_TO_INDEX = "to_index"; - /** - * Factory to create {@link MediaSource}s. - */ - public interface MediaSourceFactory { + /** Converts a {@link MediaDescriptionCompat} to a {@link MediaItem}. */ + public interface MediaDescriptionConverter { /** - * Creates a {@link MediaSource} for the given {@link MediaDescriptionCompat}. + * Returns a {@link MediaItem} for the given {@link MediaDescriptionCompat} or null if the + * description can't be converted. * - * @param description The {@link MediaDescriptionCompat} to create a media source for. - * @return A {@link MediaSource} or {@code null} if no source can be created for the given - * description. + *

If not null, the media item that is returned will be used to call {@link + * Player#addMediaItem(MediaItem)}. */ - @Nullable MediaSource createMediaSource(MediaDescriptionCompat description); + @Nullable + MediaItem convert(MediaDescriptionCompat description); } /** @@ -110,51 +107,46 @@ public final class TimelineQueueEditor public boolean equals(MediaDescriptionCompat d1, MediaDescriptionCompat d2) { return Util.areEqual(d1.getMediaId(), d2.getMediaId()); } - } private final MediaControllerCompat mediaController; private final QueueDataAdapter queueDataAdapter; - private final MediaSourceFactory sourceFactory; + private final MediaDescriptionConverter mediaDescriptionConverter; private final MediaDescriptionEqualityChecker equalityChecker; - private final ConcatenatingMediaSource queueMediaSource; /** * Creates a new {@link TimelineQueueEditor} with a given mediaSourceFactory. * * @param mediaController A {@link MediaControllerCompat} to read the current queue. - * @param queueMediaSource The {@link ConcatenatingMediaSource} to manipulate. * @param queueDataAdapter A {@link QueueDataAdapter} to change the backing data. - * @param sourceFactory The {@link MediaSourceFactory} to build media sources. + * @param mediaDescriptionConverter The {@link MediaDescriptionConverter} for converting media + * descriptions to {@link MediaItem MediaItems}. */ public TimelineQueueEditor( MediaControllerCompat mediaController, - ConcatenatingMediaSource queueMediaSource, QueueDataAdapter queueDataAdapter, - MediaSourceFactory sourceFactory) { - this(mediaController, queueMediaSource, queueDataAdapter, sourceFactory, - new MediaIdEqualityChecker()); + MediaDescriptionConverter mediaDescriptionConverter) { + this( + mediaController, queueDataAdapter, mediaDescriptionConverter, new MediaIdEqualityChecker()); } /** * Creates a new {@link TimelineQueueEditor} with a given mediaSourceFactory. * * @param mediaController A {@link MediaControllerCompat} to read the current queue. - * @param queueMediaSource The {@link ConcatenatingMediaSource} to manipulate. * @param queueDataAdapter A {@link QueueDataAdapter} to change the backing data. - * @param sourceFactory The {@link MediaSourceFactory} to build media sources. + * @param mediaDescriptionConverter The {@link MediaDescriptionConverter} for converting media + * descriptions to {@link MediaItem MediaItems}. * @param equalityChecker The {@link MediaDescriptionEqualityChecker} to match queue items. */ public TimelineQueueEditor( MediaControllerCompat mediaController, - ConcatenatingMediaSource queueMediaSource, QueueDataAdapter queueDataAdapter, - MediaSourceFactory sourceFactory, + MediaDescriptionConverter mediaDescriptionConverter, MediaDescriptionEqualityChecker equalityChecker) { this.mediaController = mediaController; - this.queueMediaSource = queueMediaSource; this.queueDataAdapter = queueDataAdapter; - this.sourceFactory = sourceFactory; + this.mediaDescriptionConverter = mediaDescriptionConverter; this.equalityChecker = equalityChecker; } @@ -165,10 +157,10 @@ public final class TimelineQueueEditor @Override public void onAddQueueItem(Player player, MediaDescriptionCompat description, int index) { - @Nullable MediaSource mediaSource = sourceFactory.createMediaSource(description); - if (mediaSource != null) { + @Nullable MediaItem mediaItem = mediaDescriptionConverter.convert(description); + if (mediaItem != null) { queueDataAdapter.add(index, description); - queueMediaSource.addMediaSource(index, mediaSource); + player.addMediaItem(index, mediaItem); } } @@ -178,7 +170,7 @@ public final class TimelineQueueEditor for (int i = 0; i < queue.size(); i++) { if (equalityChecker.equals(queue.get(i).getDescription(), description)) { queueDataAdapter.remove(i); - queueMediaSource.removeMediaSource(i); + player.removeMediaItem(i); return; } } @@ -200,9 +192,8 @@ public final class TimelineQueueEditor int to = extras.getInt(EXTRA_TO_INDEX, C.INDEX_UNSET); if (from != C.INDEX_UNSET && to != C.INDEX_UNSET) { queueDataAdapter.move(from, to); - queueMediaSource.moveMediaSource(from, to); + player.moveMediaItem(from, to); } return true; } - } 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 bc86da4a86..4a27ca9d93 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 @@ -15,6 +15,9 @@ */ package com.google.android.exoplayer2.ext.mediasession; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM; import static java.lang.Math.min; import android.os.Bundle; @@ -98,8 +101,13 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu if (!timeline.isEmpty() && !player.isPlayingAd()) { timeline.getWindow(player.getCurrentWindowIndex(), window); enableSkipTo = timeline.getWindowCount() > 1; - enablePrevious = window.isSeekable || !window.isLive() || player.hasPrevious(); - enableNext = (window.isLive() && window.isDynamic) || player.hasNext(); + enablePrevious = + player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) + || !window.isLive() + || player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + enableNext = + (window.isLive() && window.isDynamic) + || player.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); } long actions = 0; diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index d23dd22574..0b881955a1 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.okhttp; +import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader; import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.min; @@ -27,11 +28,13 @@ import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.upstream.HttpUtil; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.base.Predicate; -import java.io.EOFException; +import com.google.common.net.HttpHeaders; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; @@ -168,8 +171,6 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { } } - private static final byte[] SKIP_BUFFER = new byte[4096]; - private final Call.Factory callFactory; private final RequestProperties requestProperties; @@ -182,11 +183,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { @Nullable private Response response; @Nullable private InputStream responseByteStream; private boolean opened; - - private long bytesToSkip; private long bytesToRead; - - private long bytesSkipped; private long bytesRead; /** @deprecated Use {@link OkHttpDataSource.Factory} instead. */ @@ -278,8 +275,8 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { this.dataSpec = dataSpec; - this.bytesRead = 0; - this.bytesSkipped = 0; + bytesRead = 0; + bytesToRead = 0; transferInitializing(dataSpec); Request request = makeRequest(dataSpec); @@ -293,7 +290,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { } catch (IOException e) { @Nullable String message = e.getMessage(); if (message != null - && Util.toLowerInvariant(message).matches("cleartext communication.*not permitted.*")) { + && Ascii.toLowerCase(message).matches("cleartext communication.*not permitted.*")) { throw new CleartextNotPermittedException(e, dataSpec); } throw new HttpDataSourceException( @@ -304,6 +301,16 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { // Check for a valid response code. if (!response.isSuccessful()) { + if (responseCode == 416) { + long documentSize = + HttpUtil.getDocumentSize(response.headers().get(HttpHeaders.CONTENT_RANGE)); + if (dataSpec.position == documentSize) { + opened = true; + transferStarted(dataSpec); + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0; + } + } + byte[] errorResponseBody; try { errorResponseBody = Util.toByteArray(Assertions.checkNotNull(responseByteStream)); @@ -332,7 +339,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { // If we requested a range starting from a non-zero position and received a 200 rather than a // 206, then the server does not support partial requests. We'll need to manually skip to the // requested position. - bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; + long bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; // Determine the length of the data to be read, after skipping. if (dataSpec.length != C.LENGTH_UNSET) { @@ -345,13 +352,21 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { opened = true; transferStarted(dataSpec); + try { + if (!skipFully(bytesToSkip)) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + } catch (IOException e) { + closeConnectionQuietly(); + throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_OPEN); + } + return bytesToRead; } @Override public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException { try { - skipInternal(); return readInternal(buffer, offset, readLength); } catch (IOException e) { throw new HttpDataSourceException( @@ -368,38 +383,6 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { } } - /** - * Returns the number of bytes that have been skipped since the most recent call to - * {@link #open(DataSpec)}. - * - * @return The number of bytes skipped. - */ - protected final long bytesSkipped() { - return bytesSkipped; - } - - /** - * Returns the number of bytes that have been read since the most recent call to - * {@link #open(DataSpec)}. - * - * @return The number of bytes read. - */ - protected final long bytesRead() { - return bytesRead; - } - - /** - * Returns the number of bytes that are still to be read for the current {@link DataSpec}. - *

- * If the total length of the data being read is known, then this length minus {@code bytesRead()} - * is returned. If the total length is unknown, {@link C#LENGTH_UNSET} is returned. - * - * @return The remaining length, or {@link C#LENGTH_UNSET}. - */ - protected final long bytesRemaining() { - return bytesToRead == C.LENGTH_UNSET ? bytesToRead : bytesToRead - bytesRead; - } - /** Establishes a connection. */ private Request makeRequest(DataSpec dataSpec) throws HttpDataSourceException { long position = dataSpec.position; @@ -428,18 +411,15 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { builder.header(header.getKey(), header.getValue()); } - if (!(position == 0 && length == C.LENGTH_UNSET)) { - String rangeRequest = "bytes=" + position + "-"; - if (length != C.LENGTH_UNSET) { - rangeRequest += (position + length - 1); - } - builder.addHeader("Range", rangeRequest); + @Nullable String rangeHeader = buildRangeRequestHeader(position, length); + if (rangeHeader != null) { + builder.addHeader(HttpHeaders.RANGE, rangeHeader); } if (userAgent != null) { - builder.addHeader("User-Agent", userAgent); + builder.addHeader(HttpHeaders.USER_AGENT, userAgent); } if (!dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP)) { - builder.addHeader("Accept-Encoding", "identity"); + builder.addHeader(HttpHeaders.ACCEPT_ENCODING, "identity"); } @Nullable RequestBody requestBody = null; @@ -454,30 +434,32 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { } /** - * Skips any bytes that need skipping. Else does nothing. - *

- * This implementation is based roughly on {@code libcore.io.Streams.skipByReading()}. + * Attempts to skip the specified number of bytes in full. * + * @param bytesToSkip The number of bytes to skip. * @throws InterruptedIOException If the thread is interrupted during the operation. - * @throws EOFException If the end of the input stream is reached before the bytes are skipped. + * @throws IOException If an error occurs reading from the source. + * @return Whether the bytes were skipped in full. If {@code false} then the data ended before the + * specified number of bytes were skipped. Always {@code true} if {@code bytesToSkip == 0}. */ - private void skipInternal() throws IOException { - if (bytesSkipped == bytesToSkip) { - return; + private boolean skipFully(long bytesToSkip) throws IOException { + if (bytesToSkip == 0) { + return true; } - - while (bytesSkipped != bytesToSkip) { - int readLength = (int) min(bytesToSkip - bytesSkipped, SKIP_BUFFER.length); - int read = castNonNull(responseByteStream).read(SKIP_BUFFER, 0, readLength); + byte[] skipBuffer = new byte[4096]; + while (bytesToSkip > 0) { + int readLength = (int) min(bytesToSkip, skipBuffer.length); + int read = castNonNull(responseByteStream).read(skipBuffer, 0, readLength); if (Thread.currentThread().isInterrupted()) { throw new InterruptedIOException(); } if (read == -1) { - throw new EOFException(); + return false; } - bytesSkipped += read; + bytesToSkip -= read; bytesTransferred(read); } + return true; } /** @@ -508,10 +490,6 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { int read = castNonNull(responseByteStream).read(buffer, offset, readLength); if (read == -1) { - if (bytesToRead != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new EOFException(); - } return C.RESULT_END_OF_INPUT; } diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java index 08e337f52b..5b6a31ca92 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.ext.okhttp; - import androidx.annotation.Nullable; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory; diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index c964b0cc1c..9cb606a718 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -24,9 +24,11 @@ import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.RenderersFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; @@ -71,12 +73,12 @@ public class OpusPlaybackTest { } } - private static class TestPlaybackRunnable implements Player.EventListener, Runnable { + private static class TestPlaybackRunnable implements Player.Listener, Runnable { private final Context context; private final Uri uri; - @Nullable private ExoPlayer player; + @Nullable private SimpleExoPlayer player; @Nullable private ExoPlaybackException playbackException; public TestPlaybackRunnable(Uri uri, Context context) { @@ -87,8 +89,14 @@ public class OpusPlaybackTest { @Override public void run() { Looper.prepare(); - LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer(); - player = new ExoPlayer.Builder(context, audioRenderer).build(); + RenderersFactory renderersFactory = + (eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput) -> + new Renderer[] {new LibopusAudioRenderer(eventHandler, audioRendererEventListener)}; + player = new SimpleExoPlayer.Builder(context, renderersFactory).build(); player.addListener(this); MediaSource mediaSource = new ProgressiveMediaSource.Factory( diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java index 5529701c06..71ba1db106 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.util.LibraryLoader; import com.google.android.exoplayer2.util.Util; -/** - * Configures and queries the underlying native library. - */ +/** Configures and queries the underlying native library. */ public final class OpusLibrary { static { diff --git a/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java b/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java index db60eea269..167a4175d7 100644 --- a/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java +++ b/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java @@ -20,9 +20,7 @@ import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.HttpDataSource.Factory; import com.google.android.exoplayer2.upstream.TransferListener; -/** - * A {@link Factory} that produces {@link RtmpDataSource}. - */ +/** A {@link Factory} that produces {@link RtmpDataSource}. */ public final class RtmpDataSourceFactory implements DataSource.Factory { @Nullable private final TransferListener listener; diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 823ce02cfe..d7a19f1662 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -25,10 +25,11 @@ import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.RenderersFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; @@ -100,12 +101,12 @@ public class VpxPlaybackTest { } } - private static class TestPlaybackRunnable implements Player.EventListener, Runnable { + private static class TestPlaybackRunnable implements Player.Listener, Runnable { private final Context context; private final Uri uri; - @Nullable private ExoPlayer player; + @Nullable private SimpleExoPlayer player; @Nullable private ExoPlaybackException playbackException; public TestPlaybackRunnable(Uri uri, Context context) { @@ -116,18 +117,26 @@ public class VpxPlaybackTest { @Override public void run() { Looper.prepare(); - LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(0); - player = new ExoPlayer.Builder(context, videoRenderer).build(); + RenderersFactory renderersFactory = + (eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput) -> + new Renderer[] { + new LibvpxVideoRenderer( + /* allowedJoiningTimeMs= */ 0, + eventHandler, + videoRendererEventListener, + /* maxDroppedFramesToNotify= */ -1) + }; + player = new SimpleExoPlayer.Builder(context, renderersFactory).build(); player.addListener(this); MediaSource mediaSource = new ProgressiveMediaSource.Factory( new DefaultDataSourceFactory(context), MatroskaExtractor.FACTORY) .createMediaSource(MediaItem.fromUri(uri)); - player - .createMessage(videoRenderer) - .setType(Renderer.MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER) - .setPayload(new VideoDecoderGLSurfaceView(context).getVideoDecoderOutputBufferRenderer()) - .send(); + player.setVideoSurfaceView(new VideoDecoderGLSurfaceView(context)); player.setMediaSource(mediaSource); player.prepare(); player.play(); diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java index 5106ab67ad..339ec021c6 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.util.LibraryLoader; import com.google.android.exoplayer2.util.Util; -/** - * Configures and queries the underlying native library. - */ +/** Configures and queries the underlying native library. */ public final class VpxLibrary { static { diff --git a/extensions/workmanager/build.gradle b/extensions/workmanager/build.gradle index b3624e75dc..02a1055d87 100644 --- a/extensions/workmanager/build.gradle +++ b/extensions/workmanager/build.gradle @@ -17,7 +17,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { implementation project(modulePrefix + 'library-core') - implementation 'androidx.work:work-runtime:2.4.0' + implementation 'androidx.work:work-runtime:2.5.0' compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index eefcdc910f..8efe8c9f90 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/javadoc_library.gradle b/javadoc_library.gradle index bb17dcb035..20be99ee12 100644 --- a/javadoc_library.gradle +++ b/javadoc_library.gradle @@ -25,7 +25,7 @@ android.libraryVariants.all { variant -> task("generateJavadoc", type: Javadoc) { description = "Generates Javadoc for the ${javadocTitle}." title = "ExoPlayer ${javadocTitle}" - source = allSourceDirs + source = allSourceDirs + "${buildDir}/generated/aidl_source_output_dir/" options { links "https://developer.android.com/reference", "https://guava.dev/releases/$project.ext.guavaVersion/api/docs" diff --git a/library/all/build.gradle b/library/all/build.gradle index e18d856c83..56414c14bf 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -17,6 +17,7 @@ dependencies { api project(modulePrefix + 'library-core') api project(modulePrefix + 'library-dash') api project(modulePrefix + 'library-hls') + api project(modulePrefix + 'library-rtsp') api project(modulePrefix + 'library-smoothstreaming') api project(modulePrefix + 'library-transformer') api project(modulePrefix + 'library-ui') 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 d99a320e32..57550d589b 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 @@ -30,47 +30,70 @@ public abstract class BasePlayer implements Player { } @Override - public void setMediaItem(MediaItem mediaItem) { + public final void setMediaItem(MediaItem mediaItem) { setMediaItems(Collections.singletonList(mediaItem)); } @Override - public void setMediaItem(MediaItem mediaItem, long startPositionMs) { + public final void setMediaItem(MediaItem mediaItem, long startPositionMs) { setMediaItems(Collections.singletonList(mediaItem), /* startWindowIndex= */ 0, startPositionMs); } @Override - public void setMediaItem(MediaItem mediaItem, boolean resetPosition) { + public final void setMediaItem(MediaItem mediaItem, boolean resetPosition) { setMediaItems(Collections.singletonList(mediaItem), resetPosition); } @Override - public void setMediaItems(List mediaItems) { + public final void setMediaItems(List mediaItems) { setMediaItems(mediaItems, /* resetPosition= */ true); } @Override - public void addMediaItem(int index, MediaItem mediaItem) { + public final void addMediaItem(int index, MediaItem mediaItem) { addMediaItems(index, Collections.singletonList(mediaItem)); } @Override - public void addMediaItem(MediaItem mediaItem) { + public final void addMediaItem(MediaItem mediaItem) { addMediaItems(Collections.singletonList(mediaItem)); } @Override - public void moveMediaItem(int currentIndex, int newIndex) { + public final void addMediaItems(List mediaItems) { + addMediaItems(/* index= */ Integer.MAX_VALUE, mediaItems); + } + + @Override + public final void moveMediaItem(int currentIndex, int newIndex) { if (currentIndex != newIndex) { moveMediaItems(/* fromIndex= */ currentIndex, /* toIndex= */ currentIndex + 1, newIndex); } } @Override - public void removeMediaItem(int index) { + public final void removeMediaItem(int index) { removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1); } + @Override + public final void clearMediaItems() { + removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ Integer.MAX_VALUE); + } + + @Override + public final boolean isCommandAvailable(@Command int command) { + return getAvailableCommands().contains(command); + } + + /** @deprecated Use {@link #getPlayerError()} instead. */ + @Deprecated + @Override + @Nullable + public final ExoPlaybackException getPlaybackError() { + return getPlayerError(); + } + @Override public final void play() { setPlayWhenReady(true); @@ -129,6 +152,11 @@ public abstract class BasePlayer implements Player { } } + @Override + public final void setPlaybackSpeed(float speed) { + setPlaybackParameters(getPlaybackParameters().withSpeed(speed)); + } + @Override public final void stop() { stop(/* reset= */ false); @@ -180,12 +208,12 @@ public abstract class BasePlayer implements Player { } @Override - public int getMediaItemCount() { + public final int getMediaItemCount() { return getCurrentTimeline().getWindowCount(); } @Override - public MediaItem getMediaItemAt(int index) { + public final MediaItem getMediaItemAt(int index) { return getCurrentTimeline().getWindow(index, window).mediaItem; } @@ -249,4 +277,15 @@ public abstract class BasePlayer implements Player { @RepeatMode int repeatMode = getRepeatMode(); return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode; } + + protected Commands getAvailableCommands(Commands permanentAvailableCommands) { + 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_NEXT_MEDIA_ITEM, hasNext() && !isPlayingAd()) + .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPrevious() && !isPlayingAd()) + .addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd()) + .build(); + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/BundleListRetriever.java b/library/common/src/main/java/com/google/android/exoplayer2/BundleListRetriever.java new file mode 100644 index 0000000000..4deaf43a8f --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/BundleListRetriever.java @@ -0,0 +1,125 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import java.util.List; + +/** + * A {@link Binder} to transfer a list of {@link Bundle Bundles} across processes by splitting the + * list into multiple transactions. + * + *

Note: Using this class causes synchronous binder calls in the opposite direction regardless of + * the "oneway" property. + * + *

Example usage: + * + *

{@code
+ * // Sender
+ * List list = ...;
+ * IBinder binder = new BundleListRetriever(list);
+ * Bundle bundle = new Bundle();
+ * bundle.putBinder("list", binder);
+ *
+ * // Receiver
+ * Bundle bundle = ...; // Received from the sender
+ * IBinder binder = bundle.getBinder("list");
+ * List list = BundleListRetriever.getList(binder);
+ * }
+ */ +public final class BundleListRetriever extends Binder { + + // Soft limit of an IPC buffer size + private static final int SUGGESTED_MAX_IPC_SIZE = + Util.SDK_INT >= 30 ? IBinder.getSuggestedMaxIpcSizeBytes() : 64 * 1024; + + private static final int REPLY_END_OF_LIST = 0; + private static final int REPLY_CONTINUE = 1; + private static final int REPLY_BREAK = 2; + + private final ImmutableList list; + + /** Creates a {@link Binder} to send a list of {@link Bundle Bundles} to another process. */ + public BundleListRetriever(List list) { + this.list = ImmutableList.copyOf(list); + } + + @Override + protected boolean onTransact(int code, Parcel data, @Nullable Parcel reply, int flags) + throws RemoteException { + if (code != FIRST_CALL_TRANSACTION) { + return super.onTransact(code, data, reply, flags); + } + + if (reply == null) { + return false; + } + + int count = list.size(); + int index = data.readInt(); + while (index < count && reply.dataSize() < SUGGESTED_MAX_IPC_SIZE) { + reply.writeInt(REPLY_CONTINUE); + reply.writeBundle(list.get(index)); + index++; + } + reply.writeInt(index < count ? REPLY_BREAK : REPLY_END_OF_LIST); + return true; + } + + /** + * Gets a list of {@link Bundle Bundles} from a {@link BundleListRetriever}. + * + * @param binder A binder interface backed by {@link BundleListRetriever}. + * @return The list of {@link Bundle Bundles}. + */ + public static ImmutableList getList(IBinder binder) { + ImmutableList.Builder builder = ImmutableList.builder(); + + int index = 0; + int replyCode = REPLY_CONTINUE; + + while (replyCode != REPLY_END_OF_LIST) { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + data.writeInt(index); + try { + binder.transact(FIRST_CALL_TRANSACTION, data, reply, /* flags= */ 0); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + while ((replyCode = reply.readInt()) == REPLY_CONTINUE) { + builder.add(checkNotNull(reply.readBundle())); + index++; + } + } finally { + reply.recycle(); + data.recycle(); + } + } + + return builder.build(); + } +} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Bundleable.java b/library/common/src/main/java/com/google/android/exoplayer2/Bundleable.java new file mode 100644 index 0000000000..29dae2e50e --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/Bundleable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.os.Bundle; + +/** + * Interface for classes whose instance can be stored in a {@link Bundle} by {@link #toBundle()} and + * can be restored from the {@link Bundle} by using the static {@code CREATOR} field that implements + * {@link Bundleable.Creator}. + * + *

For example, a {@link Bundleable} class {@code Foo} supports the following: + * + *

{@code
+ * Foo foo = ...;
+ * Bundle fooBundle = foo.toBundle();
+ * Foo restoredFoo = Foo.CREATOR.fromBundle(fooBundle);
+ * assertThat(restoredFoo).isEqualTo(foo);
+ * }
+ */ +public interface Bundleable { + + /** Returns a {@link Bundle} representing the information stored in this object. */ + Bundle toBundle(); + + /** Interface for the static {@code CREATOR} field of {@link Bundleable} classes. */ + interface Creator { + + /** + * Restores a {@link Bundleable} instance from a {@link Bundle} produced by {@link + * Bundleable#toBundle()}. + * + *

It guarantees the compatibility of {@link Bundle} representations produced by different + * versions of {@link Bundleable#toBundle()} by providing best default values for missing + * fields. It throws an exception if any essential fields are missing. + */ + T fromBundle(Bundle bundle); + } +} 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 1c2cc92362..eba6c0cfcd 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 @@ -31,9 +31,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.UUID; -/** - * Defines constants used by the library. - */ +/** Defines constants used by the library. */ @SuppressWarnings("InlinedApi") public final class C { @@ -553,8 +551,8 @@ public final class C { /** Video decoder output mode that renders 4:2:0 YUV planes directly to a surface. */ public static final int VIDEO_OUTPUT_MODE_SURFACE_YUV = 1; // LINT.ThenChange( - // ../../../../../../../../../extensions/av1/src/main/jni/gav1_jni.cc, - // ../../../../../../../../../extensions/vp9/src/main/jni/vpx_jni.cc + // ../../../../../../../../../../../media/libraries/decoder_av1/src/main/jni/gav1_jni.cc, + // ../../../../../../../../../../../media/libraries/decoder_vp9/src/main/jni/vpx_jni.cc // ) /** @@ -588,7 +586,15 @@ public final class C { * Indicates that the track should be selected if user preferences do not state otherwise. */ public static final int SELECTION_FLAG_DEFAULT = 1; - /** Indicates that the track must be displayed. Only applies to text tracks. */ + /** + * Indicates that the track should be selected if its language matches the language of the + * selected audio track and user preferences do not state otherwise. Only applies to text tracks. + * + *

Tracks with this flag generally provide translation for elements that don't match the + * declared language of the selected audio track (e.g. speech in an alien language). See Netflix's summary + * for more info. + */ public static final int SELECTION_FLAG_FORCED = 1 << 1; // 2 /** * Indicates that the player may choose to play the track in absence of an explicit user @@ -601,11 +607,11 @@ public final class C { /** * Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link - * #TYPE_HLS} or {@link #TYPE_OTHER}. + * #TYPE_HLS}, {@link #TYPE_RTSP} or {@link #TYPE_OTHER}. */ @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) + @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER}) public @interface ContentType {} /** * Value returned by {@link Util#inferContentType(String)} for DASH manifests. @@ -619,11 +625,13 @@ public final class C { * Value returned by {@link Util#inferContentType(String)} for HLS manifests. */ public static final int TYPE_HLS = 2; + /** Value returned by {@link Util#inferContentType(String)} for RTSP. */ + public static final int TYPE_RTSP = 3; /** * Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or - * Smooth Streaming manifests. + * Smooth Streaming manifests, or RTSP URIs. */ - public static final int TYPE_OTHER = 3; + public static final int TYPE_OTHER = 4; /** * A return value for methods where the end of an input was encountered. @@ -774,7 +782,7 @@ public final class C { */ public static final UUID PLAYREADY_UUID = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L); - /** @deprecated Use {@code Renderer.MSG_SET_SURFACE}. */ + /** @deprecated Use {@code Renderer.MSG_SET_VIDEO_OUTPUT}. */ @Deprecated public static final int MSG_SET_SURFACE = 1; /** @deprecated Use {@code Renderer.MSG_SET_VOLUME}. */ @@ -795,9 +803,6 @@ public final class C { /** @deprecated Use {@code Renderer.MSG_SET_CAMERA_MOTION_LISTENER}. */ @Deprecated public static final int MSG_SET_CAMERA_MOTION_LISTENER = 7; - /** @deprecated Use {@code Renderer.MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER}. */ - @Deprecated public static final int MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER = 8; - /** @deprecated Use {@code Renderer.MSG_CUSTOM_BASE}. */ @Deprecated public static final int MSG_CUSTOM_BASE = 10000; @@ -930,8 +935,8 @@ public final class C { /** * Network connection type. One of {@link #NETWORK_TYPE_UNKNOWN}, {@link #NETWORK_TYPE_OFFLINE}, * {@link #NETWORK_TYPE_WIFI}, {@link #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, {@link - * #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_5G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link - * #NETWORK_TYPE_ETHERNET} or {@link #NETWORK_TYPE_OTHER}. + * #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_5G_SA}, {@link #NETWORK_TYPE_5G_NSA}, {@link + * #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or {@link #NETWORK_TYPE_OTHER}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -942,7 +947,8 @@ public final class C { NETWORK_TYPE_2G, NETWORK_TYPE_3G, NETWORK_TYPE_4G, - NETWORK_TYPE_5G, + NETWORK_TYPE_5G_SA, + NETWORK_TYPE_5G_NSA, NETWORK_TYPE_CELLULAR_UNKNOWN, NETWORK_TYPE_ETHERNET, NETWORK_TYPE_OTHER @@ -960,8 +966,10 @@ public final class C { public static final int NETWORK_TYPE_3G = 4; /** Network type for a 4G cellular connection. */ public static final int NETWORK_TYPE_4G = 5; - /** Network type for a 5G cellular connection. */ - public static final int NETWORK_TYPE_5G = 9; + /** Network type for a 5G stand-alone (SA) cellular connection. */ + public static final int NETWORK_TYPE_5G_SA = 9; + /** Network type for a 5G non-stand-alone (NSA) cellular connection. */ + public static final int NETWORK_TYPE_5G_NSA = 10; /** * Network type for cellular connections which cannot be mapped to one of {@link * #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, or {@link #NETWORK_TYPE_4G}. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java b/library/common/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java similarity index 97% rename from library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java rename to library/common/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java index d3ec2cb9db..4e9b20acf3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java @@ -19,8 +19,8 @@ import com.google.android.exoplayer2.Player.RepeatMode; /** * Dispatches operations to the {@link Player}. - *

- * Implementations may choose to suppress (e.g. prevent playback from resuming if audio focus is + * + *

Implementations may choose to suppress (e.g. prevent playback from resuming if audio focus is * denied) or modify (e.g. change the seek position to prevent a user from seeking past a * non-skippable advert) operations. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java b/library/common/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java rename to library/common/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java index 95edfdf6f4..eb04eef9c6 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2; +import android.os.Bundle; +import android.os.RemoteException; import android.os.SystemClock; import android.text.TextUtils; import androidx.annotation.CheckResult; @@ -23,13 +25,14 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C.FormatSupport; import com.google.android.exoplayer2.source.MediaPeriodId; 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; import java.lang.annotation.RetentionPolicy; /** Thrown when a non locally recoverable playback failure occurs. */ -public final class ExoPlaybackException extends Exception { +public final class ExoPlaybackException extends Exception implements Bundleable { /** * The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} @@ -102,13 +105,9 @@ public final class ExoPlaybackException extends Exception { @Nullable public final MediaPeriodId mediaPeriodId; /** - * Whether the error may be recoverable. - * - *

This is only used internally by ExoPlayer to try to recover from some errors and should not - * be used by apps. - * - *

If the {@link #type} is {@link #TYPE_RENDERER}, it may be possible to recover from the error - * by disabling and re-enabling the renderers. + * If {@link #type} is {@link #TYPE_RENDERER}, this field indicates whether the error may be + * recoverable by disabling and re-enabling (but not resetting) the renderers. For other + * {@link Type types} this field will always be {@code false}. */ /* package */ final boolean isRecoverable; @@ -271,7 +270,7 @@ public final class ExoPlaybackException extends Exception { } private ExoPlaybackException( - @Nullable String message, + String message, @Nullable Throwable cause, @Type int type, @Nullable String rendererName, @@ -282,6 +281,7 @@ public final class ExoPlaybackException extends Exception { long timestampMs, boolean isRecoverable) { super(message, cause); + Assertions.checkArgument(!isRecoverable || type == TYPE_RENDERER); this.type = type; this.cause = cause; this.rendererName = rendererName; @@ -332,7 +332,7 @@ public final class ExoPlaybackException extends Exception { @CheckResult /* package */ ExoPlaybackException copyWithMediaPeriodId(@Nullable MediaPeriodId mediaPeriodId) { return new ExoPlaybackException( - getMessage(), + Util.castNonNull(getMessage()), cause, type, rendererName, @@ -344,7 +344,6 @@ public final class ExoPlaybackException extends Exception { isRecoverable); } - @Nullable private static String deriveMessage( @Type int type, @Nullable String customMessage, @@ -352,7 +351,7 @@ public final class ExoPlaybackException extends Exception { int rendererIndex, @Nullable Format rendererFormat, @FormatSupport int rendererFormatSupport) { - @Nullable String message; + String message; switch (type) { case TYPE_SOURCE: message = "Source error"; @@ -381,4 +380,136 @@ public final class ExoPlaybackException extends Exception { } return message; } + + // Bundleable implementation. + // TODO(b/145954241): Revisit bundling fields when this class is split for Player and ExoPlayer. + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_MESSAGE, + FIELD_TYPE, + FIELD_RENDERER_NAME, + FIELD_RENDERER_INDEX, + FIELD_RENDERER_FORMAT, + FIELD_RENDERER_FORMAT_SUPPORT, + FIELD_TIME_STAMP_MS, + FIELD_IS_RECOVERABLE, + FIELD_CAUSE_CLASS_NAME, + FIELD_CAUSE_MESSAGE + }) + private @interface FieldNumber {} + + private static final int FIELD_MESSAGE = 0; + private static final int FIELD_TYPE = 1; + private static final int FIELD_RENDERER_NAME = 2; + private static final int FIELD_RENDERER_INDEX = 3; + private static final int FIELD_RENDERER_FORMAT = 4; + private static final int FIELD_RENDERER_FORMAT_SUPPORT = 5; + private static final int FIELD_TIME_STAMP_MS = 6; + private static final int FIELD_IS_RECOVERABLE = 7; + private static final int FIELD_CAUSE_CLASS_NAME = 8; + private static final int FIELD_CAUSE_MESSAGE = 9; + + /** + * {@inheritDoc} + * + *

It omits the {@link #mediaPeriodId} field. The {@link #mediaPeriodId} of an instance + * restored by {@link #CREATOR} will always be {@code null}. + */ + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putString(keyForField(FIELD_MESSAGE), getMessage()); + bundle.putInt(keyForField(FIELD_TYPE), type); + bundle.putString(keyForField(FIELD_RENDERER_NAME), rendererName); + bundle.putInt(keyForField(FIELD_RENDERER_INDEX), rendererIndex); + bundle.putParcelable(keyForField(FIELD_RENDERER_FORMAT), rendererFormat); + bundle.putInt(keyForField(FIELD_RENDERER_FORMAT_SUPPORT), rendererFormatSupport); + bundle.putLong(keyForField(FIELD_TIME_STAMP_MS), timestampMs); + bundle.putBoolean(keyForField(FIELD_IS_RECOVERABLE), isRecoverable); + if (cause != null) { + bundle.putString(keyForField(FIELD_CAUSE_CLASS_NAME), cause.getClass().getName()); + bundle.putString(keyForField(FIELD_CAUSE_MESSAGE), cause.getMessage()); + } + return bundle; + } + + /** Object that can restore {@link ExoPlaybackException} from a {@link Bundle}. */ + public static final Creator CREATOR = ExoPlaybackException::fromBundle; + + private static ExoPlaybackException fromBundle(Bundle bundle) { + int type = bundle.getInt(keyForField(FIELD_TYPE), /* defaultValue= */ TYPE_UNEXPECTED); + @Nullable String rendererName = bundle.getString(keyForField(FIELD_RENDERER_NAME)); + int rendererIndex = + bundle.getInt(keyForField(FIELD_RENDERER_INDEX), /* defaultValue= */ C.INDEX_UNSET); + @Nullable Format rendererFormat = bundle.getParcelable(keyForField(FIELD_RENDERER_FORMAT)); + int rendererFormatSupport = + bundle.getInt( + keyForField(FIELD_RENDERER_FORMAT_SUPPORT), /* defaultValue= */ C.FORMAT_HANDLED); + long timestampMs = + bundle.getLong( + keyForField(FIELD_TIME_STAMP_MS), /* defaultValue= */ SystemClock.elapsedRealtime()); + boolean isRecoverable = + bundle.getBoolean(keyForField(FIELD_IS_RECOVERABLE), /* defaultValue= */ false); + @Nullable String message = bundle.getString(keyForField(FIELD_MESSAGE)); + if (message == null) { + message = + deriveMessage( + type, + /* customMessage= */ null, + rendererName, + rendererIndex, + rendererFormat, + rendererFormatSupport); + } + + @Nullable String causeClassName = bundle.getString(keyForField(FIELD_CAUSE_CLASS_NAME)); + @Nullable String causeMessage = bundle.getString(keyForField(FIELD_CAUSE_MESSAGE)); + @Nullable Throwable cause = null; + if (!TextUtils.isEmpty(causeClassName)) { + final Class clazz; + try { + clazz = + Class.forName( + causeClassName, + /* initialize= */ true, + ExoPlaybackException.class.getClassLoader()); + if (Throwable.class.isAssignableFrom(clazz)) { + cause = createThrowable(clazz, causeMessage); + } + } catch (Throwable e) { + // Intentionally catch Throwable to catch both Exception and Error. + cause = createRemoteException(causeMessage); + } + } + + return new ExoPlaybackException( + message, + cause, + type, + rendererName, + rendererIndex, + rendererFormat, + rendererFormatSupport, + /* mediaPeriodId= */ null, + timestampMs, + isRecoverable); + } + + // Creates a new {@link Throwable} with possibly @{code null} message. + @SuppressWarnings("nullness:argument.type.incompatible") + private static Throwable createThrowable(Class throwableClazz, @Nullable String message) + throws Exception { + return (Throwable) throwableClazz.getConstructor(String.class).newInstance(message); + } + + // Creates a new {@link RemoteException} with possibly {@code null} message. + @SuppressWarnings("nullness:argument.type.incompatible") + private static RemoteException createRemoteException(@Nullable String message) { + return new RemoteException(message); + } + + 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/ExoPlayerLibraryInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 0c003e6621..217b564820 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 @@ -18,9 +18,7 @@ package com.google.android.exoplayer2; import android.os.Build; import java.util.HashSet; -/** - * Information about the ExoPlayer library. - */ +/** Information about the ExoPlayer library. */ public final class ExoPlayerLibraryInfo { /** @@ -30,11 +28,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.13.3"; + public static final String VERSION = "2.14.0"; /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.3"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.14.0"; /** * The version of the library expressed as an integer, for example 1002003. @@ -44,7 +42,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 = 2013003; + public static final int VERSION_INT = 2014000; /** * The default user agent for requests made by the library. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/HeartRating.java b/library/common/src/main/java/com/google/android/exoplayer2/HeartRating.java new file mode 100644 index 0000000000..b656d0bd35 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/HeartRating.java @@ -0,0 +1,114 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; + +import android.os.Bundle; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import com.google.common.base.Objects; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A rating expressed as "heart" or "no heart". It can be used to indicate whether the content is a + * favorite. + */ +public final class HeartRating extends Rating { + + private final boolean rated; + private final boolean isHeart; + + /** Creates a unrated instance. */ + public HeartRating() { + rated = false; + isHeart = false; + } + + /** + * Creates a rated instance. + * + * @param isHeart {@code true} for "heart", {@code false} for "no heart". + */ + public HeartRating(boolean isHeart) { + rated = true; + this.isHeart = isHeart; + } + + @Override + public boolean isRated() { + return rated; + } + + /** Returns whether the rating is "heart". */ + public boolean isHeart() { + return isHeart; + } + + @Override + public int hashCode() { + return Objects.hashCode(rated, isHeart); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof HeartRating)) { + return false; + } + HeartRating other = (HeartRating) obj; + return isHeart == other.isHeart && rated == other.rated; + } + + // Bundleable implementation. + + @RatingType private static final int TYPE = RATING_TYPE_HEART; + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_HEART}) + private @interface FieldNumber {} + + private static final int FIELD_RATED = 1; + private static final int FIELD_IS_HEART = 2; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); + bundle.putBoolean(keyForField(FIELD_RATED), rated); + bundle.putBoolean(keyForField(FIELD_IS_HEART), isHeart); + return bundle; + } + + /** Object that can restore a {@link HeartRating} from a {@link Bundle}. */ + public static final Creator CREATOR = HeartRating::fromBundle; + + private static HeartRating fromBundle(Bundle bundle) { + checkArgument( + bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT) + == TYPE); + boolean isRated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false); + return isRated + ? new HeartRating(bundle.getBoolean(keyForField(FIELD_IS_HEART), /* defaultValue= */ false)) + : new HeartRating(); + } + + 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/IllegalSeekPositionException.java b/library/common/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java index baa1cf3f79..745e86983f 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java @@ -16,8 +16,8 @@ package com.google.android.exoplayer2; /** - * Thrown when an attempt is made to seek to a position that does not exist in the player's - * {@link Timeline}. + * Thrown when an attempt is made to seek to a position that does not exist in the player's {@link + * Timeline}. */ public final class IllegalSeekPositionException extends IllegalStateException { 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 184c065e8a..8e9ecb27d1 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 @@ -19,10 +19,15 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import android.net.Uri; +import android.os.Bundle; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -32,7 +37,7 @@ import java.util.Map; import java.util.UUID; /** Representation of a media item. */ -public final class MediaItem { +public final class MediaItem implements Bundleable { /** * Creates a {@link MediaItem} for the given URI. @@ -142,17 +147,17 @@ public final class MediaItem { } /** - * Sets the optional media ID which identifies the media item. If not specified, {@link #setUri} - * must be called and the string representation of {@link PlaybackProperties#uri} is used as the - * media ID. + * Sets the optional media ID which identifies the media item. + * + *

By default {@link #DEFAULT_MEDIA_ID} is used. */ - public Builder setMediaId(@Nullable String mediaId) { - this.mediaId = mediaId; + public Builder setMediaId(String mediaId) { + this.mediaId = checkNotNull(mediaId); return this; } /** - * Sets the optional URI. If not specified, {@link #setMediaId(String)} must be called. + * Sets the optional URI. * *

If {@code uri} is null or unset no {@link PlaybackProperties} object is created during * {@link #build()} and any other {@code Builder} methods that would populate {@link @@ -163,7 +168,7 @@ public final class MediaItem { } /** - * Sets the optional URI. If not specified, {@link #setMediaId(String)} must be called. + * Sets the optional URI. * *

If {@code uri} is null or unset no {@link PlaybackProperties} object is created during * {@link #build()} and any other {@code Builder} methods that would populate {@link @@ -582,10 +587,9 @@ public final class MediaItem { customCacheKey, subtitles, tag); - mediaId = mediaId != null ? mediaId : uri.toString(); } return new MediaItem( - checkNotNull(mediaId), + mediaId != null ? mediaId : DEFAULT_MEDIA_ID, new ClippingProperties( clipStartPositionMs, clipEndPositionMs, @@ -599,7 +603,7 @@ public final class MediaItem { liveMaxOffsetMs, liveMinPlaybackSpeed, liveMaxPlaybackSpeed), - mediaMetadata != null ? mediaMetadata : new MediaMetadata.Builder().build()); + mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY); } } @@ -836,7 +840,7 @@ public final class MediaItem { } /** Live playback configuration. */ - public static final class LiveConfiguration { + public static final class LiveConfiguration implements Bundleable { /** A live playback configuration with unset values. */ public static final LiveConfiguration UNSET = @@ -930,6 +934,53 @@ public final class MediaItem { result = 31 * result + (maxPlaybackSpeed != 0 ? Float.floatToIntBits(maxPlaybackSpeed) : 0); return result; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TARGET_OFFSET_MS, + FIELD_MIN_OFFSET_MS, + FIELD_MAX_OFFSET_MS, + FIELD_MIN_PLAYBACK_SPEED, + FIELD_MAX_PLAYBACK_SPEED + }) + private @interface FieldNumber {} + + private static final int FIELD_TARGET_OFFSET_MS = 0; + private static final int FIELD_MIN_OFFSET_MS = 1; + private static final int FIELD_MAX_OFFSET_MS = 2; + private static final int FIELD_MIN_PLAYBACK_SPEED = 3; + private static final int FIELD_MAX_PLAYBACK_SPEED = 4; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putLong(keyForField(FIELD_TARGET_OFFSET_MS), targetOffsetMs); + bundle.putLong(keyForField(FIELD_MIN_OFFSET_MS), minOffsetMs); + bundle.putLong(keyForField(FIELD_MAX_OFFSET_MS), maxOffsetMs); + bundle.putFloat(keyForField(FIELD_MIN_PLAYBACK_SPEED), minPlaybackSpeed); + bundle.putFloat(keyForField(FIELD_MAX_PLAYBACK_SPEED), maxPlaybackSpeed); + return bundle; + } + + /** Object that can restore {@link LiveConfiguration} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> + new LiveConfiguration( + bundle.getLong( + keyForField(FIELD_TARGET_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), + bundle.getLong(keyForField(FIELD_MIN_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), + bundle.getLong(keyForField(FIELD_MAX_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), + bundle.getFloat( + keyForField(FIELD_MIN_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET), + bundle.getFloat( + keyForField(FIELD_MAX_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET)); + + private static String keyForField(@LiveConfiguration.FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } /** Properties for a text track. */ @@ -1029,7 +1080,7 @@ public final class MediaItem { } /** Optionally clips the media item to a custom start and end position. */ - public static final class ClippingProperties { + public static final class ClippingProperties implements Bundleable { /** The start position in milliseconds. This is a value larger than or equal to zero. */ public final long startPositionMs; @@ -1095,8 +1146,59 @@ public final class MediaItem { result = 31 * result + (startsAtKeyFrame ? 1 : 0); return result; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_START_POSITION_MS, + FIELD_END_POSITION_MS, + FIELD_RELATIVE_TO_LIVE_WINDOW, + FIELD_RELATIVE_TO_DEFAULT_POSITION, + FIELD_STARTS_AT_KEY_FRAME + }) + private @interface FieldNumber {} + + private static final int FIELD_START_POSITION_MS = 0; + private static final int FIELD_END_POSITION_MS = 1; + private static final int FIELD_RELATIVE_TO_LIVE_WINDOW = 2; + private static final int FIELD_RELATIVE_TO_DEFAULT_POSITION = 3; + private static final int FIELD_STARTS_AT_KEY_FRAME = 4; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putLong(keyForField(FIELD_START_POSITION_MS), startPositionMs); + bundle.putLong(keyForField(FIELD_END_POSITION_MS), endPositionMs); + bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), relativeToLiveWindow); + bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), relativeToDefaultPosition); + bundle.putBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), startsAtKeyFrame); + return bundle; + } + + /** Object that can restore {@link ClippingProperties} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> + new ClippingProperties( + bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0), + bundle.getLong( + keyForField(FIELD_END_POSITION_MS), /* defaultValue= */ C.TIME_END_OF_SOURCE), + bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), false), + bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), false), + bundle.getBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), false)); + + private static String keyForField(@ClippingProperties.FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } + /** + * The default media ID that is used if the media ID is not explicitly set by {@link + * Builder#setMediaId(String)}. + */ + public static final String DEFAULT_MEDIA_ID = ""; + /** Identifies the media item. */ public final String mediaId; @@ -1157,4 +1259,87 @@ public final class MediaItem { result = 31 * result + mediaMetadata.hashCode(); return result; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_MEDIA_ID, + FIELD_LIVE_CONFIGURATION, + FIELD_MEDIA_METADATA, + FIELD_CLIPPING_PROPERTIES + }) + private @interface FieldNumber {} + + private static final int FIELD_MEDIA_ID = 0; + private static final int FIELD_LIVE_CONFIGURATION = 1; + private static final int FIELD_MEDIA_METADATA = 2; + private static final int FIELD_CLIPPING_PROPERTIES = 3; + + /** + * {@inheritDoc} + * + *

It omits the {@link #playbackProperties} field. The {@link #playbackProperties} of an + * instance restored by {@link #CREATOR} will always be {@code null}. + */ + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putString(keyForField(FIELD_MEDIA_ID), mediaId); + bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); + bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle()); + bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingProperties.toBundle()); + return bundle; + } + + /** + * Object that can restore {@link MediaItem} from a {@link Bundle}. + * + *

The {@link #playbackProperties} of a restored instance will always be {@code null}. + */ + public static final Creator CREATOR = MediaItem::fromBundle; + + private static MediaItem fromBundle(Bundle bundle) { + String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID), DEFAULT_MEDIA_ID)); + @Nullable + Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION)); + LiveConfiguration liveConfiguration; + if (liveConfigurationBundle == null) { + liveConfiguration = LiveConfiguration.UNSET; + } else { + liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle); + } + @Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA)); + MediaMetadata mediaMetadata; + if (mediaMetadataBundle == null) { + mediaMetadata = MediaMetadata.EMPTY; + } else { + mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle); + } + @Nullable + Bundle clippingPropertiesBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES)); + ClippingProperties clippingProperties; + if (clippingPropertiesBundle == null) { + clippingProperties = + new ClippingProperties( + /* startPositionMs= */ 0, + /* endPositionMs= */ C.TIME_END_OF_SOURCE, + /* relativeToLiveWindow= */ false, + /* relativeToDefaultPosition= */ false, + /* startsAtKeyFrame= */ false); + } else { + clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle); + } + return new MediaItem( + mediaId, + clippingProperties, + /* playbackProperties= */ null, + liveConfiguration, + mediaMetadata); + } + + 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/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 37fb8fcb0d..a346827911 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,34 +15,205 @@ */ package com.google.android.exoplayer2; +import android.net.Uri; +import android.os.Bundle; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Objects; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; -/** Metadata of the {@link MediaItem}. */ -public final class MediaMetadata { +/** + * Metadata of a {@link MediaItem}, playlist, or a combination of multiple sources of {@link + * Metadata}. + */ +public final class MediaMetadata implements Bundleable { /** A builder for {@link MediaMetadata} instances. */ public static final class Builder { - @Nullable private String title; + @Nullable private CharSequence title; + @Nullable private CharSequence artist; + @Nullable private CharSequence albumTitle; + @Nullable private CharSequence albumArtist; + @Nullable private CharSequence displayTitle; + @Nullable private CharSequence subtitle; + @Nullable private CharSequence description; + @Nullable private Uri mediaUri; + @Nullable private Rating userRating; + @Nullable private Rating overallRating; - /** Sets the optional title. */ - public Builder setTitle(@Nullable String title) { + public Builder() {} + + private Builder(MediaMetadata mediaMetadata) { + this.title = mediaMetadata.title; + this.artist = mediaMetadata.artist; + this.albumTitle = mediaMetadata.albumTitle; + this.albumArtist = mediaMetadata.albumArtist; + this.displayTitle = mediaMetadata.displayTitle; + this.subtitle = mediaMetadata.subtitle; + this.description = mediaMetadata.description; + this.mediaUri = mediaMetadata.mediaUri; + this.userRating = mediaMetadata.userRating; + this.overallRating = mediaMetadata.overallRating; + } + + /** Sets the title. */ + public Builder setTitle(@Nullable CharSequence title) { this.title = title; return this; } + /** Sets the artist. */ + public Builder setArtist(@Nullable CharSequence artist) { + this.artist = artist; + return this; + } + + /** Sets the album title. */ + public Builder setAlbumTitle(@Nullable CharSequence albumTitle) { + this.albumTitle = albumTitle; + return this; + } + + /** Sets the album artist. */ + public Builder setAlbumArtist(@Nullable CharSequence albumArtist) { + this.albumArtist = albumArtist; + return this; + } + + /** Sets the display title. */ + public Builder setDisplayTitle(@Nullable CharSequence displayTitle) { + this.displayTitle = displayTitle; + return this; + } + + /** + * Sets the subtitle. + * + *

This is the secondary title of the media, unrelated to closed captions. + */ + public Builder setSubtitle(@Nullable CharSequence subtitle) { + this.subtitle = subtitle; + return this; + } + + /** Sets the description. */ + public Builder setDescription(@Nullable CharSequence description) { + this.description = description; + return this; + } + + /** Sets the media {@link Uri}. */ + public Builder setMediaUri(@Nullable Uri mediaUri) { + this.mediaUri = mediaUri; + return this; + } + + /** Sets the user {@link Rating}. */ + public Builder setUserRating(@Nullable Rating userRating) { + this.userRating = userRating; + return this; + } + + /** Sets the overall {@link Rating}. */ + public Builder setOverallRating(@Nullable Rating overallRating) { + this.overallRating = overallRating; + return this; + } + + /** + * Sets all fields supported by the {@link Metadata.Entry entries} within the {@link Metadata}. + * + *

Fields are only set if the {@link Metadata.Entry} has an implementation for {@link + * Metadata.Entry#populateMediaMetadata(Builder)}. + * + *

In the event that multiple {@link Metadata.Entry} objects within the {@link Metadata} + * relate to the same {@link MediaMetadata} field, then the last one will be used. + */ + public Builder populateFromMetadata(Metadata metadata) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + entry.populateMediaMetadata(this); + } + return this; + } + + /** + * Sets all fields supported by the {@link Metadata.Entry entries} within the list of {@link + * Metadata}. + * + *

Fields are only set if the {@link Metadata.Entry} has an implementation for {@link + * Metadata.Entry#populateMediaMetadata(Builder)}. + * + *

In the event that multiple {@link Metadata.Entry} objects within any of the {@link + * Metadata} relate to the same {@link MediaMetadata} field, then the last one will be used. + */ + public Builder populateFromMetadata(List metadataList) { + for (int i = 0; i < metadataList.size(); i++) { + Metadata metadata = metadataList.get(i); + for (int j = 0; j < metadata.length(); j++) { + Metadata.Entry entry = metadata.get(j); + entry.populateMediaMetadata(this); + } + } + return this; + } + /** Returns a new {@link MediaMetadata} instance with the current builder values. */ public MediaMetadata build() { - return new MediaMetadata(title); + return new MediaMetadata(/* builder= */ this); } } - /** Optional title. */ - @Nullable public final String title; + /** Empty {@link MediaMetadata}. */ + public static final MediaMetadata EMPTY = new MediaMetadata.Builder().build(); - private MediaMetadata(@Nullable String title) { - this.title = title; + /** Optional title. */ + @Nullable public final CharSequence title; + /** Optional artist. */ + @Nullable public final CharSequence artist; + /** Optional album title. */ + @Nullable public final CharSequence albumTitle; + /** Optional album artist. */ + @Nullable public final CharSequence albumArtist; + /** Optional display title. */ + @Nullable public final CharSequence displayTitle; + /** + * Optional subtitle. + * + *

This is the secondary title of the media, unrelated to closed captions. + */ + @Nullable public final CharSequence subtitle; + /** Optional description. */ + @Nullable public final CharSequence description; + /** Optional media {@link Uri}. */ + @Nullable public final Uri mediaUri; + /** Optional user {@link Rating}. */ + @Nullable public final Rating userRating; + /** Optional overall {@link Rating}. */ + @Nullable public final Rating overallRating; + + private MediaMetadata(Builder builder) { + this.title = builder.title; + this.artist = builder.artist; + this.albumTitle = builder.albumTitle; + this.albumArtist = builder.albumArtist; + this.displayTitle = builder.displayTitle; + this.subtitle = builder.subtitle; + this.description = builder.description; + this.mediaUri = builder.mediaUri; + this.userRating = builder.userRating; + this.overallRating = builder.overallRating; + } + + /** Returns a new {@link Builder} instance with the current {@link MediaMetadata} fields. */ + public Builder buildUpon() { + return new Builder(/* mediaMetadata= */ this); } @Override @@ -53,13 +224,117 @@ public final class MediaMetadata { if (obj == null || getClass() != obj.getClass()) { return false; } - MediaMetadata other = (MediaMetadata) obj; - - return Util.areEqual(title, other.title); + MediaMetadata that = (MediaMetadata) obj; + return Util.areEqual(title, that.title) + && Util.areEqual(artist, that.artist) + && Util.areEqual(albumTitle, that.albumTitle) + && Util.areEqual(albumArtist, that.albumArtist) + && Util.areEqual(displayTitle, that.displayTitle) + && Util.areEqual(subtitle, that.subtitle) + && Util.areEqual(description, that.description) + && Util.areEqual(mediaUri, that.mediaUri) + && Util.areEqual(userRating, that.userRating) + && Util.areEqual(overallRating, that.overallRating); } @Override public int hashCode() { - return title == null ? 0 : title.hashCode(); + return Objects.hashCode( + title, + artist, + albumTitle, + albumArtist, + displayTitle, + subtitle, + description, + mediaUri, + userRating, + overallRating); + } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TITLE, + FIELD_ARTIST, + FIELD_ALBUM_TITLE, + FIELD_ALBUM_ARTIST, + FIELD_DISPLAY_TITLE, + FIELD_SUBTITLE, + FIELD_DESCRIPTION, + FIELD_MEDIA_URI, + FIELD_USER_RATING, + FIELD_OVERALL_RATING, + }) + private @interface FieldNumber {} + + private static final int FIELD_TITLE = 0; + private static final int FIELD_ARTIST = 1; + private static final int FIELD_ALBUM_TITLE = 2; + private static final int FIELD_ALBUM_ARTIST = 3; + private static final int FIELD_DISPLAY_TITLE = 4; + private static final int FIELD_SUBTITLE = 5; + private static final int FIELD_DESCRIPTION = 6; + private static final int FIELD_MEDIA_URI = 7; + private static final int FIELD_USER_RATING = 8; + private static final int FIELD_OVERALL_RATING = 9; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putCharSequence(keyForField(FIELD_TITLE), title); + bundle.putCharSequence(keyForField(FIELD_ARTIST), artist); + bundle.putCharSequence(keyForField(FIELD_ALBUM_TITLE), albumTitle); + bundle.putCharSequence(keyForField(FIELD_ALBUM_ARTIST), albumArtist); + bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle); + bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle); + bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description); + bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri); + + if (userRating != null) { + bundle.putBundle(keyForField(FIELD_USER_RATING), userRating.toBundle()); + } + if (overallRating != null) { + bundle.putBundle(keyForField(FIELD_OVERALL_RATING), overallRating.toBundle()); + } + + return bundle; + } + + /** Object that can restore {@link MediaMetadata} from a {@link Bundle}. */ + public static final Creator CREATOR = MediaMetadata::fromBundle; + + private static MediaMetadata fromBundle(Bundle bundle) { + Builder builder = new Builder(); + builder + .setTitle(bundle.getCharSequence(keyForField(FIELD_TITLE))) + .setArtist(bundle.getCharSequence(keyForField(FIELD_ARTIST))) + .setAlbumTitle(bundle.getCharSequence(keyForField(FIELD_ALBUM_TITLE))) + .setAlbumArtist(bundle.getCharSequence(keyForField(FIELD_ALBUM_ARTIST))) + .setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE))) + .setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE))) + .setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION))) + .setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI))); + + if (bundle.containsKey(keyForField(FIELD_USER_RATING))) { + @Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_USER_RATING)); + if (fieldBundle != null) { + builder.setUserRating(Rating.CREATOR.fromBundle(fieldBundle)); + } + } + if (bundle.containsKey(keyForField(FIELD_OVERALL_RATING))) { + @Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_OVERALL_RATING)); + if (fieldBundle != null) { + builder.setUserRating(Rating.CREATOR.fromBundle(fieldBundle)); + } + } + + return 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/ParserException.java b/library/common/src/main/java/com/google/android/exoplayer2/ParserException.java index e0cae0cf3a..716eceda94 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ParserException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ParserException.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2; import java.io.IOException; -/** - * Thrown when an error occurs parsing media data and metadata. - */ +/** Thrown when an error occurs parsing media data and metadata. */ public class ParserException extends IOException { public ParserException() { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PercentageRating.java b/library/common/src/main/java/com/google/android/exoplayer2/PercentageRating.java new file mode 100644 index 0000000000..1953e7e1d1 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/PercentageRating.java @@ -0,0 +1,108 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; + +import android.os.Bundle; +import androidx.annotation.FloatRange; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import com.google.common.base.Objects; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** A rating expressed as a percentage. */ +public final class PercentageRating extends Rating { + + private final float percent; + + /** Creates a unrated instance. */ + public PercentageRating() { + percent = RATING_UNSET; + } + + /** + * Creates a rated instance with the given percentage. + * + * @param percent The percentage value of the rating. + */ + public PercentageRating(@FloatRange(from = 0, to = 100) float percent) { + checkArgument(percent >= 0.0f && percent <= 100.0f, "percent must be in the range of [0, 100]"); + this.percent = percent; + } + + @Override + public boolean isRated() { + return percent != RATING_UNSET; + } + + /** + * Returns the percent value of this rating. Will be within the range {@code [0f, 100f]}, or + * {@link #RATING_UNSET} if unrated. + */ + public float getPercent() { + return percent; + } + + @Override + public int hashCode() { + return Objects.hashCode(percent); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof PercentageRating)) { + return false; + } + return percent == ((PercentageRating) obj).percent; + } + + // Bundleable implementation. + + @RatingType private static final int TYPE = RATING_TYPE_PERCENTAGE; + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_RATING_TYPE, FIELD_PERCENT}) + private @interface FieldNumber {} + + private static final int FIELD_PERCENT = 1; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); + bundle.putFloat(keyForField(FIELD_PERCENT), percent); + return bundle; + } + + /** Object that can restore a {@link PercentageRating} from a {@link Bundle}. */ + public static final Creator CREATOR = PercentageRating::fromBundle; + + private static PercentageRating fromBundle(Bundle bundle) { + checkArgument( + bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT) + == TYPE); + float percent = bundle.getFloat(keyForField(FIELD_PERCENT), /* defaultValue= */ RATING_UNSET); + return percent == RATING_UNSET ? new PercentageRating() : new PercentageRating(percent); + } + + 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/PlaybackParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java index ff4f262812..806bf11064 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java @@ -15,13 +15,18 @@ */ package com.google.android.exoplayer2; +import android.os.Bundle; import androidx.annotation.CheckResult; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** Parameters that apply to playback, including speed setting. */ -public final class PlaybackParameters { +public final class PlaybackParameters implements Bundleable { /** The default playback parameters: real-time playback with no silence skipping. */ public static final PlaybackParameters DEFAULT = new PlaybackParameters(/* speed= */ 1f); @@ -106,4 +111,34 @@ public final class PlaybackParameters { public String toString() { return Util.formatInvariant("PlaybackParameters(speed=%.2f, pitch=%.2f)", speed, pitch); } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_SPEED, FIELD_PITCH}) + private @interface FieldNumber {} + + private static final int FIELD_SPEED = 0; + private static final int FIELD_PITCH = 1; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putFloat(keyForField(FIELD_SPEED), speed); + bundle.putFloat(keyForField(FIELD_PITCH), pitch); + return bundle; + } + + /** Object that can restore {@link PlaybackParameters} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + float speed = bundle.getFloat(keyForField(FIELD_SPEED), /* defaultValue= */ 1f); + float pitch = bundle.getFloat(keyForField(FIELD_PITCH), /* defaultValue= */ 1f); + return new PlaybackParameters(speed, pitch); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java rename to library/common/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java 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 3d58521357..50739cfc24 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,7 +15,7 @@ */ package com.google.android.exoplayer2; -import android.content.Context; +import android.os.Bundle; import android.os.Looper; import android.view.Surface; import android.view.SurfaceHolder; @@ -25,7 +25,6 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioListener; -import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.device.DeviceInfo; import com.google.android.exoplayer2.device.DeviceListener; import com.google.android.exoplayer2.metadata.Metadata; @@ -34,11 +33,11 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.util.MutableFlags; +import com.google.android.exoplayer2.util.ExoFlags; import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; -import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import com.google.android.exoplayer2.video.VideoSize; +import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -48,6 +47,11 @@ import java.util.List; * 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. * + *

This interface includes some convenience methods that can be implemented by calling other + * methods in the interface. {@link BasePlayer} implements these convenience methods so inheriting + * {@link BasePlayer} is recommended when implementing the interface so that only the minimal set of + * required methods can be implemented. + * *

Some important properties of media players that implement this interface are: * *

    @@ -55,326 +59,13 @@ import java.util.List; * which can be obtained by calling {@link #getCurrentTimeline()}. *
  • They can provide a {@link TrackGroupArray} defining the currently available tracks, which * can be obtained by calling {@link #getCurrentTrackGroups()}. - *
  • They contain a number of renderers, each of which is able to render tracks of a single type - * (e.g. audio, video or text). The number of renderers and their respective track types can - * be obtained by calling {@link #getRendererCount()} and {@link #getRendererType(int)}. *
  • They can provide a {@link TrackSelectionArray} defining which of the currently available - * tracks are selected to be rendered by each renderer. This can be obtained by calling {@link + * tracks are selected to be rendered. This can be obtained by calling {@link * #getCurrentTrackSelections()}}. *
*/ public interface Player { - /** The audio component of a {@link Player}. */ - interface AudioComponent { - - /** - * Adds a listener to receive audio events. - * - * @param listener The listener to register. - */ - void addAudioListener(AudioListener listener); - - /** - * Removes a listener of audio events. - * - * @param listener The listener to unregister. - */ - void removeAudioListener(AudioListener listener); - - /** - * 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 {@link Util#getStreamTypeForAudioUsage(int)}. - * - *

If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link - * C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link - * IllegalArgumentException}. - * - * @param audioAttributes The attributes to use for audio playback. - * @param handleAudioFocus True if the player should handle audio focus, false otherwise. - */ - void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus); - - /** Returns the attributes for audio playback. */ - AudioAttributes getAudioAttributes(); - - /** - * Sets the ID of the audio session to attach to the underlying {@link - * android.media.AudioTrack}. - * - *

The audio session ID can be generated using {@link C#generateAudioSessionIdV21(Context)} - * for API 21+. - * - * @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} if it should - * be generated by the framework. - */ - void setAudioSessionId(int audioSessionId); - - /** Returns the audio session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} if not set. */ - int getAudioSessionId(); - - /** Sets information on an auxiliary audio effect to attach to the underlying audio track. */ - void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); - - /** Detaches any previously attached auxiliary audio effect from the underlying audio track. */ - void clearAuxEffectInfo(); - - /** - * Sets the audio volume, with 0 being silence and 1 being unity gain. - * - * @param audioVolume The audio volume. - */ - void setVolume(float audioVolume); - - /** Returns the audio volume, with 0 being silence and 1 being unity gain. */ - float getVolume(); - - /** - * Sets whether skipping silences in the audio stream is enabled. - * - * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled. - */ - void setSkipSilenceEnabled(boolean skipSilenceEnabled); - - /** Returns whether skipping silences in the audio stream is enabled. */ - boolean getSkipSilenceEnabled(); - } - - /** The video component of a {@link Player}. */ - interface VideoComponent { - - /** - * Sets the {@link C.VideoScalingMode}. - * - * @param videoScalingMode The {@link C.VideoScalingMode}. - */ - void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode); - - /** Returns the {@link C.VideoScalingMode}. */ - @C.VideoScalingMode - int getVideoScalingMode(); - - /** - * Adds a listener to receive video events. - * - * @param listener The listener to register. - */ - void addVideoListener(VideoListener listener); - - /** - * Removes a listener of video events. - * - * @param listener The listener to unregister. - */ - void removeVideoListener(VideoListener listener); - - /** - * Sets a listener to receive video frame metadata events. - * - *

This method is intended to be called by the same component that sets the {@link Surface} - * onto which video will be rendered. If using ExoPlayer's standard UI components, this method - * should not be called directly from application code. - * - * @param listener The listener. - */ - void setVideoFrameMetadataListener(VideoFrameMetadataListener listener); - - /** - * Clears the listener which receives video frame metadata events if it matches the one passed. - * Else does nothing. - * - * @param listener The listener to clear. - */ - void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener); - - /** - * Sets a listener of camera motion events. - * - * @param listener The listener. - */ - void setCameraMotionListener(CameraMotionListener listener); - - /** - * Clears the listener which receives camera motion events if it matches the one passed. Else - * does nothing. - * - * @param listener The listener to clear. - */ - void clearCameraMotionListener(CameraMotionListener listener); - - /** - * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} - * currently set on the player. - */ - void clearVideoSurface(); - - /** - * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. - * Else does nothing. - * - * @param surface The surface to clear. - */ - void clearVideoSurface(@Nullable Surface surface); - - /** - * Sets the {@link 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 {@code - * setVideoSurface(null)} if the surface is destroyed. - * - *

If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link - * SurfaceHolder} then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, {@link - * #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} rather - * than this method, since passing the holder allows the player to track the lifecycle of the - * surface automatically. - * - * @param surface The {@link Surface}. - */ - void setVideoSurface(@Nullable Surface surface); - - /** - * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be - * rendered. The player will track the lifecycle of the surface automatically. - * - * @param surfaceHolder The surface holder. - */ - void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); - - /** - * Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being - * rendered if it matches the one passed. Else does nothing. - * - * @param surfaceHolder The surface holder to clear. - */ - void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); - - /** - * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the - * lifecycle of the surface automatically. - * - * @param surfaceView The surface view. - */ - void setVideoSurfaceView(@Nullable SurfaceView surfaceView); - - /** - * Clears the {@link SurfaceView} onto which video is being rendered if it matches the one - * passed. Else does nothing. - * - * @param surfaceView The texture view to clear. - */ - void clearVideoSurfaceView(@Nullable SurfaceView surfaceView); - - /** - * Sets the {@link TextureView} onto which video will be rendered. The player will track the - * lifecycle of the surface automatically. - * - * @param textureView The texture view. - */ - void setVideoTextureView(@Nullable TextureView textureView); - - /** - * Clears the {@link TextureView} onto which video is being rendered if it matches the one - * passed. Else does nothing. - * - * @param textureView The texture view to clear. - */ - void clearVideoTextureView(@Nullable TextureView textureView); - } - - /** The text component of a {@link Player}. */ - interface TextComponent { - - /** - * Registers an output to receive text events. - * - * @param listener The output to register. - */ - void addTextOutput(TextOutput listener); - - /** - * Removes a text output. - * - * @param listener The output to remove. - */ - void removeTextOutput(TextOutput listener); - - /** Returns the current {@link Cue Cues}. This list may be empty. */ - List getCurrentCues(); - } - - /** The metadata component of a {@link Player}. */ - interface MetadataComponent { - - /** - * Adds a {@link MetadataOutput} to receive metadata. - * - * @param output The output to register. - */ - void addMetadataOutput(MetadataOutput output); - - /** - * Removes a {@link MetadataOutput}. - * - * @param output The output to remove. - */ - void removeMetadataOutput(MetadataOutput output); - } - - /** The device component of a {@link Player}. */ - interface DeviceComponent { - - /** Adds a listener to receive device events. */ - void addDeviceListener(DeviceListener listener); - - /** Removes a listener of device events. */ - void removeDeviceListener(DeviceListener listener); - - /** Gets the device information. */ - DeviceInfo getDeviceInfo(); - - /** - * Gets the current volume of the device. - * - *

For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume - * returned by this method varies according to the current {@link C.StreamType stream type}. The - * stream type is determined by {@link AudioAttributes#usage} which can be converted to stream - * type with {@link Util#getStreamTypeForAudioUsage(int)}. The audio attributes can be set to - * the player by calling {@link AudioComponent#setAudioAttributes}. - * - *

For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of - * the remote device is returned. - */ - int getDeviceVolume(); - - /** Gets whether the device is muted or not. */ - boolean isDeviceMuted(); - - /** - * Sets the volume of the device. - * - * @param volume The volume to set. - */ - void setDeviceVolume(int volume); - - /** Increases the volume of the device. */ - void increaseDeviceVolume(); - - /** Decreases the volume of the device. */ - void decreaseDeviceVolume(); - - /** Sets the mute state of the device. */ - void setDeviceMuted(boolean muted); - } - /** * Listener of changes in player state. * @@ -383,16 +74,18 @@ public interface Player { *

Listeners can choose to implement individual events (e.g. {@link * #onIsPlayingChanged(boolean)}) or {@link #onEvents(Player, Events)}, which is called after one * or more events occurred together. + * + * @deprecated Use {@link Player.Listener}. */ + @Deprecated interface EventListener { /** * Called when the timeline has been refreshed. * - *

Note that if the timeline has changed then a position discontinuity may also have - * occurred. For example, the current period index may have changed as a result of periods being - * added or removed from the timeline. This will not be reported via a separate call to - * {@link #onPositionDiscontinuity(int)}. + *

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 {@link + * #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} callback will be triggered. * *

{@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. @@ -400,30 +93,9 @@ public interface Player { * @param timeline The latest timeline. Never null, but may be empty. * @param reason The {@link TimelineChangeReason} responsible for this timeline change. */ - @SuppressWarnings("deprecation") - default void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) { - Object manifest = null; - if (timeline.getWindowCount() == 1) { - // Legacy behavior was to report the manifest for single window timelines only. - Timeline.Window window = new Timeline.Window(); - manifest = timeline.getWindow(0, window).manifest; - } - // Call deprecated version. - onTimelineChanged(timeline, manifest, reason); - } + default void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) {} /** - * Called when the timeline and/or manifest has been refreshed. - * - *

Note that if the timeline has changed then a position discontinuity may also have - * occurred. For example, the current period index may have changed as a result of periods being - * added or removed from the timeline. This will not be reported via a separate call to - * {@link #onPositionDiscontinuity(int)}. - * - * @param timeline The latest timeline. Never null, but may be empty. - * @param manifest The latest manifest in case the timeline has a single window only. Always - * null if the timeline has more than a single window. - * @param reason The {@link TimelineChangeReason} responsible for this timeline change. * @deprecated Use {@link #onTimelineChanged(Timeline, int)} instead. The manifest can be * accessed by using {@link #getCurrentManifest()} or {@code timeline.getWindow(windowIndex, * window).manifest} for a given window index. @@ -455,8 +127,10 @@ public interface Player { * other events that happen in the same {@link Looper} message queue iteration. * * @param trackGroups The available tracks. Never null, but may be of length zero. - * @param trackSelections The track selections for each renderer. Never null and always of - * length {@link #getRendererCount()}, but may contain null elements. + * @param 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. */ default void onTracksChanged( TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} @@ -480,6 +154,20 @@ public interface Player { */ default void onStaticMetadataChanged(List metadataList) {} + /** + * Called when the combined {@link MediaMetadata} changes. + * + *

The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} + * and the static and dynamic metadata sourced from {@link #onStaticMetadataChanged(List)} and + * {@link MetadataOutput#onMetadata(Metadata)}. + * + *

{@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. + * + * @param mediaMetadata The combined {@link MediaMetadata}. + */ + default void onMediaMetadataChanged(MediaMetadata mediaMetadata) {} + /** * Called when the player starts or stops loading the source. * @@ -488,15 +176,23 @@ public interface Player { * * @param isLoading Whether the source is currently being loaded. */ - @SuppressWarnings("deprecation") - default void onIsLoadingChanged(boolean isLoading) { - onLoadingChanged(isLoading); - } + default void onIsLoadingChanged(boolean isLoading) {} /** @deprecated Use {@link #onIsLoadingChanged(boolean)} instead. */ @Deprecated default void onLoadingChanged(boolean isLoading) {} + /** + * Called when the value returned from {@link #isCommandAvailable(int)} changes for at least one + * {@link Command}. + * + *

{@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. + * + * @param availableCommands The available {@link Commands}. + */ + default void onAvailableCommandsChanged(Commands availableCommands) {} + /** * @deprecated Use {@link #onPlaybackStateChanged(int)} and {@link * #onPlayWhenReadyChanged(boolean, int)} instead. @@ -580,21 +276,27 @@ public interface Player { default void onPlayerError(ExoPlaybackException error) {} /** - * Called when a position discontinuity occurs without a change to the timeline. A position - * discontinuity occurs when the current window or period index changes (as a result of playback - * transitioning from one period in the timeline to the next), or when the playback position - * jumps within the period currently being played (as a result of a seek being performed, or - * when the source introduces a discontinuity internally). + * @deprecated Use {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} instead. + */ + @Deprecated + default void onPositionDiscontinuity(@DiscontinuityReason int reason) {} + + /** + * Called when a position discontinuity occurs. * - *

When a position discontinuity occurs as a result of a change to the timeline this method - * is not called. {@link #onTimelineChanged(Timeline, int)} is called in this case. + *

A position discontinuity occurs when the playing period changes, the playback position + * jumps within the period currently being played, or when the playing period has been skipped + * or removed. * *

{@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. * + * @param oldPosition The position before the discontinuity. + * @param newPosition The position after the discontinuity. * @param reason The {@link DiscontinuityReason} responsible for the discontinuity. */ - default void onPositionDiscontinuity(@DiscontinuityReason int reason) {} + default void onPositionDiscontinuity( + PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {} /** * Called when the current playback parameters change. The playback parameters may change due to @@ -611,29 +313,12 @@ public interface Player { /** * @deprecated Seeks are processed without delay. Listen to {@link - * #onPositionDiscontinuity(int)} with reason {@link #DISCONTINUITY_REASON_SEEK} instead. + * #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} with reason {@link + * #DISCONTINUITY_REASON_SEEK} instead. */ @Deprecated default void onSeekProcessed() {} - /** - * Called when the player has started or stopped offload scheduling. - * - *

If using ExoPlayer, this is done by calling {@code - * ExoPlayer#experimentalSetOffloadSchedulingEnabled(boolean)}. - * - *

This method is experimental, and will be renamed or removed in a future release. - */ - // TODO(b/172315872) Move this method in a new ExoPlayer.EventListener. - default void onExperimentalOffloadSchedulingEnabledChanged(boolean offloadSchedulingEnabled) {} - - /** - * Called when the player has started or finished sleeping for offload. - * - *

This method is experimental, and will be renamed or removed in a future release. - */ - default void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {} - /** * Called when one or more player states changed. * @@ -665,44 +350,28 @@ public interface Player { default void onEvents(Player player, Events events) {} } - /** - * @deprecated Use {@link EventListener} interface directly for selective overrides as all methods - * are implemented as no-op default methods. - */ - @Deprecated - abstract class DefaultEventListener implements EventListener { - - @Override - public void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) { - Object manifest = null; - if (timeline.getWindowCount() == 1) { - // Legacy behavior was to report the manifest for single window timelines only. - Timeline.Window window = new Timeline.Window(); - manifest = timeline.getWindow(0, window).manifest; - } - // Call deprecated version. - onTimelineChanged(timeline, manifest, reason); - } - - @Override - public void onTimelineChanged( - Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) { - // Do nothing. - } - } - /** A set of {@link EventFlags}. */ - final class Events extends MutableFlags { + final class Events { + + private final ExoFlags flags; + + /** + * Creates an instance. + * + * @param flags The {@link ExoFlags} containing the {@link EventFlags} in the set. + */ + public Events(ExoFlags flags) { + this.flags = flags; + } + /** * Returns whether the given event occurred. * * @param event The {@link EventFlags event}. * @return Whether the event occurred. */ - @Override public boolean contains(@EventFlags int event) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.contains(event); + return flags.contains(event); } /** @@ -711,10 +380,13 @@ public interface Player { * @param events The {@link EventFlags events}. * @return Whether any of the events occurred. */ - @Override public boolean containsAny(@EventFlags int... events) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.containsAny(events); + return flags.containsAny(events); + } + + /** Returns the number of events in the set. */ + public int size() { + return flags.size(); } /** @@ -725,15 +397,317 @@ public interface Player { * * @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive). * @return The {@link EventFlags event} at the given index. + * @throws IndexOutOfBoundsException If index is outside the allowed range. */ - @Override @EventFlags public int get(int index) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.get(index); + return flags.get(index); } } + /** Position info describing a playback position involved in a discontinuity. */ + final class PositionInfo implements Bundleable { + + /** + * The UID of the window, or {@code null}, if the timeline is {@link Timeline#isEmpty() empty}. + */ + @Nullable public final Object windowUid; + /** The window index. */ + public final int windowIndex; + /** + * The UID of the period, or {@code null}, if the timeline is {@link Timeline#isEmpty() empty}. + */ + @Nullable public final Object periodUid; + /** The period index. */ + public final int periodIndex; + /** The playback position, in milliseconds. */ + public final long positionMs; + /** + * The content position, in milliseconds. + * + *

If {@link #adGroupIndex} is {@link C#INDEX_UNSET}, this is the same as {@link + * #positionMs}. + */ + public final long contentPositionMs; + /** + * The ad group index if the playback position is within an ad, {@link C#INDEX_UNSET} otherwise. + */ + public final int adGroupIndex; + /** + * The index of the ad within the ad group if the playback position is within an ad, {@link + * C#INDEX_UNSET} otherwise. + */ + public final int adIndexInAdGroup; + + /** Creates an instance. */ + public PositionInfo( + @Nullable Object windowUid, + int windowIndex, + @Nullable Object periodUid, + int periodIndex, + long positionMs, + long contentPositionMs, + int adGroupIndex, + int adIndexInAdGroup) { + this.windowUid = windowUid; + this.windowIndex = windowIndex; + this.periodUid = periodUid; + this.periodIndex = periodIndex; + this.positionMs = positionMs; + this.contentPositionMs = contentPositionMs; + this.adGroupIndex = adGroupIndex; + this.adIndexInAdGroup = adIndexInAdGroup; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PositionInfo that = (PositionInfo) o; + return windowIndex == that.windowIndex + && periodIndex == that.periodIndex + && positionMs == that.positionMs + && contentPositionMs == that.contentPositionMs + && adGroupIndex == that.adGroupIndex + && adIndexInAdGroup == that.adIndexInAdGroup + && Objects.equal(windowUid, that.windowUid) + && Objects.equal(periodUid, that.periodUid); + } + + @Override + public int hashCode() { + return Objects.hashCode( + windowUid, + windowIndex, + periodUid, + periodIndex, + windowIndex, + positionMs, + contentPositionMs, + adGroupIndex, + adIndexInAdGroup); + } + + // Bundleable implementation. + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_WINDOW_INDEX, + FIELD_PERIOD_INDEX, + FIELD_POSITION_MS, + FIELD_CONTENT_POSITION_MS, + FIELD_AD_GROUP_INDEX, + FIELD_AD_INDEX_IN_AD_GROUP + }) + private @interface FieldNumber {} + + private static final int FIELD_WINDOW_INDEX = 0; + private static final int FIELD_PERIOD_INDEX = 1; + private static final int FIELD_POSITION_MS = 2; + private static final int FIELD_CONTENT_POSITION_MS = 3; + private static final int FIELD_AD_GROUP_INDEX = 4; + private static final int FIELD_AD_INDEX_IN_AD_GROUP = 5; + + /** + * {@inheritDoc} + * + *

It omits the {@link #windowUid} and {@link #periodUid} fields. The {@link #windowUid} and + * {@link #periodUid} of an instance restored by {@link #CREATOR} will always be {@code null}. + */ + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex); + bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex); + bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs); + bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs); + bundle.putInt(keyForField(FIELD_AD_GROUP_INDEX), adGroupIndex); + bundle.putInt(keyForField(FIELD_AD_INDEX_IN_AD_GROUP), adIndexInAdGroup); + return bundle; + } + + /** Object that can restore {@link PositionInfo} from a {@link Bundle}. */ + public static final Creator CREATOR = PositionInfo::fromBundle; + + private static PositionInfo fromBundle(Bundle bundle) { + int windowIndex = + bundle.getInt(keyForField(FIELD_WINDOW_INDEX), /* defaultValue= */ C.INDEX_UNSET); + int periodIndex = + bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET); + long positionMs = + bundle.getLong(keyForField(FIELD_POSITION_MS), /* defaultValue= */ C.TIME_UNSET); + long contentPositionMs = + bundle.getLong(keyForField(FIELD_CONTENT_POSITION_MS), /* defaultValue= */ C.TIME_UNSET); + int adGroupIndex = + bundle.getInt(keyForField(FIELD_AD_GROUP_INDEX), /* defaultValue= */ C.INDEX_UNSET); + int adIndexInAdGroup = + bundle.getInt(keyForField(FIELD_AD_INDEX_IN_AD_GROUP), /* defaultValue= */ C.INDEX_UNSET); + return new PositionInfo( + /* windowUid= */ null, + windowIndex, + /* periodUid= */ null, + periodIndex, + positionMs, + contentPositionMs, + adGroupIndex, + adIndexInAdGroup); + } + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } + } + + /** + * A set of {@link Command commands}. + * + *

Instances are immutable. + */ + final class Commands { + + /** A builder for {@link Commands} instances. */ + public static final class Builder { + + private final ExoFlags.Builder flagsBuilder; + + /** Creates a builder. */ + public Builder() { + flagsBuilder = new ExoFlags.Builder(); + } + + /** + * Adds a {@link Command}. + * + * @param command A {@link Command}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder add(@Command int command) { + flagsBuilder.add(command); + return this; + } + + /** + * Adds a {@link Command} if the provided condition is true. Does nothing otherwise. + * + * @param command A {@link Command}. + * @param condition A condition. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder addIf(@Command int command, boolean condition) { + flagsBuilder.addIf(command, condition); + return this; + } + + /** + * Adds {@link Command commands}. + * + * @param commands The {@link Command commands} to add. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder addAll(@Command int... commands) { + flagsBuilder.addAll(commands); + return this; + } + + /** + * Adds {@link Commands}. + * + * @param commands The set of {@link Command commands} to add. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder addAll(Commands commands) { + flagsBuilder.addAll(commands.flags); + return this; + } + + /** + * Builds a {@link Commands} instance. + * + * @throws IllegalStateException If this method has already been called. + */ + public Commands build() { + return new Commands(flagsBuilder.build()); + } + } + + /** An empty set of commands. */ + public static final Commands EMPTY = new Builder().build(); + + private final ExoFlags flags; + + private Commands(ExoFlags flags) { + this.flags = flags; + } + + /** Returns whether the set of commands contains the specified {@link Command}. */ + public boolean contains(@Command int command) { + return flags.contains(command); + } + + /** Returns the number of commands in this set. */ + public int size() { + return flags.size(); + } + + /** + * Returns the {@link Command} at the given index. + * + * @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive). + * @return The {@link Command} at the given index. + * @throws IndexOutOfBoundsException If index is outside the allowed range. + */ + @Command + public int get(int index) { + return flags.get(index); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Commands)) { + return false; + } + Commands commands = (Commands) obj; + return flags.equals(commands.flags); + } + + @Override + public int hashCode() { + return flags.hashCode(); + } + } + + /** + * Listener of all changes in the Player. + * + *

All methods have no-op default implementations to allow selective overrides. + */ + interface Listener + extends VideoListener, + AudioListener, + TextOutput, + MetadataOutput, + DeviceListener, + EventListener { + + // For backward compatibility TextOutput and MetadataOutput must stay functional interfaces. + @Override + default void onCues(List cues) {} + + @Override + default void onMetadata(Metadata metadata) {} + } + /** * Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or * {@link #STATE_ENDED}. @@ -831,36 +805,44 @@ public interface Player { int REPEAT_MODE_ALL = 2; /** - * Reasons for position discontinuities. One of {@link #DISCONTINUITY_REASON_PERIOD_TRANSITION}, + * Reasons for position discontinuities. One of {@link #DISCONTINUITY_REASON_AUTO_TRANSITION}, * {@link #DISCONTINUITY_REASON_SEEK}, {@link #DISCONTINUITY_REASON_SEEK_ADJUSTMENT}, {@link - * #DISCONTINUITY_REASON_AD_INSERTION} or {@link #DISCONTINUITY_REASON_INTERNAL}. + * #DISCONTINUITY_REASON_SKIP}, {@link #DISCONTINUITY_REASON_REMOVE} or {@link + * #DISCONTINUITY_REASON_INTERNAL}. */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ - DISCONTINUITY_REASON_PERIOD_TRANSITION, + DISCONTINUITY_REASON_AUTO_TRANSITION, DISCONTINUITY_REASON_SEEK, DISCONTINUITY_REASON_SEEK_ADJUSTMENT, - DISCONTINUITY_REASON_AD_INSERTION, + DISCONTINUITY_REASON_SKIP, + DISCONTINUITY_REASON_REMOVE, DISCONTINUITY_REASON_INTERNAL }) @interface DiscontinuityReason {} /** - * Automatic playback transition from one period in the timeline to the next. The period index may - * be the same as it was before the discontinuity in case the current period is repeated. + * Automatic playback transition from one period in the timeline to the next without explicit + * interaction by this player. The period index may be the same as it was before the discontinuity + * in case the current period is repeated. + * + *

This reason also indicates an automatic transition from the content period to an inserted ad + * period or vice versa. */ - int DISCONTINUITY_REASON_PERIOD_TRANSITION = 0; - /** Seek within the current period or to another period. */ + int DISCONTINUITY_REASON_AUTO_TRANSITION = 0; + /** Seek within the current period or to another period by this player. */ int DISCONTINUITY_REASON_SEEK = 1; /** * Seek adjustment due to being unable to seek to the requested position or because the seek was * permitted to be inexact. */ int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2; - /** Discontinuity to or from an ad within one period in the timeline. */ - int DISCONTINUITY_REASON_AD_INSERTION = 3; - /** Discontinuity introduced internally by the source. */ - int DISCONTINUITY_REASON_INTERNAL = 4; + /** Discontinuity introduced by a skipped period (for instance a skipped ad). */ + int DISCONTINUITY_REASON_SKIP = 3; + /** Discontinuity caused by the removal of the current period from the {@link Timeline}. */ + int DISCONTINUITY_REASON_REMOVE = 4; + /** Discontinuity introduced internally (e.g. by the source). */ + int DISCONTINUITY_REASON_INTERNAL = 5; /** * Reasons for timeline changes. One of {@link #TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED} or {@link @@ -903,7 +885,7 @@ public interface Player { int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED = 3; /** - * Events that can be reported via {@link EventListener#onEvents(Player, Events)}. + * Events that can be reported via {@link Listener#onEvents(Player, Events)}. * *

One of the {@link Player}{@code .EVENT_*} flags. */ @@ -923,7 +905,9 @@ public interface Player { EVENT_SHUFFLE_MODE_ENABLED_CHANGED, EVENT_PLAYER_ERROR, EVENT_POSITION_DISCONTINUITY, - EVENT_PLAYBACK_PARAMETERS_CHANGED + EVENT_PLAYBACK_PARAMETERS_CHANGED, + EVENT_AVAILABLE_COMMANDS_CHANGED, + EVENT_MEDIA_METADATA_CHANGED }) @interface EventFlags {} /** {@link #getCurrentTimeline()} changed. */ @@ -950,32 +934,102 @@ public interface Player { int EVENT_SHUFFLE_MODE_ENABLED_CHANGED = 10; /** {@link #getPlayerError()} changed. */ int EVENT_PLAYER_ERROR = 11; - /** A position discontinuity occurred. See {@link EventListener#onPositionDiscontinuity(int)}. */ + /** + * A position discontinuity occurred. See {@link Listener#onPositionDiscontinuity(PositionInfo, + * PositionInfo, int)}. + */ int EVENT_POSITION_DISCONTINUITY = 12; /** {@link #getPlaybackParameters()} changed. */ int EVENT_PLAYBACK_PARAMETERS_CHANGED = 13; - - /** Returns the component of this player for audio output, or null if audio is not supported. */ - @Nullable - AudioComponent getAudioComponent(); - - /** Returns the component of this player for video output, or null if video is not supported. */ - @Nullable - VideoComponent getVideoComponent(); - - /** Returns the component of this player for text output, or null if text is not supported. */ - @Nullable - TextComponent getTextComponent(); + /** {@link #isCommandAvailable(int)} changed for at least one {@link Command}. */ + int EVENT_AVAILABLE_COMMANDS_CHANGED = 14; + /** {@link #getMediaMetadata()} changed. */ + int EVENT_MEDIA_METADATA_CHANGED = 15; /** - * Returns the component of this player for metadata output, or null if metadata is not supported. + * Commands that can be executed on a {@code Player}. One of {@link #COMMAND_PLAY_PAUSE}, {@link + * #COMMAND_PREPARE_STOP}, {@link #COMMAND_SEEK_TO_DEFAULT_POSITION}, {@link + * #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM}, {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM}, {@link + * #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM}, {@link #COMMAND_SEEK_TO_MEDIA_ITEM}, {@link + * #COMMAND_SET_SPEED_AND_PITCH}, {@link #COMMAND_SET_SHUFFLE_MODE}, {@link + * #COMMAND_SET_REPEAT_MODE}, {@link #COMMAND_GET_CURRENT_MEDIA_ITEM}, {@link + * #COMMAND_GET_MEDIA_ITEMS}, {@link #COMMAND_GET_MEDIA_ITEMS_METADATA}, {@link + * #COMMAND_CHANGE_MEDIA_ITEMS}, {@link #COMMAND_GET_AUDIO_ATTRIBUTES}, {@link + * #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link + * #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link + * #COMMAND_SET_VIDEO_SURFACE} or {@link #COMMAND_GET_TEXT}. */ - @Nullable - MetadataComponent getMetadataComponent(); - - /** Returns the component of this player for playback device, or null if it's not supported. */ - @Nullable - DeviceComponent getDeviceComponent(); + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + COMMAND_PLAY_PAUSE, + COMMAND_PREPARE_STOP, + COMMAND_SEEK_TO_DEFAULT_POSITION, + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, + COMMAND_SEEK_TO_MEDIA_ITEM, + COMMAND_SET_SPEED_AND_PITCH, + COMMAND_SET_SHUFFLE_MODE, + COMMAND_SET_REPEAT_MODE, + COMMAND_GET_CURRENT_MEDIA_ITEM, + COMMAND_GET_MEDIA_ITEMS, + COMMAND_GET_MEDIA_ITEMS_METADATA, + COMMAND_CHANGE_MEDIA_ITEMS, + COMMAND_GET_AUDIO_ATTRIBUTES, + COMMAND_GET_VOLUME, + COMMAND_GET_DEVICE_VOLUME, + COMMAND_SET_VOLUME, + COMMAND_SET_DEVICE_VOLUME, + COMMAND_ADJUST_DEVICE_VOLUME, + COMMAND_SET_VIDEO_SURFACE, + COMMAND_GET_TEXT + }) + @interface Command {} + /** Command to start, pause or resume playback. */ + int COMMAND_PLAY_PAUSE = 1; + /** Command to prepare the player, stop playback or release the player. */ + int COMMAND_PREPARE_STOP = 2; + /** Command to seek to the default position of the current window. */ + int COMMAND_SEEK_TO_DEFAULT_POSITION = 3; + /** Command to seek anywhere into the current window. */ + int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 4; + /** Command to seek to the default position of the next window. */ + int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 5; + /** Command to seek to the default position of the previous window. */ + int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; + /** Command to seek anywhere in any window. */ + int COMMAND_SEEK_TO_MEDIA_ITEM = 7; + /** Command to set the playback speed and pitch. */ + int COMMAND_SET_SPEED_AND_PITCH = 8; + /** Command to enable shuffling. */ + int COMMAND_SET_SHUFFLE_MODE = 9; + /** Command to set the repeat mode. */ + int COMMAND_SET_REPEAT_MODE = 10; + /** Command to get the {@link MediaItem} of the current window. */ + int COMMAND_GET_CURRENT_MEDIA_ITEM = 11; + /** Command to get the current timeline and its {@link MediaItem MediaItems}. */ + int COMMAND_GET_MEDIA_ITEMS = 12; + /** Command to get the {@link MediaItem MediaItems} metadata. */ + int COMMAND_GET_MEDIA_ITEMS_METADATA = 13; + /** Command to change the {@link MediaItem MediaItems} in the playlist. */ + int COMMAND_CHANGE_MEDIA_ITEMS = 14; + /** Command to get the player current {@link AudioAttributes}. */ + int COMMAND_GET_AUDIO_ATTRIBUTES = 15; + /** Command to get the player volume. */ + int COMMAND_GET_VOLUME = 16; + /** Command to get the device volume and whether it is muted. */ + int COMMAND_GET_DEVICE_VOLUME = 17; + /** Command to set the player volume. */ + int COMMAND_SET_VOLUME = 18; + /** Command to set the device volume and mute it. */ + int COMMAND_SET_DEVICE_VOLUME = 19; + /** Command to increase and decrease the device volume and mute it. */ + int COMMAND_ADJUST_DEVICE_VOLUME = 20; + /** Command to set and clear the surface on which to render the video. */ + int COMMAND_SET_VIDEO_SURFACE = 21; + /** Command to get the text that should currently be displayed by the player. */ + int COMMAND_GET_TEXT = 22; /** * Returns the {@link Looper} associated with the application thread that's used to access the @@ -984,20 +1038,40 @@ public interface Player { Looper getApplicationLooper(); /** - * Register 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 {@link Looper}, then the listener will be called on the main thread. + * 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 {@link Looper}, then the listener will be called on the main thread. * * @param listener The listener to register. + * @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead. */ + @Deprecated void addListener(EventListener listener); /** - * Unregister a listener. The listener will no longer receive events from the player. + * Registers a listener to receive all events from the player. + * + * @param listener The listener to register. + */ + void addListener(Listener listener); + + /** + * Unregister a listener registered through {@link #addListener(EventListener)}. The listener will + * no longer receive events from the player. + * + * @param listener The listener to unregister. + * @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead. + */ + @Deprecated + void removeListener(EventListener listener); + + /** + * Unregister a listener registered through {@link #addListener(Listener)}. The listener will no + * longer receive events. * * @param listener The listener to unregister. */ - void removeListener(EventListener listener); + void removeListener(Listener listener); /** * Clears the playlist, adds the specified {@link MediaItem MediaItems} and resets the position to @@ -1027,7 +1101,7 @@ public interface Player { * 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 windowIndex} is not within the + * @throws IllegalSeekPositionException If the provided {@code startWindowIndex} is not within the * bounds of the list of media items. */ void setMediaItems(List mediaItems, int startWindowIndex, long startPositionMs); @@ -1068,7 +1142,8 @@ public interface Player { /** * Adds a media item at the given index of the playlist. * - * @param index The index at which to add the item. + * @param index The index at which to add the media item. If the index is larger than the size of + * the playlist, the media item is added to the end of the playlist. * @param mediaItem The {@link MediaItem} to add. */ void addMediaItem(int index, MediaItem mediaItem); @@ -1083,7 +1158,8 @@ public interface Player { /** * Adds a list of media items at the given index of the playlist. * - * @param index The index at which to add the media items. + * @param 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. * @param mediaItems The {@link MediaItem MediaItems} to add. */ void addMediaItems(int index, List mediaItems); @@ -1119,13 +1195,51 @@ public interface Player { * Removes a range of media items from the playlist. * * @param fromIndex The index at which to start removing media items. - * @param toIndex The index of the first item to be kept (exclusive). + * @param 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. */ void removeMediaItems(int fromIndex, int toIndex); /** Clears the playlist. */ void clearMediaItems(); + /** + * Returns whether the provided {@link Command} is available. + * + *

This method does not execute the command. + * + *

Executing a command that is not available (for example, calling {@link #next()} 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_NEXT_MEDIA_ITEM} and {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} + * are unavailable if there is no such {@link MediaItem}. + * + * @param command A {@link Command}. + * @return Whether the {@link Command} is available. + * @see Listener#onAvailableCommandsChanged(Commands) + */ + boolean isCommandAvailable(@Command int command); + + /** + * Returns the player's currently available {@link Commands}. + * + *

The returned {@link Commands} are not updated when available commands change. Use {@link + * Listener#onAvailableCommandsChanged(Commands)} to get an update when the available commands + * change. + * + *

Executing a command that is not available (for example, calling {@link #next()} 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_NEXT_MEDIA_ITEM} and {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} + * are unavailable if there is no such {@link MediaItem}. + * + * @return The currently available {@link Commands}. + * @see Listener#onAvailableCommandsChanged + */ + Commands getAvailableCommands(); + /** Prepares the player. */ void prepare(); @@ -1133,7 +1247,7 @@ public interface Player { * Returns the current {@link State playback state} of the player. * * @return The current {@link State playback state}. - * @see EventListener#onPlaybackStateChanged(int) + * @see Listener#onPlaybackStateChanged(int) */ @State int getPlaybackState(); @@ -1143,13 +1257,13 @@ public interface Player { * true}, or {@link #PLAYBACK_SUPPRESSION_REASON_NONE} if playback is not suppressed. * * @return The current {@link PlaybackSuppressionReason playback suppression reason}. - * @see EventListener#onPlaybackSuppressionReasonChanged(int) + * @see Listener#onPlaybackSuppressionReasonChanged(int) */ @PlaybackSuppressionReason int getPlaybackSuppressionReason(); /** - * Returns whether the player is playing, i.e. {@link #getContentPosition()} is advancing. + * Returns whether the player is playing, i.e. {@link #getCurrentPosition()} is advancing. * *

If {@code false}, then at least one of the following is true: * @@ -1160,20 +1274,20 @@ public interface Player { * * * @return Whether the player is playing. - * @see EventListener#onIsPlayingChanged(boolean) + * @see Listener#onIsPlayingChanged(boolean) */ boolean isPlaying(); /** * Returns the error that caused playback to fail. This is the same error that will have been - * reported via {@link Player.EventListener#onPlayerError(ExoPlaybackException)} at the time of - * failure. It can be queried using this method until the player is re-prepared. + * reported via {@link Listener#onPlayerError(ExoPlaybackException)} 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 {@code null} if {@link #getPlaybackState()} is not * {@link #STATE_IDLE}. * * @return The error, or {@code null}. - * @see EventListener#onPlayerError(ExoPlaybackException) + * @see Listener#onPlayerError(ExoPlaybackException) */ @Nullable ExoPlaybackException getPlayerError(); @@ -1205,7 +1319,7 @@ public interface Player { * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. * * @return Whether playback will proceed when ready. - * @see EventListener#onPlayWhenReadyChanged(boolean, int) + * @see Listener#onPlayWhenReadyChanged(boolean, int) */ boolean getPlayWhenReady(); @@ -1220,7 +1334,7 @@ public interface Player { * Returns the current {@link RepeatMode} used for playback. * * @return The current repeat mode. - * @see EventListener#onRepeatModeChanged(int) + * @see Listener#onRepeatModeChanged(int) */ @RepeatMode int getRepeatMode(); @@ -1235,7 +1349,7 @@ public interface Player { /** * Returns whether shuffling of windows is enabled. * - * @see EventListener#onShuffleModeEnabledChanged(boolean) + * @see Listener#onShuffleModeEnabledChanged(boolean) */ boolean getShuffleModeEnabled(); @@ -1243,7 +1357,7 @@ public interface Player { * Whether the player is currently loading the source. * * @return Whether the player is currently loading the source. - * @see EventListener#onIsLoadingChanged(boolean) + * @see Listener#onIsLoadingChanged(boolean) */ boolean isLoading(); @@ -1327,21 +1441,32 @@ public interface Player { void next(); /** - * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the - * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment. + * Attempts to set the playback parameters. Passing {@link 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. {@link - * EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever the - * currently active playback parameters change. + * Listener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever the currently + * active playback parameters change. * - * @param playbackParameters The playback parameters, or {@code null} to use the defaults. + * @param playbackParameters The playback parameters. */ - void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters); + void setPlaybackParameters(PlaybackParameters playbackParameters); + + /** + * Changes the rate at which playback occurs. The pitch is not changed. + * + *

This is equivalent to {@code + * setPlaybackParameters(getPlaybackParameters().withSpeed(speed))}. + * + * @param speed The linear factor by which playback will be sped up. Must be higher than 0. 1 is + * normal speed, 2 is twice as fast, 0.5 is half normal speed... + */ + void setPlaybackSpeed(float speed); /** * Returns the currently active playback parameters. * - * @see EventListener#onPlaybackParametersChanged(PlaybackParameters) + * @see Listener#onPlaybackParametersChanged(PlaybackParameters) */ PlaybackParameters getPlaybackParameters(); @@ -1372,33 +1497,21 @@ public interface Player { */ void release(); - /** Returns the number of renderers. */ - int getRendererCount(); - - /** - * Returns the track type that the renderer at a given index handles. - * - *

For example, a video renderer will return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will - * return {@link C#TRACK_TYPE_AUDIO} and a text renderer will return {@link C#TRACK_TYPE_TEXT}. - * - * @param index The index of the renderer. - * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. - */ - int getRendererType(int index); - /** * Returns the available track groups. * - * @see EventListener#onTracksChanged(TrackGroupArray, TrackSelectionArray) + * @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray) */ TrackGroupArray getCurrentTrackGroups(); /** - * Returns the current track selections for each renderer. + * 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 Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray) */ TrackSelectionArray getCurrentTrackSelections(); @@ -1413,10 +1526,20 @@ public interface Player { *

This metadata is considered static in that it comes from the tracks' declared Formats, * rather than being timed (or dynamic) metadata, which is represented within a metadata track. * - * @see EventListener#onStaticMetadataChanged(List) + * @see Listener#onStaticMetadataChanged(List) */ List getCurrentStaticMetadata(); + /** + * Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not + * supported. + * + *

This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the + * static and dynamic metadata sourced from {@link Listener#onStaticMetadataChanged(List)} and + * {@link MetadataOutput#onMetadata(Metadata)}. + */ + MediaMetadata getMediaMetadata(); + /** * Returns the current manifest. The type depends on the type of media being played. May be null. */ @@ -1426,7 +1549,7 @@ public interface Player { /** * Returns the current {@link Timeline}. Never null, but may be empty. * - * @see EventListener#onTimelineChanged(Timeline, int) + * @see Listener#onTimelineChanged(Timeline, int) */ Timeline getCurrentTimeline(); @@ -1474,7 +1597,7 @@ public interface Player { * Returns the media item of the current window in the timeline. May be null if the timeline is * empty. * - * @see EventListener#onMediaItemTransition(MediaItem, int) + * @see Listener#onMediaItemTransition(MediaItem, int) */ @Nullable MediaItem getCurrentMediaItem(); @@ -1587,4 +1710,146 @@ public interface Player { * playing, the returned position is the same as that returned by {@link #getBufferedPosition()}. */ long getContentBufferedPosition(); + + /** Returns the attributes for audio playback. */ + AudioAttributes getAudioAttributes(); + + /** + * Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + * + * @param audioVolume Linear output gain to apply to all audio channels. + */ + void setVolume(float audioVolume); + + /** + * Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + * + * @return The linear gain applied to all audio channels. + */ + float getVolume(); + + /** + * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} + * currently set on the player. + */ + void clearVideoSurface(); + + /** + * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surface The surface to clear. + */ + void clearVideoSurface(@Nullable Surface surface); + + /** + * Sets the {@link 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 {@code + * setVideoSurface(null)} if the surface is destroyed. + * + *

If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link + * SurfaceHolder} then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, {@link + * #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} rather than + * this method, since passing the holder allows the player to track the lifecycle of the surface + * automatically. + * + * @param surface The {@link Surface}. + */ + void setVideoSurface(@Nullable Surface surface); + + /** + * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be + * rendered. The player will track the lifecycle of the surface automatically. + * + * @param surfaceHolder The surface holder. + */ + void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); + + /** + * Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being + * rendered if it matches the one passed. Else does nothing. + * + * @param surfaceHolder The surface holder to clear. + */ + void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); + + /** + * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param surfaceView The surface view. + */ + void setVideoSurfaceView(@Nullable SurfaceView surfaceView); + + /** + * Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surfaceView The texture view to clear. + */ + void clearVideoSurfaceView(@Nullable SurfaceView surfaceView); + + /** + * Sets the {@link TextureView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param textureView The texture view. + */ + void setVideoTextureView(@Nullable TextureView textureView); + + /** + * Clears the {@link TextureView} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param textureView The texture view to clear. + */ + void clearVideoTextureView(@Nullable TextureView textureView); + + /** + * Gets the size of the video. + * + *

The video's width and height are {@code 0} if there is no video or its size has not been + * determined yet. + * + * @see Listener#onVideoSizeChanged(VideoSize) + */ + VideoSize getVideoSize(); + + /** Returns the current {@link Cue Cues}. This list may be empty. */ + List getCurrentCues(); + + /** Gets the device information. */ + DeviceInfo getDeviceInfo(); + + /** + * Gets the current volume of the device. + * + *

For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume returned + * by this method varies according to the current {@link C.StreamType stream type}. The stream + * type is determined by {@link AudioAttributes#usage} which can be converted to stream type with + * {@link Util#getStreamTypeForAudioUsage(int)}. + * + *

For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of the + * remote device is returned. + */ + int getDeviceVolume(); + + /** Gets whether the device is muted or not. */ + boolean isDeviceMuted(); + + /** + * Sets the volume of the device. + * + * @param volume The volume to set. + */ + void setDeviceVolume(int volume); + + /** Increases the volume of the device. */ + void increaseDeviceVolume(); + + /** Decreases the volume of the device. */ + void decreaseDeviceVolume(); + + /** Sets the mute state of the device. */ + void setDeviceMuted(boolean muted); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Rating.java b/library/common/src/main/java/com/google/android/exoplayer2/Rating.java new file mode 100644 index 0000000000..0477bbce0e --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/Rating.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.os.Bundle; +import androidx.annotation.IntDef; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A rating for media content. The style of a rating can be one of {@link HeartRating}, {@link + * PercentageRating}, {@link StarRating}, or {@link ThumbRating}. + */ +public abstract class Rating implements Bundleable { + + /** A float value that denotes the rating is unset. */ + public static final float RATING_UNSET = -1.0f; + + // Default package-private constructor to prevent extending Rating class outside this package. + /* package */ Rating() {} + + /** Whether the rating exists or not. */ + public abstract boolean isRated(); + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + RATING_TYPE_DEFAULT, + RATING_TYPE_HEART, + RATING_TYPE_PERCENTAGE, + RATING_TYPE_STAR, + RATING_TYPE_THUMB + }) + /* package */ @interface RatingType {} + + /* package */ static final int RATING_TYPE_DEFAULT = -1; + /* package */ static final int RATING_TYPE_HEART = 0; + /* package */ static final int RATING_TYPE_PERCENTAGE = 1; + /* package */ static final int RATING_TYPE_STAR = 2; + /* package */ static final int RATING_TYPE_THUMB = 3; + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_RATING_TYPE}) + private @interface FieldNumber {} + + /* package */ static final int FIELD_RATING_TYPE = 0; + + /** Object that can restore a {@link Rating} from a {@link Bundle}. */ + public static final Creator CREATOR = Rating::fromBundle; + + private static Rating fromBundle(Bundle bundle) { + @RatingType + int ratingType = + bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT); + switch (ratingType) { + case RATING_TYPE_HEART: + return HeartRating.CREATOR.fromBundle(bundle); + case RATING_TYPE_PERCENTAGE: + return PercentageRating.CREATOR.fromBundle(bundle); + case RATING_TYPE_STAR: + return StarRating.CREATOR.fromBundle(bundle); + case RATING_TYPE_THUMB: + return ThumbRating.CREATOR.fromBundle(bundle); + default: + throw new IllegalArgumentException("Encountered unknown rating type: " + ratingType); + } + } + + 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/StarRating.java b/library/common/src/main/java/com/google/android/exoplayer2/StarRating.java new file mode 100644 index 0000000000..543228185e --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/StarRating.java @@ -0,0 +1,142 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; + +import android.os.Bundle; +import androidx.annotation.FloatRange; +import androidx.annotation.IntDef; +import androidx.annotation.IntRange; +import androidx.annotation.Nullable; +import com.google.common.base.Objects; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** A rating expressed as a fractional number of stars. */ +public final class StarRating extends Rating { + + @IntRange(from = 1) + private final int maxStars; + + private final float starRating; + + /** + * Creates a unrated instance with {@code maxStars}. If {@code maxStars} is not a positive + * integer, it will throw an {@link IllegalArgumentException}. + * + * @param maxStars The maximum number of stars this rating can have. + */ + public StarRating(@IntRange(from = 1) int maxStars) { + checkArgument(maxStars > 0, "maxStars must be a positive integer"); + this.maxStars = maxStars; + starRating = RATING_UNSET; + } + + /** + * Creates a rated instance with {@code maxStars} and the given fractional number of stars. + * Non-integer values may be used to represent an average rating value. If {@code maxStars} is not + * a positive integer or {@code starRating} is out of range, it will throw an {@link + * IllegalArgumentException}. + * + * @param maxStars The maximum number of stars this rating can have. + * @param starRating A fractional number of stars of this rating from {@code 0f} to {@code + * maxStars}. + */ + public StarRating(@IntRange(from = 1) int maxStars, @FloatRange(from = 0.0) float starRating) { + checkArgument(maxStars > 0, "maxStars must be a positive integer"); + checkArgument( + starRating >= 0.0f && starRating <= maxStars, "starRating is out of range [0, maxStars]"); + this.maxStars = maxStars; + this.starRating = starRating; + } + + @Override + public boolean isRated() { + return starRating != RATING_UNSET; + } + + /** Returns the maximum number of stars. Must be a positive number. */ + @IntRange(from = 1) + public int getMaxStars() { + return maxStars; + } + + /** + * Returns the fractional number of stars of this rating. Will range from {@code 0f} to {@link + * #maxStars}, or {@link #RATING_UNSET} if unrated. + */ + public float getStarRating() { + return starRating; + } + + @Override + public int hashCode() { + return Objects.hashCode(maxStars, starRating); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof StarRating)) { + return false; + } + StarRating other = (StarRating) obj; + return maxStars == other.maxStars && starRating == other.starRating; + } + + // Bundleable implementation. + + @RatingType private static final int TYPE = RATING_TYPE_STAR; + private static final int MAX_STARS_DEFAULT = 5; + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_RATING_TYPE, FIELD_MAX_STARS, FIELD_STAR_RATING}) + private @interface FieldNumber {} + + private static final int FIELD_MAX_STARS = 1; + private static final int FIELD_STAR_RATING = 2; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); + bundle.putInt(keyForField(FIELD_MAX_STARS), maxStars); + bundle.putFloat(keyForField(FIELD_STAR_RATING), starRating); + return bundle; + } + + /** Object that can restore a {@link StarRating} from a {@link Bundle}. */ + public static final Creator CREATOR = StarRating::fromBundle; + + private static StarRating fromBundle(Bundle bundle) { + checkArgument( + bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT) + == TYPE); + int maxStars = + bundle.getInt(keyForField(FIELD_MAX_STARS), /* defaultValue= */ MAX_STARS_DEFAULT); + float starRating = + bundle.getFloat(keyForField(FIELD_STAR_RATING), /* defaultValue= */ RATING_UNSET); + return starRating == RATING_UNSET + ? new StarRating(maxStars) + : new StarRating(maxStars, starRating); + } + + 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/ThumbRating.java b/library/common/src/main/java/com/google/android/exoplayer2/ThumbRating.java new file mode 100644 index 0000000000..07e0ee38d3 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/ThumbRating.java @@ -0,0 +1,112 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; + +import android.os.Bundle; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import com.google.common.base.Objects; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** A rating expressed as "thumbs up" or "thumbs down". */ +public final class ThumbRating extends Rating { + + private final boolean rated; + private final boolean isThumbsUp; + + /** Creates a unrated instance. */ + public ThumbRating() { + rated = false; + isThumbsUp = false; + } + + /** + * Creates a rated instance. + * + * @param isThumbsUp {@code true} for "thumbs up", {@code false} for "thumbs down". + */ + public ThumbRating(boolean isThumbsUp) { + rated = true; + this.isThumbsUp = isThumbsUp; + } + + @Override + public boolean isRated() { + return rated; + } + + /** Returns whether the rating is "thumbs up". */ + public boolean isThumbsUp() { + return isThumbsUp; + } + + @Override + public int hashCode() { + return Objects.hashCode(rated, isThumbsUp); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ThumbRating)) { + return false; + } + ThumbRating other = (ThumbRating) obj; + return isThumbsUp == other.isThumbsUp && rated == other.rated; + } + + // Bundleable implementation. + + @RatingType private static final int TYPE = RATING_TYPE_THUMB; + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_THUMBS_UP}) + private @interface FieldNumber {} + + private static final int FIELD_RATED = 1; + private static final int FIELD_IS_THUMBS_UP = 2; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); + bundle.putBoolean(keyForField(FIELD_RATED), rated); + bundle.putBoolean(keyForField(FIELD_IS_THUMBS_UP), isThumbsUp); + return bundle; + } + + /** Object that can restore a {@link ThumbRating} from a {@link Bundle}. */ + public static final Creator CREATOR = ThumbRating::fromBundle; + + private static ThumbRating fromBundle(Bundle bundle) { + checkArgument( + bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT) + == TYPE); + boolean rated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false); + return rated + ? new ThumbRating( + bundle.getBoolean(keyForField(FIELD_IS_THUMBS_UP), /* defaultValue= */ false)) + : new ThumbRating(); + } + + 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/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index d7e1e955db..10e91fc22f 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 @@ -15,15 +15,26 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkState; import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; import android.os.SystemClock; import android.util.Pair; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.BundleUtil; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * A flexible representation of the structure of media. A timeline is able to represent the @@ -119,7 +130,7 @@ import com.google.android.exoplayer2.util.Util; *

This case includes mid-roll ad groups, which are defined as part of the timeline's single * period. The period can be queried for information about the ad groups and the ads they contain. */ -public abstract class Timeline { +public abstract class Timeline implements Bundleable { /** * Holds information about a window in a {@link Timeline}. A window usually corresponds to one @@ -131,13 +142,15 @@ public abstract class Timeline { *

Information defined by a
    * timeline window */ - public static final class Window { + public static final class Window implements Bundleable { /** * A {@link #uid} for a window that must be used for single-window {@link Timeline Timelines}. */ public static final Object SINGLE_WINDOW_UID = new Object(); + private static final Object FAKE_WINDOW_UID = new Object(); + private static final MediaItem EMPTY_MEDIA_ITEM = new MediaItem.Builder() .setMediaId("com.google.android.exoplayer2.Timeline") @@ -208,14 +221,6 @@ public abstract class Timeline { */ public boolean isPlaceholder; - /** The index of the first period that belongs to this window. */ - public int firstPeriodIndex; - - /** - * The index of the last period that belongs to this window. - */ - public int lastPeriodIndex; - /** * The default position relative to the start of the window at which to begin playback, in * microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a @@ -229,6 +234,12 @@ public abstract class Timeline { */ public long durationUs; + /** The index of the first period that belongs to this window. */ + public int firstPeriodIndex; + + /** The index of the last period that belongs to this window. */ + public int lastPeriodIndex; + /** * The position of the start of this window relative to the start of the first period belonging * to it, in microseconds. @@ -399,6 +410,142 @@ public abstract class Timeline { result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32)); return result; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_MEDIA_ITEM, + FIELD_PRESENTATION_START_TIME_MS, + FIELD_WINDOW_START_TIME_MS, + FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS, + FIELD_IS_SEEKABLE, + FIELD_IS_DYNAMIC, + FIELD_LIVE_CONFIGURATION, + FIELD_IS_PLACEHOLDER, + FIELD_DEFAULT_POSITION_US, + FIELD_DURATION_US, + FIELD_FIRST_PERIOD_INDEX, + FIELD_LAST_PERIOD_INDEX, + FIELD_POSITION_IN_FIRST_PERIOD_US, + }) + private @interface FieldNumber {} + + private static final int FIELD_MEDIA_ITEM = 1; + private static final int FIELD_PRESENTATION_START_TIME_MS = 2; + private static final int FIELD_WINDOW_START_TIME_MS = 3; + private static final int FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS = 4; + private static final int FIELD_IS_SEEKABLE = 5; + private static final int FIELD_IS_DYNAMIC = 6; + private static final int FIELD_LIVE_CONFIGURATION = 7; + private static final int FIELD_IS_PLACEHOLDER = 8; + private static final int FIELD_DEFAULT_POSITION_US = 9; + private static final int FIELD_DURATION_US = 10; + private static final int FIELD_FIRST_PERIOD_INDEX = 11; + private static final int FIELD_LAST_PERIOD_INDEX = 12; + private static final int FIELD_POSITION_IN_FIRST_PERIOD_US = 13; + + /** + * {@inheritDoc} + * + *

It omits the {@link #uid} and {@link #manifest} fields. The {@link #uid} of an instance + * restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the + * instance will be {@code null}. + */ + // TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise. + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle()); + bundle.putLong(keyForField(FIELD_PRESENTATION_START_TIME_MS), presentationStartTimeMs); + bundle.putLong(keyForField(FIELD_WINDOW_START_TIME_MS), windowStartTimeMs); + bundle.putLong( + keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), elapsedRealtimeEpochOffsetMs); + bundle.putBoolean(keyForField(FIELD_IS_SEEKABLE), isSeekable); + bundle.putBoolean(keyForField(FIELD_IS_DYNAMIC), isDynamic); + @Nullable MediaItem.LiveConfiguration liveConfiguration = this.liveConfiguration; + if (liveConfiguration != null) { + bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); + } + bundle.putBoolean(keyForField(FIELD_IS_PLACEHOLDER), isPlaceholder); + bundle.putLong(keyForField(FIELD_DEFAULT_POSITION_US), defaultPositionUs); + bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); + bundle.putInt(keyForField(FIELD_FIRST_PERIOD_INDEX), firstPeriodIndex); + bundle.putInt(keyForField(FIELD_LAST_PERIOD_INDEX), lastPeriodIndex); + bundle.putLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), positionInFirstPeriodUs); + return bundle; + } + + /** + * Object that can restore {@link Period} from a {@link Bundle}. + * + *

The {@link #uid} of a restored instance will be a fake {@link Object} and the {@link + * #manifest} of the instance will be {@code null}. + */ + public static final Creator CREATOR = Window::fromBundle; + + private static Window fromBundle(Bundle bundle) { + @Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)); + @Nullable + MediaItem mediaItem = + mediaItemBundle != null ? MediaItem.CREATOR.fromBundle(mediaItemBundle) : null; + long presentationStartTimeMs = + bundle.getLong( + keyForField(FIELD_PRESENTATION_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET); + long windowStartTimeMs = + bundle.getLong(keyForField(FIELD_WINDOW_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET); + long elapsedRealtimeEpochOffsetMs = + bundle.getLong( + keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), + /* defaultValue= */ C.TIME_UNSET); + boolean isSeekable = + bundle.getBoolean(keyForField(FIELD_IS_SEEKABLE), /* defaultValue= */ false); + boolean isDynamic = + bundle.getBoolean(keyForField(FIELD_IS_DYNAMIC), /* defaultValue= */ false); + @Nullable + Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION)); + @Nullable + MediaItem.LiveConfiguration liveConfiguration = + liveConfigurationBundle != null + ? MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle) + : null; + boolean isPlaceHolder = + bundle.getBoolean(keyForField(FIELD_IS_PLACEHOLDER), /* defaultValue= */ false); + long defaultPositionUs = + bundle.getLong(keyForField(FIELD_DEFAULT_POSITION_US), /* defaultValue= */ 0); + long durationUs = + bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET); + int firstPeriodIndex = + bundle.getInt(keyForField(FIELD_FIRST_PERIOD_INDEX), /* defaultValue= */ 0); + int lastPeriodIndex = + bundle.getInt(keyForField(FIELD_LAST_PERIOD_INDEX), /* defaultValue= */ 0); + long positionInFirstPeriodUs = + bundle.getLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), /* defaultValue= */ 0); + + Window window = new Window(); + window.set( + FAKE_WINDOW_UID, + mediaItem, + /* manifest= */ null, + presentationStartTimeMs, + windowStartTimeMs, + elapsedRealtimeEpochOffsetMs, + isSeekable, + isDynamic, + liveConfiguration, + defaultPositionUs, + durationUs, + firstPeriodIndex, + lastPeriodIndex, + positionInFirstPeriodUs); + window.isPlaceholder = isPlaceHolder; + return window; + } + + private static String keyForField(@Window.FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } /** @@ -412,7 +559,7 @@ public abstract class Timeline { *

Information defined by a
    * period */ - public static final class Period { + public static final class Period implements Bundleable { /** * An identifier for the period. Not necessarily unique. May be null if the ids of the period @@ -435,7 +582,19 @@ public abstract class Timeline { */ public long durationUs; - private long positionInWindowUs; + /** + * The position of the start of this period relative to the start of the window to which it + * belongs, in microseconds. May be negative if the start of the period is not within the + * window. + */ + public long positionInWindowUs; + + /** + * Whether this period contains placeholder information because the real information has yet to + * be loaded. + */ + public boolean isPlaceholder; + private AdPlaybackState adPlaybackState; /** Creates a new instance with no ad playback state. */ @@ -464,7 +623,14 @@ public abstract class Timeline { int windowIndex, long durationUs, long positionInWindowUs) { - return set(id, uid, windowIndex, durationUs, positionInWindowUs, AdPlaybackState.NONE); + return set( + id, + uid, + windowIndex, + durationUs, + positionInWindowUs, + AdPlaybackState.NONE, + /* isPlaceholder= */ false); } /** @@ -482,6 +648,8 @@ public abstract class Timeline { * period is not within the window. * @param adPlaybackState The state of the period's ads, or {@link AdPlaybackState#NONE} if * there are no ads. + * @param isPlaceholder Whether this period contains placeholder information because the real + * information has yet to be loaded. * @return This period, for convenience. */ public Period set( @@ -490,13 +658,15 @@ public abstract class Timeline { int windowIndex, long durationUs, long positionInWindowUs, - AdPlaybackState adPlaybackState) { + AdPlaybackState adPlaybackState, + boolean isPlaceholder) { this.id = id; this.uid = uid; this.windowIndex = windowIndex; this.durationUs = durationUs; this.positionInWindowUs = positionInWindowUs; this.adPlaybackState = adPlaybackState; + this.isPlaceholder = isPlaceholder; return this; } @@ -592,9 +762,10 @@ public abstract class Timeline { } /** - * Returns the index of the ad group at or before {@code positionUs} in the period, if that ad - * group is unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code - * positionUs} has no ads remaining to be played, or if there is no such ad group. + * Returns the index of the ad group at or before {@code positionUs} in the period that should + * be played before the content at {@code positionUs}. Returns {@link C#INDEX_UNSET} if the ad + * group at or before {@code positionUs} has no ads remaining to be played, or if there is no + * such ad group. * * @param positionUs The period position at or before which to find an ad group, in * microseconds. @@ -606,7 +777,7 @@ public abstract class Timeline { /** * Returns the index of the next ad group after {@code positionUs} in the period that has ads - * remaining to be played. Returns {@link C#INDEX_UNSET} if there is no such ad group. + * that should be played. Returns {@link C#INDEX_UNSET} if there is no such ad group. * * @param positionUs The period position after which to find an ad group, in microseconds. * @return The index of the ad group, or {@link C#INDEX_UNSET}. @@ -661,6 +832,7 @@ public abstract class Timeline { && windowIndex == that.windowIndex && durationUs == that.durationUs && positionInWindowUs == that.positionInWindowUs + && isPlaceholder == that.isPlaceholder && Util.areEqual(adPlaybackState, that.adPlaybackState); } @@ -672,9 +844,84 @@ public abstract class Timeline { result = 31 * result + windowIndex; result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); result = 31 * result + (int) (positionInWindowUs ^ (positionInWindowUs >>> 32)); + result = 31 * result + (isPlaceholder ? 1 : 0); result = 31 * result + adPlaybackState.hashCode(); return result; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_WINDOW_INDEX, + FIELD_DURATION_US, + FIELD_POSITION_IN_WINDOW_US, + FIELD_PLACEHOLDER, + FIELD_AD_PLAYBACK_STATE + }) + private @interface FieldNumber {} + + private static final int FIELD_WINDOW_INDEX = 0; + private static final int FIELD_DURATION_US = 1; + private static final int FIELD_POSITION_IN_WINDOW_US = 2; + private static final int FIELD_PLACEHOLDER = 3; + private static final int FIELD_AD_PLAYBACK_STATE = 4; + + /** + * {@inheritDoc} + * + *

It omits the {@link #id} and {@link #uid} fields so these fields of an instance restored + * by {@link #CREATOR} will always be {@code null}. + */ + // TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise. + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex); + bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); + bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs); + bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder); + bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle()); + return bundle; + } + + /** + * Object that can restore {@link Period} from a {@link Bundle}. + * + *

The {@link #id} and {@link #uid} of restored instances will always be {@code null}. + */ + public static final Creator CREATOR = Period::fromBundle; + + private static Period fromBundle(Bundle bundle) { + int windowIndex = bundle.getInt(keyForField(FIELD_WINDOW_INDEX), /* defaultValue= */ 0); + long durationUs = + bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET); + long positionInWindowUs = + bundle.getLong(keyForField(FIELD_POSITION_IN_WINDOW_US), /* defaultValue= */ 0); + boolean isPlaceholder = bundle.getBoolean(keyForField(FIELD_PLACEHOLDER)); + @Nullable + Bundle adPlaybackStateBundle = bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATE)); + AdPlaybackState adPlaybackState = + adPlaybackStateBundle != null + ? AdPlaybackState.CREATOR.fromBundle(adPlaybackStateBundle) + : AdPlaybackState.NONE; + + Period period = new Period(); + period.set( + /* id= */ null, + /* uid= */ null, + windowIndex, + durationUs, + positionInWindowUs, + adPlaybackState, + isPlaceholder); + return period; + } + + private static String keyForField(@Period.FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } /** An empty timeline. */ @@ -922,13 +1169,14 @@ public abstract class Timeline { } } int periodIndex = window.firstPeriodIndex; - long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs; - long periodDurationUs = getPeriod(periodIndex, period, /* setIds= */ true).getDurationUs(); - while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs - && periodIndex < window.lastPeriodIndex) { - periodPositionUs -= periodDurationUs; - periodDurationUs = getPeriod(++periodIndex, period, /* setIds= */ true).getDurationUs(); + getPeriod(periodIndex, period); + while (periodIndex < window.lastPeriodIndex + && period.positionInWindowUs != windowPositionUs + && getPeriod(periodIndex + 1, period).positionInWindowUs <= windowPositionUs) { + periodIndex++; } + getPeriod(periodIndex, period, /* setIds= */ true); + long periodPositionUs = windowPositionUs - period.positionInWindowUs; return Pair.create(Assertions.checkNotNull(period.uid), periodPositionUs); } @@ -1029,4 +1277,243 @@ public abstract class Timeline { } return result; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_WINDOWS, + FIELD_PERIODS, + FIELD_SHUFFLED_WINDOW_INDICES, + }) + private @interface FieldNumber {} + + private static final int FIELD_WINDOWS = 0; + private static final int FIELD_PERIODS = 1; + private static final int FIELD_SHUFFLED_WINDOW_INDICES = 2; + + /** + * {@inheritDoc} + * + *

The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of + * an instance restored by {@link #CREATOR} may have missing fields as described in {@link + * Window#toBundle()} and {@link Period#toBundle()}. + */ + @Override + public final Bundle toBundle() { + List windowBundles = new ArrayList<>(); + int windowCount = getWindowCount(); + Window window = new Window(); + for (int i = 0; i < windowCount; i++) { + windowBundles.add(getWindow(i, window, /* defaultPositionProjectionUs= */ 0).toBundle()); + } + + List periodBundles = new ArrayList<>(); + int periodCount = getPeriodCount(); + Period period = new Period(); + for (int i = 0; i < periodCount; i++) { + periodBundles.add(getPeriod(i, period, /* setIds= */ false).toBundle()); + } + + int[] shuffledWindowIndices = new int[windowCount]; + if (windowCount > 0) { + shuffledWindowIndices[0] = getFirstWindowIndex(/* shuffleModeEnabled= */ true); + } + for (int i = 1; i < windowCount; i++) { + shuffledWindowIndices[i] = + getNextWindowIndex( + shuffledWindowIndices[i - 1], Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true); + } + + Bundle bundle = new Bundle(); + BundleUtil.putBinder( + bundle, keyForField(FIELD_WINDOWS), new BundleListRetriever(windowBundles)); + BundleUtil.putBinder( + bundle, keyForField(FIELD_PERIODS), new BundleListRetriever(periodBundles)); + bundle.putIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES), shuffledWindowIndices); + return bundle; + } + + /** + * Object that can restore a {@link Timeline} from a {@link Bundle}. + * + *

The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of + * a restored instance may have missing fields as described in {@link Window#CREATOR} and {@link + * Period#CREATOR}. + */ + public static final Creator CREATOR = Timeline::fromBundle; + + private static Timeline fromBundle(Bundle bundle) { + ImmutableList windows = + fromBundleListRetriever( + Window.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_WINDOWS))); + ImmutableList periods = + fromBundleListRetriever( + Period.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_PERIODS))); + @Nullable + int[] shuffledWindowIndices = bundle.getIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES)); + return new RemotableTimeline( + windows, + periods, + shuffledWindowIndices == null + ? generateUnshuffledIndices(windows.size()) + : shuffledWindowIndices); + } + + private static ImmutableList fromBundleListRetriever( + Creator creator, @Nullable IBinder binder) { + if (binder == null) { + return ImmutableList.of(); + } + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + List bundleList = BundleListRetriever.getList(binder); + for (int i = 0; i < bundleList.size(); i++) { + builder.add(creator.fromBundle(bundleList.get(i))); + } + return builder.build(); + } + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } + + private static int[] generateUnshuffledIndices(int n) { + int[] indices = new int[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + return indices; + } + + /** + * A concrete class of {@link Timeline} to restore a {@link Timeline} instance from a {@link + * Bundle} sent by another process via {@link IBinder}. + */ + private static final class RemotableTimeline extends Timeline { + + private final ImmutableList windows; + private final ImmutableList periods; + private final int[] shuffledWindowIndices; + private final int[] windowIndicesInShuffled; + + public RemotableTimeline( + ImmutableList windows, ImmutableList periods, int[] shuffledWindowIndices) { + checkArgument(windows.size() == shuffledWindowIndices.length); + this.windows = windows; + this.periods = periods; + this.shuffledWindowIndices = shuffledWindowIndices; + windowIndicesInShuffled = new int[shuffledWindowIndices.length]; + for (int i = 0; i < shuffledWindowIndices.length; i++) { + windowIndicesInShuffled[shuffledWindowIndices[i]] = i; + } + } + + @Override + public int getWindowCount() { + return windows.size(); + } + + @Override + public Window getWindow( + int windowIndex, Window window, long ignoredDefaultPositionProjectionUs) { + Window w = windows.get(windowIndex); + window.set( + w.uid, + w.mediaItem, + w.manifest, + w.presentationStartTimeMs, + w.windowStartTimeMs, + w.elapsedRealtimeEpochOffsetMs, + w.isSeekable, + w.isDynamic, + w.liveConfiguration, + w.defaultPositionUs, + w.durationUs, + w.firstPeriodIndex, + w.lastPeriodIndex, + w.positionInFirstPeriodUs); + window.isPlaceholder = w.isPlaceholder; + return window; + } + + @Override + public int getNextWindowIndex( + int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) { + if (repeatMode == Player.REPEAT_MODE_ONE) { + return windowIndex; + } + if (windowIndex == getLastWindowIndex(shuffleModeEnabled)) { + return repeatMode == Player.REPEAT_MODE_ALL + ? getFirstWindowIndex(shuffleModeEnabled) + : C.INDEX_UNSET; + } + return shuffleModeEnabled + ? shuffledWindowIndices[windowIndicesInShuffled[windowIndex] + 1] + : windowIndex + 1; + } + + @Override + public int getPreviousWindowIndex( + int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) { + if (repeatMode == Player.REPEAT_MODE_ONE) { + return windowIndex; + } + if (windowIndex == getFirstWindowIndex(shuffleModeEnabled)) { + return repeatMode == Player.REPEAT_MODE_ALL + ? getLastWindowIndex(shuffleModeEnabled) + : C.INDEX_UNSET; + } + return shuffleModeEnabled + ? shuffledWindowIndices[windowIndicesInShuffled[windowIndex] - 1] + : windowIndex - 1; + } + + @Override + public int getLastWindowIndex(boolean shuffleModeEnabled) { + if (isEmpty()) { + return C.INDEX_UNSET; + } + return shuffleModeEnabled + ? shuffledWindowIndices[getWindowCount() - 1] + : getWindowCount() - 1; + } + + @Override + public int getFirstWindowIndex(boolean shuffleModeEnabled) { + if (isEmpty()) { + return C.INDEX_UNSET; + } + return shuffleModeEnabled ? shuffledWindowIndices[0] : 0; + } + + @Override + public int getPeriodCount() { + return periods.size(); + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean ignoredSetIds) { + Period p = periods.get(periodIndex); + period.set( + p.id, + p.uid, + p.windowIndex, + p.durationUs, + p.positionInWindowUs, + p.adPlaybackState, + p.isPlaceholder); + return period; + } + + @Override + public int getIndexOfPeriod(Object uid) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getUidOfPeriod(int periodIndex) { + throw new UnsupportedOperationException(); + } + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java b/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java index 71ffb00982..b91e642d14 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java @@ -15,10 +15,16 @@ */ package com.google.android.exoplayer2.audio; +import android.os.Bundle; +import androidx.annotation.IntDef; 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.util.Util; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Attributes for audio playback, which configure the underlying platform {@link @@ -31,7 +37,7 @@ import com.google.android.exoplayer2.util.Util; *

This class is based on {@link android.media.AudioAttributes}, but can be used on all supported * API versions. */ -public final class AudioAttributes { +public final class AudioAttributes implements Bundleable { public static final AudioAttributes DEFAULT = new Builder().build(); @@ -159,4 +165,48 @@ public final class AudioAttributes { return result; } + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_CONTENT_TYPE, FIELD_FLAGS, FIELD_USAGE, FIELD_ALLOWED_CAPTURE_POLICY}) + private @interface FieldNumber {} + + private static final int FIELD_CONTENT_TYPE = 0; + private static final int FIELD_FLAGS = 1; + private static final int FIELD_USAGE = 2; + private static final int FIELD_ALLOWED_CAPTURE_POLICY = 3; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_CONTENT_TYPE), contentType); + bundle.putInt(keyForField(FIELD_FLAGS), flags); + bundle.putInt(keyForField(FIELD_USAGE), usage); + bundle.putInt(keyForField(FIELD_ALLOWED_CAPTURE_POLICY), allowedCapturePolicy); + return bundle; + } + + /** Object that can restore {@link AudioAttributes} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + Builder builder = new Builder(); + if (bundle.containsKey(keyForField(FIELD_CONTENT_TYPE))) { + builder.setContentType(bundle.getInt(keyForField(FIELD_CONTENT_TYPE))); + } + if (bundle.containsKey(keyForField(FIELD_FLAGS))) { + builder.setFlags(bundle.getInt(keyForField(FIELD_FLAGS))); + } + if (bundle.containsKey(keyForField(FIELD_USAGE))) { + builder.setUsage(bundle.getInt(keyForField(FIELD_USAGE))); + } + if (bundle.containsKey(keyForField(FIELD_ALLOWED_CAPTURE_POLICY))) { + builder.setAllowedCapturePolicy(bundle.getInt(keyForField(FIELD_ALLOWED_CAPTURE_POLICY))); + } + return 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/audio/AudioListener.java b/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioListener.java index 1abe6b2f3c..99e83e3060 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioListener.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioListener.java @@ -15,7 +15,14 @@ */ package com.google.android.exoplayer2.audio; -/** A listener for changes in audio configuration. */ +import com.google.android.exoplayer2.Player; + +/** + * A listener for changes in audio configuration. + * + * @deprecated Use {@link Player.Listener}. + */ +@Deprecated public interface AudioListener { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java index 8640c46e1a..3a0f0e093d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java @@ -23,9 +23,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import java.nio.ByteBuffer; import java.util.Arrays; -/** - * Utility methods for parsing DTS frames. - */ +/** Utility methods for parsing DTS frames. */ public final class DtsUtil { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java b/library/common/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java index 8fd25f2cf9..6d74bf820e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.decoder; import com.google.android.exoplayer2.C; -/** - * Base class for buffers with flags. - */ +/** Base class for buffers with flags. */ public abstract class Buffer { @C.BufferFlags diff --git a/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java index 7eaab6ae1d..80486feb91 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; -/** - * Compatibility wrapper for {@link android.media.MediaCodec.CryptoInfo}. - */ +/** Compatibility wrapper for {@link android.media.MediaCodec.CryptoInfo}. */ public final class CryptoInfo { /** @@ -121,12 +119,6 @@ public final class CryptoInfo { return frameworkCryptoInfo; } - /** @deprecated Use {@link #getFrameworkCryptoInfo()}. */ - @Deprecated - public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() { - return getFrameworkCryptoInfo(); - } - /** * Increases the number of clear data for the first sub sample by {@code count}. * diff --git a/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index 4d00ee7748..ff295657e1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -24,9 +24,7 @@ import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -/** - * Holds input for a decoder. - */ +/** Holds input for a decoder. */ public class DecoderInputBuffer extends Buffer { /** @@ -111,12 +109,8 @@ public class DecoderInputBuffer extends Buffer { @BufferReplacementMode private final int bufferReplacementMode; private final int paddingSize; - /** - * Creates a new instance for which {@link #isFlagsOnly()} will return true. - * - * @return A new flags only input buffer. - */ - public static DecoderInputBuffer newFlagsOnlyInstance() { + /** Returns a new instance that's not able to hold any data. */ + public static DecoderInputBuffer newNoDataInstance() { return new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); } @@ -200,14 +194,6 @@ public class DecoderInputBuffer extends Buffer { data = newData; } - /** - * Returns whether the buffer is only able to hold flags, meaning {@link #data} is null and - * its replacement mode is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. - */ - public final boolean isFlagsOnly() { - return data == null && bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DISABLED; - } - /** * Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/device/DeviceInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/device/DeviceInfo.java index 8d662c318e..bfcbcee8ae 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/device/DeviceInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/device/DeviceInfo.java @@ -15,8 +15,10 @@ */ package com.google.android.exoplayer2.device; +import android.os.Bundle; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -24,7 +26,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** Information about the playback device. */ -public final class DeviceInfo { +public final class DeviceInfo implements Bundleable { /** Types of playback. One of {@link #PLAYBACK_TYPE_LOCAL} or {@link #PLAYBACK_TYPE_REMOTE}. */ @Documented @@ -80,4 +82,39 @@ public final class DeviceInfo { result = 31 * result + maxVolume; return result; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_PLAYBACK_TYPE, FIELD_MIN_VOLUME, FIELD_MAX_VOLUME}) + private @interface FieldNumber {} + + private static final int FIELD_PLAYBACK_TYPE = 0; + private static final int FIELD_MIN_VOLUME = 1; + private static final int FIELD_MAX_VOLUME = 2; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_PLAYBACK_TYPE), playbackType); + bundle.putInt(keyForField(FIELD_MIN_VOLUME), minVolume); + bundle.putInt(keyForField(FIELD_MAX_VOLUME), maxVolume); + return bundle; + } + + /** Object that can restore {@link DeviceInfo} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + int playbackType = + bundle.getInt( + keyForField(FIELD_PLAYBACK_TYPE), /* defaultValue= */ PLAYBACK_TYPE_LOCAL); + int minVolume = bundle.getInt(keyForField(FIELD_MIN_VOLUME), /* defaultValue= */ 0); + int maxVolume = bundle.getInt(keyForField(FIELD_MAX_VOLUME), /* defaultValue= */ 0); + return new DeviceInfo(playbackType, minVolume, maxVolume); + }; + + 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/device/DeviceListener.java b/library/common/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java index 3d35c6ad54..408868da79 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java @@ -17,7 +17,12 @@ package com.google.android.exoplayer2.device; import com.google.android.exoplayer2.Player; -/** A listener for changes of {@link Player.DeviceComponent}. */ +/** + * A listener for changes of {@link DeviceInfo} or device volume. + * + * @deprecated Use {@link Player.Listener}. + */ +@Deprecated public interface DeviceListener { /** Called when the device information changes. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java index bc2b8bba86..4113f4c27d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java @@ -29,9 +29,7 @@ import java.util.Comparator; import java.util.List; import java.util.UUID; -/** - * Initialization data for one or more DRM schemes. - */ +/** Initialization data for one or more DRM schemes. */ public final class DrmInitData implements Comparator, Parcelable { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java index 21dacd4f9b..01ae340609 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java @@ -19,13 +19,12 @@ import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.List; -/** - * A collection of metadata entries. - */ +/** A collection of metadata entries. */ public final class Metadata implements Parcelable { /** A metadata entry. */ @@ -48,6 +47,17 @@ public final class Metadata implements Parcelable { default byte[] getWrappedMetadataBytes() { return null; } + + /** + * Updates the {@link MediaMetadata.Builder} with the type specific values stored in this Entry. + * + *

The order of the {@link Entry} objects in the {@link Metadata} matters. If two {@link + * Entry} entries attempt to populate the same {@link MediaMetadata} field, then the last one in + * the list is used. + * + * @param builder The builder to be updated. + */ + default void populateMediaMetadata(MediaMetadata.Builder builder) {} } private final Entry[] entries; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java index 46501ce002..825f690fe8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.metadata; import androidx.annotation.Nullable; import java.nio.ByteBuffer; -/** - * Decodes metadata from binary data. - */ +/** Decodes metadata from binary data. */ public interface MetadataDecoder { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java index a09b565653..55e0b75f71 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.metadata; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -/** - * A {@link DecoderInputBuffer} for a {@link MetadataDecoder}. - */ +/** A {@link DecoderInputBuffer} for a {@link MetadataDecoder}. */ public final class MetadataInputBuffer extends DecoderInputBuffer { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataOutput.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataOutput.java index b635cbc4b2..d203ffd801 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataOutput.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataOutput.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.metadata; -/** - * Receives metadata output. - */ +/** Receives metadata output. */ public interface MetadataOutput { /** @@ -26,5 +24,4 @@ public interface MetadataOutput { * @param metadata The metadata. */ void onMetadata(Metadata metadata); - } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java index 3f4a400677..4f05cc7f08 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java @@ -23,9 +23,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; -/** - * APIC (Attached Picture) ID3 frame. - */ +/** APIC (Attached Picture) ID3 frame. */ public final class ApicFrame extends Id3Frame { public static final String ID = "APIC"; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java index 6c6057bb7a..995418f3b4 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java @@ -22,9 +22,7 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import java.util.Arrays; -/** - * Binary ID3 frame. - */ +/** Binary ID3 frame. */ public final class BinaryFrame extends Id3Frame { public final byte[] data; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java index bf5d2de6ea..120b9269f1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java @@ -23,9 +23,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; -/** - * Chapter information ID3 frame. - */ +/** Chapter information ID3 frame. */ public final class ChapterFrame extends Id3Frame { public static final String ID = "CHAP"; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java index c8aa9bd9ad..5e662c388c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java @@ -22,9 +22,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; -/** - * Chapter table of contents ID3 frame. - */ +/** Chapter table of contents ID3 frame. */ public final class ChapterTocFrame extends Id3Frame { public static final String ID = "CTOC"; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java index 363057f17a..8b2d14444d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java @@ -22,9 +22,7 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; -/** - * Comment ID3 frame. - */ +/** Comment ID3 frame. */ public final class CommentFrame extends Id3Frame { public static final String ID = "COMM"; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java index 6023f76aa1..c0c8ad631f 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java @@ -23,9 +23,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; -/** - * GEOB (General Encapsulated Object) ID3 frame. - */ +/** GEOB (General Encapsulated Object) ID3 frame. */ public final class GeobFrame extends Id3Frame { public static final String ID = "GEOB"; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index f660e21bfd..5bd0e1e3e8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -539,13 +540,13 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int mimeTypeEndIndex; if (majorVersion == 2) { mimeTypeEndIndex = 2; - mimeType = "image/" + Util.toLowerInvariant(new String(data, 0, 3, "ISO-8859-1")); + mimeType = "image/" + Ascii.toLowerCase(new String(data, 0, 3, "ISO-8859-1")); if ("image/jpg".equals(mimeType)) { mimeType = "image/jpeg"; } } else { mimeTypeEndIndex = indexOfZeroByte(data, 0); - mimeType = Util.toLowerInvariant(new String(data, 0, mimeTypeEndIndex, "ISO-8859-1")); + mimeType = Ascii.toLowerCase(new String(data, 0, mimeTypeEndIndex, "ISO-8859-1")); if (mimeType.indexOf('/') == -1) { mimeType = "image/" + mimeType; } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java index 27ea833deb..24e1188879 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.metadata.id3; import com.google.android.exoplayer2.metadata.Metadata; -/** - * Base class for ID3 frames. - */ +/** Base class for ID3 frames. */ public abstract class Id3Frame implements Metadata.Entry { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java index 6e53485453..773e49e846 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java @@ -23,9 +23,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; -/** - * PRIV (Private) ID3 frame. - */ +/** PRIV (Private) ID3 frame. */ public final class PrivFrame extends Id3Frame { public static final String ID = "PRIV"; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java index 8337911c0d..4a36b7afe7 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java @@ -20,11 +20,10 @@ 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.MediaMetadata; import com.google.android.exoplayer2.util.Util; -/** - * Text information ID3 frame. - */ +/** Text information ID3 frame. */ public final class TextInformationFrame extends Id3Frame { @Nullable public final String description; @@ -42,6 +41,30 @@ public final class TextInformationFrame extends Id3Frame { value = castNonNull(in.readString()); } + @Override + public void populateMediaMetadata(MediaMetadata.Builder builder) { + switch (id) { + case "TT2": + case "TIT2": + builder.setTitle(value); + break; + case "TP1": + case "TPE1": + builder.setArtist(value); + break; + case "TP2": + case "TPE2": + builder.setAlbumArtist(value); + break; + case "TAL": + case "TALB": + builder.setAlbumTitle(value); + break; + default: + break; + } + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { @@ -51,7 +74,8 @@ public final class TextInformationFrame extends Id3Frame { return false; } TextInformationFrame other = (TextInformationFrame) obj; - return id.equals(other.id) && Util.areEqual(description, other.description) + return Util.areEqual(id, other.id) + && Util.areEqual(description, other.description) && Util.areEqual(value, other.value); } @@ -90,7 +114,5 @@ public final class TextInformationFrame extends Id3Frame { public TextInformationFrame[] newArray(int size) { return new TextInformationFrame[size]; } - }; - } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java index 298558b662..d9b73ab011 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java @@ -22,9 +22,7 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; -/** - * Url link ID3 frame. - */ +/** Url link ID3 frame. */ public final class UrlLinkFrame extends Id3Frame { @Nullable public final String description; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/MediaPeriodId.java b/library/common/src/main/java/com/google/android/exoplayer2/source/MediaPeriodId.java index ad0e289ba9..8192486a1b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/MediaPeriodId.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/MediaPeriodId.java @@ -152,6 +152,14 @@ public class MediaPeriodId { newPeriodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, nextAdGroupIndex); } + /** Returns a copy of this period identifier with a new {@code windowSequenceNumber}. */ + public MediaPeriodId copyWithWindowSequenceNumber(long windowSequenceNumber) { + return this.windowSequenceNumber == windowSequenceNumber + ? this + : new MediaPeriodId( + periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, nextAdGroupIndex); + } + /** Returns whether this period identifier identifies an ad in an ad group in a period. */ public boolean isAd() { return adGroupIndex != C.INDEX_UNSET; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java index 607f797103..2df780ce93 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java @@ -21,11 +21,14 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Log; import java.util.Arrays; /** Defines an immutable group of tracks identified by their format identity. */ public final class TrackGroup implements Parcelable { + private static final String TAG = "TrackGroup"; + /** The number of tracks in the group. */ public final int length; @@ -41,6 +44,7 @@ public final class TrackGroup implements Parcelable { Assertions.checkState(formats.length > 0); this.formats = formats; this.length = formats.length; + verifyCorrectness(); } /* package */ TrackGroup(Parcel in) { @@ -129,4 +133,62 @@ public final class TrackGroup implements Parcelable { return new TrackGroup[size]; } }; + + private void verifyCorrectness() { + // TrackGroups should only contain tracks with exactly the same content (but in different + // qualities). We only log an error instead of throwing to not break backwards-compatibility for + // cases where malformed TrackGroups happen to work by chance (e.g. because adaptive selections + // are always disabled). + String language = normalizeLanguage(formats[0].language); + @C.RoleFlags int roleFlags = normalizeRoleFlags(formats[0].roleFlags); + for (int i = 1; i < formats.length; i++) { + if (!language.equals(normalizeLanguage(formats[i].language))) { + logErrorMessage( + /* mismatchField= */ "languages", + /* valueIndex0= */ formats[0].language, + /* otherValue=* */ formats[i].language, + /* otherIndex= */ i); + return; + } + if (roleFlags != normalizeRoleFlags(formats[i].roleFlags)) { + logErrorMessage( + /* mismatchField= */ "role flags", + /* valueIndex0= */ Integer.toBinaryString(formats[0].roleFlags), + /* otherValue=* */ Integer.toBinaryString(formats[i].roleFlags), + /* otherIndex= */ i); + return; + } + } + } + + private static String normalizeLanguage(@Nullable String language) { + // Treat all variants of undetermined or unknown languages as compatible. + return language == null || language.equals(C.LANGUAGE_UNDETERMINED) ? "" : language; + } + + @C.RoleFlags + private static int normalizeRoleFlags(@C.RoleFlags int roleFlags) { + // Treat trick-play and non-trick-play formats as compatible. + return roleFlags | C.ROLE_FLAG_TRICK_PLAY; + } + + private static void logErrorMessage( + String mismatchField, + @Nullable String valueIndex0, + @Nullable String otherValue, + int otherIndex) { + Log.e( + TAG, + "", + new IllegalStateException( + "Different " + + mismatchField + + " combined in one TrackGroup: '" + + valueIndex0 + + "' (track 0) and '" + + otherValue + + "' (track " + + otherIndex + + ")")); + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index b70dd10c38..5b207492d9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -15,18 +15,21 @@ */ package com.google.android.exoplayer2.source.ads; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static java.lang.Math.max; import android.net.Uri; +import android.os.Bundle; import androidx.annotation.CheckResult; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -36,7 +39,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; *

Instances are immutable. Call the {@code with*} methods to get new instances that have the * required changes. */ -public final class AdPlaybackState { +public final class AdPlaybackState implements Bundleable { /** * Represents a group of ads, with information about their states. @@ -44,7 +47,7 @@ public final class AdPlaybackState { *

Instances are immutable. Call the {@code with*} methods to get new instances that have the * required changes. */ - public static final class AdGroup { + public static final class AdGroup implements Bundleable { /** The number of ads in the ad group, or {@link C#LENGTH_UNSET} if unknown. */ public final int count; @@ -66,7 +69,7 @@ public final class AdPlaybackState { private AdGroup( int count, @AdState int[] states, @NullableType Uri[] uris, long[] durationsUs) { - Assertions.checkArgument(states.length == uris.length); + checkArgument(states.length == uris.length); this.count = count; this.states = states; this.uris = uris; @@ -162,9 +165,9 @@ public final class AdPlaybackState { */ @CheckResult public AdGroup withAdState(@AdState int state, int index) { - Assertions.checkArgument(count == C.LENGTH_UNSET || index < count); + checkArgument(count == C.LENGTH_UNSET || index < count); @AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1); - Assertions.checkArgument( + checkArgument( states[index] == AD_STATE_UNAVAILABLE || states[index] == AD_STATE_AVAILABLE || states[index] == state); @@ -230,6 +233,55 @@ public final class AdPlaybackState { Arrays.fill(durationsUs, oldDurationsUsCount, newDurationsUsCount, C.TIME_UNSET); return durationsUs; } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FIELD_COUNT, FIELD_URIS, FIELD_STATES, FIELD_DURATIONS_US}) + private @interface FieldNumber {} + + private static final int FIELD_COUNT = 0; + private static final int FIELD_URIS = 1; + private static final int FIELD_STATES = 2; + private static final int FIELD_DURATIONS_US = 3; + + // putParcelableArrayList actually supports null elements. + @SuppressWarnings("nullness:argument.type.incompatible") + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_COUNT), count); + bundle.putParcelableArrayList( + keyForField(FIELD_URIS), new ArrayList<@NullableType Uri>(Arrays.asList(uris))); + bundle.putIntArray(keyForField(FIELD_STATES), states); + bundle.putLongArray(keyForField(FIELD_DURATIONS_US), durationsUs); + return bundle; + } + + /** Object that can restore {@link AdGroup} from a {@link Bundle}. */ + public static final Creator CREATOR = AdGroup::fromBundle; + + // getParcelableArrayList may have null elements. + @SuppressWarnings("nullness:type.argument.type.incompatible") + private static AdGroup fromBundle(Bundle bundle) { + int count = bundle.getInt(keyForField(FIELD_COUNT), /* defaultValue= */ C.LENGTH_UNSET); + @Nullable + ArrayList<@NullableType Uri> uriList = bundle.getParcelableArrayList(keyForField(FIELD_URIS)); + @Nullable + @AdState + int[] states = bundle.getIntArray(keyForField(FIELD_STATES)); + @Nullable long[] durationsUs = bundle.getLongArray(keyForField(FIELD_DURATIONS_US)); + return new AdGroup( + count, + states == null ? new int[0] : states, + uriList == null ? new Uri[0] : uriList.toArray(new Uri[0]), + durationsUs == null ? new long[0] : durationsUs); + } + + private static String keyForField(@AdGroup.FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } /** @@ -312,6 +364,7 @@ public final class AdPlaybackState { @Nullable AdGroup[] adGroups, long adResumePositionUs, long contentDurationUs) { + checkArgument(adGroups == null || adGroups.length == adGroupTimesUs.length); this.adsId = adsId; this.adGroupTimesUs = adGroupTimesUs; this.adResumePositionUs = adResumePositionUs; @@ -327,9 +380,9 @@ public final class AdPlaybackState { } /** - * Returns the index of the ad group at or before {@code positionUs}, if that ad group is - * unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code positionUs} has no - * ads remaining to be played, or if there is no such ad group. + * Returns the index of the ad group at or before {@code positionUs} that should be played before + * the content at {@code positionUs}. Returns {@link C#INDEX_UNSET} if the ad group at or before + * {@code positionUs} has no ads remaining to be played, or if there is no such ad group. * * @param positionUs The period position at or before which to find an ad group, in microseconds, * or {@link C#TIME_END_OF_SOURCE} for the end of the stream (in which case the index of any @@ -349,8 +402,8 @@ public final class AdPlaybackState { } /** - * Returns the index of the next ad group after {@code positionUs} that has ads remaining to be - * played. Returns {@link C#INDEX_UNSET} if there is no such ad group. + * Returns the index of the next ad group after {@code positionUs} that should be played. Returns + * {@link C#INDEX_UNSET} if there is no such ad group. * * @param positionUs The period position after which to find an ad group, in microseconds, or * {@link C#TIME_END_OF_SOURCE} for the end of the stream (in which case there can be no ad @@ -368,8 +421,8 @@ public final class AdPlaybackState { // In practice we expect there to be few ad groups so the search shouldn't be expensive. int index = 0; while (index < adGroupTimesUs.length - && adGroupTimesUs[index] != C.TIME_END_OF_SOURCE - && (positionUs >= adGroupTimesUs[index] || !adGroups[index].hasUnplayedAds())) { + && ((adGroupTimesUs[index] != C.TIME_END_OF_SOURCE && adGroupTimesUs[index] <= positionUs) + || !adGroups[index].hasUnplayedAds())) { index++; } return index < adGroupTimesUs.length ? index : C.INDEX_UNSET; @@ -393,7 +446,7 @@ public final class AdPlaybackState { */ @CheckResult public AdPlaybackState withAdCount(int adGroupIndex, int adCount) { - Assertions.checkArgument(adCount > 0); + checkArgument(adCount > 0); if (adGroups[adGroupIndex].count == adCount) { return this; } @@ -578,4 +631,79 @@ public final class AdPlaybackState { return positionUs < adGroupPositionUs; } } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_AD_GROUP_TIMES_US, + FIELD_AD_GROUPS, + FIELD_AD_RESUME_POSITION_US, + FIELD_CONTENT_DURATION_US + }) + private @interface FieldNumber {} + + private static final int FIELD_AD_GROUP_TIMES_US = 1; + private static final int FIELD_AD_GROUPS = 2; + private static final int FIELD_AD_RESUME_POSITION_US = 3; + private static final int FIELD_CONTENT_DURATION_US = 4; + + /** + * {@inheritDoc} + * + *

It omits the {@link #adsId} field so the {@link #adsId} of instances restored by {@link + * #CREATOR} will always be {@code null}. + */ + // TODO(b/166765820): See if missing adsId would be okay and add adsId to the Bundle otherwise. + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putLongArray(keyForField(FIELD_AD_GROUP_TIMES_US), adGroupTimesUs); + ArrayList adGroupBundleList = new ArrayList<>(); + for (AdGroup adGroup : adGroups) { + adGroupBundleList.add(adGroup.toBundle()); + } + bundle.putParcelableArrayList(keyForField(FIELD_AD_GROUPS), adGroupBundleList); + bundle.putLong(keyForField(FIELD_AD_RESUME_POSITION_US), adResumePositionUs); + bundle.putLong(keyForField(FIELD_CONTENT_DURATION_US), contentDurationUs); + return bundle; + } + + /** + * Object that can restore {@link AdPlaybackState} from a {@link Bundle}. + * + *

The {@link #adsId} of restored instances will always be {@code null}. + */ + public static final Bundleable.Creator CREATOR = AdPlaybackState::fromBundle; + + private static AdPlaybackState fromBundle(Bundle bundle) { + @Nullable long[] adGroupTimesUs = bundle.getLongArray(keyForField(FIELD_AD_GROUP_TIMES_US)); + @Nullable + ArrayList adGroupBundleList = + bundle.getParcelableArrayList(keyForField(FIELD_AD_GROUPS)); + @Nullable AdGroup[] adGroups; + if (adGroupBundleList == null) { + adGroups = null; + } else { + adGroups = new AdGroup[adGroupBundleList.size()]; + for (int i = 0; i < adGroupBundleList.size(); i++) { + adGroups[i] = AdGroup.CREATOR.fromBundle(adGroupBundleList.get(i)); + } + } + long adResumePositionUs = + bundle.getLong(keyForField(FIELD_AD_RESUME_POSITION_US), /* defaultValue= */ 0); + long contentDurationUs = + bundle.getLong(keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ C.TIME_UNSET); + return new AdPlaybackState( + /* adsId= */ null, + adGroupTimesUs == null ? new long[0] : adGroupTimesUs, + adGroups, + adResumePositionUs, + contentDurationUs); + } + + 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/text/Cue.java b/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java index 49a45e1b22..46f865782f 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 @@ -140,6 +140,12 @@ public final class Cue { /** The alignment of the cue text within the cue box, or null if the alignment is undefined. */ @Nullable public final Alignment textAlignment; + /** + * The alignment of multiple lines of text relative to the longest line, or null if the alignment + * is undefined. + */ + @Nullable public final Alignment multiRowAlignment; + /** The cue image, or null if this is a text cue. */ @Nullable public final Bitmap bitmap; @@ -364,6 +370,7 @@ public final class Cue { this( text, textAlignment, + /* multiRowAlignment= */ null, /* bitmap= */ null, line, lineType, @@ -410,6 +417,7 @@ public final class Cue { this( text, textAlignment, + /* multiRowAlignment= */ null, /* bitmap= */ null, line, lineType, @@ -429,6 +437,7 @@ public final class Cue { private Cue( @Nullable CharSequence text, @Nullable Alignment textAlignment, + @Nullable Alignment multiRowAlignment, @Nullable Bitmap bitmap, float line, @LineType int lineType, @@ -451,6 +460,7 @@ public final class Cue { } this.text = text; this.textAlignment = textAlignment; + this.multiRowAlignment = multiRowAlignment; this.bitmap = bitmap; this.line = line; this.lineType = lineType; @@ -477,6 +487,7 @@ public final class Cue { @Nullable private CharSequence text; @Nullable private Bitmap bitmap; @Nullable private Alignment textAlignment; + @Nullable private Alignment multiRowAlignment; private float line; @LineType private int lineType; @AnchorType private int lineAnchor; @@ -495,6 +506,7 @@ public final class Cue { text = null; bitmap = null; textAlignment = null; + multiRowAlignment = null; line = DIMEN_UNSET; lineType = TYPE_UNSET; lineAnchor = TYPE_UNSET; @@ -513,6 +525,7 @@ public final class Cue { text = cue.text; bitmap = cue.bitmap; textAlignment = cue.textAlignment; + multiRowAlignment = cue.multiRowAlignment; line = cue.line; lineType = cue.lineType; lineAnchor = cue.lineAnchor; @@ -592,6 +605,18 @@ public final class Cue { return textAlignment; } + /** + * Sets the multi-row alignment of the cue. + * + *

Passing null means the alignment is undefined. + * + * @see Cue#multiRowAlignment + */ + public Builder setMultiRowAlignment(@Nullable Layout.Alignment multiRowAlignment) { + this.multiRowAlignment = multiRowAlignment; + return this; + } + /** * Sets the position of the cue box within the viewport in the direction orthogonal to the * writing direction. @@ -827,6 +852,7 @@ public final class Cue { return new Cue( text, textAlignment, + multiRowAlignment, bitmap, line, lineType, diff --git a/library/common/src/main/java/com/google/android/exoplayer2/text/TextOutput.java b/library/common/src/main/java/com/google/android/exoplayer2/text/TextOutput.java index a039255fa9..12c781c5f9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/text/TextOutput.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/text/TextOutput.java @@ -17,14 +17,15 @@ package com.google.android.exoplayer2.text; import java.util.List; -/** - * Receives text output. - */ +/** Receives text output. */ public interface TextOutput { /** * Called when there is a change in the {@link Cue Cues}. * + *

{@code cues} is in ascending order of priority. If any of the cue boxes overlap when + * displayed, the {@link Cue} nearer the end of the list should be shown on top. + * * @param cues The {@link Cue Cues}. May be empty. */ void onCues(List cues); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java index dca840790d..e439b5d00e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java @@ -27,6 +27,21 @@ import com.google.android.exoplayer2.source.TrackGroup; */ public interface TrackSelection { + /** An unspecified track selection type. */ + int TYPE_UNSET = 0; + /** The first value that can be used for application specific track selection types. */ + int TYPE_CUSTOM_BASE = 10000; + + /** + * Returns an integer specifying the type of the selection, or {@link #TYPE_UNSET} if not + * specified. + * + *

Track selection types are specific to individual applications, but should be defined + * starting from {@link #TYPE_CUSTOM_BASE} to ensure they don't conflict with any types that may + * be added to the library in the future. + */ + int getType(); + /** Returns the {@link TrackGroup} to which the selected tracks belong. */ TrackGroup getTrackGroup(); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ui/AdOverlayInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/ui/AdOverlayInfo.java new file mode 100644 index 0000000000..b3eee8970a --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/ui/AdOverlayInfo.java @@ -0,0 +1,74 @@ +/* + * 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.ui; + +import android.view.View; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Provides information about an overlay view shown on top of an ad view group. */ +public final class AdOverlayInfo { + + /** + * The purpose of the overlay. One of {@link #PURPOSE_CONTROLS}, {@link #PURPOSE_CLOSE_AD}, {@link + * #PURPOSE_OTHER} or {@link #PURPOSE_NOT_VISIBLE}. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({PURPOSE_CONTROLS, PURPOSE_CLOSE_AD, PURPOSE_OTHER, PURPOSE_NOT_VISIBLE}) + public @interface Purpose {} + /** Purpose for playback controls overlaying the player. */ + public static final int PURPOSE_CONTROLS = 0; + /** Purpose for ad close buttons overlaying the player. */ + public static final int PURPOSE_CLOSE_AD = 1; + /** Purpose for other overlays. */ + public static final int PURPOSE_OTHER = 2; + /** Purpose for overlays that are not visible. */ + public static final int PURPOSE_NOT_VISIBLE = 3; + + /** The overlay view. */ + public final View view; + /** The purpose of the overlay view. */ + @Purpose public final int purpose; + /** An optional, detailed reason that the overlay view is needed. */ + @Nullable public final String reasonDetail; + + /** + * Creates a new overlay info. + * + * @param view The view that is overlaying the player. + * @param purpose The purpose of the view. + */ + public AdOverlayInfo(View view, @Purpose int purpose) { + this(view, purpose, /* detailedReason= */ null); + } + + /** + * Creates a new overlay info. + * + * @param view The view that is overlaying the player. + * @param purpose The purpose of the view. + * @param detailedReason An optional, detailed reason that the view is on top of the player. + */ + public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) { + this.view = view; + this.purpose = purpose; + this.reasonDetail = detailedReason; + } +} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ui/AdViewProvider.java b/library/common/src/main/java/com/google/android/exoplayer2/ui/AdViewProvider.java new file mode 100644 index 0000000000..dd6fa84184 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/ui/AdViewProvider.java @@ -0,0 +1,48 @@ +/* + * 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.ui; + +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import com.google.common.collect.ImmutableList; +import java.util.List; + +/** Provides information about views for the ad playback UI. */ +public interface AdViewProvider { + + /** + * Returns the {@link ViewGroup} on top of the player that will show any ad UI, or {@code null} if + * playing audio-only ads. Any views on top of the returned view group must be described by {@link + * AdOverlayInfo AdOverlayInfos} returned by {@link #getAdOverlayInfos()}, for accurate + * viewability measurement. + */ + @Nullable + ViewGroup getAdViewGroup(); + + /** + * Returns a list of {@link AdOverlayInfo} instances describing views that are on top of the ad + * view group, but that are essential for controlling playback and should be excluded from ad + * viewability measurements. + * + *

Each view must be either a fully transparent overlay (for capturing touch events), or a + * small piece of transient UI that is essential to the user experience of playback (such as a + * button to pause/resume playback or a transient full-screen or cast button). For more + * information see the documentation for your ads loader. + */ + default List getAdOverlayInfos() { + return ImmutableList.of(); + } +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/package-info.java b/library/common/src/main/java/com/google/android/exoplayer2/ui/package-info.java similarity index 100% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/package-info.java rename to library/common/src/main/java/com/google/android/exoplayer2/ui/package-info.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java index bbc182d7af..c157002809 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java @@ -45,17 +45,31 @@ public interface DataSource extends DataReader { void addTransferListener(TransferListener transferListener); /** - * Opens the source to read the specified data. - *

- * Note: If an {@link IOException} is thrown, callers must still call {@link #close()} to ensure - * that any partial effects of the invocation are cleaned up. + * Opens the source to read the specified data. If an {@link IOException} is thrown, callers must + * still call {@link #close()} to ensure that any partial effects of the invocation are cleaned + * up. + * + *

The following edge case behaviors apply: + * + *

    + *
  • If the {@link DataSpec#position requested position} is within the resource, but the + * {@link DataSpec#length requested length} extends beyond the end of the resource, then + * {@link #open} will succeed and data from the requested position to the end of the + * resource will be made available through {@link #read}. + *
  • If the {@link DataSpec#position requested position} is equal to the length of the + * resource, then {@link #open} will succeed, and {@link #read} will immediately return + * {@link C#RESULT_END_OF_INPUT}. + *
  • If the {@link DataSpec#position requested position} is greater than the length of the + * resource, then {@link #open} will throw an {@link IOException} for which {@link + * DataSourceException#isCausedByPositionOutOfRange} will be {@code true}. + *
* * @param dataSpec Defines the data to be read. * @throws IOException If an error occurs opening the source. {@link DataSourceException} can be * thrown or used as a cause of the thrown exception to specify the reason of the error. * @return The number of bytes that can be read from the opened source. For unbounded requests - * (i.e. requests where {@link DataSpec#length} equals {@link C#LENGTH_UNSET}) this value - * is the resolved length of the request, or {@link C#LENGTH_UNSET} if the length is still + * (i.e., requests where {@link DataSpec#length} equals {@link C#LENGTH_UNSET}) this value is + * the resolved length of the request, or {@link C#LENGTH_UNSET} if the length is still * unresolved. For all other requests, the value returned will be equal to the request's * {@link DataSpec#length}. */ @@ -82,10 +96,8 @@ public interface DataSource extends DataReader { } /** - * Closes the source. - *

- * Note: This method must be called even if the corresponding call to {@link #open(DataSpec)} - * threw an {@link IOException}. See {@link #open(DataSpec)} for more details. + * Closes the source. This method must be called even if the corresponding call to {@link + * #open(DataSpec)} threw an {@link IOException}. * * @throws IOException If an error occurs closing the source. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java index a45b7db2f2..c3ccdb88d9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.upstream; import androidx.annotation.Nullable; import java.io.IOException; -/** - * Used to specify reason of a DataSource error. - */ +/** Used to specify reason of a DataSource error. */ public final class DataSourceException extends IOException { /** @@ -41,6 +39,10 @@ public final class DataSourceException extends IOException { return false; } + /** + * Indicates that the {@link DataSpec#position starting position} of the request was outside the + * bounds of the data. + */ public static final int POSITION_OUT_OF_RANGE = 0; /** @@ -56,5 +58,4 @@ public final class DataSourceException extends IOException { public DataSourceException(int reason) { this.reason = reason; } - } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 575a10b6cd..88193c2646 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -15,21 +15,21 @@ */ package com.google.android.exoplayer2.upstream; +import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; -import static java.lang.Math.max; import static java.lang.Math.min; import android.net.Uri; -import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.base.Predicate; -import java.io.EOFException; +import com.google.common.net.HttpHeaders; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; @@ -43,10 +43,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}. @@ -207,8 +204,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou private static final int HTTP_STATUS_TEMPORARY_REDIRECT = 307; private static final int HTTP_STATUS_PERMANENT_REDIRECT = 308; private static final long MAX_BYTES_TO_DRAIN = 2048; - private static final Pattern CONTENT_RANGE_HEADER = - Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$"); private final boolean allowCrossProtocolRedirects; private final int connectTimeoutMillis; @@ -221,14 +216,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou @Nullable private DataSpec dataSpec; @Nullable private HttpURLConnection connection; @Nullable private InputStream inputStream; - private byte @MonotonicNonNull [] skipBuffer; private boolean opened; private int responseCode; - - private long bytesToSkip; private long bytesToRead; - - private long bytesSkipped; private long bytesRead; /** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */ @@ -341,8 +331,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { this.dataSpec = dataSpec; - this.bytesRead = 0; - this.bytesSkipped = 0; + bytesRead = 0; + bytesToRead = 0; transferInitializing(dataSpec); try { @@ -350,7 +340,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou } catch (IOException e) { @Nullable String message = e.getMessage(); if (message != null - && Util.toLowerInvariant(message).matches("cleartext http traffic.*not permitted.*")) { + && Ascii.toLowerCase(message).matches("cleartext http traffic.*not permitted.*")) { throw new CleartextNotPermittedException(e, dataSpec); } throw new HttpDataSourceException( @@ -371,6 +361,16 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou // Check for a valid response code. if (responseCode < 200 || responseCode > 299) { Map> headers = connection.getHeaderFields(); + if (responseCode == 416) { + long documentSize = + HttpUtil.getDocumentSize(connection.getHeaderField(HttpHeaders.CONTENT_RANGE)); + if (dataSpec.position == documentSize) { + opened = true; + transferStarted(dataSpec); + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0; + } + } + @Nullable InputStream errorStream = connection.getErrorStream(); byte[] errorResponseBody; try { @@ -383,7 +383,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou InvalidResponseCodeException exception = new InvalidResponseCodeException( responseCode, responseMessage, headers, dataSpec, errorResponseBody); - if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } @@ -400,7 +399,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou // If we requested a range starting from a non-zero position and received a 200 rather than a // 206, then the server does not support partial requests. We'll need to manually skip to the // requested position. - bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; + long bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; // Determine the length of the data to be read, after skipping. boolean isCompressed = isCompressed(connection); @@ -408,7 +407,10 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou if (dataSpec.length != C.LENGTH_UNSET) { bytesToRead = dataSpec.length; } else { - long contentLength = getContentLength(connection); + long contentLength = + HttpUtil.getContentLength( + connection.getHeaderField(HttpHeaders.CONTENT_LENGTH), + connection.getHeaderField(HttpHeaders.CONTENT_RANGE)); bytesToRead = contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) : C.LENGTH_UNSET; } @@ -432,13 +434,21 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou opened = true; transferStarted(dataSpec); + try { + if (!skipFully(bytesToSkip)) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + } catch (IOException e) { + closeConnectionQuietly(); + throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_OPEN); + } + return bytesToRead; } @Override public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException { try { - skipInternal(); return readInternal(buffer, offset, readLength); } catch (IOException e) { throw new HttpDataSourceException( @@ -451,7 +461,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou try { @Nullable InputStream inputStream = this.inputStream; if (inputStream != null) { - maybeTerminateInputStream(connection, bytesRemaining()); + long bytesRemaining = + bytesToRead == C.LENGTH_UNSET ? C.LENGTH_UNSET : bytesToRead - bytesRead; + maybeTerminateInputStream(connection, bytesRemaining); try { inputStream.close(); } catch (IOException e) { @@ -469,48 +481,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou } } - /** - * Returns the current connection, or null if the source is not currently opened. - * - * @return The current open connection, or null. - */ - @Nullable - protected final HttpURLConnection getConnection() { - return connection; - } - - /** - * Returns the number of bytes that have been skipped since the most recent call to - * {@link #open(DataSpec)}. - * - * @return The number of bytes skipped. - */ - protected final long bytesSkipped() { - return bytesSkipped; - } - - /** - * Returns the number of bytes that have been read since the most recent call to - * {@link #open(DataSpec)}. - * - * @return The number of bytes read. - */ - protected final long bytesRead() { - return bytesRead; - } - - /** - * Returns the number of bytes that are still to be read for the current {@link DataSpec}. - *

- * If the total length of the data being read is known, then this length minus {@code bytesRead()} - * is returned. If the total length is unknown, {@link C#LENGTH_UNSET} is returned. - * - * @return The remaining length, or {@link C#LENGTH_UNSET}. - */ - protected final long bytesRemaining() { - return bytesToRead == C.LENGTH_UNSET ? bytesToRead : bytesToRead - bytesRead; - } - /** * Establishes a connection, following redirects to do so where permitted. */ @@ -616,17 +586,14 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou connection.setRequestProperty(property.getKey(), property.getValue()); } - if (!(position == 0 && length == C.LENGTH_UNSET)) { - String rangeRequest = "bytes=" + position + "-"; - if (length != C.LENGTH_UNSET) { - rangeRequest += (position + length - 1); - } - connection.setRequestProperty("Range", rangeRequest); + @Nullable String rangeHeader = buildRangeRequestHeader(position, length); + if (rangeHeader != null) { + connection.setRequestProperty(HttpHeaders.RANGE, rangeHeader); } if (userAgent != null) { - connection.setRequestProperty("User-Agent", userAgent); + connection.setRequestProperty(HttpHeaders.USER_AGENT, userAgent); } - connection.setRequestProperty("Accept-Encoding", allowGzip ? "gzip" : "identity"); + connection.setRequestProperty(HttpHeaders.ACCEPT_ENCODING, allowGzip ? "gzip" : "identity"); connection.setInstanceFollowRedirects(followRedirects); connection.setDoOutput(httpBody != null); connection.setRequestMethod(DataSpec.getStringForHttpMethod(httpMethod)); @@ -679,80 +646,32 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou } /** - * Attempts to extract the length of the content from the response headers of an open connection. - * - * @param connection The open connection. - * @return The extracted length, or {@link C#LENGTH_UNSET}. - */ - private static long getContentLength(HttpURLConnection connection) { - long contentLength = C.LENGTH_UNSET; - String contentLengthHeader = connection.getHeaderField("Content-Length"); - if (!TextUtils.isEmpty(contentLengthHeader)) { - try { - contentLength = Long.parseLong(contentLengthHeader); - } catch (NumberFormatException e) { - Log.e(TAG, "Unexpected Content-Length [" + contentLengthHeader + "]"); - } - } - String contentRangeHeader = connection.getHeaderField("Content-Range"); - if (!TextUtils.isEmpty(contentRangeHeader)) { - Matcher matcher = CONTENT_RANGE_HEADER.matcher(contentRangeHeader); - if (matcher.find()) { - try { - long contentLengthFromRange = - Long.parseLong(checkNotNull(matcher.group(2))) - - Long.parseLong(checkNotNull(matcher.group(1))) - + 1; - if (contentLength < 0) { - // Some proxy servers strip the Content-Length header. Fall back to the length - // calculated here in this case. - contentLength = contentLengthFromRange; - } else if (contentLength != contentLengthFromRange) { - // If there is a discrepancy between the Content-Length and Content-Range headers, - // assume the one with the larger value is correct. We have seen cases where carrier - // change one of them to reduce the size of a request, but it is unlikely anybody would - // increase it. - Log.w(TAG, "Inconsistent headers [" + contentLengthHeader + "] [" + contentRangeHeader - + "]"); - contentLength = max(contentLength, contentLengthFromRange); - } - } catch (NumberFormatException e) { - Log.e(TAG, "Unexpected Content-Range [" + contentRangeHeader + "]"); - } - } - } - return contentLength; - } - - /** - * Skips any bytes that need skipping. Else does nothing. - *

- * This implementation is based roughly on {@code libcore.io.Streams.skipByReading()}. + * Attempts to skip the specified number of bytes in full. * + * @param bytesToSkip The number of bytes to skip. * @throws InterruptedIOException If the thread is interrupted during the operation. - * @throws EOFException If the end of the input stream is reached before the bytes are skipped. + * @throws IOException If an error occurs reading from the source. + * @return Whether the bytes were skipped in full. If {@code false} then the data ended before the + * specified number of bytes were skipped. Always {@code true} if {@code bytesToSkip == 0}. */ - private void skipInternal() throws IOException { - if (bytesSkipped == bytesToSkip) { - return; + private boolean skipFully(long bytesToSkip) throws IOException { + if (bytesToSkip == 0) { + return true; } - - if (skipBuffer == null) { - skipBuffer = new byte[4096]; - } - - while (bytesSkipped != bytesToSkip) { - int readLength = (int) min(bytesToSkip - bytesSkipped, skipBuffer.length); + byte[] skipBuffer = new byte[4096]; + while (bytesToSkip > 0) { + int readLength = (int) min(bytesToSkip, skipBuffer.length); int read = castNonNull(inputStream).read(skipBuffer, 0, readLength); if (Thread.currentThread().isInterrupted()) { throw new InterruptedIOException(); } if (read == -1) { - throw new EOFException(); + return false; } - bytesSkipped += read; + bytesToSkip -= read; bytesTransferred(read); } + return true; } /** @@ -783,10 +702,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou int read = castNonNull(inputStream).read(buffer, offset, readLength); if (read == -1) { - if (bytesToRead != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new EOFException(); - } return C.RESULT_END_OF_INPUT; } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index e2ae38b013..dbaa686066 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -19,6 +19,7 @@ import android.text.TextUtils; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.base.Predicate; import java.io.IOException; import java.lang.annotation.Documented; @@ -29,9 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -/** - * An HTTP {@link DataSource}. - */ +/** An HTTP {@link DataSource}. */ public interface HttpDataSource extends DataSource { /** @@ -182,7 +181,10 @@ public interface HttpDataSource extends DataSource { /** A {@link Predicate} that rejects content types often used for pay-walls. */ Predicate REJECT_PAYWALL_TYPES = contentType -> { - contentType = Util.toLowerInvariant(contentType); + if (contentType == null) { + return false; + } + contentType = Ascii.toLowerCase(contentType); return !TextUtils.isEmpty(contentType) && (!contentType.contains("text") || contentType.contains("text/vtt")) && !contentType.contains("html") diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java new file mode 100644 index 0000000000..ac433009a7 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpUtil.java @@ -0,0 +1,129 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static java.lang.Math.max; + +import android.text.TextUtils; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Log; +import com.google.common.net.HttpHeaders; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Utility methods for HTTP. */ +public final class HttpUtil { + + private static final String TAG = "HttpUtil"; + private static final Pattern CONTENT_RANGE_WITH_START_AND_END = + Pattern.compile("bytes (\\d+)-(\\d+)/(?:\\d+|\\*)"); + private static final Pattern CONTENT_RANGE_WITH_SIZE = + Pattern.compile("bytes (?:(?:\\d+-\\d+)|\\*)/(\\d+)"); + + /** Class only contains static methods. */ + private HttpUtil() {} + + /** + * Builds a {@link HttpHeaders#RANGE Range header} for the given position and length. + * + * @param position The request position. + * @param length The request length, or {@link C#LENGTH_UNSET} if the request is unbounded. + * @return The corresponding range header, or {@code null} if a header is unnecessary because the + * whole resource is being requested. + */ + @Nullable + public static String buildRangeRequestHeader(long position, long length) { + if (position == 0 && length == C.LENGTH_UNSET) { + return null; + } + StringBuilder rangeValue = new StringBuilder(); + rangeValue.append("bytes="); + rangeValue.append(position); + rangeValue.append("-"); + if (length != C.LENGTH_UNSET) { + rangeValue.append(position + length - 1); + } + return rangeValue.toString(); + } + + /** + * Attempts to parse the document size from a {@link HttpHeaders#CONTENT_RANGE Content-Range + * header}. + * + * @param contentRangeHeader The {@link HttpHeaders#CONTENT_RANGE Content-Range header}, or {@code + * null} if not set. + * @return The document size, or {@link C#LENGTH_UNSET} if it could not be determined. + */ + public static long getDocumentSize(@Nullable String contentRangeHeader) { + if (TextUtils.isEmpty(contentRangeHeader)) { + return C.LENGTH_UNSET; + } + Matcher matcher = CONTENT_RANGE_WITH_SIZE.matcher(contentRangeHeader); + return matcher.matches() ? Long.parseLong(checkNotNull(matcher.group(1))) : C.LENGTH_UNSET; + } + + /** + * Attempts to parse the length of a response body from the corresponding response headers. + * + * @param contentLengthHeader The {@link HttpHeaders#CONTENT_LENGTH Content-Length header}, or + * {@code null} if not set. + * @param contentRangeHeader The {@link HttpHeaders#CONTENT_RANGE Content-Range header}, or {@code + * null} if not set. + * @return The length of the response body, or {@link C#LENGTH_UNSET} if it could not be + * determined. + */ + public static long getContentLength( + @Nullable String contentLengthHeader, @Nullable String contentRangeHeader) { + long contentLength = C.LENGTH_UNSET; + if (!TextUtils.isEmpty(contentLengthHeader)) { + try { + contentLength = Long.parseLong(contentLengthHeader); + } catch (NumberFormatException e) { + Log.e(TAG, "Unexpected Content-Length [" + contentLengthHeader + "]"); + } + } + if (!TextUtils.isEmpty(contentRangeHeader)) { + Matcher matcher = CONTENT_RANGE_WITH_START_AND_END.matcher(contentRangeHeader); + if (matcher.matches()) { + try { + long contentLengthFromRange = + Long.parseLong(checkNotNull(matcher.group(2))) + - Long.parseLong(checkNotNull(matcher.group(1))) + + 1; + if (contentLength < 0) { + // Some proxy servers strip the Content-Length header. Fall back to the length + // calculated here in this case. + contentLength = contentLengthFromRange; + } else if (contentLength != contentLengthFromRange) { + // If there is a discrepancy between the Content-Length and Content-Range headers, + // assume the one with the larger value is correct. We have seen cases where carrier + // change one of them to reduce the size of a request, but it is unlikely anybody would + // increase it. + Log.w( + TAG, + "Inconsistent headers [" + contentLengthHeader + "] [" + contentRangeHeader + "]"); + contentLength = max(contentLength, contentLengthFromRange); + } + } catch (NumberFormatException e) { + Log.e(TAG, "Unexpected Content-Range [" + contentRangeHeader + "]"); + } + } + } + return contentLength; + } +} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Assertions.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Assertions.java index c6173730ff..0bb65e55e0 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Assertions.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Assertions.java @@ -22,9 +22,7 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.dataflow.qual.Pure; -/** - * Provides methods for asserting the truth of expressions and properties. - */ +/** Provides methods for asserting the truth of expressions and properties. */ public final class Assertions { private Assertions() {} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/BundleUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/BundleUtil.java new file mode 100644 index 0000000000..1c1e139d80 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/BundleUtil.java @@ -0,0 +1,111 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import android.os.Bundle; +import android.os.IBinder; +import androidx.annotation.Nullable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** Utilities for {@link Bundle}. */ +public final class BundleUtil { + + private static final String TAG = "BundleUtil"; + + @Nullable private static Method getIBinderMethod; + @Nullable private static Method putIBinderMethod; + + /** + * Gets an {@link IBinder} inside a {@link Bundle} for all Android versions. + * + * @param bundle The bundle to get the {@link IBinder}. + * @param key The key to use while getting the {@link IBinder}. + * @return The {@link IBinder} that was obtained. + */ + @Nullable + public static IBinder getBinder(Bundle bundle, @Nullable String key) { + if (Util.SDK_INT >= 18) { + return bundle.getBinder(key); + } else { + return getBinderByReflection(bundle, key); + } + } + + /** + * Puts an {@link IBinder} inside a {@link Bundle} for all Android versions. + * + * @param bundle The bundle to insert the {@link IBinder}. + * @param key The key to use while putting the {@link IBinder}. + * @param binder The {@link IBinder} to put. + */ + public static void putBinder(Bundle bundle, @Nullable String key, @Nullable IBinder binder) { + if (Util.SDK_INT >= 18) { + bundle.putBinder(key, binder); + } else { + putBinderByReflection(bundle, key, binder); + } + } + + // Method.invoke may take null "key". + @SuppressWarnings("nullness:argument.type.incompatible") + @Nullable + private static IBinder getBinderByReflection(Bundle bundle, @Nullable String key) { + @Nullable Method getIBinder = getIBinderMethod; + if (getIBinder == null) { + try { + getIBinderMethod = Bundle.class.getMethod("getIBinder", String.class); + getIBinderMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + Log.i(TAG, "Failed to retrieve getIBinder method", e); + return null; + } + getIBinder = getIBinderMethod; + } + + try { + return (IBinder) getIBinder.invoke(bundle, key); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + Log.i(TAG, "Failed to invoke getIBinder via reflection", e); + return null; + } + } + + // Method.invoke may take null "key" and "binder". + @SuppressWarnings("nullness:argument.type.incompatible") + private static void putBinderByReflection( + Bundle bundle, @Nullable String key, @Nullable IBinder binder) { + @Nullable Method putIBinder = putIBinderMethod; + if (putIBinder == null) { + try { + putIBinderMethod = Bundle.class.getMethod("putIBinder", String.class, IBinder.class); + putIBinderMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + Log.i(TAG, "Failed to retrieve putIBinder method", e); + return; + } + putIBinder = putIBinderMethod; + } + + try { + putIBinder.invoke(bundle, key, binder); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + Log.i(TAG, "Failed to invoke putIBinder via reflection", e); + } + } + + private BundleUtil() {} +} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Clock.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Clock.java index ffb8236bd1..8ecb2ab8ec 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Clock.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Clock.java @@ -43,9 +43,6 @@ public interface Clock { /** @see android.os.SystemClock#uptimeMillis() */ long uptimeMillis(); - /** @see android.os.SystemClock#sleep(long) */ - void sleep(long sleepTimeMs); - /** * Creates a {@link HandlerWrapper} using a specified looper and a specified callback for handling * messages. @@ -53,4 +50,12 @@ public interface Clock { * @see Handler#Handler(Looper, Handler.Callback) */ HandlerWrapper createHandler(Looper looper, @Nullable Handler.Callback callback); + + /** + * Notifies the clock that the current thread is about to be blocked and won't return until a + * condition on another thread becomes true. + * + *

Should be a no-op for all non-test cases. + */ + void onThreadBlocked(); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/CopyOnWriteMultiset.java b/library/common/src/main/java/com/google/android/exoplayer2/util/CopyOnWriteMultiset.java index 505ff55cbe..c473e2206b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/CopyOnWriteMultiset.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/CopyOnWriteMultiset.java @@ -138,4 +138,11 @@ public final class CopyOnWriteMultiset implements Iterable return elements.iterator(); } } + + /** Returns the number of occurrences of an element in this multiset. */ + public int count(E element) { + synchronized (lock) { + return elementCounts.containsKey(element) ? elementCounts.get(element) : 0; + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ErrorMessageProvider.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ErrorMessageProvider.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/util/ErrorMessageProvider.java rename to library/common/src/main/java/com/google/android/exoplayer2/util/ErrorMessageProvider.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ExoFlags.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ExoFlags.java new file mode 100644 index 0000000000..46c9e486df --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ExoFlags.java @@ -0,0 +1,179 @@ +/* + * 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.util; + +import static com.google.android.exoplayer2.util.Assertions.checkIndex; +import static com.google.android.exoplayer2.util.Assertions.checkState; + +import android.util.SparseBooleanArray; +import androidx.annotation.Nullable; + +/** + * A set of integer flags. + * + *

Intended for usages where the number of flags may exceed 32 and can no longer be represented + * by an IntDef. + * + *

Instances are immutable. + */ +public final class ExoFlags { + + /** A builder for {@link ExoFlags} instances. */ + public static final class Builder { + + private final SparseBooleanArray flags; + + private boolean buildCalled; + + /** Creates a builder. */ + public Builder() { + flags = new SparseBooleanArray(); + } + + /** + * Adds a flag. + * + * @param flag A flag. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder add(int flag) { + checkState(!buildCalled); + flags.append(flag, /* value= */ true); + return this; + } + + /** + * Adds a flag if the provided condition is true. Does nothing otherwise. + * + * @param flag A flag. + * @param condition A condition. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder addIf(int flag, boolean condition) { + if (condition) { + return add(flag); + } + return this; + } + + /** + * Adds flags. + * + * @param flags The flags to add. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder addAll(int... flags) { + for (int flag : flags) { + add(flag); + } + return this; + } + + /** + * Adds {@link ExoFlags flags}. + * + * @param flags The set of flags to add. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder addAll(ExoFlags flags) { + for (int i = 0; i < flags.size(); i++) { + add(flags.get(i)); + } + return this; + } + + /** + * Builds an {@link ExoFlags} instance. + * + * @throws IllegalStateException If this method has already been called. + */ + public ExoFlags build() { + checkState(!buildCalled); + buildCalled = true; + return new ExoFlags(flags); + } + } + + // A SparseBooleanArray is used instead of a Set to avoid auto-boxing the flag values. + private final SparseBooleanArray flags; + + private ExoFlags(SparseBooleanArray flags) { + this.flags = flags; + } + + /** + * Returns whether the set contains the given flag. + * + * @param flag The flag. + * @return Whether the set contains the flag. + */ + public boolean contains(int flag) { + return flags.get(flag); + } + + /** + * Returns whether the set contains at least one of the given flags. + * + * @param flags The flags. + * @return Whether the set contains at least one of the flags. + */ + public boolean containsAny(int... flags) { + for (int flag : flags) { + if (contains(flag)) { + return true; + } + } + return false; + } + + /** Returns the number of flags in this set. */ + public int size() { + return flags.size(); + } + + /** + * Returns the flag at the given index. + * + * @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive). + * @return The flag at the given index. + * @throws IndexOutOfBoundsException If index is outside the allowed range. + */ + public int get(int index) { + checkIndex(index, /* start= */ 0, /* limit= */ size()); + return flags.keyAt(index); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ExoFlags)) { + return false; + } + ExoFlags that = (ExoFlags) o; + return flags.equals(that.flags); + } + + @Override + public int hashCode() { + return flags.hashCode(); + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java rename to library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java b/library/common/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java index edf775bd5b..8247447d93 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.util; import android.os.Handler; import android.os.Looper; -import android.os.Message; import androidx.annotation.Nullable; /** @@ -26,6 +25,16 @@ import androidx.annotation.Nullable; */ public interface HandlerWrapper { + /** A message obtained from the handler. */ + interface Message { + + /** See {@link android.os.Message#sendToTarget()}. */ + void sendToTarget(); + + /** See {@link android.os.Message#getTarget()}. */ + HandlerWrapper getTarget(); + } + /** See {@link Handler#getLooper()}. */ Looper getLooper(); @@ -44,6 +53,9 @@ public interface HandlerWrapper { /** See {@link Handler#obtainMessage(int, int, int, Object)}. */ Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj); + /** See {@link Handler#sendMessageAtFrontOfQueue(android.os.Message)}. */ + boolean sendMessageAtFrontOfQueue(Message message); + /** See {@link Handler#sendEmptyMessage(int)}. */ boolean sendEmptyMessage(int what); @@ -64,4 +76,7 @@ public interface HandlerWrapper { /** See {@link Handler#postDelayed(Runnable, long)}. */ boolean postDelayed(Runnable runnable, long delayMs); + + /** See {@link android.os.Handler#postAtFrontOfQueue(Runnable)}. */ + boolean postAtFrontOfQueue(Runnable runnable); } 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 a9a749e47f..fe220b1946 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 @@ -20,7 +20,6 @@ import android.os.Message; import androidx.annotation.CheckResult; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.common.base.Supplier; import java.util.ArrayDeque; import java.util.concurrent.CopyOnWriteArraySet; import javax.annotation.Nonnull; @@ -35,9 +34,8 @@ import javax.annotation.Nonnull; * was enqueued and haven't been removed since. * * @param The listener type. - * @param The {@link MutableFlags} type used to indicate which events occurred. */ -public final class ListenerSet { +public final class ListenerSet { /** * An event sent to a listener. @@ -55,17 +53,17 @@ public final class ListenerSet { * iteration were handled by the listener. * * @param The listener type. - * @param The {@link MutableFlags} type used to indicate which events occurred. */ - public interface IterationFinishedEvent { + public interface IterationFinishedEvent { /** * Invokes the iteration finished event. * * @param listener The listener to invoke the event on. - * @param eventFlags The combined event flags of all events sent in this iteration. + * @param eventFlags The combined event {@link ExoFlags flags} of all events sent in this + * iteration. */ - void invoke(T listener, E eventFlags); + void invoke(T listener, ExoFlags eventFlags); } private static final int MSG_ITERATION_FINISHED = 0; @@ -73,9 +71,8 @@ public final class ListenerSet { private final Clock clock; private final HandlerWrapper handler; - private final Supplier eventFlagsSupplier; - private final IterationFinishedEvent iterationFinishedEvent; - private final CopyOnWriteArraySet> listeners; + private final IterationFinishedEvent iterationFinishedEvent; + private final CopyOnWriteArraySet> listeners; private final ArrayDeque flushingEvents; private final ArrayDeque queuedEvents; @@ -87,33 +84,24 @@ public final class ListenerSet { * @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used * to call all other methods of this class. * @param clock A {@link Clock}. - * @param eventFlagsSupplier A {@link Supplier} for new instances of {@link E the event flags - * type}. * @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent * during one {@link Looper} message queue iteration were handled by the listeners. */ - public ListenerSet( - Looper looper, - Clock clock, - Supplier eventFlagsSupplier, - IterationFinishedEvent iterationFinishedEvent) { + public ListenerSet(Looper looper, Clock clock, IterationFinishedEvent iterationFinishedEvent) { this( /* listeners= */ new CopyOnWriteArraySet<>(), looper, clock, - eventFlagsSupplier, iterationFinishedEvent); } private ListenerSet( - CopyOnWriteArraySet> listeners, + CopyOnWriteArraySet> listeners, Looper looper, Clock clock, - Supplier eventFlagsSupplier, - IterationFinishedEvent iterationFinishedEvent) { + IterationFinishedEvent iterationFinishedEvent) { this.clock = clock; this.listeners = listeners; - this.eventFlagsSupplier = eventFlagsSupplier; this.iterationFinishedEvent = iterationFinishedEvent; flushingEvents = new ArrayDeque<>(); queuedEvents = new ArrayDeque<>(); @@ -132,9 +120,8 @@ public final class ListenerSet { * @return The copied listener set. */ @CheckResult - public ListenerSet copy( - Looper looper, IterationFinishedEvent iterationFinishedEvent) { - return new ListenerSet<>(listeners, looper, clock, eventFlagsSupplier, iterationFinishedEvent); + public ListenerSet copy(Looper looper, IterationFinishedEvent iterationFinishedEvent) { + return new ListenerSet<>(listeners, looper, clock, iterationFinishedEvent); } /** @@ -149,7 +136,7 @@ public final class ListenerSet { return; } Assertions.checkNotNull(listener); - listeners.add(new ListenerHolder<>(listener, eventFlagsSupplier)); + listeners.add(new ListenerHolder<>(listener)); } /** @@ -160,7 +147,7 @@ public final class ListenerSet { * @param listener The listener to be removed. */ public void remove(T listener) { - for (ListenerHolder listenerHolder : listeners) { + for (ListenerHolder listenerHolder : listeners) { if (listenerHolder.listener.equals(listener)) { listenerHolder.release(iterationFinishedEvent); listeners.remove(listenerHolder); @@ -176,11 +163,10 @@ public final class ListenerSet { * @param event The event. */ public void queueEvent(int eventFlag, Event event) { - CopyOnWriteArraySet> listenerSnapshot = - new CopyOnWriteArraySet<>(listeners); + CopyOnWriteArraySet> listenerSnapshot = new CopyOnWriteArraySet<>(listeners); queuedEvents.add( () -> { - for (ListenerHolder holder : listenerSnapshot) { + for (ListenerHolder holder : listenerSnapshot) { holder.invoke(eventFlag, event); } }); @@ -226,7 +212,7 @@ public final class ListenerSet { *

This will ensure no events are sent to any listener after this method has been called. */ public void release() { - for (ListenerHolder listenerHolder : listeners) { + for (ListenerHolder listenerHolder : listeners) { listenerHolder.release(iterationFinishedEvent); } listeners.clear(); @@ -249,8 +235,8 @@ public final class ListenerSet { private boolean handleMessage(Message message) { if (message.what == MSG_ITERATION_FINISHED) { - for (ListenerHolder holder : listeners) { - holder.iterationFinished(eventFlagsSupplier, iterationFinishedEvent); + for (ListenerHolder holder : listeners) { + holder.iterationFinished(iterationFinishedEvent); if (handler.hasMessages(MSG_ITERATION_FINISHED)) { // The invocation above triggered new events (and thus scheduled a new message). We need // to stop here because this new message will take care of informing every listener about @@ -268,45 +254,44 @@ public final class ListenerSet { return true; } - private static final class ListenerHolder { + private static final class ListenerHolder { @Nonnull public final T listener; - private E eventsFlags; + private ExoFlags.Builder flagsBuilder; private boolean needsIterationFinishedEvent; private boolean released; - public ListenerHolder(@Nonnull T listener, Supplier eventFlagSupplier) { + public ListenerHolder(@Nonnull T listener) { this.listener = listener; - this.eventsFlags = eventFlagSupplier.get(); + this.flagsBuilder = new ExoFlags.Builder(); } - public void release(IterationFinishedEvent event) { + public void release(IterationFinishedEvent event) { released = true; if (needsIterationFinishedEvent) { - event.invoke(listener, eventsFlags); + event.invoke(listener, flagsBuilder.build()); } } public void invoke(int eventFlag, Event event) { if (!released) { if (eventFlag != C.INDEX_UNSET) { - eventsFlags.add(eventFlag); + flagsBuilder.add(eventFlag); } needsIterationFinishedEvent = true; event.invoke(listener); } } - public void iterationFinished( - Supplier eventFlagSupplier, IterationFinishedEvent event) { + public void iterationFinished(IterationFinishedEvent event) { if (!released && needsIterationFinishedEvent) { // Reset flags before invoking the listener to ensure we keep all new flags that are set by // recursive events triggered from this callback. - E flagToNotify = eventsFlags; - eventsFlags = eventFlagSupplier.get(); + ExoFlags flagsToNotify = flagsBuilder.build(); + flagsBuilder = new ExoFlags.Builder(); needsIterationFinishedEvent = false; - event.invoke(listener, flagToNotify); + event.invoke(listener, flagsToNotify); } } @@ -318,7 +303,7 @@ public final class ListenerSet { if (other == null || getClass() != other.getClass()) { return false; } - return listener.equals(((ListenerHolder) other).listener); + return listener.equals(((ListenerHolder) other).listener); } @Override diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/LongArray.java b/library/common/src/main/java/com/google/android/exoplayer2/util/LongArray.java index 6d9725ad3d..d6a1733697 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/LongArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/LongArray.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.util; import java.util.Arrays; -/** - * An append-only, auto-growing {@code long[]}. - */ +/** An append-only, auto-growing {@code long[]}. */ public final class LongArray { private static final int DEFAULT_INITIAL_CAPACITY = 32; 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 new file mode 100644 index 0000000000..653a1e3a99 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MediaFormatUtil.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2018 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.util; + +import android.annotation.SuppressLint; +import android.media.AudioFormat; +import android.media.MediaFormat; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.video.ColorInfo; +import java.nio.ByteBuffer; +import java.util.List; + +/** Helper class containing utility methods for managing {@link MediaFormat} instances. */ +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 = + "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 + * C#ENCODING_PCM_24BIT}, and {@link C#ENCODING_PCM_32BIT}. + */ + public static final String KEY_EXO_PCM_ENCODING = "exo-pcm-encoding-int"; + + private static final int MAX_POWER_OF_TWO_INT = 1 << 30; + + /** + * Returns a {@link MediaFormat} representing the given ExoPlayer {@link Format}. + * + *

May include the following custom keys: + * + *

    + *
  • {@link #KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT}. + *
  • {@link #KEY_EXO_PCM_ENCODING}. + *
+ */ + @SuppressLint("InlinedApi") // Inlined MediaFormat keys. + public static MediaFormat createMediaFormatFromFormat(Format format) { + MediaFormat result = new MediaFormat(); + maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate); + maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount); + + maybeSetColorInfo(result, format.colorInfo); + + maybeSetString(result, MediaFormat.KEY_MIME, format.sampleMimeType); + maybeSetString(result, MediaFormat.KEY_CODECS_STRING, format.codecs); + maybeSetFloat(result, MediaFormat.KEY_FRAME_RATE, format.frameRate); + maybeSetInteger(result, MediaFormat.KEY_WIDTH, format.width); + maybeSetInteger(result, MediaFormat.KEY_HEIGHT, format.height); + + setCsdBuffers(result, format.initializationData); + maybeSetPcmEncoding(result, format.pcmEncoding); + maybeSetString(result, MediaFormat.KEY_LANGUAGE, format.language); + maybeSetInteger(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); + maybeSetInteger(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate); + maybeSetInteger(result, MediaFormat.KEY_CAPTION_SERVICE_NUMBER, format.accessibilityChannel); + result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees); + + int selectionFlags = format.selectionFlags; + setBooleanAsInt( + result, MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT); + setBooleanAsInt(result, MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT); + setBooleanAsInt( + result, MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED); + + result.setInteger(MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay); + result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding); + + maybeSetPixelAspectRatio(result, format.pixelWidthHeightRatio); + return result; + } + + /** + * Sets a {@link MediaFormat} {@link String} value. Does nothing if {@code value} is null. + * + * @param format The {@link MediaFormat} being configured. + * @param key The key to set. + * @param value The value to set. + */ + public static void maybeSetString(MediaFormat format, String key, @Nullable String value) { + if (value != null) { + format.setString(key, value); + } + } + + /** + * Sets a {@link MediaFormat}'s codec specific data buffers. + * + * @param format The {@link MediaFormat} being configured. + * @param csdBuffers The csd buffers to set. + */ + public static void setCsdBuffers(MediaFormat format, List csdBuffers) { + for (int i = 0; i < csdBuffers.size(); i++) { + format.setByteBuffer("csd-" + i, ByteBuffer.wrap(csdBuffers.get(i))); + } + } + + /** + * Sets a {@link MediaFormat} integer value. Does nothing if {@code value} is {@link + * Format#NO_VALUE}. + * + * @param format The {@link MediaFormat} being configured. + * @param key The key to set. + * @param value The value to set. + */ + public static void maybeSetInteger(MediaFormat format, String key, int value) { + if (value != Format.NO_VALUE) { + format.setInteger(key, value); + } + } + + /** + * Sets a {@link MediaFormat} float value. Does nothing if {@code value} is {@link + * Format#NO_VALUE}. + * + * @param format The {@link MediaFormat} being configured. + * @param key The key to set. + * @param value The value to set. + */ + public static void maybeSetFloat(MediaFormat format, String key, float value) { + if (value != Format.NO_VALUE) { + format.setFloat(key, value); + } + } + + /** + * Sets a {@link MediaFormat} {@link ByteBuffer} value. Does nothing if {@code value} is null. + * + * @param format The {@link MediaFormat} being configured. + * @param key The key to set. + * @param value The byte array that will be wrapped to obtain the value. + */ + public static void maybeSetByteBuffer(MediaFormat format, String key, @Nullable byte[] value) { + if (value != null) { + format.setByteBuffer(key, ByteBuffer.wrap(value)); + } + } + + /** + * Sets a {@link MediaFormat}'s color information. Does nothing if {@code colorInfo} is null. + * + * @param format The {@link MediaFormat} being configured. + * @param colorInfo The color info to set. + */ + @SuppressWarnings("InlinedApi") + public static void maybeSetColorInfo(MediaFormat format, @Nullable ColorInfo colorInfo) { + if (colorInfo != null) { + maybeSetInteger(format, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); + maybeSetInteger(format, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace); + maybeSetInteger(format, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange); + maybeSetByteBuffer(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo); + } + } + + // Internal methods. + + private static void setBooleanAsInt(MediaFormat format, String key, int value) { + format.setInteger(key, value != 0 ? 1 : 0); + } + + // Inlined MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH and MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT. + @SuppressLint("InlinedApi") + private static void maybeSetPixelAspectRatio( + MediaFormat mediaFormat, float pixelWidthHeightRatio) { + mediaFormat.setFloat(KEY_EXO_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 + // pixel aspect ratio width and height by using a large power of two factor. + if (pixelWidthHeightRatio < 1.0f) { + pixelAspectRatioHeight = MAX_POWER_OF_TWO_INT; + pixelAspectRatioWidth = (int) (pixelWidthHeightRatio * pixelAspectRatioHeight); + } else if (pixelWidthHeightRatio > 1.0f) { + pixelAspectRatioWidth = MAX_POWER_OF_TWO_INT; + pixelAspectRatioHeight = (int) (pixelAspectRatioWidth / pixelWidthHeightRatio); + } + mediaFormat.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, pixelAspectRatioWidth); + mediaFormat.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, pixelAspectRatioHeight); + } + + @SuppressLint("InlinedApi") // Inlined KEY_PCM_ENCODING. + private static void maybeSetPcmEncoding( + MediaFormat mediaFormat, @C.PcmEncoding int exoPcmEncoding) { + if (exoPcmEncoding == Format.NO_VALUE) { + return; + } + int mediaFormatPcmEncoding; + maybeSetInteger(mediaFormat, KEY_EXO_PCM_ENCODING, exoPcmEncoding); + switch (exoPcmEncoding) { + case C.ENCODING_PCM_8BIT: + mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT; + break; + case C.ENCODING_PCM_16BIT: + mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_16BIT; + break; + case C.ENCODING_PCM_FLOAT: + mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_FLOAT; + break; + default: + // No matching value. Do nothing. + return; + } + mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding); + } + + private MediaFormatUtil() {} +} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index 13cf6b18c3..8615bc826e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -20,13 +20,12 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.audio.AacUtil; +import com.google.common.base.Ascii; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * Defines common MIME types and helper methods. - */ +/** Defines common MIME types and helper methods. */ public final class MimeTypes { public static final String BASE_TYPE_VIDEO = "video"; @@ -113,6 +112,7 @@ public final class MimeTypes { public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif"; public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy"; public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/vnd.dvb.ait"; + public static final String APPLICATION_RTSP = BASE_TYPE_APPLICATION + "/x-rtsp"; public static final String IMAGE_JPEG = BASE_TYPE_IMAGE + "/jpeg"; @@ -340,7 +340,7 @@ public final class MimeTypes { if (codec == null) { return null; } - codec = Util.toLowerInvariant(codec.trim()); + codec = Ascii.toLowerCase(codec.trim()); if (codec.startsWith("avc1") || codec.startsWith("avc3")) { return MimeTypes.VIDEO_H264; } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MutableFlags.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MutableFlags.java deleted file mode 100644 index 6ed425de18..0000000000 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MutableFlags.java +++ /dev/null @@ -1,108 +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.util; - -import android.util.SparseBooleanArray; -import androidx.annotation.Nullable; - -/** - * A set of integer flags. - * - *

Intended for usages where the number of flags may exceed 32 and can no longer be represented - * by an IntDef. - */ -public class MutableFlags { - - private final SparseBooleanArray flags; - - /** Creates the set of flags. */ - public MutableFlags() { - flags = new SparseBooleanArray(); - } - - /** Clears all previously set flags. */ - public void clear() { - flags.clear(); - } - - /** - * Adds a flag to the set. - * - * @param flag The flag to add. - */ - public void add(int flag) { - flags.append(flag, /* value= */ true); - } - - /** - * Returns whether the set contains the given flag. - * - * @param flag The flag. - * @return Whether the set contains the flag. - */ - public boolean contains(int flag) { - return flags.get(flag); - } - - /** - * Returns whether the set contains at least one of the given flags. - * - * @param flags The flags. - * @return Whether the set contains at least one of the flags. - */ - public boolean containsAny(int... flags) { - for (int flag : flags) { - if (contains(flag)) { - return true; - } - } - return false; - } - - /** Returns the number of flags in this set. */ - public int size() { - return flags.size(); - } - - /** - * Returns the flag at the given index. - * - * @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive). - * @return The flag at the given index. - * @throws IllegalArgumentException If index is outside the allowed range. - */ - public int get(int index) { - Assertions.checkArgument(index >= 0 && index < size()); - return flags.keyAt(index); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (!(o instanceof MutableFlags)) { - return false; - } - MutableFlags that = (MutableFlags) o; - return flags.equals(that.flags); - } - - @Override - public int hashCode() { - return flags.hashCode(); - } -} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java index 4831ec59e2..d6d4a7aa01 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java @@ -19,9 +19,7 @@ import androidx.annotation.Nullable; import java.nio.ByteBuffer; import java.util.Arrays; -/** - * Utility methods for handling H.264/AVC and H.265/HEVC NAL units. - */ +/** Utility methods for handling H.264/AVC and H.265/HEVC NAL units. */ public final class NalUnitUtil { private static final String TAG = "NalUnitUtil"; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index 3ad5fd9703..c14eee6f88 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -20,9 +20,7 @@ import static java.lang.Math.min; import com.google.common.base.Charsets; import java.nio.charset.Charset; -/** - * Wraps a byte array, providing methods that allow it to be read as a bitstream. - */ +/** Wraps a byte array, providing methods that allow it to be read as a bitstream. */ public final class ParsableBitArray { public byte[] data; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java index 6d34a4190e..6738ed7b0e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java @@ -17,9 +17,9 @@ package com.google.android.exoplayer2.util; /** * Wraps a byte array, providing methods that allow it to be read as a NAL unit bitstream. - *

- * Whenever the byte sequence [0, 0, 3] appears in the wrapped byte array, it is treated as [0, 0] - * for all reading/skipping operations, which makes the bitstream appear to be unescaped. + * + *

Whenever the byte sequence [0, 0, 3] appears in the wrapped byte array, it is treated as [0, + * 0] for all reading/skipping operations, which makes the bitstream appear to be unescaped. */ public final class ParsableNalUnitBitArray { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java similarity index 98% rename from library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java rename to library/common/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java index 3485877bc4..0acc430b0c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java @@ -21,9 +21,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * Util class for repeat mode handling. - */ +/** Util class for repeat mode handling. */ public final class RepeatModeUtil { // LINT.IfChange @@ -91,5 +89,4 @@ public final class RepeatModeUtil { return false; } } - } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/SystemClock.java b/library/common/src/main/java/com/google/android/exoplayer2/util/SystemClock.java index 89e1c60d7a..c3b31aa5c9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/SystemClock.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/SystemClock.java @@ -43,13 +43,13 @@ public class SystemClock implements Clock { return android.os.SystemClock.uptimeMillis(); } - @Override - public void sleep(long sleepTimeMs) { - android.os.SystemClock.sleep(sleepTimeMs); - } - @Override public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) { return new SystemHandlerWrapper(new Handler(looper, callback)); } + + @Override + public void onThreadBlocked() { + // Do nothing. + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java b/library/common/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java index 7b504f0779..ecb5ad64b1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java @@ -15,13 +15,23 @@ */ package com.google.android.exoplayer2.util; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + +import android.os.Handler; import android.os.Looper; -import android.os.Message; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; /** The standard implementation of {@link HandlerWrapper}. */ /* package */ final class SystemHandlerWrapper implements HandlerWrapper { + private static final int MAX_POOL_SIZE = 50; + + @GuardedBy("messagePool") + private static final List messagePool = new ArrayList<>(MAX_POOL_SIZE); + private final android.os.Handler handler; public SystemHandlerWrapper(android.os.Handler handler) { @@ -40,22 +50,29 @@ import androidx.annotation.Nullable; @Override public Message obtainMessage(int what) { - return handler.obtainMessage(what); + return obtainSystemMessage().setMessage(handler.obtainMessage(what), /* handler= */ this); } @Override public Message obtainMessage(int what, @Nullable Object obj) { - return handler.obtainMessage(what, obj); + return obtainSystemMessage().setMessage(handler.obtainMessage(what, obj), /* handler= */ this); } @Override public Message obtainMessage(int what, int arg1, int arg2) { - return handler.obtainMessage(what, arg1, arg2); + return obtainSystemMessage() + .setMessage(handler.obtainMessage(what, arg1, arg2), /* handler= */ this); } @Override public Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj) { - return handler.obtainMessage(what, arg1, arg2, obj); + return obtainSystemMessage() + .setMessage(handler.obtainMessage(what, arg1, arg2, obj), /* handler= */ this); + } + + @Override + public boolean sendMessageAtFrontOfQueue(Message message) { + return ((SystemMessage) message).sendAtFrontOfQueue(handler); } @Override @@ -92,4 +109,60 @@ import androidx.annotation.Nullable; public boolean postDelayed(Runnable runnable, long delayMs) { return handler.postDelayed(runnable, delayMs); } + + @Override + public boolean postAtFrontOfQueue(Runnable runnable) { + return handler.postAtFrontOfQueue(runnable); + } + + private static SystemMessage obtainSystemMessage() { + synchronized (messagePool) { + return messagePool.isEmpty() + ? new SystemMessage() + : messagePool.remove(messagePool.size() - 1); + } + } + + private static void recycleMessage(SystemMessage message) { + synchronized (messagePool) { + if (messagePool.size() < MAX_POOL_SIZE) { + messagePool.add(message); + } + } + } + + private static final class SystemMessage implements Message { + + @Nullable private android.os.Message message; + @Nullable private SystemHandlerWrapper handler; + + public SystemMessage setMessage(android.os.Message message, SystemHandlerWrapper handler) { + this.message = message; + this.handler = handler; + return this; + } + + public boolean sendAtFrontOfQueue(Handler handler) { + boolean success = handler.sendMessageAtFrontOfQueue(checkNotNull(message)); + recycle(); + return success; + } + + @Override + public void sendToTarget() { + checkNotNull(message).sendToTarget(); + recycle(); + } + + @Override + public HandlerWrapper getTarget() { + return checkNotNull(handler); + } + + private void recycle() { + message = null; + handler = null; + recycleMessage(this); + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java b/library/common/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java rename to library/common/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java index 823fd1a0a5..df6ffd557c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.util; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; -/** - * Calls through to {@link android.os.Trace} methods on supported API levels. - */ +/** Calls through to {@link android.os.Trace} methods on supported API levels. */ public final class TraceUtil { private TraceUtil() {} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 61907c5175..07c7a0a776 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -37,8 +37,6 @@ import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; import android.media.AudioFormat; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Handler; @@ -98,9 +96,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.PolyNull; -/** - * Miscellaneous utility methods. - */ +/** Miscellaneous utility methods. */ public final class Util { /** @@ -538,7 +534,7 @@ public final class Util { * @param threadName The name of the thread. * @return The executor. */ - public static ExecutorService newSingleThreadExecutor(final String threadName) { + public static ExecutorService newSingleThreadExecutor(String threadName) { return Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, threadName)); } @@ -672,11 +668,11 @@ public final class Util { // Locale data (especially for API < 21) may produce tags with '_' instead of the // standard-conformant '-'. String normalizedTag = language.replace('_', '-'); - if (normalizedTag.isEmpty() || "und".equals(normalizedTag)) { + if (normalizedTag.isEmpty() || normalizedTag.equals(C.LANGUAGE_UNDETERMINED)) { // Tag isn't valid, keep using the original. normalizedTag = language; } - normalizedTag = Util.toLowerInvariant(normalizedTag); + normalizedTag = Ascii.toLowerCase(normalizedTag); String mainLanguage = Util.splitAtFirst(normalizedTag, "-")[0]; if (languageTagReplacementMap == null) { languageTagReplacementMap = createIsoLanguageReplacementMap(); @@ -762,26 +758,6 @@ public final class Util { return c == '\n' || c == '\r'; } - /** - * Converts text to lower case using {@link Locale#US}. - * - * @param text The text to convert. - * @return The lower case text, or null if {@code text} is null. - */ - public static @PolyNull String toLowerInvariant(@PolyNull String text) { - return text == null ? text : text.toLowerCase(Locale.US); - } - - /** - * Converts text to upper case using {@link Locale#US}. - * - * @param text The text to convert. - * @return The upper case text, or null if {@code text} is null. - */ - public static @PolyNull String toUpperInvariant(@PolyNull String text) { - return text == null ? text : text.toUpperCase(Locale.US); - } - /** * Formats a string using {@link Locale#US}. * @@ -1786,7 +1762,7 @@ public final class Util { * @return The derived {@link UUID}, or {@code null} if one could not be derived. */ public static @Nullable UUID getDrmUuid(String drmScheme) { - switch (toLowerInvariant(drmScheme)) { + switch (Ascii.toLowerCase(drmScheme)) { case "widevine": return C.WIDEVINE_UUID; case "playready": @@ -1824,6 +1800,11 @@ public final class Util { */ @ContentType public static int inferContentType(Uri uri) { + @Nullable String scheme = uri.getScheme(); + if (scheme != null && Ascii.equalsIgnoreCase("rtsp", scheme)) { + return C.TYPE_RTSP; + } + @Nullable String path = uri.getPath(); return path == null ? C.TYPE_OTHER : inferContentType(path); } @@ -1836,7 +1817,7 @@ public final class Util { */ @ContentType public static int inferContentType(String fileName) { - fileName = toLowerInvariant(fileName); + fileName = Ascii.toLowerCase(fileName); if (fileName.endsWith(".mpd")) { return C.TYPE_DASH; } else if (fileName.endsWith(".m3u8")) { @@ -1876,6 +1857,8 @@ public final class Util { return C.TYPE_HLS; case MimeTypes.APPLICATION_SS: return C.TYPE_SS; + case MimeTypes.APPLICATION_RTSP: + return C.TYPE_RTSP; default: return C.TYPE_OTHER; } @@ -1909,11 +1892,11 @@ public final class Util { * @return The fixed URI. */ public static Uri fixSmoothStreamingIsmManifestUri(Uri uri) { - @Nullable String path = toLowerInvariant(uri.getPath()); + @Nullable String path = uri.getPath(); if (path == null) { return uri; } - Matcher ismMatcher = ISM_URL_PATTERN.matcher(path); + Matcher ismMatcher = ISM_URL_PATTERN.matcher(Ascii.toLowerCase(path)); if (ismMatcher.matches() && ismMatcher.group(1) == null) { // Add missing "Manifest" suffix. return Uri.withAppendedPath(uri, "Manifest"); @@ -2148,52 +2131,6 @@ public final class Util { return buffer.order() == ByteOrder.BIG_ENDIAN ? value : Integer.reverseBytes(value); } - /** - * Returns the {@link C.NetworkType} of the current network connection. - * - * @param context A context to access the connectivity manager. - * @return The {@link C.NetworkType} of the current network connection. - */ - // Intentional null check to guard against user input. - @SuppressWarnings("known.nonnull") - @C.NetworkType - public static int getNetworkType(Context context) { - if (context == null) { - // Note: This is for backward compatibility only (context used to be @Nullable). - return C.NETWORK_TYPE_UNKNOWN; - } - NetworkInfo networkInfo; - @Nullable - ConnectivityManager connectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivityManager == null) { - return C.NETWORK_TYPE_UNKNOWN; - } - try { - networkInfo = connectivityManager.getActiveNetworkInfo(); - } catch (SecurityException e) { - // Expected if permission was revoked. - return C.NETWORK_TYPE_UNKNOWN; - } - if (networkInfo == null || !networkInfo.isConnected()) { - return C.NETWORK_TYPE_OFFLINE; - } - switch (networkInfo.getType()) { - case ConnectivityManager.TYPE_WIFI: - return C.NETWORK_TYPE_WIFI; - case ConnectivityManager.TYPE_WIMAX: - return C.NETWORK_TYPE_4G; - case ConnectivityManager.TYPE_MOBILE: - case ConnectivityManager.TYPE_MOBILE_DUN: - case ConnectivityManager.TYPE_MOBILE_HIPRI: - return getMobileNetworkType(networkInfo); - case ConnectivityManager.TYPE_ETHERNET: - return C.NETWORK_TYPE_ETHERNET; - default: - return C.NETWORK_TYPE_OTHER; - } - } - /** * Returns the upper-case ISO 3166-1 alpha-2 country code of the current registered operator's MCC * (Mobile Country Code), or the country code of the default Locale if not available. @@ -2209,11 +2146,11 @@ public final class Util { if (telephonyManager != null) { String countryCode = telephonyManager.getNetworkCountryIso(); if (!TextUtils.isEmpty(countryCode)) { - return toUpperInvariant(countryCode); + return Ascii.toUpperCase(countryCode); } } } - return toUpperInvariant(Locale.getDefault().getCountry()); + return Ascii.toUpperCase(Locale.getDefault().getCountry()); } /** @@ -2482,38 +2419,6 @@ public final class Util { return locale.toLanguageTag(); } - private static @C.NetworkType int getMobileNetworkType(NetworkInfo networkInfo) { - switch (networkInfo.getSubtype()) { - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_GPRS: - return C.NETWORK_TYPE_2G; - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_IDEN: - case TelephonyManager.NETWORK_TYPE_UMTS: - case TelephonyManager.NETWORK_TYPE_EHRPD: - case TelephonyManager.NETWORK_TYPE_HSPAP: - case TelephonyManager.NETWORK_TYPE_TD_SCDMA: - return C.NETWORK_TYPE_3G; - case TelephonyManager.NETWORK_TYPE_LTE: - return C.NETWORK_TYPE_4G; - case TelephonyManager.NETWORK_TYPE_NR: - return SDK_INT >= 29 ? C.NETWORK_TYPE_5G : C.NETWORK_TYPE_UNKNOWN; - case TelephonyManager.NETWORK_TYPE_IWLAN: - return C.NETWORK_TYPE_WIFI; - case TelephonyManager.NETWORK_TYPE_GSM: - case TelephonyManager.NETWORK_TYPE_UNKNOWN: - default: // Future mobile network types. - return C.NETWORK_TYPE_CELLULAR_UNKNOWN; - } - } - private static HashMap createIsoLanguageReplacementMap() { String[] iso2Languages = Locale.getISOLanguages(); HashMap replacedLanguages = diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java index a9b252b775..9b291f6f85 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java @@ -19,9 +19,7 @@ import androidx.annotation.Nullable; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -/** - * {@link XmlPullParser} utility methods. - */ +/** {@link XmlPullParser} utility methods. */ public final class XmlPullParserUtil { private XmlPullParserUtil() {} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java b/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java index 183cfe09df..975b66f093 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java @@ -25,9 +25,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import java.util.ArrayList; import java.util.List; -/** - * AVC configuration data. - */ +/** AVC configuration data. */ public final class AvcConfig { public final List initializationData; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java index d45d6c55b2..6d66c91f6c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java @@ -23,9 +23,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; -/** - * Stores color info. - */ +/** Stores color info. */ public final class ColorInfo implements Parcelable { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java b/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java index 9ef12a5c33..c058457665 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java @@ -24,9 +24,7 @@ import com.google.android.exoplayer2.util.ParsableNalUnitBitArray; import java.util.Collections; import java.util.List; -/** - * HEVC configuration data. - */ +/** HEVC configuration data. */ public final class HevcConfig { /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java index eb013ed425..6a05230dce 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java @@ -16,26 +16,25 @@ package com.google.android.exoplayer2.video; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Player; -/** A listener for metadata corresponding to video being rendered. */ +/** + * A listener for metadata corresponding to video being rendered. + * + * @deprecated Use {@link Player.Listener}. + */ +@Deprecated public interface VideoListener { /** * Called each time there's a change in the size of the video being rendered. * - * @param width The video width in pixels. - * @param height The video height in pixels. - * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise - * rotation in degrees that the application should apply for the video for it to be rendered - * in the correct orientation. This value will always be zero on API levels 21 and above, - * since the renderer will apply all necessary rotations internally. On earlier API levels - * this is not possible. Applications that use {@link android.view.TextureView} can apply the - * rotation by calling {@link android.view.TextureView#setTransform}. Applications that do not - * expect to encounter rotated videos can safely ignore this parameter. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of - * square pixels this will be equal to 1.0. Different values are indicative of anamorphic - * content. + * @param videoSize The new size of the video. */ + default void onVideoSizeChanged(VideoSize videoSize) {} + + /** @deprecated Use {@link #onVideoSizeChanged(VideoSize videoSize)}. */ + @Deprecated default void onVideoSizeChanged( int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java new file mode 100644 index 0000000000..d6e16a44ae --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java @@ -0,0 +1,171 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import android.os.Bundle; +import androidx.annotation.FloatRange; +import androidx.annotation.IntDef; +import androidx.annotation.IntRange; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Represents the video size. */ +public final class VideoSize implements Bundleable { + + private static final int DEFAULT_WIDTH = 0; + private static final int DEFAULT_HEIGHT = 0; + private static final int DEFAULT_UNAPPLIED_ROTATION_DEGREES = 0; + private static final float DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO = 1F; + + public static final VideoSize UNKNOWN = new VideoSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + + /** The video width in pixels, 0 when unknown. */ + @IntRange(from = 0) + public final int width; + + /** The video height in pixels, 0 when unknown. */ + @IntRange(from = 0) + public final int height; + + /** + * Clockwise rotation in degrees that the application should apply for the video for it to be + * rendered in the correct orientation. + * + *

Is 0 if unknown or if no rotation is needed. + * + *

Player should apply video rotation internally, in which case unappliedRotationDegrees is 0. + * But when a player can't apply the rotation, for example before API level 21, the unapplied + * rotation is reported by this field for application to handle. + * + *

Applications that use {@link android.view.TextureView} can apply the rotation by calling + * {@link android.view.TextureView#setTransform}. + */ + @IntRange(from = 0, to = 359) + public final int unappliedRotationDegrees; + + /** + * The width to height ratio of each pixel, 1 if unknown. + * + *

For the normal case of square pixels this will be equal to 1.0. Different values are + * indicative of anamorphic content. + */ + @FloatRange(from = 0, fromInclusive = false) + public final float pixelWidthHeightRatio; + + /** + * Creates a VideoSize without unapplied rotation or anamorphic content. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + */ + public VideoSize(@IntRange(from = 0) int width, @IntRange(from = 0) int height) { + this(width, height, DEFAULT_UNAPPLIED_ROTATION_DEGREES, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO); + } + + /** + * Creates a VideoSize. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + * @param unappliedRotationDegrees Clockwise rotation in degrees that the application should apply + * for the video for it to be rendered in the correct orientation. See {@link + * #unappliedRotationDegrees}. + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of + * square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * content. + */ + public VideoSize( + @IntRange(from = 0) int width, + @IntRange(from = 0) int height, + @IntRange(from = 0, to = 359) int unappliedRotationDegrees, + @FloatRange(from = 0, fromInclusive = false) float pixelWidthHeightRatio) { + this.width = width; + this.height = height; + this.unappliedRotationDegrees = unappliedRotationDegrees; + this.pixelWidthHeightRatio = pixelWidthHeightRatio; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof VideoSize) { + VideoSize other = (VideoSize) obj; + return width == other.width + && height == other.height + && unappliedRotationDegrees == other.unappliedRotationDegrees + && pixelWidthHeightRatio == other.pixelWidthHeightRatio; + } + return false; + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + width; + result = 31 * result + height; + result = 31 * result + unappliedRotationDegrees; + result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio); + return result; + } + + // Bundleable implementation. + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_WIDTH, + FIELD_HEIGHT, + FIELD_UNAPPLIED_ROTATION_DEGREES, + FIELD_PIXEL_WIDTH_HEIGHT_RATIO, + }) + private @interface FieldNumber {} + + private static final int FIELD_WIDTH = 0; + private static final int FIELD_HEIGHT = 1; + private static final int FIELD_UNAPPLIED_ROTATION_DEGREES = 2; + private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 3; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_WIDTH), width); + bundle.putInt(keyForField(FIELD_HEIGHT), height); + bundle.putInt(keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), unappliedRotationDegrees); + bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); + return bundle; + } + + public static final Creator CREATOR = + bundle -> { + int width = bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT_WIDTH); + int height = bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT_HEIGHT); + int unappliedRotationDegrees = + bundle.getInt( + keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), DEFAULT_UNAPPLIED_ROTATION_DEGREES); + float pixelWidthHeightRatio = + bundle.getFloat( + keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO); + return new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/BundleListRetrieverTest.java b/library/common/src/test/java/com/google/android/exoplayer2/BundleListRetrieverTest.java new file mode 100644 index 0000000000..e74c95f0f3 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/BundleListRetrieverTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.os.Bundle; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.ext.truth.os.BundleSubject; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link BundleListRetriever}. */ +@RunWith(AndroidJUnit4.class) +public class BundleListRetrieverTest { + + @Test + public void getList_preservedLargeList() { + int count = 100_000; + List listBefore = new ArrayList<>(); + for (int i = 0; i < count; i++) { + Bundle bundle = new Bundle(); + bundle.putInt("i", i); + listBefore.add(bundle); + } + + List listAfter = BundleListRetriever.getList(new BundleListRetriever(listBefore)); + + for (int i = 0; i < count; i++) { + Bundle bundle = listAfter.get(i); + BundleSubject.assertThat(bundle).integer("i").isEqualTo(i); + } + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/ExoPlaybackExceptionTest.java b/library/common/src/test/java/com/google/android/exoplayer2/ExoPlaybackExceptionTest.java new file mode 100644 index 0000000000..1721ce807a --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/ExoPlaybackExceptionTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.util.Util; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link ExoPlaybackException}. */ +@RunWith(AndroidJUnit4.class) +public class ExoPlaybackExceptionTest { + + @Test + public void roundTripViaBundle_ofExoPlaybackExceptionTypeRemote_yieldsEqualInstance() { + ExoPlaybackException before = ExoPlaybackException.createForRemote(/* message= */ "test"); + ExoPlaybackException after = ExoPlaybackException.CREATOR.fromBundle(before.toBundle()); + assertThat(areEqual(before, after)).isTrue(); + } + + @Test + public void roundTripViaBundle_ofExoPlaybackExceptionTypeRenderer_yieldsEqualInstance() { + ExoPlaybackException before = + ExoPlaybackException.createForRenderer( + new IllegalStateException("ExoPlaybackExceptionTest"), + /* rendererName= */ "rendererName", + /* rendererIndex= */ 123, + /* rendererFormat= */ new Format.Builder().setCodecs("anyCodec").build(), + /* rendererFormatSupport= */ C.FORMAT_UNSUPPORTED_SUBTYPE, + /* isRecoverable= */ true); + + ExoPlaybackException after = ExoPlaybackException.CREATOR.fromBundle(before.toBundle()); + assertThat(areEqual(before, after)).isTrue(); + } + + @Test + public void + roundTripViaBundle_ofExoPlaybackExceptionTypeRendererWithPrivateCause_yieldsRemoteExceptionWithSameMessage() { + ExoPlaybackException before = + ExoPlaybackException.createForRenderer( + new Exception(/* message= */ "anonymous exception that class loader cannot know") {}); + ExoPlaybackException after = ExoPlaybackException.CREATOR.fromBundle(before.toBundle()); + + assertThat(after.getCause()).isInstanceOf(RemoteException.class); + assertThat(after.getCause()).hasMessageThat().isEqualTo(before.getCause().getMessage()); + } + + private static boolean areEqual(ExoPlaybackException a, ExoPlaybackException b) { + if (a == null || b == null) { + return a == b; + } + return Util.areEqual(a.getMessage(), b.getMessage()) + && a.type == b.type + && Util.areEqual(a.rendererName, b.rendererName) + && a.rendererIndex == b.rendererIndex + && Util.areEqual(a.rendererFormat, b.rendererFormat) + && a.rendererFormatSupport == b.rendererFormatSupport + && a.timestampMs == b.timestampMs + && a.isRecoverable == b.isRecoverable + && areEqual(a.getCause(), b.getCause()); + } + + private static boolean areEqual(Throwable a, Throwable b) { + if (a == null || b == null) { + return a == b; + } + return a.getClass() == b.getClass() && Util.areEqual(a.getMessage(), b.getMessage()); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java index 0243fe50f4..8ebadc17e0 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java @@ -37,19 +37,13 @@ public class MediaItemTest { private static final String URI_STRING = "http://www.google.com"; - @Test - public void builder_needsUriOrMediaId() { - assertThrows(NullPointerException.class, () -> new MediaItem.Builder().build()); - } - @Test public void builderWithUri_setsUri() { Uri uri = Uri.parse(URI_STRING); MediaItem mediaItem = MediaItem.fromUri(uri); - assertThat(mediaItem.playbackProperties.uri.toString()).isEqualTo(URI_STRING); - assertThat(mediaItem.mediaId).isEqualTo(URI_STRING); + assertThat(mediaItem.playbackProperties.uri).isEqualTo(uri); assertThat(mediaItem.mediaMetadata).isNotNull(); } @@ -58,7 +52,13 @@ public class MediaItemTest { MediaItem mediaItem = MediaItem.fromUri(URI_STRING); assertThat(mediaItem.playbackProperties.uri.toString()).isEqualTo(URI_STRING); - assertThat(mediaItem.mediaId).isEqualTo(URI_STRING); + } + + @Test + public void builderWithoutMediaId_usesDefaultMediaId() { + MediaItem mediaItem = MediaItem.fromUri(URI_STRING); + + assertThat(mediaItem.mediaId).isEqualTo(MediaItem.DEFAULT_MEDIA_ID); } @Test @@ -393,4 +393,34 @@ public class MediaItemTest { assertThat(copy).isEqualTo(mediaItem); } + + @Test + public void roundTripViaBundle_withoutPlaybackProperties_yieldsEqualInstance() { + MediaItem mediaItem = + new MediaItem.Builder() + .setMediaId("mediaId") + .setLiveTargetOffsetMs(20_000) + .setLiveMinOffsetMs(2_222) + .setLiveMaxOffsetMs(4_444) + .setLiveMinPlaybackSpeed(.9f) + .setLiveMaxPlaybackSpeed(1.1f) + .setMediaMetadata(new MediaMetadata.Builder().setTitle("title").build()) + .setClipStartPositionMs(100) + .setClipEndPositionMs(1_000) + .setClipRelativeToDefaultPosition(true) + .setClipRelativeToLiveWindow(true) + .setClipStartsAtKeyFrame(true) + .build(); + + assertThat(mediaItem.playbackProperties).isNull(); + assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle())).isEqualTo(mediaItem); + } + + @Test + public void roundTripViaBundle_withPlaybackProperties_dropsPlaybackProperties() { + MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build(); + + assertThat(mediaItem.playbackProperties).isNotNull(); + assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle()).playbackProperties).isNull(); + } } 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 43d5bc5a2c..b8a0509221 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 @@ -18,6 +18,8 @@ package com.google.android.exoplayer2; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +40,24 @@ public class MediaMetadataTest { MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle(title).build(); - assertThat(mediaMetadata.title).isEqualTo(title); + assertThat(mediaMetadata.title.toString()).isEqualTo(title); + } + + @Test + public void roundTripViaBundle_yieldsEqualInstance() { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build(); + + assertThat(MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle())).isEqualTo(mediaMetadata); + } + + @Test + public void builderPopulatedFromMetadataEntry_setsTitleCorrectly() { + String title = "the title"; + Metadata.Entry entry = + new TextInformationFrame(/* id= */ "TT2", /* description= */ null, /* value= */ title); + MediaMetadata.Builder builder = MediaMetadata.EMPTY.buildUpon(); + + entry.populateMediaMetadata(builder); + assertThat(builder.build().title.toString()).isEqualTo(title); } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/PlaybackParametersTest.java b/library/common/src/test/java/com/google/android/exoplayer2/PlaybackParametersTest.java new file mode 100644 index 0000000000..004ac57d4b --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/PlaybackParametersTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link PlaybackParameters}. */ +@RunWith(AndroidJUnit4.class) +public class PlaybackParametersTest { + + @Test + public void roundTripViaBundle_ofPlaybackParameters_yieldsEqualInstance() { + PlaybackParameters playbackParameters = + new PlaybackParameters(/* speed= */ 2.9f, /* pitch= */ 1.2f); + + assertThat(PlaybackParameters.CREATOR.fromBundle(playbackParameters.toBundle())) + .isEqualTo(playbackParameters); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/PositionInfoTest.java b/library/common/src/test/java/com/google/android/exoplayer2/PositionInfoTest.java new file mode 100644 index 0000000000..7b7cfb0f37 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/PositionInfoTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.Player.PositionInfo; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link Player.PositionInfo}. */ +@RunWith(AndroidJUnit4.class) +public class PositionInfoTest { + + @Test + public void roundTripViaBundle_ofPositionInfoWithoutObjectFields_yieldsEqualInstance() { + PositionInfo positionInfo = + new PositionInfo( + /* windowUid= */ null, + /* windowIndex= */ 23, + /* periodUid= */ null, + /* periodIndex= */ 11, + /* positionMs= */ 8787L, + /* contentPositionMs= */ 12L, + /* adGroupIndex= */ 2, + /* adIndexInAdGroup= */ 444); + + assertThat(PositionInfo.CREATOR.fromBundle(positionInfo.toBundle())).isEqualTo(positionInfo); + } + + @Test + public void roundTripViaBundle_ofPositionInfoWithWindowUid_yieldsNullWindowUid() { + PositionInfo positionInfo = + new PositionInfo( + /* windowUid= */ new Object(), + /* windowIndex= */ 23, + /* periodUid= */ null, + /* periodIndex= */ 11, + /* positionMs= */ 8787L, + /* contentPositionMs= */ 12L, + /* adGroupIndex= */ 2, + /* adIndexInAdGroup= */ 444); + + PositionInfo positionInfoFromBundle = PositionInfo.CREATOR.fromBundle(positionInfo.toBundle()); + assertThat(positionInfoFromBundle.windowUid).isNull(); + } + + @Test + public void roundTripViaBundle_ofPositionInfoWithPeriodUid_yieldsNullPeriodUid() { + PositionInfo positionInfo = + new PositionInfo( + /* windowUid= */ null, + /* windowIndex= */ 23, + /* periodUid= */ new Object(), + /* periodIndex= */ 11, + /* positionMs= */ 8787L, + /* contentPositionMs= */ 12L, + /* adGroupIndex= */ 2, + /* adIndexInAdGroup= */ 444); + + PositionInfo positionInfoFromBundle = PositionInfo.CREATOR.fromBundle(positionInfo.toBundle()); + assertThat(positionInfoFromBundle.periodUid).isNull(); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/RatingTest.java b/library/common/src/test/java/com/google/android/exoplayer2/RatingTest.java new file mode 100644 index 0000000000..be77acd83b --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/RatingTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.internal.DoNotInstrument; + +/** Tests for {@link Rating} and its subclasses. */ +@RunWith(AndroidJUnit4.class) +@DoNotInstrument +public class RatingTest { + + @Test + public void unratedHeartRating() { + HeartRating rating = new HeartRating(); + assertThat(rating.isRated()).isFalse(); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + @Test + public void ratedHeartRating() { + boolean hasHeart = true; + HeartRating rating = new HeartRating(hasHeart); + assertThat(rating.isRated()).isTrue(); + assertThat(rating.isHeart()).isEqualTo(hasHeart); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + @Test + public void unratedPercentageRating() { + PercentageRating rating = new PercentageRating(); + assertThat(rating.isRated()).isFalse(); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + @Test + public void ratedPercentageRating() { + float percentage = 20.5f; + PercentageRating rating = new PercentageRating(percentage); + assertThat(rating.isRated()).isTrue(); + assertThat(rating.getPercent()).isEqualTo(percentage); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + @Test + public void unratedThumbRating() { + ThumbRating rating = new ThumbRating(); + assertThat(rating.isRated()).isFalse(); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + @Test + public void ratedThumbRating() { + boolean isThumbUp = true; + ThumbRating rating = new ThumbRating(isThumbUp); + assertThat(rating.isRated()).isTrue(); + assertThat(rating.isThumbsUp()).isEqualTo(isThumbUp); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + @Test + public void unratedStarRating() { + int maxStars = 5; + StarRating rating = new StarRating(maxStars); + assertThat(rating.isRated()).isFalse(); + assertThat(rating.getMaxStars()).isEqualTo(maxStars); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + @Test + public void ratedStarRating() { + int maxStars = 5; + float starRating = 3.1f; + StarRating rating = new StarRating(maxStars, starRating); + assertThat(rating.isRated()).isTrue(); + assertThat(rating.getMaxStars()).isEqualTo(maxStars); + assertThat(rating.getStarRating()).isEqualTo(starRating); + assertThat(roundTripViaBundle(rating)).isEqualTo(rating); + } + + private static Rating roundTripViaBundle(Rating rating) { + return Rating.CREATOR.fromBundle(rating.toBundle()); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java b/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java similarity index 56% rename from library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java rename to library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java index fe3edf4177..689716bb25 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.MediaItem.LiveConfiguration; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.TimelineAsserts; @@ -174,11 +175,16 @@ public class TimelineTest { otherPeriod.durationUs = 11L; assertThat(period).isNotEqualTo(otherPeriod); + otherPeriod = new Timeline.Period(); + otherPeriod.isPlaceholder = true; + assertThat(period).isNotEqualTo(otherPeriod); + otherPeriod = new Timeline.Period(); period.id = new Object(); period.uid = new Object(); period.windowIndex = 1; period.durationUs = 123L; + period.isPlaceholder = true; otherPeriod = otherPeriod.set( period.id, @@ -186,6 +192,7 @@ public class TimelineTest { period.windowIndex, period.durationUs, /* positionInWindowUs= */ 0); + otherPeriod.isPlaceholder = true; assertThat(period).isEqualTo(otherPeriod); } @@ -201,6 +208,137 @@ public class TimelineTest { assertThat(period.hashCode()).isEqualTo(otherPeriod.hashCode()); } + @Test + public void roundTripViaBundle_ofTimeline_yieldsEqualInstanceExceptIdsAndManifest() { + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 2, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ true, + /* isLive= */ true, + /* isPlaceholder= */ false, + /* durationUs= */ 2, + /* defaultPositionUs= */ 22, + /* windowOffsetInFirstPeriodUs= */ 222, + AdPlaybackState.NONE, + new MediaItem.Builder().setMediaId("mediaId2").build()), + new TimelineWindowDefinition( + /* periodCount= */ 3, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ true, + /* isLive= */ true, + /* isPlaceholder= */ false, + /* durationUs= */ 3, + /* defaultPositionUs= */ 33, + /* windowOffsetInFirstPeriodUs= */ 333, + AdPlaybackState.NONE, + new MediaItem.Builder().setMediaId("mediaId3").build())); + + Timeline restoredTimeline = Timeline.CREATOR.fromBundle(timeline.toBundle()); + + TimelineAsserts.assertEqualsExceptIdsAndManifest( + /* expectedTimeline= */ timeline, /* actualTimeline= */ restoredTimeline); + } + + @Test + public void roundTripViaBundle_ofTimeline_preservesWindowIndices() { + int windowCount = 10; + FakeTimeline timeline = new FakeTimeline(windowCount); + + Timeline restoredTimeline = Timeline.CREATOR.fromBundle(timeline.toBundle()); + + assertThat(restoredTimeline.getLastWindowIndex(/* shuffleModeEnabled= */ false)) + .isEqualTo(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ false)); + assertThat(restoredTimeline.getLastWindowIndex(/* shuffleModeEnabled= */ true)) + .isEqualTo(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ true)); + assertThat(restoredTimeline.getFirstWindowIndex(/* shuffleModeEnabled= */ false)) + .isEqualTo(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ false)); + assertThat(restoredTimeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)) + .isEqualTo(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)); + TimelineAsserts.assertEqualNextWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false); + TimelineAsserts.assertEqualNextWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true); + TimelineAsserts.assertEqualNextWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false); + TimelineAsserts.assertEqualNextWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true); + TimelineAsserts.assertEqualNextWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false); + TimelineAsserts.assertEqualNextWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true); + TimelineAsserts.assertEqualPreviousWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false); + TimelineAsserts.assertEqualPreviousWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true); + TimelineAsserts.assertEqualPreviousWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false); + TimelineAsserts.assertEqualPreviousWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true); + TimelineAsserts.assertEqualPreviousWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false); + TimelineAsserts.assertEqualPreviousWindowIndices( + timeline, restoredTimeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true); + } + + @Test + public void roundTripViaBundle_ofEmptyTimeline_returnsEmptyTimeline() { + TimelineAsserts.assertEmpty(Timeline.CREATOR.fromBundle(Timeline.EMPTY.toBundle())); + } + + @Test + public void roundTripViaBundle_ofWindow_yieldsEqualInstanceExceptUidAndManifest() { + Timeline.Window window = new Timeline.Window(); + window.uid = new Object(); + window.mediaItem = new MediaItem.Builder().setMediaId("mediaId").build(); + window.manifest = new Object(); + window.presentationStartTimeMs = 111; + window.windowStartTimeMs = 222; + window.elapsedRealtimeEpochOffsetMs = 333; + window.isSeekable = true; + window.isDynamic = true; + window.liveConfiguration = + new LiveConfiguration( + /* targetOffsetMs= */ 1, + /* minOffsetMs= */ 2, + /* maxOffsetMs= */ 3, + /* minPlaybackSpeed= */ 0.5f, + /* maxPlaybackSpeed= */ 1.5f); + window.isPlaceholder = true; + window.defaultPositionUs = 444; + window.durationUs = 555; + window.firstPeriodIndex = 6; + window.lastPeriodIndex = 7; + window.positionInFirstPeriodUs = 888; + + Timeline.Window restoredWindow = Timeline.Window.CREATOR.fromBundle(window.toBundle()); + + assertThat(restoredWindow.manifest).isNull(); + TimelineAsserts.assertWindowEqualsExceptUidAndManifest( + /* expectedWindow= */ window, /* actualWindow= */ restoredWindow); + } + + @Test + public void roundTripViaBundle_ofPeriod_yieldsEqualInstanceExceptIds() { + Timeline.Period period = new Timeline.Period(); + period.id = new Object(); + period.uid = new Object(); + period.windowIndex = 1; + period.durationUs = 123_000; + period.positionInWindowUs = 4_000; + period.isPlaceholder = true; + + Timeline.Period restoredPeriod = Timeline.Period.CREATOR.fromBundle(period.toBundle()); + + assertThat(restoredPeriod.id).isNull(); + assertThat(restoredPeriod.uid).isNull(); + TimelineAsserts.assertPeriodEqualsExceptIds( + /* expectedPeriod= */ period, /* actualPeriod= */ restoredPeriod); + } + @SuppressWarnings("deprecation") // Populates the deprecated window.tag property. private static Timeline.Window populateWindow( @Nullable MediaItem mediaItem, @Nullable Object tag) { diff --git a/library/common/src/test/java/com/google/android/exoplayer2/audio/AudioAttributesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/audio/AudioAttributesTest.java new file mode 100644 index 0000000000..9296f60dd7 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/audio/AudioAttributesTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.audio; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link AudioAttributes}. */ +@RunWith(AndroidJUnit4.class) +public class AudioAttributesTest { + + @Test + public void roundTripViaBundle_yieldsEqualInstance() { + AudioAttributes audioAttributes = + new AudioAttributes.Builder() + .setContentType(C.CONTENT_TYPE_SONIFICATION) + .setFlags(C.FLAG_AUDIBILITY_ENFORCED) + .setUsage(C.USAGE_ALARM) + .setAllowedCapturePolicy(C.ALLOW_CAPTURE_BY_SYSTEM) + .build(); + + assertThat(AudioAttributes.CREATOR.fromBundle(audioAttributes.toBundle())) + .isEqualTo(audioAttributes); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/device/DeviceInfoTest.java b/library/common/src/test/java/com/google/android/exoplayer2/device/DeviceInfoTest.java new file mode 100644 index 0000000000..922c38d046 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/device/DeviceInfoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.device; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link DeviceInfo}. */ +@RunWith(AndroidJUnit4.class) +public class DeviceInfoTest { + + @Test + public void roundTripViaBundle_yieldsEqualInstance() { + DeviceInfo deviceInfo = + new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 1, /* maxVolume= */ 9); + + assertThat(DeviceInfo.CREATOR.fromBundle(deviceInfo.toBundle())).isEqualTo(deviceInfo); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java similarity index 71% rename from library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java rename to library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index de998bb8b1..948cbe2c69 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.source.ads; +import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_AVAILABLE; +import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_PLAYED; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -25,9 +27,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -/** Unit test for {@link AdPlaybackState}. */ +/** Unit tests for {@link AdPlaybackState}. */ @RunWith(AndroidJUnit4.class) -public final class AdPlaybackStateTest { +public class AdPlaybackStateTest { private static final long[] TEST_AD_GROUP_TMES_US = new long[] {0, C.msToUs(10_000)}; private static final Uri TEST_URI = Uri.EMPTY; @@ -134,7 +136,7 @@ public final class AdPlaybackStateTest { try { state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); fail(); - } catch (Exception e) { + } catch (RuntimeException e) { // Expected. } } @@ -146,4 +148,44 @@ public final class AdPlaybackStateTest { assertThat(state.adGroups[0].count).isEqualTo(0); assertThat(state.adGroups[1].count).isEqualTo(0); } + + @Test + public void roundTripViaBundle_yieldsEqualFieldsExceptAdsId() { + AdPlaybackState originalState = + state + .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) + .withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0) + .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI) + .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2) + .withSkippedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0) + .withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1) + .withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, TEST_URI) + .withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1, TEST_URI) + .withAdDurationsUs(new long[][] {{12}, {34, 56}}) + .withAdResumePositionUs(123) + .withContentDurationUs(456); + + AdPlaybackState restoredState = AdPlaybackState.CREATOR.fromBundle(originalState.toBundle()); + + assertThat(restoredState.adsId).isNull(); + assertThat(restoredState.adGroupCount).isEqualTo(originalState.adGroupCount); + assertThat(restoredState.adGroupTimesUs).isEqualTo(originalState.adGroupTimesUs); + assertThat(restoredState.adGroups).isEqualTo(originalState.adGroups); + assertThat(restoredState.adResumePositionUs).isEqualTo(originalState.adResumePositionUs); + assertThat(restoredState.contentDurationUs).isEqualTo(originalState.contentDurationUs); + } + + @Test + public void roundTripViaBundle_ofAdGroup_yieldsEqualInstance() { + AdPlaybackState.AdGroup adGroup = + new AdPlaybackState.AdGroup() + .withAdCount(2) + .withAdState(AD_STATE_AVAILABLE, /* index= */ 0) + .withAdState(AD_STATE_PLAYED, /* index= */ 1) + .withAdUri(Uri.parse("https://www.google.com"), /* index= */ 0) + .withAdUri(Uri.EMPTY, /* index= */ 1) + .withAdDurationsUs(new long[] {1234, 5678}); + + assertThat(AdPlaybackState.AdGroup.CREATOR.fromBundle(adGroup.toBundle())).isEqualTo(adGroup); + } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java new file mode 100644 index 0000000000..52c605bd05 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/upstream/HttpUtilTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader; +import static com.google.android.exoplayer2.upstream.HttpUtil.getContentLength; +import static com.google.android.exoplayer2.upstream.HttpUtil.getDocumentSize; +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link DefaultHttpDataSource}. */ +@RunWith(AndroidJUnit4.class) +public class HttpUtilTest { + + @Test + public void buildRangeRequestHeader_buildsHeader() { + assertThat(buildRangeRequestHeader(0, C.LENGTH_UNSET)).isNull(); + assertThat(buildRangeRequestHeader(1, C.LENGTH_UNSET)).isEqualTo("bytes=1-"); + assertThat(buildRangeRequestHeader(0, 5)).isEqualTo("bytes=0-4"); + assertThat(buildRangeRequestHeader(5, 15)).isEqualTo("bytes=5-19"); + } + + @Test + public void getContentLength_bothHeadersMissing_returnsUnset() { + assertThat(getContentLength(null, null)).isEqualTo(C.LENGTH_UNSET); + assertThat(getContentLength("", "")).isEqualTo(C.LENGTH_UNSET); + } + + @Test + public void getContentLength_onlyContentLengthHeaderSet_returnsCorrectValue() { + assertThat(getContentLength("5", null)).isEqualTo(5); + assertThat(getContentLength("5", "")).isEqualTo(5); + } + + @Test + public void getContentLength_onlyContentRangeHeaderSet_returnsCorrectValue() { + assertThat(getContentLength(null, "bytes 5-9/100")).isEqualTo(5); + assertThat(getContentLength("", "bytes 5-9/100")).isEqualTo(5); + assertThat(getContentLength("", "bytes 5-9/*")).isEqualTo(5); + } + + @Test + public void getContentLength_bothHeadersSet_returnsCorrectValue() { + assertThat(getContentLength("5", "bytes 5-9/100")).isEqualTo(5); + } + + @Test + public void getContentLength_headersInconsistent_returnsLargerValue() { + assertThat(getContentLength("10", "bytes 0-4/100")).isEqualTo(10); + assertThat(getContentLength("5", "bytes 0-9/100")).isEqualTo(10); + } + + @Test + public void getContentLength_ignoresInvalidValues() { + assertThat(getContentLength("Invalid", "Invalid")).isEqualTo(C.LENGTH_UNSET); + assertThat(getContentLength("Invalid", "bytes 5-9/100")).isEqualTo(5); + assertThat(getContentLength("5", "Invalid")).isEqualTo(5); + } + + @Test + public void getContentLength_ignoresUnhandledRangeUnits() { + assertThat(getContentLength(null, "unhandled 5-9/100")).isEqualTo(C.LENGTH_UNSET); + assertThat(getContentLength("10", "unhandled 0-4/100")).isEqualTo(10); + } + + @Test + public void getDocumentSize_noHeader_returnsUnset() { + assertThat(getDocumentSize(null)).isEqualTo(C.LENGTH_UNSET); + assertThat(getDocumentSize("")).isEqualTo(C.LENGTH_UNSET); + } + + @Test + public void getDocumentSize_returnsSize() { + assertThat(getDocumentSize("bytes */20")).isEqualTo(20); + assertThat(getDocumentSize("bytes 0-4/20")).isEqualTo(20); + } + + @Test + public void getDocumentSize_ignoresUnhandledRangeUnits() { + assertThat(getDocumentSize("unhandled */20")).isEqualTo(C.LENGTH_UNSET); + assertThat(getDocumentSize("unhandled 0-4/20")).isEqualTo(C.LENGTH_UNSET); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/BundleUtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/BundleUtilTest.java new file mode 100644 index 0000000000..b7cbe5c77e --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/BundleUtilTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link BundleUtil}. */ +@RunWith(AndroidJUnit4.class) +public class BundleUtilTest { + + @Test + public void getPutBinder() { + String key = "key"; + IBinder binder = new Binder(); + Bundle bundle = new Bundle(); + + BundleUtil.putBinder(bundle, key, binder); + IBinder returnedBinder = BundleUtil.getBinder(bundle, key); + + assertThat(returnedBinder).isSameInstanceAs(binder); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/CopyOnWriteMultisetTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/CopyOnWriteMultisetTest.java index 92e4124a6a..1b41a2a79d 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/CopyOnWriteMultisetTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/CopyOnWriteMultisetTest.java @@ -107,4 +107,44 @@ public final class CopyOnWriteMultisetTest { assertThrows(UnsupportedOperationException.class, () -> elementSet.remove("a string")); } + + @Test + public void count() { + CopyOnWriteMultiset multiset = new CopyOnWriteMultiset<>(); + multiset.add("a string"); + multiset.add("a string"); + + assertThat(multiset.count("a string")).isEqualTo(2); + assertThat(multiset.count("another string")).isEqualTo(0); + } + + @Test + public void modifyingWhileIteratingElements_succeeds() { + CopyOnWriteMultiset multiset = new CopyOnWriteMultiset<>(); + multiset.add("a string"); + multiset.add("a string"); + multiset.add("another string"); + + // A traditional collection would throw a ConcurrentModificationException here. + for (String element : multiset) { + multiset.remove(element); + } + + assertThat(multiset).isEmpty(); + } + + @Test + public void modifyingWhileIteratingElementSet_succeeds() { + CopyOnWriteMultiset multiset = new CopyOnWriteMultiset<>(); + multiset.add("a string"); + multiset.add("a string"); + multiset.add("another string"); + + // A traditional collection would throw a ConcurrentModificationException here. + for (String element : multiset.elementSet()) { + multiset.remove(element); + } + + assertThat(multiset).containsExactly("a string"); + } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/ExoFlagsTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/ExoFlagsTest.java new file mode 100644 index 0000000000..0cc71a2881 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/ExoFlagsTest.java @@ -0,0 +1,156 @@ +/* + * 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.util; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link ExoFlags}. */ +@RunWith(AndroidJUnit4.class) +public final class ExoFlagsTest { + + @Test + public void contains_withoutAdd_returnsFalseForAllValues() { + ExoFlags flags = new ExoFlags.Builder().build(); + + assertThat(flags.contains(/* flag= */ -1234)).isFalse(); + assertThat(flags.contains(/* flag= */ 0)).isFalse(); + assertThat(flags.contains(/* flag= */ 2)).isFalse(); + assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isFalse(); + } + + @Test + public void contains_afterAdd_returnsTrueForAddedValues() { + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ -1234) + .add(/* flag= */ 0) + .add(/* flag= */ 2) + .add(/* flag= */ Integer.MAX_VALUE) + .build(); + + assertThat(flags.contains(/* flag= */ -1235)).isFalse(); + assertThat(flags.contains(/* flag= */ -1234)).isTrue(); + assertThat(flags.contains(/* flag= */ 0)).isTrue(); + assertThat(flags.contains(/* flag= */ 1)).isFalse(); + assertThat(flags.contains(/* flag= */ 2)).isTrue(); + assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE - 1)).isFalse(); + assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isTrue(); + } + + @Test + public void contains_afterAddIf_returnsTrueForAddedValues() { + ExoFlags flags = + new ExoFlags.Builder() + .addIf(/* flag= */ -1234, /* condition= */ true) + .addIf(/* flag= */ 0, /* condition= */ false) + .addIf(/* flag= */ 2, /* condition= */ true) + .addIf(/* flag= */ Integer.MAX_VALUE, /* condition= */ false) + .build(); + + assertThat(flags.contains(/* flag= */ -1235)).isFalse(); + assertThat(flags.contains(/* flag= */ -1234)).isTrue(); + assertThat(flags.contains(/* flag= */ 0)).isFalse(); + assertThat(flags.contains(/* flag= */ 1)).isFalse(); + assertThat(flags.contains(/* flag= */ 2)).isTrue(); + assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE - 1)).isFalse(); + assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isFalse(); + } + + @Test + public void containsAny_withoutAdd_returnsFalseForAllValues() { + ExoFlags flags = new ExoFlags.Builder().build(); + + assertThat(flags.containsAny(/* flags...= */ -1234, 0, 2, Integer.MAX_VALUE)).isFalse(); + } + + @Test + public void containsAny_afterAdd_returnsTrueForAddedValues() { + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ -1234) + .add(/* flag= */ 0) + .add(/* flag= */ 2) + .add(/* flag= */ Integer.MAX_VALUE) + .build(); + + assertThat( + flags.containsAny( + /* flags...= */ -1235, -1234, 0, 1, 2, Integer.MAX_VALUE - 1, Integer.MAX_VALUE)) + .isTrue(); + assertThat(flags.containsAny(/* flags...= */ -1235, 1, Integer.MAX_VALUE - 1)).isFalse(); + } + + @Test + public void size_withoutAdd_returnsZero() { + ExoFlags flags = new ExoFlags.Builder().build(); + + assertThat(flags.size()).isEqualTo(0); + } + + @Test + public void size_afterAdd_returnsNumberUniqueOfElements() { + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 123) + .add(/* flag= */ 123) + .build(); + + assertThat(flags.size()).isEqualTo(2); + } + + @Test + public void get_withNegativeIndex_throwsIndexOutOfBoundsException() { + ExoFlags flags = new ExoFlags.Builder().build(); + + assertThrows(IndexOutOfBoundsException.class, () -> flags.get(/* index= */ -1)); + } + + @Test + public void get_withIndexExceedingSize_throwsIndexOutOfBoundsException() { + ExoFlags flags = new ExoFlags.Builder().add(/* flag= */ 0).add(/* flag= */ 123).build(); + + assertThrows(IndexOutOfBoundsException.class, () -> flags.get(/* index= */ 2)); + } + + @Test + public void get_afterAdd_returnsAllUniqueValues() { + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 123) + .add(/* flag= */ 123) + .add(/* flag= */ 456) + .build(); + + List values = new ArrayList<>(); + for (int i = 0; i < flags.size(); i++) { + values.add(flags.get(i)); + } + assertThat(values).containsExactly(0, 123, 456); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java index 7d0b77664d..f8e6061998 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java @@ -42,9 +42,8 @@ public class ListenerSetTest { @Test public void queueEvent_withoutFlush_sendsNoEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.add(listener); @@ -57,9 +56,8 @@ public class ListenerSetTest { @Test public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); listenerSet.add(listener1); @@ -82,9 +80,8 @@ public class ListenerSetTest { @Test public void flushEvents_recursive_sendsEventsInCorrectOrder() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); // Listener1 sends callback3 recursively when receiving callback1. TestListener listener1 = spy( @@ -116,9 +113,8 @@ public class ListenerSetTest { @Test public void flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); // Listener1 sends callback1 recursively when receiving callback3. TestListener listener1 = spy( @@ -152,30 +148,29 @@ public class ListenerSetTest { InOrder inOrder = Mockito.inOrder(listener1, listener2); inOrder.verify(listener1).callback2(); inOrder.verify(listener2).callback2(); - inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_2)); - inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2)); + inOrder.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_2)); inOrder.verify(listener1).callback1(); inOrder.verify(listener2).callback1(); inOrder.verify(listener1).callback2(); inOrder.verify(listener2).callback2(); inOrder.verify(listener1).callback1(); inOrder.verify(listener2).callback1(); - inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2)); - inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); inOrder.verify(listener1).callback3(); inOrder.verify(listener2).callback3(); inOrder.verify(listener1).callback1(); inOrder.verify(listener2).callback1(); - inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_3)); - inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_3)); + inOrder.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_3)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_3)); inOrder.verifyNoMoreInteractions(); } @Test public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); // Listener2 sends callback1 recursively when receiving the iteration finished event. TestListener listener2 = spy( @@ -183,7 +178,7 @@ public class ListenerSetTest { boolean eventSent; @Override - public void iterationFinished(Flags flags) { + public void iterationFinished(ExoFlags flags) { if (!eventSent) { listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1); eventSent = true; @@ -203,22 +198,21 @@ public class ListenerSetTest { inOrder.verify(listener1).callback2(); inOrder.verify(listener2).callback2(); inOrder.verify(listener3).callback2(); - inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_2)); - inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2)); + inOrder.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_2)); inOrder.verify(listener1).callback1(); inOrder.verify(listener2).callback1(); inOrder.verify(listener3).callback1(); - inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1)); - inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_1)); - inOrder.verify(listener3).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_1)); + inOrder.verify(listener3).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); inOrder.verifyNoMoreInteractions(); } @Test public void flushEvents_withUnsetEventFlag_doesNotThrow() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); listenerSet.queueEvent(/* eventFlag= */ C.INDEX_UNSET, TestListener::callback1); listenerSet.flushEvents(); @@ -229,9 +223,8 @@ public class ListenerSetTest { @Test public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 adds listener2 recursively. TestListener listener1 = @@ -255,16 +248,15 @@ public class ListenerSetTest { inOrder.verify(listener1).callback1(); inOrder.verify(listener1).callback2(); inOrder.verify(listener2).callback2(); - inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2)); - inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2)); + inOrder.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_2)); inOrder.verifyNoMoreInteractions(); } @Test public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); @@ -281,16 +273,15 @@ public class ListenerSetTest { inOrder.verify(listener1).callback1(); inOrder.verify(listener1).callback2(); inOrder.verify(listener2).callback2(); - inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2)); - inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2)); + inOrder.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_2)); inOrder.verifyNoMoreInteractions(); } @Test public void remove_withRecursion_stopsReceivingEventsImmediately() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 removes listener2 recursively. TestListener listener1 = @@ -311,15 +302,14 @@ public class ListenerSetTest { ShadowLooper.runMainLooperToNextTask(); verify(listener1).callback1(); - verify(listener1).iterationFinished(Flags.create(EVENT_ID_1)); + verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1)); verifyNoMoreInteractions(listener1, listener2); } @Test public void remove_withQueueing_stopsReceivingEventsImmediately() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); listenerSet.add(listener1); @@ -333,15 +323,14 @@ public class ListenerSetTest { ShadowLooper.runMainLooperToNextTask(); verify(listener2, times(2)).callback1(); - verify(listener2).iterationFinished(Flags.create(EVENT_ID_1)); + verify(listener2).iterationFinished(createExoFlags(EVENT_ID_1)); verifyNoMoreInteractions(listener1, listener2); } @Test public void release_stopsForwardingEventsImmediately() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 releases the set from within the callback. TestListener listener1 = @@ -361,15 +350,14 @@ public class ListenerSetTest { ShadowLooper.runMainLooperToNextTask(); verify(listener1).callback1(); - verify(listener1).iterationFinished(Flags.create(EVENT_ID_1)); + verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1)); verifyNoMoreInteractions(listener1, listener2); } @Test public void release_preventsRegisteringNewListeners() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.release(); @@ -381,9 +369,8 @@ public class ListenerSetTest { @Test public void lazyRelease_stopsForwardingEventsFromNewHandlerMessagesAndCallsReleaseCallback() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.add(listener); @@ -403,8 +390,8 @@ public class ListenerSetTest { // lazy release. verify(listener, times(3)).callback1(); verify(listener).callback3(); - verify(listener).iterationFinished(Flags.create(EVENT_ID_1)); - verify(listener).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_3)); + verify(listener).iterationFinished(createExoFlags(EVENT_ID_1)); + verify(listener).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_3)); verifyNoMoreInteractions(listener); } @@ -415,17 +402,14 @@ public class ListenerSetTest { default void callback3() {} - default void iterationFinished(Flags flags) {} + default void iterationFinished(ExoFlags flags) {} } - private static final class Flags extends MutableFlags { - - public static Flags create(int... flagValues) { - Flags flags = new Flags(); - for (int value : flagValues) { - flags.add(value); - } - return flags; + private static ExoFlags createExoFlags(int... flagValues) { + ExoFlags.Builder flagsBuilder = new ExoFlags.Builder(); + for (int value : flagValues) { + flagsBuilder.add(value); } + return flagsBuilder.build(); } } 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 new file mode 100644 index 0000000000..0d41e21496 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/MediaFormatUtilTest.java @@ -0,0 +1,157 @@ +/* + * 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.util; + +import static com.google.common.truth.Truth.assertThat; + +import android.media.MediaFormat; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.video.ColorInfo; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Unit tests for {@link MediaFormatUtil}. */ +@RunWith(AndroidJUnit4.class) +@Config(sdk = 29) // Allows using MediaFormat.getKeys() to make assertions over the expected keys. +public class MediaFormatUtilTest { + + @Test + public void createMediaFormatFromEmptyExoPlayerFormat_generatesExpectedEntries() { + MediaFormat mediaFormat = + MediaFormatUtil.createMediaFormatFromFormat(new Format.Builder().build()); + // Assert that no invalid keys are accidentally being populated. + assertThat(mediaFormat.getKeys()) + .containsExactly( + MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, + MediaFormat.KEY_ENCODER_DELAY, + MediaFormat.KEY_ENCODER_PADDING, + MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, + MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, + MediaFormat.KEY_IS_DEFAULT, + MediaFormat.KEY_IS_FORCED_SUBTITLE, + MediaFormat.KEY_IS_AUTOSELECT, + MediaFormat.KEY_ROTATION); + assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_EXO_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); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)).isEqualTo(1); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT)).isEqualTo(1); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)).isEqualTo(0); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)).isEqualTo(0); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)).isEqualTo(0); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_ROTATION)).isEqualTo(0); + } + + @Test + public void createMediaFormatFromPopulatedExoPlayerFormat_generatesExpectedMediaFormatEntries() { + Format format = + new Format.Builder() + .setAverageBitrate(1) + .setChannelCount(2) + .setColorInfo( + new ColorInfo( + /* colorSpace= */ C.COLOR_SPACE_BT601, + /* colorRange= */ C.COLOR_RANGE_FULL, + /* colorTransfer= */ C.COLOR_TRANSFER_HLG, + new byte[] {3})) + .setSampleMimeType(MimeTypes.VIDEO_H264) + .setCodecs("avc.123") + .setFrameRate(4) + .setWidth(5) + .setHeight(6) + .setInitializationData(ImmutableList.of(new byte[] {7}, new byte[] {8})) + .setPcmEncoding(C.ENCODING_PCM_8BIT) + .setLanguage("en") + .setMaxInputSize(9) + .setRotationDegrees(10) + .setSampleRate(11) + .setAccessibilityChannel(12) + .setSelectionFlags( + C.SELECTION_FLAG_AUTOSELECT | C.SELECTION_FLAG_DEFAULT | C.SELECTION_FLAG_FORCED) + .setEncoderDelay(13) + .setEncoderPadding(14) + .setPixelWidthHeightRatio(.5f) + .build(); + MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format); + + assertThat(mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE)).isEqualTo(format.bitrate); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)) + .isEqualTo(format.channelCount); + + ColorInfo colorInfo = Assertions.checkNotNull(format.colorInfo); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER)) + .isEqualTo(colorInfo.colorTransfer); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE)).isEqualTo(colorInfo.colorRange); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_COLOR_STANDARD)) + .isEqualTo(colorInfo.colorSpace); + assertThat(mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO).array()) + .isEqualTo(colorInfo.hdrStaticInfo); + + assertThat(mediaFormat.getString(MediaFormat.KEY_MIME)).isEqualTo(format.sampleMimeType); + assertThat(mediaFormat.getString(MediaFormat.KEY_CODECS_STRING)).isEqualTo(format.codecs); + assertThat(mediaFormat.getFloat(MediaFormat.KEY_FRAME_RATE)).isEqualTo(format.frameRate); + + assertThat(mediaFormat.getInteger(MediaFormat.KEY_WIDTH)).isEqualTo(format.width); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT)).isEqualTo(format.height); + + assertThat(mediaFormat.getByteBuffer("csd-0").array()) + .isEqualTo(format.initializationData.get(0)); + assertThat(mediaFormat.getByteBuffer("csd-1").array()) + .isEqualTo(format.initializationData.get(1)); + + assertThat(mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING)).isEqualTo(format.pcmEncoding); + assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING)) + .isEqualTo(format.pcmEncoding); + + assertThat(mediaFormat.getString(MediaFormat.KEY_LANGUAGE)).isEqualTo(format.language); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)) + .isEqualTo(format.maxInputSize); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_ROTATION)).isEqualTo(format.rotationDegrees); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)).isEqualTo(format.sampleRate); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)) + .isEqualTo(format.accessibilityChannel); + + assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)).isNotEqualTo(0); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)).isNotEqualTo(0); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)).isNotEqualTo(0); + + assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_DELAY)) + .isEqualTo(format.encoderDelay); + assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_PADDING)) + .isEqualTo(format.encoderPadding); + + float calculatedPixelAspectRatio = + (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)) + .isEqualTo(format.pixelWidthHeightRatio); + } + + @Test + public void createMediaFormatWithExoPlayerPcmEncoding_containsExoPlayerSpecificEncoding() { + Format format = new Format.Builder().setPcmEncoding(C.ENCODING_PCM_32BIT).build(); + MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format); + assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING)) + .isEqualTo(C.ENCODING_PCM_32BIT); + assertThat(mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING)).isFalse(); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/video/VideoSizeTest.java b/library/common/src/test/java/com/google/android/exoplayer2/video/VideoSizeTest.java new file mode 100644 index 0000000000..ff9f7c5758 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/video/VideoSizeTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link VideoSize}. */ +@RunWith(AndroidJUnit4.class) +public final class VideoSizeTest { + + @Test + public void roundTripViaBundle_ofVideoSizeUnknown_yieldsEqualInstance() { + assertThat(roundTripViaBundle(VideoSize.UNKNOWN)).isEqualTo(VideoSize.UNKNOWN); + } + + @Test + public void roundTripViaBundle_ofArbitraryVideoSize_yieldsEqualInstance() { + VideoSize videoSize = + new VideoSize( + /* width= */ 9, + /* height= */ 8, + /* unappliedRotationDegrees= */ 7, + /* pixelWidthHeightRatio= */ 6); + assertThat(roundTripViaBundle(videoSize)).isEqualTo(videoSize); + } + + @Test + public void fromBundle_ofEmptyBundle_yieldsVideoSizeUnknown() { + assertThat(VideoSize.CREATOR.fromBundle(new Bundle())).isEqualTo(VideoSize.UNKNOWN); + } + + private static VideoSize roundTripViaBundle(VideoSize videoSize) { + return VideoSize.CREATOR.fromBundle(videoSize.toBundle()); + } +} diff --git a/library/core/build.gradle b/library/core/build.gradle index ae8e7b773f..a4cd3bd2a3 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -19,6 +19,7 @@ android { // "pm clear" command after each test invocation. This command ensures // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' + multiDexEnabled true } buildTypes { diff --git a/library/core/proguard-rules.txt b/library/core/proguard-rules.txt index 53adccd090..0d11dfe27f 100644 --- a/library/core/proguard-rules.txt +++ b/library/core/proguard-rules.txt @@ -1,7 +1,7 @@ # Proguard rules specific to the core 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. --keep class com.google.android.exoplayer2.upstream.RawResourceDataSource { +-keepclassmembers class com.google.android.exoplayer2.upstream.RawResourceDataSource { public static android.net.Uri buildRawResourceUri(int); } @@ -60,3 +60,7 @@ -keepclasseswithmembers class com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory { (com.google.android.exoplayer2.upstream.DataSource$Factory); } +-dontnote com.google.android.exoplayer2.source.rtsp.RtspMediaSource$Factory +-keepclasseswithmembers class com.google.android.exoplayer2.source.rtsp.RtspMediaSource$Factory { + (); +} diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ClippedPlaybackTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ClippedPlaybackTest.java index 9e757574b8..5a33dcf07c 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ClippedPlaybackTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ClippedPlaybackTest.java @@ -66,7 +66,7 @@ public final class ClippedPlaybackTest { player .get() .addListener( - new Player.EventListener() { + new Player.Listener() { @Override public void onPlaybackStateChanged(@Player.State int state) { if (state == Player.STATE_ENDED) { @@ -120,7 +120,7 @@ public final class ClippedPlaybackTest { player .get() .addListener( - new Player.EventListener() { + new Player.Listener() { @Override public void onPlaybackStateChanged(@Player.State int state) { if (state == Player.STATE_ENDED) { diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java index 79b661e717..b59a6e1618 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java @@ -17,15 +17,12 @@ package com.google.android.exoplayer2.upstream; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.fail; -import static org.junit.Assert.assertThrows; import android.net.Uri; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.TestUtil; -import com.google.android.exoplayer2.upstream.ContentDataSource.ContentDataSourceException; -import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @@ -85,36 +82,6 @@ public final class ContentDataSourceTest { } } - @Test - public void read_positionPastEndOfContent_throwsEOFException() throws Exception { - Uri contentUri = TestContentProvider.buildUri(DATA_PATH, /* pipeMode= */ false); - ContentDataSource dataSource = - new ContentDataSource(ApplicationProvider.getApplicationContext()); - DataSpec dataSpec = new DataSpec(contentUri, /* position= */ 1025, C.LENGTH_UNSET); - try { - ContentDataSourceException exception = - assertThrows(ContentDataSourceException.class, () -> dataSource.open(dataSpec)); - assertThat(exception).hasCauseThat().isInstanceOf(EOFException.class); - } finally { - dataSource.close(); - } - } - - @Test - public void readPipeMode_positionPastEndOfContent_throwsEOFException() throws Exception { - Uri contentUri = TestContentProvider.buildUri(DATA_PATH, /* pipeMode= */ true); - ContentDataSource dataSource = - new ContentDataSource(ApplicationProvider.getApplicationContext()); - DataSpec dataSpec = new DataSpec(contentUri, /* position= */ 1025, C.LENGTH_UNSET); - try { - ContentDataSourceException exception = - assertThrows(ContentDataSourceException.class, () -> dataSource.open(dataSpec)); - assertThat(exception).hasCauseThat().isInstanceOf(EOFException.class); - } finally { - dataSource.close(); - } - } - private static void assertData(int offset, int length, boolean pipeMode) throws IOException { Uri contentUri = TestContentProvider.buildUri(DATA_PATH, pipeMode); ContentDataSource dataSource = @@ -130,5 +97,4 @@ public final class ContentDataSourceTest { dataSource.close(); } } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java new file mode 100644 index 0000000000..d55e162a49 --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java @@ -0,0 +1,85 @@ +/* + * 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.upstream; + +import android.content.res.Resources; +import android.net.Uri; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.core.test.R; +import com.google.android.exoplayer2.testutil.DataSourceContractTest; +import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import org.junit.runner.RunWith; + +/** {@link DataSource} contract tests for {@link RawResourceDataSource}. */ +@RunWith(AndroidJUnit4.class) +public final class RawResourceDataSourceContractTest extends DataSourceContractTest { + + private static final byte[] RESOURCE_1_DATA = Util.getUtf8Bytes("resource1 abc\n"); + private static final byte[] RESOURCE_2_DATA = Util.getUtf8Bytes("resource2 abcdef\n"); + + @Override + protected DataSource createDataSource() { + return new RawResourceDataSource(ApplicationProvider.getApplicationContext()); + } + + @Override + protected ImmutableList getTestResources() { + // Android packages raw resources into a single file. When reading a resource other than the + // last one, Android does not prevent accidentally reading beyond the end of the resource and + // into the next one. We use two resources in this test to ensure that when packaged, at least + // one of them has a subsequent resource. This allows the contract test to enforce that the + // RawResourceDataSource implementation doesn't erroneously read into the second resource when + // opened to read the first. + return ImmutableList.of( + new TestResource.Builder() + .setName("resource 1") + .setUri(RawResourceDataSource.buildRawResourceUri(R.raw.resource1)) + .setExpectedBytes(RESOURCE_1_DATA) + .build(), + new TestResource.Builder() + .setName("resource 2") + .setUri(RawResourceDataSource.buildRawResourceUri(R.raw.resource2)) + .setExpectedBytes(RESOURCE_2_DATA) + .build(), + // Additional resources using different URI schemes. + new TestResource.Builder() + .setName("android.resource:// with path") + .setUri( + Uri.parse( + "android.resource://" + + ApplicationProvider.getApplicationContext().getPackageName() + + "/raw/resource1")) + .setExpectedBytes(RESOURCE_1_DATA) + .build(), + new TestResource.Builder() + .setName("android.resource:// with ID") + .setUri( + Uri.parse( + "android.resource://" + + ApplicationProvider.getApplicationContext().getPackageName() + + "/" + + R.raw.resource1)) + .setExpectedBytes(RESOURCE_1_DATA) + .build()); + } + + @Override + protected Uri getNotFoundUri() { + return RawResourceDataSource.buildRawResourceUri(Resources.ID_NULL); + } +} diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/TestContentProvider.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/TestContentProvider.java index 42bfd178e2..3d071c204a 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/TestContentProvider.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/TestContentProvider.java @@ -23,8 +23,9 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.OsConstants; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.TestUtil; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -73,7 +74,7 @@ public final class TestContentProvider extends ContentProvider openPipeHelper( uri, /* mimeType= */ null, /* opts= */ null, /* args= */ null, /* func= */ this); return new AssetFileDescriptor( - fileDescriptor, /* startOffset= */ 0, /* length= */ C.LENGTH_UNSET); + fileDescriptor, /* startOffset= */ 0, AssetFileDescriptor.UNKNOWN_LENGTH); } else { return getContext().getAssets().openFd(fileName); } @@ -111,12 +112,17 @@ public final class TestContentProvider extends ContentProvider String mimeType, @Nullable Bundle opts, @Nullable Object args) { - try { + try (FileOutputStream outputStream = new FileOutputStream(output.getFileDescriptor())) { byte[] data = TestUtil.getByteArray(getContext(), getFileName(uri)); - FileOutputStream outputStream = new FileOutputStream(output.getFileDescriptor()); outputStream.write(data); - outputStream.close(); } catch (IOException e) { + if (e.getCause() instanceof ErrnoException + && ((ErrnoException) e.getCause()).errno == OsConstants.EPIPE) { + // Swallow the exception if it's caused by a broken pipe - this indicates the reader has + // closed the pipe and is therefore no longer interested in the data being written. + // [See internal b/186728171]. + return; + } throw new RuntimeException("Error writing to pipe", e); } } diff --git a/library/core/src/androidTest/res/raw/resource1 b/library/core/src/androidTest/res/raw/resource1 new file mode 100644 index 0000000000..8b59777753 --- /dev/null +++ b/library/core/src/androidTest/res/raw/resource1 @@ -0,0 +1 @@ +resource1 abc diff --git a/library/core/src/androidTest/res/raw/resource2 b/library/core/src/androidTest/res/raw/resource2 new file mode 100644 index 0000000000..37927749f2 --- /dev/null +++ b/library/core/src/androidTest/res/raw/resource2 @@ -0,0 +1 @@ +resource2 abcdef diff --git a/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java index b56e8838c5..6ab7e4fc4a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java @@ -103,7 +103,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Nullable private AudioAttributes audioAttributes; @AudioFocusState private int audioFocusState; - @C.AudioFocusGain private int focusGain; + @C.AudioFocusGain private int focusGainToRequest; private float volumeMultiplier = VOLUME_MULTIPLIER_DEFAULT; private @MonotonicNonNull AudioFocusRequest audioFocusRequest; @@ -142,9 +142,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) { if (!Util.areEqual(this.audioAttributes, audioAttributes)) { this.audioAttributes = audioAttributes; - focusGain = convertAudioAttributesToFocusGain(audioAttributes); + focusGainToRequest = convertAudioAttributesToFocusGain(audioAttributes); Assertions.checkArgument( - focusGain == C.AUDIOFOCUS_GAIN || focusGain == C.AUDIOFOCUS_NONE, + focusGainToRequest == C.AUDIOFOCUS_GAIN || focusGainToRequest == C.AUDIOFOCUS_NONE, "Automatic handling of audio focus is only available for USAGE_MEDIA and USAGE_GAME."); } } @@ -158,8 +158,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; */ @PlayerCommand public int updateAudioFocus(boolean playWhenReady, @Player.State int playbackState) { - if (shouldAbandonAudioFocus(playbackState)) { - abandonAudioFocus(); + if (shouldAbandonAudioFocusIfHeld(playbackState)) { + abandonAudioFocusIfHeld(); return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY; } return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY; @@ -171,7 +171,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; */ public void release() { playerControl = null; - abandonAudioFocus(); + abandonAudioFocusIfHeld(); } // Internal methods. @@ -181,8 +181,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return focusListener; } - private boolean shouldAbandonAudioFocus(@Player.State int playbackState) { - return playbackState == Player.STATE_IDLE || focusGain != C.AUDIOFOCUS_GAIN; + private boolean shouldAbandonAudioFocusIfHeld(@Player.State int playbackState) { + return playbackState == Player.STATE_IDLE || focusGainToRequest != C.AUDIOFOCUS_GAIN; } @PlayerCommand @@ -200,7 +200,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } } - private void abandonAudioFocus() { + private void abandonAudioFocusIfHeld() { if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) { return; } @@ -216,7 +216,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return audioManager.requestAudioFocus( focusListener, Util.getStreamTypeForAudioUsage(checkNotNull(audioAttributes).usage), - focusGain); + focusGainToRequest); } @RequiresApi(26) @@ -224,7 +224,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (audioFocusRequest == null || rebuildAudioFocusRequest) { AudioFocusRequest.Builder builder = audioFocusRequest == null - ? new AudioFocusRequest.Builder(focusGain) + ? new AudioFocusRequest.Builder(focusGainToRequest) : new AudioFocusRequest.Builder(audioFocusRequest); boolean willPauseWhenDucked = willPauseWhenDucked(); @@ -361,7 +361,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return; case AudioManager.AUDIOFOCUS_LOSS: executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY); - abandonAudioFocus(); + abandonAudioFocusIfHeld(); return; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 36dc8027f6..198244d9fe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -19,14 +19,15 @@ import static java.lang.Math.max; import androidx.annotation.Nullable; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException; import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; +import com.google.android.exoplayer2.source.SampleStream.ReadFlags; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MediaClock; import java.io.IOException; -/** - * An abstract base class suitable for most {@link Renderer} implementations. - */ +/** An abstract base class suitable for most {@link Renderer} implementations. */ public abstract class BaseRenderer implements Renderer, RendererCapabilities { private final int trackType; @@ -193,7 +194,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { // PlayerMessage.Target implementation. @Override - public void handleMessage(int what, @Nullable Object object) throws ExoPlaybackException { + public void handleMessage(int messageType, @Nullable Object payload) throws ExoPlaybackException { // Do nothing. } @@ -224,8 +225,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { * @param formats The enabled formats. * @param startPositionUs The start position of the new stream in renderer time (microseconds). * @param offsetUs The offset that will be added to the timestamps of buffers read via {@link - * #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input buffers have - * monotonically increasing timestamps. + * #readSource} so that decoder input buffers have monotonically increasing timestamps. * @throws ExoPlaybackException If an error occurs. */ protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) @@ -382,16 +382,17 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the * end of the stream. If the end of the stream has been reached, the {@link * C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. - * @param formatRequired Whether the caller requires that the format of the stream be read even if - * it's not changing. A sample will never be read if set to true, however it is still possible - * for the end of stream or nothing to be read. - * @return The status of read, one of {@link SampleStream.ReadDataResult}. + * @param readFlags Flags controlling the behavior of this read operation. + * @return The {@link ReadDataResult result} of the read operation. + * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold + * the data of a sample being read. The buffer {@link DecoderInputBuffer#timeUs timestamp} and + * flags are populated if this exception is thrown, but the read position is not advanced. */ - @SampleStream.ReadDataResult + @ReadDataResult protected final int readSource( - FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) { - @SampleStream.ReadDataResult - int result = Assertions.checkNotNull(stream).readData(formatHolder, buffer, formatRequired); + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { + @ReadDataResult + int result = Assertions.checkNotNull(stream).readData(formatHolder, buffer, readFlags); if (result == C.RESULT_BUFFER_READ) { if (buffer.isEndOfStream()) { readingPositionUs = C.TIME_END_OF_SOURCE; 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 5176e49df0..d9958fd608 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 @@ -44,9 +44,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.util.ArrayList; -/** - * Default {@link RenderersFactory} implementation. - */ +/** Default {@link RenderersFactory} implementation. */ public class DefaultRenderersFactory implements RenderersFactory { /** @@ -641,6 +639,8 @@ public class DefaultRenderersFactory implements RenderersFactory { new DefaultAudioProcessorChain(), enableFloatOutput, enableAudioTrackPlaybackParams, - enableOffload); + enableOffload + ? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED + : DefaultAudioSink.OFFLOAD_MODE_DISABLED); } } 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 ffd2bb0c1f..de1e8a00f4 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 @@ -18,24 +18,30 @@ package com.google.android.exoplayer2; import android.content.Context; import android.media.AudioTrack; 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 androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.analytics.AnalyticsCollector; +import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioCapabilities; +import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AudioSink; +import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.audio.DefaultAudioSink; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; +import com.google.android.exoplayer2.device.DeviceInfo; +import com.google.android.exoplayer2.device.DeviceListener; +import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.metadata.MetadataRenderer; -import com.google.android.exoplayer2.source.ClippingMediaSource; -import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; -import com.google.android.exoplayer2.source.LoopingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceFactory; -import com.google.android.exoplayer2.source.MergingMediaSource; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.ShuffleOrder; -import com.google.android.exoplayer2.source.SingleSampleMediaSource; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector; @@ -46,11 +52,15 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; +import com.google.android.exoplayer2.video.VideoListener; +import com.google.android.exoplayer2.video.VideoSize; +import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import java.util.List; /** * An extensible media player that plays {@link MediaSource}s. Instances can be obtained from {@link - * SimpleExoPlayer.Builder} or {@link ExoPlayer.Builder}. + * SimpleExoPlayer.Builder}. * *

Player components

* @@ -61,20 +71,19 @@ import java.util.List; * Components common to all ExoPlayer implementations are: * *
    - *
  • A {@link MediaSource} that defines the media to be played, loads the media, and from - * which the loaded media can be read. A MediaSource is injected via {@link - * #setMediaSource(MediaSource)} at the start of playback. The library modules provide default - * implementations for progressive media files ({@link ProgressiveMediaSource}), DASH - * (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS (HlsMediaSource), an - * implementation for loading single media samples ({@link SingleSampleMediaSource}) that's - * most often used for side-loaded subtitle files, and implementations for building more - * complex MediaSources from simpler ones ({@link MergingMediaSource}, {@link - * ConcatenatingMediaSource}, {@link LoopingMediaSource} and {@link ClippingMediaSource}). + *
  • {@link MediaSource MediaSources} that define the media to be played, load the media, + * and from which the loaded media can be read. MediaSources are created from {@link MediaItem + * MediaItems} by the {@link MediaSourceFactory} injected into the player {@link + * SimpleExoPlayer.Builder#setMediaSourceFactory Builder}, or can be added directly by methods + * like {@link #setMediaSource(MediaSource)}. The library provides a {@link + * DefaultMediaSourceFactory} for progressive media files, DASH, SmoothStreaming and HLS, + * which also includes functionality for side-loading subtitle files and clipping media. *
  • {@link Renderer}s that render individual components of the media. The library * provides default implementations for common media types ({@link MediaCodecVideoRenderer}, * {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A * Renderer consumes media from the MediaSource being played. Renderers are injected when the - * player is created. + * player is created. The number of renderers and their respective track types can be obtained + * by calling {@link #getRendererCount()} and {@link #getRendererType(int)}. *
  • A {@link TrackSelector} that selects tracks provided by the MediaSource to be * consumed by each of the available Renderers. The library provides a default implementation * ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected @@ -133,17 +142,391 @@ import java.util.List; */ public interface ExoPlayer extends Player { + /** The audio component of an {@link ExoPlayer}. */ + interface AudioComponent { + + /** + * Adds a listener to receive audio events. + * + * @param listener The listener to register. + * @deprecated Use {@link #addListener(Listener)}. + */ + @Deprecated + void addAudioListener(AudioListener listener); + + /** + * Removes a listener of audio events. + * + * @param listener The listener to unregister. + * @deprecated Use {@link #removeListener(Listener)}. + */ + @Deprecated + void removeAudioListener(AudioListener listener); + + /** + * 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 {@link Util#getStreamTypeForAudioUsage(int)}. + * + *

    If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link + * C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link + * IllegalArgumentException}. + * + * @param audioAttributes The attributes to use for audio playback. + * @param handleAudioFocus True if the player should handle audio focus, false otherwise. + */ + void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus); + + /** Returns the attributes for audio playback. */ + AudioAttributes getAudioAttributes(); + + /** + * Sets the ID of the audio session to attach to the underlying {@link + * android.media.AudioTrack}. + * + *

    The audio session ID can be generated using {@link C#generateAudioSessionIdV21(Context)} + * for API 21+. + * + * @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} if it should + * be generated by the framework. + */ + void setAudioSessionId(int audioSessionId); + + /** Returns the audio session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} if not set. */ + int getAudioSessionId(); + + /** Sets information on an auxiliary audio effect to attach to the underlying audio track. */ + void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); + + /** Detaches any previously attached auxiliary audio effect from the underlying audio track. */ + void clearAuxEffectInfo(); + + /** + * Sets the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + * + * @param audioVolume Linear output gain to apply to all audio channels. + */ + void setVolume(float audioVolume); + + /** + * Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + * + * @return The linear gain applied to all audio channels. + */ + float getVolume(); + + /** + * Sets whether skipping silences in the audio stream is enabled. + * + * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled. + */ + void setSkipSilenceEnabled(boolean skipSilenceEnabled); + + /** Returns whether skipping silences in the audio stream is enabled. */ + boolean getSkipSilenceEnabled(); + } + + /** The video component of an {@link ExoPlayer}. */ + interface VideoComponent { + + /** + * Sets the {@link C.VideoScalingMode}. + * + * @param videoScalingMode The {@link C.VideoScalingMode}. + */ + void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode); + + /** Returns the {@link C.VideoScalingMode}. */ + @C.VideoScalingMode + int getVideoScalingMode(); + + /** + * Adds a listener to receive video events. + * + * @param listener The listener to register. + * @deprecated Use {@link #addListener(Listener)}. + */ + @Deprecated + void addVideoListener(VideoListener listener); + + /** + * Removes a listener of video events. + * + * @param listener The listener to unregister. + * @deprecated Use {@link #removeListener(Listener)}. + */ + @Deprecated + void removeVideoListener(VideoListener listener); + + /** + * Sets a listener to receive video frame metadata events. + * + *

    This method is intended to be called by the same component that sets the {@link Surface} + * onto which video will be rendered. If using ExoPlayer's standard UI components, this method + * should not be called directly from application code. + * + * @param listener The listener. + */ + void setVideoFrameMetadataListener(VideoFrameMetadataListener listener); + + /** + * Clears the listener which receives video frame metadata events if it matches the one passed. + * Else does nothing. + * + * @param listener The listener to clear. + */ + void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener); + + /** + * Sets a listener of camera motion events. + * + * @param listener The listener. + */ + void setCameraMotionListener(CameraMotionListener listener); + + /** + * Clears the listener which receives camera motion events if it matches the one passed. Else + * does nothing. + * + * @param listener The listener to clear. + */ + void clearCameraMotionListener(CameraMotionListener listener); + + /** + * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} + * currently set on the player. + */ + void clearVideoSurface(); + + /** + * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surface The surface to clear. + */ + void clearVideoSurface(@Nullable Surface surface); + + /** + * Sets the {@link 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 {@code + * setVideoSurface(null)} if the surface is destroyed. + * + *

    If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link + * SurfaceHolder} then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, {@link + * #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} rather + * than this method, since passing the holder allows the player to track the lifecycle of the + * surface automatically. + * + * @param surface The {@link Surface}. + */ + void setVideoSurface(@Nullable Surface surface); + + /** + * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be + * rendered. The player will track the lifecycle of the surface automatically. + * + * @param surfaceHolder The surface holder. + */ + void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); + + /** + * Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being + * rendered if it matches the one passed. Else does nothing. + * + * @param surfaceHolder The surface holder to clear. + */ + void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); + + /** + * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param surfaceView The surface view. + */ + void setVideoSurfaceView(@Nullable SurfaceView surfaceView); + + /** + * Clears the {@link SurfaceView} onto which video is being rendered if it matches the one + * passed. Else does nothing. + * + * @param surfaceView The texture view to clear. + */ + void clearVideoSurfaceView(@Nullable SurfaceView surfaceView); + + /** + * Sets the {@link TextureView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param textureView The texture view. + */ + void setVideoTextureView(@Nullable TextureView textureView); + + /** + * Clears the {@link TextureView} onto which video is being rendered if it matches the one + * passed. Else does nothing. + * + * @param textureView The texture view to clear. + */ + void clearVideoTextureView(@Nullable TextureView textureView); + + /** + * 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 Listener#onVideoSizeChanged(int, int, int, float) + */ + VideoSize getVideoSize(); + } + + /** The text component of an {@link ExoPlayer}. */ + interface TextComponent { + + /** + * Registers an output to receive text events. + * + * @param listener The output to register. + * @deprecated Use {@link #addListener(Listener)}. + */ + @Deprecated + void addTextOutput(TextOutput listener); + + /** + * Removes a text output. + * + * @param listener The output to remove. + * @deprecated Use {@link #removeListener(Listener)}. + */ + @Deprecated + void removeTextOutput(TextOutput listener); + + /** Returns the current {@link Cue Cues}. This list may be empty. */ + List getCurrentCues(); + } + + /** The metadata component of an {@link ExoPlayer}. */ + interface MetadataComponent { + + /** + * Adds a {@link MetadataOutput} to receive metadata. + * + * @param output The output to register. + * @deprecated Use {@link #addListener(Listener)}. + */ + @Deprecated + void addMetadataOutput(MetadataOutput output); + + /** + * Removes a {@link MetadataOutput}. + * + * @param output The output to remove. + * @deprecated Use {@link #removeListener(Listener)}. + */ + @Deprecated + void removeMetadataOutput(MetadataOutput output); + } + + /** The device component of an {@link ExoPlayer}. */ + interface DeviceComponent { + + /** + * Adds a listener to receive device events. + * + * @deprecated Use {@link #addListener(Listener)}. + */ + @Deprecated + void addDeviceListener(DeviceListener listener); + + /** + * Removes a listener of device events. + * + * @deprecated Use {@link #removeListener(Listener)}. + */ + @Deprecated + void removeDeviceListener(DeviceListener listener); + + /** Gets the device information. */ + DeviceInfo getDeviceInfo(); + + /** + * Gets the current volume of the device. + * + *

    For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume + * returned by this method varies according to the current {@link C.StreamType stream type}. The + * stream type is determined by {@link AudioAttributes#usage} which can be converted to stream + * type with {@link Util#getStreamTypeForAudioUsage(int)}. + * + *

    For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of + * the remote device is returned. + */ + int getDeviceVolume(); + + /** Gets whether the device is muted or not. */ + boolean isDeviceMuted(); + + /** + * Sets the volume of the device. + * + * @param volume The volume to set. + */ + void setDeviceVolume(int volume); + + /** Increases the volume of the device. */ + void increaseDeviceVolume(); + + /** Decreases the volume of the device. */ + void decreaseDeviceVolume(); + + /** Sets the mute state of the device. */ + void setDeviceMuted(boolean muted); + } + /** * The default timeout for calls to {@link #release} and {@link #setForegroundMode}, in * milliseconds. */ long DEFAULT_RELEASE_TIMEOUT_MS = 500; + /** + * A listener for audio offload events. + * + *

    This class is experimental, and might be renamed, moved or removed in a future release. + */ + interface AudioOffloadListener { + /** + * Called when the player has started or stopped offload scheduling using {@link + * ExoPlayer#experimentalSetOffloadSchedulingEnabled(boolean)}. + * + *

    This method is experimental, and will be renamed or removed in a future release. + */ + default void onExperimentalOffloadSchedulingEnabledChanged(boolean offloadSchedulingEnabled) {} + + /** + * Called when the player has started or finished sleeping for offload. + * + *

    This method is experimental, and will be renamed or removed in a future release. + */ + default void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {} + } + /** * A builder for {@link ExoPlayer} instances. * *

    See {@link #Builder(Context, Renderer...)} for the list of default values. + * + * @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ + @Deprecated final class Builder { private final Renderer[] renderers; @@ -233,13 +616,14 @@ public interface ExoPlayer extends Player { /** * Set a limit on the time a call to {@link ExoPlayer#setForegroundMode} can spend. If a call to * {@link ExoPlayer#setForegroundMode} takes more than {@code timeoutMs} milliseconds to - * complete, the player will raise an error via {@link Player.EventListener#onPlayerError}. + * complete, the player will raise an error via {@link Player.Listener#onPlayerError}. * *

    This method is experimental, and will be renamed or removed in a future release. * * @param timeoutMs The time limit in milliseconds. */ public Builder experimentalSetForegroundModeTimeoutMs(long timeoutMs) { + Assertions.checkState(!buildCalled); setForegroundModeTimeoutMs = timeoutMs; return this; } @@ -358,7 +742,7 @@ public interface ExoPlayer extends Player { * *

    If a call to {@link #release} or {@link #setForegroundMode} takes more than {@code * timeoutMs} to complete, the player will report an error via {@link - * Player.EventListener#onPlayerError}. + * Player.Listener#onPlayerError}. * * @param releaseTimeoutMs The release timeout, in milliseconds. * @return This builder. @@ -375,7 +759,7 @@ public interface ExoPlayer extends Player { * *

    This means the player will pause at the end of each window in the current {@link * #getCurrentTimeline() timeline}. Listeners will be informed by a call to {@link - * Player.EventListener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link + * Player.Listener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link * Player#PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM} when this happens. * * @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item. @@ -440,7 +824,8 @@ public interface ExoPlayer extends Player { pauseAtEndOfMediaItems, clock, looper, - /* wrappingPlayer= */ null); + /* wrappingPlayer= */ null, + /* additionalPermanentAvailableCommands= */ Commands.EMPTY); if (setForegroundModeTimeoutMs > 0) { player.experimentalSetForegroundModeTimeoutMs(setForegroundModeTimeoutMs); @@ -449,6 +834,56 @@ public interface ExoPlayer extends Player { } } + /** Returns the component of this player for audio output, or null if audio is not supported. */ + @Nullable + AudioComponent getAudioComponent(); + + /** Returns the component of this player for video output, or null if video is not supported. */ + @Nullable + VideoComponent getVideoComponent(); + + /** Returns the component of this player for text output, or null if text is not supported. */ + @Nullable + TextComponent getTextComponent(); + + /** + * Returns the component of this player for metadata output, or null if metadata is not supported. + */ + @Nullable + MetadataComponent getMetadataComponent(); + + /** Returns the component of this player for playback device, or null if it's not supported. */ + @Nullable + DeviceComponent getDeviceComponent(); + + /** + * Adds a listener to receive audio offload events. + * + * @param listener The listener to register. + */ + void addAudioOffloadListener(AudioOffloadListener listener); + + /** + * Removes a listener of audio offload events. + * + * @param listener The listener to unregister. + */ + void removeAudioOffloadListener(AudioOffloadListener listener); + + /** Returns the number of renderers. */ + int getRendererCount(); + + /** + * Returns the track type that the renderer at a given index handles. + * + *

    For example, a video renderer will return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will + * return {@link C#TRACK_TYPE_AUDIO} and a text renderer will return {@link C#TRACK_TYPE_TEXT}. + * + * @param index The index of the renderer. + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getRendererType(int index); + /** * Returns the track selector that this player uses, or null if track selection is not supported. */ @@ -625,7 +1060,7 @@ public interface ExoPlayer extends Player { * *

    This means the player will pause at the end of each window in the current {@link * #getCurrentTimeline() timeline}. Listeners will be informed by a call to {@link - * Player.EventListener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link + * Player.Listener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link * Player#PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM} when this happens. * * @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item. @@ -650,7 +1085,7 @@ public interface ExoPlayer extends Player { *

    While offload scheduling is enabled, player events may be delivered severely delayed and * apps should not interact with the player. When returning to the foreground, disable offload * scheduling and wait for {@link - * Player.EventListener#onExperimentalOffloadSchedulingEnabledChanged(boolean)} to be called with + * AudioOffloadListener#onExperimentalOffloadSchedulingEnabledChanged(boolean)} to be called with * {@code offloadSchedulingEnabled = false} before interacting with the player. * *

    This mode should save significant power when the phone is playing offload audio with the @@ -663,7 +1098,7 @@ public interface ExoPlayer extends Player { *

  • Audio offload rendering is enabled in {@link * DefaultRenderersFactory#setEnableAudioOffload} or the equivalent option passed to {@link * DefaultAudioSink#DefaultAudioSink(AudioCapabilities, - * DefaultAudioSink.AudioProcessorChain, boolean, boolean, boolean)}. + * DefaultAudioSink.AudioProcessorChain, boolean, boolean, int)}. *
  • An audio track is playing in a format that the device supports offloading (for example, * MP3 or AAC). *
  • The {@link AudioSink} is playing with an offload {@link AudioTrack}. @@ -682,7 +1117,7 @@ public interface ExoPlayer extends Player { * Returns whether the player has paused its main loop to save power in offload scheduling mode. * * @see #experimentalSetOffloadSchedulingEnabled(boolean) - * @see EventListener#onExperimentalSleepingForOffloadChanged(boolean) + * @see AudioOffloadListener#onExperimentalSleepingForOffloadChanged(boolean) */ boolean experimentalIsSleepingForOffload(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java deleted file mode 100644 index 40c3473abd..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import android.content.Context; -import android.os.Looper; -import com.google.android.exoplayer2.analytics.AnalyticsCollector; -import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelector; -import com.google.android.exoplayer2.upstream.BandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.util.Clock; -import com.google.android.exoplayer2.util.Util; - -/** @deprecated Use {@link SimpleExoPlayer.Builder} or {@link ExoPlayer.Builder} instead. */ -@Deprecated -public final class ExoPlayerFactory { - - private ExoPlayerFactory() {} - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, - TrackSelector trackSelector, - LoadControl loadControl, - @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) { - RenderersFactory renderersFactory = - new DefaultRenderersFactory(context).setExtensionRendererMode(extensionRendererMode); - return newSimpleInstance(context, renderersFactory, trackSelector, loadControl); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, - TrackSelector trackSelector, - LoadControl loadControl, - @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode, - long allowedVideoJoiningTimeMs) { - RenderersFactory renderersFactory = - new DefaultRenderersFactory(context) - .setExtensionRendererMode(extensionRendererMode) - .setAllowedVideoJoiningTimeMs(allowedVideoJoiningTimeMs); - return newSimpleInstance(context, renderersFactory, trackSelector, loadControl); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance(Context context) { - return newSimpleInstance(context, new DefaultTrackSelector(context)); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) { - return newSimpleInstance(context, new DefaultRenderersFactory(context), trackSelector); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, RenderersFactory renderersFactory, TrackSelector trackSelector) { - return newSimpleInstance(context, renderersFactory, trackSelector, new DefaultLoadControl()); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, TrackSelector trackSelector, LoadControl loadControl) { - RenderersFactory renderersFactory = new DefaultRenderersFactory(context); - return newSimpleInstance(context, renderersFactory, trackSelector, loadControl); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, - RenderersFactory renderersFactory, - TrackSelector trackSelector, - LoadControl loadControl) { - return newSimpleInstance( - context, renderersFactory, trackSelector, loadControl, Util.getCurrentOrMainLooper()); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, - RenderersFactory renderersFactory, - TrackSelector trackSelector, - LoadControl loadControl, - BandwidthMeter bandwidthMeter) { - return newSimpleInstance( - context, - renderersFactory, - trackSelector, - loadControl, - bandwidthMeter, - new AnalyticsCollector(Clock.DEFAULT), - Util.getCurrentOrMainLooper()); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, - RenderersFactory renderersFactory, - TrackSelector trackSelector, - LoadControl loadControl, - AnalyticsCollector analyticsCollector) { - return newSimpleInstance( - context, - renderersFactory, - trackSelector, - loadControl, - analyticsCollector, - Util.getCurrentOrMainLooper()); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, - RenderersFactory renderersFactory, - TrackSelector trackSelector, - LoadControl loadControl, - Looper applicationLooper) { - return newSimpleInstance( - context, - renderersFactory, - trackSelector, - loadControl, - new AnalyticsCollector(Clock.DEFAULT), - applicationLooper); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static SimpleExoPlayer newSimpleInstance( - Context context, - RenderersFactory renderersFactory, - TrackSelector trackSelector, - LoadControl loadControl, - AnalyticsCollector analyticsCollector, - Looper applicationLooper) { - return newSimpleInstance( - context, - renderersFactory, - trackSelector, - loadControl, - DefaultBandwidthMeter.getSingletonInstance(context), - analyticsCollector, - applicationLooper); - } - - /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */ - @SuppressWarnings("deprecation") - @Deprecated - public static SimpleExoPlayer newSimpleInstance( - Context context, - RenderersFactory renderersFactory, - TrackSelector trackSelector, - LoadControl loadControl, - BandwidthMeter bandwidthMeter, - AnalyticsCollector analyticsCollector, - Looper applicationLooper) { - return new SimpleExoPlayer( - context, - renderersFactory, - trackSelector, - new DefaultMediaSourceFactory(context), - loadControl, - bandwidthMeter, - analyticsCollector, - /* useLazyPreparation= */ true, - Clock.DEFAULT, - applicationLooper); - } - - /** @deprecated Use {@link ExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static ExoPlayer newInstance( - Context context, Renderer[] renderers, TrackSelector trackSelector) { - return newInstance(context, renderers, trackSelector, new DefaultLoadControl()); - } - - /** @deprecated Use {@link ExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static ExoPlayer newInstance( - Context context, Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { - return newInstance( - context, renderers, trackSelector, loadControl, Util.getCurrentOrMainLooper()); - } - - /** @deprecated Use {@link ExoPlayer.Builder} instead. */ - @Deprecated - @SuppressWarnings("deprecation") - public static ExoPlayer newInstance( - Context context, - Renderer[] renderers, - TrackSelector trackSelector, - LoadControl loadControl, - Looper applicationLooper) { - return newInstance( - context, - renderers, - trackSelector, - loadControl, - DefaultBandwidthMeter.getSingletonInstance(context), - applicationLooper); - } - - /** @deprecated Use {@link ExoPlayer.Builder} instead. */ - @Deprecated - public static ExoPlayer newInstance( - Context context, - Renderer[] renderers, - TrackSelector trackSelector, - LoadControl loadControl, - BandwidthMeter bandwidthMeter, - Looper applicationLooper) { - return new ExoPlayerImpl( - renderers, - trackSelector, - new DefaultMediaSourceFactory(context), - loadControl, - bandwidthMeter, - /* analyticsCollector= */ null, - /* useLazyPreparation= */ true, - SeekParameters.DEFAULT, - new DefaultLivePlaybackSpeedControl.Builder().build(), - ExoPlayer.DEFAULT_RELEASE_TIMEOUT_MS, - /* pauseAtEndOfMediaItems= */ false, - Clock.DEFAULT, - applicationLooper, - /* wrappingPlayer= */ null); - } -} 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 de8aa48891..8ee7cf8687 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 @@ -25,15 +25,22 @@ import android.annotation.SuppressLint; import android.os.Handler; import android.os.Looper; import android.util.Pair; +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.PlayerMessage.Target; import com.google.android.exoplayer2.analytics.AnalyticsCollector; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.device.DeviceInfo; import com.google.android.exoplayer2.metadata.Metadata; 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.TrackGroupArray; +import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; @@ -45,14 +52,14 @@ import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CopyOnWriteArraySet; -/** - * An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayer.Builder}. - */ +/** An {@link ExoPlayer} implementation. */ /* package */ final class ExoPlayerImpl extends BasePlayer implements ExoPlayer { private static final String TAG = "ExoPlayerImpl"; @@ -65,13 +72,15 @@ import java.util.List; * operation. */ /* package */ final TrackSelectorResult emptyTrackSelectorResult; + /* package */ final Commands permanentAvailableCommands; private final Renderer[] renderers; private final TrackSelector trackSelector; private final HandlerWrapper playbackInfoUpdateHandler; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal internalPlayer; - private final ListenerSet listeners; + private final ListenerSet listeners; + private final CopyOnWriteArraySet audioOffloadListeners; private final Timeline.Period period; private final List mediaSourceHolderSnapshots; private final boolean useLazyPreparation; @@ -84,13 +93,15 @@ import java.util.List; @RepeatMode private int repeatMode; private boolean shuffleModeEnabled; private int pendingOperationAcks; - private boolean hasPendingDiscontinuity; @DiscontinuityReason private int pendingDiscontinuityReason; + private boolean pendingDiscontinuity; @PlayWhenReadyChangeReason private int pendingPlayWhenReadyChangeReason; private boolean foregroundMode; private SeekParameters seekParameters; private ShuffleOrder shuffleOrder; private boolean pauseAtEndOfMediaItems; + private Commands availableCommands; + private MediaMetadata mediaMetadata; // Playback information when there is no pending seek/set source operation. private PlaybackInfo playbackInfo; @@ -121,6 +132,8 @@ import java.util.List; * which is used to call listeners on. * @param wrappingPlayer The {@link Player} wrapping this one if applicable. This player instance * should be used for all externally visible callbacks. + * @param additionalPermanentAvailableCommands The {@link Commands} that are permanently available + * in the wrapping player but that are not in this player. */ @SuppressLint("HandlerLeak") public ExoPlayerImpl( @@ -137,7 +150,8 @@ import java.util.List; boolean pauseAtEndOfMediaItems, Clock clock, Looper applicationLooper, - @Nullable Player wrappingPlayer) { + @Nullable Player wrappingPlayer, + Commands additionalPermanentAvailableCommands) { Log.i( TAG, "Init " @@ -164,8 +178,8 @@ import java.util.List; new ListenerSet<>( applicationLooper, clock, - Player.Events::new, - (listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags)); + (listener, flags) -> listener.onEvents(playerForListeners, new Events(flags))); + audioOffloadListeners = new CopyOnWriteArraySet<>(); mediaSourceHolderSnapshots = new ArrayList<>(); shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); emptyTrackSelectorResult = @@ -174,6 +188,27 @@ import java.util.List; new ExoTrackSelection[renderers.length], /* info= */ null); period = new Timeline.Period(); + permanentAvailableCommands = + new Commands.Builder() + .addAll( + COMMAND_PLAY_PAUSE, + COMMAND_PREPARE_STOP, + COMMAND_SET_SPEED_AND_PITCH, + COMMAND_SET_SHUFFLE_MODE, + COMMAND_SET_REPEAT_MODE, + COMMAND_GET_CURRENT_MEDIA_ITEM, + COMMAND_GET_MEDIA_ITEMS, + COMMAND_GET_MEDIA_ITEMS_METADATA, + COMMAND_CHANGE_MEDIA_ITEMS) + .addAll(additionalPermanentAvailableCommands) + .build(); + availableCommands = + new Commands.Builder() + .addAll(permanentAvailableCommands) + .add(COMMAND_SEEK_TO_DEFAULT_POSITION) + .add(COMMAND_SEEK_TO_MEDIA_ITEM) + .build(); + mediaMetadata = MediaMetadata.EMPTY; maskingWindowIndex = C.INDEX_UNSET; playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null); playbackInfoUpdateListener = @@ -207,7 +242,7 @@ import java.util.List; /** * Set a limit on the time a call to {@link #setForegroundMode} can spend. If a call to {@link * #setForegroundMode} takes more than {@code timeoutMs} milliseconds to complete, the player will - * raise an error via {@link Player.EventListener#onPlayerError}. + * raise an error via {@link Player.Listener#onPlayerError}. * *

    This method is experimental, and will be renamed or removed in a future release. It should * only be called before the player is used. @@ -273,16 +308,43 @@ import java.util.List; return clock; } + @Override + public void addListener(Listener listener) { + EventListener eventListener = listener; + addListener(eventListener); + } + @Override public void addListener(Player.EventListener listener) { listeners.add(listener); } + @Override + public void removeListener(Listener listener) { + EventListener eventListener = listener; + removeListener(eventListener); + } + @Override public void removeListener(Player.EventListener listener) { listeners.remove(listener); } + @Override + public void addAudioOffloadListener(AudioOffloadListener listener) { + audioOffloadListeners.add(listener); + } + + @Override + public void removeAudioOffloadListener(AudioOffloadListener listener) { + audioOffloadListeners.remove(listener); + } + + @Override + public Commands getAvailableCommands() { + return availableCommands; + } + @Override @State public int getPlaybackState() { @@ -295,13 +357,6 @@ import java.util.List; return playbackInfo.playbackSuppressionReason; } - @Deprecated - @Override - @Nullable - public ExoPlaybackException getPlaybackError() { - return getPlayerError(); - } - @Override @Nullable public ExoPlaybackException getPlayerError() { @@ -332,11 +387,13 @@ import java.util.List; internalPlayer.prepare(); updatePlaybackInfo( playbackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ TIMELINE_CHANGE_REASON_SOURCE_UPDATE, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + /* positionDiscontinuity= */ false, + /* ignored */ DISCONTINUITY_REASON_INTERNAL, + /* ignored */ C.TIME_UNSET, + /* ignored */ C.INDEX_UNSET); } /** @@ -408,13 +465,9 @@ import java.util.List; mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false); } - @Override - public void addMediaItems(List mediaItems) { - addMediaItems(/* index= */ mediaSourceHolderSnapshots.size(), mediaItems); - } - @Override public void addMediaItems(int index, List mediaItems) { + index = min(index, mediaSourceHolderSnapshots.size()); addMediaSources(index, createMediaSources(mediaItems)); } @@ -448,23 +501,30 @@ import java.util.List; internalPlayer.addMediaSources(index, holders, shuffleOrder); updatePlaybackInfo( newPlaybackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + /* positionDiscontinuity= */ false, + /* ignored */ DISCONTINUITY_REASON_INTERNAL, + /* ignored */ C.TIME_UNSET, + /* ignored */ C.INDEX_UNSET); } @Override public void removeMediaItems(int fromIndex, int toIndex) { - PlaybackInfo playbackInfo = removeMediaItemsInternal(fromIndex, toIndex); + toIndex = min(toIndex, mediaSourceHolderSnapshots.size()); + PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex); + boolean positionDiscontinuity = + !newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid); updatePlaybackInfo( - playbackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL, + newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + positionDiscontinuity, + Player.DISCONTINUITY_REASON_REMOVE, + /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), + /* ignored */ C.INDEX_UNSET); } @Override @@ -487,16 +547,13 @@ import java.util.List; internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder); updatePlaybackInfo( newPlaybackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); - } - - @Override - public void clearMediaItems() { - removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ mediaSourceHolderSnapshots.size()); + /* seekProcessed= */ false, + /* positionDiscontinuity= */ false, + /* ignored */ DISCONTINUITY_REASON_INTERNAL, + /* ignored */ C.TIME_UNSET, + /* ignored */ C.INDEX_UNSET); } @Override @@ -513,11 +570,13 @@ import java.util.List; internalPlayer.setShuffleOrder(shuffleOrder); updatePlaybackInfo( newPlaybackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + /* positionDiscontinuity= */ false, + /* ignored */ DISCONTINUITY_REASON_INTERNAL, + /* ignored */ C.TIME_UNSET, + /* ignored */ C.INDEX_UNSET); } @Override @@ -556,11 +615,13 @@ import java.util.List; internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason); updatePlaybackInfo( playbackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, playWhenReadyChangeReason, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + /* positionDiscontinuity= */ false, + /* ignored */ DISCONTINUITY_REASON_INTERNAL, + /* ignored */ C.TIME_UNSET, + /* ignored */ C.INDEX_UNSET); } @Override @@ -573,8 +634,10 @@ import java.util.List; if (this.repeatMode != repeatMode) { this.repeatMode = repeatMode; internalPlayer.setRepeatMode(repeatMode); - listeners.sendEvent( + listeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode)); + updateAvailableCommands(); + listeners.flushEvents(); } } @@ -588,9 +651,11 @@ import java.util.List; if (this.shuffleModeEnabled != shuffleModeEnabled) { this.shuffleModeEnabled = shuffleModeEnabled; internalPlayer.setShuffleModeEnabled(shuffleModeEnabled); - listeners.sendEvent( + listeners.queueEvent( Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled)); + updateAvailableCommands(); + listeners.flushEvents(); } } @@ -625,7 +690,8 @@ import java.util.List; @Player.State int newPlaybackState = getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING; - PlaybackInfo newPlaybackInfo = this.playbackInfo.copyWithPlaybackState(newPlaybackState); + int oldMaskingWindowIndex = getCurrentWindowIndex(); + PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState); newPlaybackInfo = maskTimelineAndPosition( newPlaybackInfo, @@ -634,15 +700,17 @@ import java.util.List; internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); updatePlaybackInfo( newPlaybackInfo, - /* positionDiscontinuity= */ true, - /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ true); + /* seekProcessed= */ true, + /* positionDiscontinuity= */ true, + /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, + /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), + oldMaskingWindowIndex); } @Override - public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { + public void setPlaybackParameters(PlaybackParameters playbackParameters) { if (playbackParameters == null) { playbackParameters = PlaybackParameters.DEFAULT; } @@ -654,11 +722,13 @@ import java.util.List; internalPlayer.setPlaybackParameters(playbackParameters); updatePlaybackInfo( newPlaybackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + /* positionDiscontinuity= */ false, + /* ignored */ DISCONTINUITY_REASON_INTERNAL, + /* ignored */ C.TIME_UNSET, + /* ignored */ C.INDEX_UNSET); } @Override @@ -727,13 +797,17 @@ import java.util.List; } pendingOperationAcks++; internalPlayer.stop(); + boolean positionDiscontinuity = + playbackInfo.timeline.isEmpty() && !this.playbackInfo.timeline.isEmpty(); updatePlaybackInfo( playbackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ DISCONTINUITY_REASON_INTERNAL, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + positionDiscontinuity, + DISCONTINUITY_REASON_REMOVE, + /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(playbackInfo), + /* ignored */ C.INDEX_UNSET); } @Override @@ -808,13 +882,7 @@ import java.util.List; @Override public long getCurrentPosition() { - if (playbackInfo.timeline.isEmpty()) { - return maskingWindowPositionMs; - } else if (playbackInfo.periodId.isAd()) { - return C.usToMs(playbackInfo.positionUs); - } else { - return periodPositionUsToWindowPositionMs(playbackInfo.periodId, playbackInfo.positionUs); - } + return C.usToMs(getCurrentPositionUsInternal(playbackInfo)); } @Override @@ -878,8 +946,9 @@ import java.util.List; contentBufferedPositionUs = loadingPeriod.durationUs; } } - return periodPositionUsToWindowPositionMs( - playbackInfo.loadingMediaPeriodId, contentBufferedPositionUs); + return C.usToMs( + periodPositionUsToWindowPositionUs( + playbackInfo.timeline, playbackInfo.loadingMediaPeriodId, contentBufferedPositionUs)); } @Override @@ -913,11 +982,125 @@ import java.util.List; return playbackInfo.staticMetadata; } + @Override + public MediaMetadata getMediaMetadata() { + return mediaMetadata; + } + + public void onMetadata(Metadata metadata) { + MediaMetadata newMediaMetadata = + mediaMetadata.buildUpon().populateFromMetadata(metadata).build(); + if (newMediaMetadata.equals(mediaMetadata)) { + return; + } + mediaMetadata = newMediaMetadata; + listeners.sendEvent( + EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(mediaMetadata)); + } + @Override public Timeline getCurrentTimeline() { return playbackInfo.timeline; } + /** This method is not supported and returns {@link AudioAttributes#DEFAULT}. */ + @Override + public AudioAttributes getAudioAttributes() { + return AudioAttributes.DEFAULT; + } + + /** This method is not supported and does nothing. */ + @Override + public void setVolume(float audioVolume) {} + + /** This method is not supported and returns 1. */ + @Override + public float getVolume() { + return 1; + } + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurface() {} + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurface(@Nullable Surface surface) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoSurface(@Nullable Surface surface) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {} + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {} + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {} + + /** This method is not supported and does nothing. */ + @Override + public void setVideoTextureView(@Nullable TextureView textureView) {} + + /** This method is not supported and does nothing. */ + @Override + public void clearVideoTextureView(@Nullable TextureView textureView) {} + + /** This method is not supported and returns {@link VideoSize#UNKNOWN}. */ + @Override + public VideoSize getVideoSize() { + return VideoSize.UNKNOWN; + } + + /** This method is not supported and returns an empty list. */ + @Override + public ImmutableList getCurrentCues() { + return ImmutableList.of(); + } + + /** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */ + @Override + public DeviceInfo getDeviceInfo() { + return DeviceInfo.UNKNOWN; + } + + /** This method is not supported and always returns {@code 0}. */ + @Override + public int getDeviceVolume() { + return 0; + } + + /** This method is not supported and always returns {@link false}. */ + @Override + public boolean isDeviceMuted() { + return false; + } + + /** This method is not supported and does nothing. */ + @Override + public void setDeviceVolume(int volume) {} + + /** This method is not supported and does nothing. */ + @Override + public void increaseDeviceVolume() {} + + /** This method is not supported and does nothing. */ + @Override + public void decreaseDeviceVolume() {} + + /** This method is not supported and does nothing. */ + @Override + public void setDeviceMuted(boolean muted) {} + private int getCurrentWindowIndexInternal() { if (playbackInfo.timeline.isEmpty()) { return maskingWindowIndex; @@ -927,6 +1110,17 @@ import java.util.List; } } + private long getCurrentPositionUsInternal(PlaybackInfo playbackInfo) { + if (playbackInfo.timeline.isEmpty()) { + return C.msToUs(maskingWindowPositionMs); + } else if (playbackInfo.periodId.isAd()) { + return playbackInfo.positionUs; + } else { + return periodPositionUsToWindowPositionUs( + playbackInfo.timeline, playbackInfo.periodId, playbackInfo.positionUs); + } + } + private List createMediaSources(List mediaItems) { List mediaSources = new ArrayList<>(); for (int i = 0; i < mediaItems.size(); i++) { @@ -938,8 +1132,8 @@ import java.util.List; private void handlePlaybackInfo(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate) { pendingOperationAcks -= playbackInfoUpdate.operationAcks; if (playbackInfoUpdate.positionDiscontinuity) { - hasPendingDiscontinuity = true; pendingDiscontinuityReason = playbackInfoUpdate.discontinuityReason; + pendingDiscontinuity = true; } if (playbackInfoUpdate.hasPlayWhenReadyChangeReason) { pendingPlayWhenReadyChangeReason = playbackInfoUpdate.playWhenReadyChangeReason; @@ -960,15 +1154,33 @@ import java.util.List; mediaSourceHolderSnapshots.get(i).timeline = timelines.get(i); } } - boolean positionDiscontinuity = hasPendingDiscontinuity; - hasPendingDiscontinuity = false; + boolean positionDiscontinuity = false; + long discontinuityWindowStartPositionUs = C.TIME_UNSET; + if (pendingDiscontinuity) { + positionDiscontinuity = + !playbackInfoUpdate.playbackInfo.periodId.equals(playbackInfo.periodId) + || playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs + != playbackInfo.positionUs; + if (positionDiscontinuity) { + discontinuityWindowStartPositionUs = + newTimeline.isEmpty() || playbackInfoUpdate.playbackInfo.periodId.isAd() + ? playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs + : periodPositionUsToWindowPositionUs( + newTimeline, + playbackInfoUpdate.playbackInfo.periodId, + playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs); + } + } + pendingDiscontinuity = false; updatePlaybackInfo( playbackInfoUpdate.playbackInfo, - positionDiscontinuity, - pendingDiscontinuityReason, TIMELINE_CHANGE_REASON_SOURCE_UPDATE, pendingPlayWhenReadyChangeReason, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + positionDiscontinuity, + pendingDiscontinuityReason, + discontinuityWindowStartPositionUs, + /* ignored */ C.INDEX_UNSET); } } @@ -976,11 +1188,14 @@ import java.util.List; @SuppressWarnings("deprecation") private void updatePlaybackInfo( PlaybackInfo playbackInfo, - boolean positionDiscontinuity, - @DiscontinuityReason int positionDiscontinuityReason, @TimelineChangeReason int timelineChangeReason, @PlayWhenReadyChangeReason int playWhenReadyChangeReason, - boolean seekProcessed) { + boolean seekProcessed, + boolean positionDiscontinuity, + @DiscontinuityReason int positionDiscontinuityReason, + long discontinuityWindowStartPositionUs, + int oldMaskingWindowIndex) { + // 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. PlaybackInfo previousPlaybackInfo = this.playbackInfo; @@ -996,29 +1211,56 @@ import java.util.List; !previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)); boolean mediaItemTransitioned = mediaItemTransitionInfo.first; int mediaItemTransitionReason = mediaItemTransitionInfo.second; - if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) { - listeners.queueEvent( - Player.EVENT_TIMELINE_CHANGED, - listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason)); - } - if (positionDiscontinuity) { - listeners.queueEvent( - Player.EVENT_POSITION_DISCONTINUITY, - listener -> listener.onPositionDiscontinuity(positionDiscontinuityReason)); - } + MediaMetadata newMediaMetadata = mediaMetadata; + @Nullable MediaItem mediaItem = null; if (mediaItemTransitioned) { - @Nullable final MediaItem mediaItem; if (!newPlaybackInfo.timeline.isEmpty()) { int windowIndex = newPlaybackInfo.timeline.getPeriodByUid(newPlaybackInfo.periodId.periodUid, period) .windowIndex; mediaItem = newPlaybackInfo.timeline.getWindow(windowIndex, window).mediaItem; - } else { - mediaItem = null; } + mediaMetadata = mediaItem != null ? mediaItem.mediaMetadata : MediaMetadata.EMPTY; + } + if (!previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) { + newMediaMetadata = + newMediaMetadata.buildUpon().populateFromMetadata(newPlaybackInfo.staticMetadata).build(); + } + boolean metadataChanged = !newMediaMetadata.equals(mediaMetadata); + mediaMetadata = newMediaMetadata; + + if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) { + listeners.queueEvent( + Player.EVENT_TIMELINE_CHANGED, + listener -> { + @Nullable Object manifest = null; + if (newPlaybackInfo.timeline.getWindowCount() == 1) { + // Legacy behavior was to report the manifest for single window timelines only. + Timeline.Window window = new Timeline.Window(); + manifest = newPlaybackInfo.timeline.getWindow(0, window).manifest; + } + listener.onTimelineChanged(newPlaybackInfo.timeline, manifest, timelineChangeReason); + listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason); + }); + } + if (positionDiscontinuity) { + PositionInfo previousPositionInfo = + getPreviousPositionInfo( + positionDiscontinuityReason, previousPlaybackInfo, oldMaskingWindowIndex); + PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); + listeners.queueEvent( + Player.EVENT_POSITION_DISCONTINUITY, + listener -> { + listener.onPositionDiscontinuity(positionDiscontinuityReason); + listener.onPositionDiscontinuity( + previousPositionInfo, positionInfo, positionDiscontinuityReason); + }); + } + if (mediaItemTransitioned) { + @Nullable final MediaItem finalMediaItem = mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, - listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason)); + listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason)); } if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError && newPlaybackInfo.playbackError != null) { @@ -1039,10 +1281,19 @@ import java.util.List; Player.EVENT_STATIC_METADATA_CHANGED, listener -> listener.onStaticMetadataChanged(newPlaybackInfo.staticMetadata)); } + if (metadataChanged) { + final MediaMetadata finalMediaMetadata = mediaMetadata; + listeners.queueEvent( + Player.EVENT_MEDIA_METADATA_CHANGED, + listener -> listener.onMediaMetadataChanged(finalMediaMetadata)); + } if (previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading) { listeners.queueEvent( Player.EVENT_IS_LOADING_CHANGED, - listener -> listener.onIsLoadingChanged(newPlaybackInfo.isLoading)); + listener -> { + listener.onLoadingChanged(newPlaybackInfo.isLoading); + listener.onIsLoadingChanged(newPlaybackInfo.isLoading); + }); } if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState || previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) { @@ -1085,20 +1336,107 @@ import java.util.List; if (seekProcessed) { listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); } + updateAvailableCommands(); + listeners.flushEvents(); + if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) { - listeners.queueEvent( - /* eventFlag= */ C.INDEX_UNSET, - listener -> - listener.onExperimentalOffloadSchedulingEnabledChanged( - newPlaybackInfo.offloadSchedulingEnabled)); + for (AudioOffloadListener listener : audioOffloadListeners) { + listener.onExperimentalOffloadSchedulingEnabledChanged( + newPlaybackInfo.offloadSchedulingEnabled); + } } if (previousPlaybackInfo.sleepingForOffload != newPlaybackInfo.sleepingForOffload) { - listeners.queueEvent( - /* eventFlag= */ C.INDEX_UNSET, - listener -> - listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload)); + for (AudioOffloadListener listener : audioOffloadListeners) { + listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload); + } } - listeners.flushEvents(); + } + + private PositionInfo getPreviousPositionInfo( + @DiscontinuityReason int positionDiscontinuityReason, + PlaybackInfo oldPlaybackInfo, + int oldMaskingWindowIndex) { + @Nullable Object oldWindowUid = null; + @Nullable Object oldPeriodUid = null; + int oldWindowIndex = oldMaskingWindowIndex; + int oldPeriodIndex = C.INDEX_UNSET; + Timeline.Period oldPeriod = new Timeline.Period(); + if (!oldPlaybackInfo.timeline.isEmpty()) { + oldPeriodUid = oldPlaybackInfo.periodId.periodUid; + oldPlaybackInfo.timeline.getPeriodByUid(oldPeriodUid, oldPeriod); + oldWindowIndex = oldPeriod.windowIndex; + oldPeriodIndex = oldPlaybackInfo.timeline.getIndexOfPeriod(oldPeriodUid); + oldWindowUid = oldPlaybackInfo.timeline.getWindow(oldWindowIndex, window).uid; + } + long oldPositionUs; + long oldContentPositionUs; + if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { + oldPositionUs = oldPeriod.positionInWindowUs + oldPeriod.durationUs; + oldContentPositionUs = oldPositionUs; + if (oldPlaybackInfo.periodId.isAd()) { + // The old position is the end of the previous ad. + oldPositionUs = + oldPeriod.getAdDurationUs( + oldPlaybackInfo.periodId.adGroupIndex, oldPlaybackInfo.periodId.adIndexInAdGroup); + // The ad cue point is stored in the old requested content position. + oldContentPositionUs = getRequestedContentPositionUs(oldPlaybackInfo); + } else if (oldPlaybackInfo.periodId.nextAdGroupIndex != C.INDEX_UNSET + && playbackInfo.periodId.isAd()) { + // If it's a transition from content to an ad in the same window, the old position is the + // ad cue point that is the same as current content position. + oldPositionUs = getRequestedContentPositionUs(playbackInfo); + oldContentPositionUs = oldPositionUs; + } + } else if (oldPlaybackInfo.periodId.isAd()) { + oldPositionUs = oldPlaybackInfo.positionUs; + oldContentPositionUs = getRequestedContentPositionUs(oldPlaybackInfo); + } else { + oldPositionUs = oldPeriod.positionInWindowUs + oldPlaybackInfo.positionUs; + oldContentPositionUs = oldPositionUs; + } + return new PositionInfo( + oldWindowUid, + oldWindowIndex, + oldPeriodUid, + oldPeriodIndex, + C.usToMs(oldPositionUs), + C.usToMs(oldContentPositionUs), + oldPlaybackInfo.periodId.adGroupIndex, + oldPlaybackInfo.periodId.adIndexInAdGroup); + } + + private PositionInfo getPositionInfo(long discontinuityWindowStartPositionUs) { + @Nullable Object newWindowUid = null; + @Nullable Object newPeriodUid = null; + int newWindowIndex = getCurrentWindowIndex(); + int newPeriodIndex = C.INDEX_UNSET; + 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; + } + long positionMs = C.usToMs(discontinuityWindowStartPositionUs); + return new PositionInfo( + newWindowUid, + newWindowIndex, + newPeriodUid, + newPeriodIndex, + positionMs, + /* contentPositionMs= */ playbackInfo.periodId.isAd() + ? C.usToMs(getRequestedContentPositionUs(playbackInfo)) + : positionMs, + playbackInfo.periodId.adGroupIndex, + playbackInfo.periodId.adIndexInAdGroup); + } + + private static long getRequestedContentPositionUs(PlaybackInfo playbackInfo) { + Timeline.Window window = new Timeline.Window(); + Timeline.Period period = new Timeline.Period(); + playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); + return playbackInfo.requestedContentPositionUs == C.TIME_UNSET + ? playbackInfo.timeline.getWindow(period.windowIndex, window).getDefaultPositionUs() + : period.getPositionInWindowUs() + playbackInfo.requestedContentPositionUs; } private Pair evaluateMediaItemTransitionReason( @@ -1122,11 +1460,10 @@ import java.util.List; int newWindowIndex = newTimeline.getPeriodByUid(playbackInfo.periodId.periodUid, period).windowIndex; Object newWindowUid = newTimeline.getWindow(newWindowIndex, window).uid; - int firstPeriodIndexInNewWindow = window.firstPeriodIndex; if (!oldWindowUid.equals(newWindowUid)) { @Player.MediaItemTransitionReason int transitionReason; if (positionDiscontinuity - && positionDiscontinuityReason == DISCONTINUITY_REASON_PERIOD_TRANSITION) { + && positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { transitionReason = MEDIA_ITEM_TRANSITION_REASON_AUTO; } else if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK) { @@ -1139,14 +1476,24 @@ import java.util.List; } return new Pair<>(/* isTransitioning */ true, transitionReason); } else if (positionDiscontinuity - && positionDiscontinuityReason == DISCONTINUITY_REASON_PERIOD_TRANSITION - && newTimeline.getIndexOfPeriod(playbackInfo.periodId.periodUid) - == firstPeriodIndexInNewWindow) { + && positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION + && oldPlaybackInfo.periodId.windowSequenceNumber + < playbackInfo.periodId.windowSequenceNumber) { return new Pair<>(/* isTransitioning */ true, MEDIA_ITEM_TRANSITION_REASON_REPEAT); } return new Pair<>(/* isTransitioning */ false, /* mediaItemTransitionReason */ C.INDEX_UNSET); } + private void updateAvailableCommands() { + Commands previousAvailableCommands = availableCommands; + availableCommands = getAvailableCommands(permanentAvailableCommands); + if (!availableCommands.equals(previousAvailableCommands)) { + listeners.queueEvent( + Player.EVENT_AVAILABLE_COMMANDS_CHANGED, + listener -> listener.onAvailableCommandsChanged(availableCommands)); + } + } + private void setMediaSourcesInternal( List mediaSources, int startWindowIndex, @@ -1192,13 +1539,18 @@ import java.util.List; newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState); internalPlayer.setMediaSources( holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder); + boolean positionDiscontinuity = + !playbackInfo.periodId.periodUid.equals(newPlaybackInfo.periodId.periodUid) + && !playbackInfo.timeline.isEmpty(); updatePlaybackInfo( newPlaybackInfo, - /* positionDiscontinuity= */ false, - /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false); + /* seekProcessed= */ false, + /* positionDiscontinuity= */ positionDiscontinuity, + Player.DISCONTINUITY_REASON_REMOVE, + /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), + /* ignored */ C.INDEX_UNSET); } private List addMediaSourceHolders( @@ -1266,11 +1618,13 @@ import java.util.List; if (timeline.isEmpty()) { // Reset periodId and loadingPeriodId. MediaPeriodId dummyMediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline(); + long positionUs = C.msToUs(maskingWindowPositionMs); playbackInfo = playbackInfo.copyWithNewPosition( dummyMediaPeriodId, - /* positionUs= */ C.msToUs(maskingWindowPositionMs), - /* requestedContentPositionUs= */ C.msToUs(maskingWindowPositionMs), + positionUs, + /* requestedContentPositionUs= */ positionUs, + /* discontinuityStartPositionUs= */ positionUs, /* totalBufferedDurationUs= */ 0, TrackGroupArray.EMPTY, emptyTrackSelectorResult, @@ -1299,6 +1653,7 @@ import java.util.List; newPeriodId, /* positionUs= */ newContentPositionUs, /* requestedContentPositionUs= */ newContentPositionUs, + /* discontinuityStartPositionUs= */ newContentPositionUs, /* totalBufferedDurationUs= */ 0, playingPeriodChanged ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, playingPeriodChanged ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, @@ -1324,6 +1679,7 @@ import java.util.List; newPeriodId, /* positionUs= */ playbackInfo.positionUs, /* requestedContentPositionUs= */ playbackInfo.positionUs, + playbackInfo.discontinuityStartPositionUs, /* totalBufferedDurationUs= */ maskedBufferedPositionUs - playbackInfo.positionUs, playbackInfo.trackGroups, playbackInfo.trackSelectorResult, @@ -1347,6 +1703,7 @@ import java.util.List; newPeriodId, /* positionUs= */ newContentPositionUs, /* requestedContentPositionUs= */ newContentPositionUs, + /* discontinuityStartPositionUs= */ newContentPositionUs, maskedTotalBufferedDurationUs, playbackInfo.trackGroups, playbackInfo.trackSelectorResult, @@ -1415,11 +1772,11 @@ import java.util.List; return timeline.getPeriodPosition(window, period, windowIndex, C.msToUs(windowPositionMs)); } - private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) { - long positionMs = C.usToMs(positionUs); - playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); - positionMs += period.getPositionInWindowMs(); - return positionMs; + private long periodPositionUsToWindowPositionUs( + Timeline timeline, MediaPeriodId periodId, long positionUs) { + timeline.getPeriodByUid(periodId.periodUid, period); + positionUs += period.getPositionInWindowUs(); + return positionUs; } private static boolean isPlaying(PlaybackInfo playbackInfo) { 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 3da8409de1..116cb7d19f 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 @@ -146,7 +146,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_PLAYLIST_UPDATE_REQUESTED = 22; private static final int MSG_SET_PAUSE_AT_END_OF_WINDOW = 23; private static final int MSG_SET_OFFLOAD_SCHEDULING_ENABLED = 24; - private static final int MSG_ATTEMPT_ERROR_RECOVERY = 25; + private static final int MSG_ATTEMPT_RENDERER_ERROR_RECOVERY = 25; private static final int ACTIVE_INTERVAL_MS = 10; private static final int IDLE_INTERVAL_MS = 1000; @@ -203,7 +203,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private long rendererPositionUs; private int nextPendingMessageIndexHint; private boolean deliverPendingMessageAtStartPositionRequired; - @Nullable private ExoPlaybackException pendingRecoverableError; + @Nullable private ExoPlaybackException pendingRecoverableRendererError; private long setForegroundModeTimeoutMs; @@ -535,8 +535,8 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_SET_OFFLOAD_SCHEDULING_ENABLED: setOffloadSchedulingEnabledInternal(msg.arg1 == 1); break; - case MSG_ATTEMPT_ERROR_RECOVERY: - attemptErrorRecovery((ExoPlaybackException) msg.obj); + case MSG_ATTEMPT_RENDERER_ERROR_RECOVERY: + attemptRendererErrorRecovery(); break; case MSG_RELEASE: releaseInternal(); @@ -555,17 +555,17 @@ import java.util.concurrent.atomic.AtomicBoolean; e = e.copyWithMediaPeriodId(readingPeriod.info.id); } } - if (e.isRecoverable && pendingRecoverableError == null) { - Log.w(TAG, "Recoverable playback error", e); - pendingRecoverableError = e; - Message message = handler.obtainMessage(MSG_ATTEMPT_ERROR_RECOVERY, e); + if (e.isRecoverable && pendingRecoverableRendererError == null) { + Log.w(TAG, "Recoverable renderer error", e); + pendingRecoverableRendererError = e; // Given that the player is now in an unhandled exception state, the error needs to be // recovered or the player stopped before any other message is handled. - message.getTarget().sendMessageAtFrontOfQueue(message); + handler.sendMessageAtFrontOfQueue( + handler.obtainMessage(MSG_ATTEMPT_RENDERER_ERROR_RECOVERY, e)); } else { - if (pendingRecoverableError != null) { - e.addSuppressed(pendingRecoverableError); - pendingRecoverableError = null; + if (pendingRecoverableRendererError != null) { + pendingRecoverableRendererError.addSuppressed(e); + e = pendingRecoverableRendererError; } Log.e(TAG, "Playback error", e); stopInternal(/* forceResetRenderers= */ true, /* acknowledgeStop= */ false); @@ -595,19 +595,6 @@ import java.util.concurrent.atomic.AtomicBoolean; // Private methods. - private void attemptErrorRecovery(ExoPlaybackException exceptionToRecoverFrom) - throws ExoPlaybackException { - Assertions.checkArgument( - exceptionToRecoverFrom.isRecoverable - && exceptionToRecoverFrom.type == ExoPlaybackException.TYPE_RENDERER); - try { - seekToCurrentPosition(/* sendDiscontinuity= */ true); - } catch (Exception e) { - exceptionToRecoverFrom.addSuppressed(e); - throw exceptionToRecoverFrom; - } - } - /** * Blocks the current thread until a condition becomes true or the specified amount of time has * elapsed. @@ -625,6 +612,7 @@ import java.util.concurrent.atomic.AtomicBoolean; boolean wasInterrupted = false; while (!condition.get() && remainingMs > 0) { try { + clock.onThreadBlocked(); wait(remainingMs); } catch (InterruptedException e) { wasInterrupted = true; @@ -680,7 +668,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mediaSourceList.setMediaSources( mediaSourceListUpdateMessage.mediaSourceHolders, mediaSourceListUpdateMessage.shuffleOrder); - handleMediaSourceListInfoRefreshed(timeline); + handleMediaSourceListInfoRefreshed(timeline, /* isSourceRefresh= */ false); } private void addMediaItemsInternal(MediaSourceListUpdateMessage addMessage, int insertionIndex) @@ -691,7 +679,7 @@ import java.util.concurrent.atomic.AtomicBoolean; insertionIndex == C.INDEX_UNSET ? mediaSourceList.getSize() : insertionIndex, addMessage.mediaSourceHolders, addMessage.shuffleOrder); - handleMediaSourceListInfoRefreshed(timeline); + handleMediaSourceListInfoRefreshed(timeline, /* isSourceRefresh= */ false); } private void moveMediaItemsInternal(MoveMediaItemsMessage moveMediaItemsMessage) @@ -703,24 +691,25 @@ import java.util.concurrent.atomic.AtomicBoolean; moveMediaItemsMessage.toIndex, moveMediaItemsMessage.newFromIndex, moveMediaItemsMessage.shuffleOrder); - handleMediaSourceListInfoRefreshed(timeline); + handleMediaSourceListInfoRefreshed(timeline, /* isSourceRefresh= */ false); } private void removeMediaItemsInternal(int fromIndex, int toIndex, ShuffleOrder shuffleOrder) throws ExoPlaybackException { playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1); Timeline timeline = mediaSourceList.removeMediaSourceRange(fromIndex, toIndex, shuffleOrder); - handleMediaSourceListInfoRefreshed(timeline); + handleMediaSourceListInfoRefreshed(timeline, /* isSourceRefresh= */ false); } private void mediaSourceListUpdateRequestedInternal() throws ExoPlaybackException { - handleMediaSourceListInfoRefreshed(mediaSourceList.createTimeline()); + handleMediaSourceListInfoRefreshed( + mediaSourceList.createTimeline(), /* isSourceRefresh= */ true); } private void setShuffleOrderInternal(ShuffleOrder shuffleOrder) throws ExoPlaybackException { playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1); Timeline timeline = mediaSourceList.setShuffleOrder(shuffleOrder); - handleMediaSourceListInfoRefreshed(timeline); + handleMediaSourceListInfoRefreshed(timeline, /* isSourceRefresh= */ false); } private void notifyTrackSelectionPlayWhenReadyChanged(boolean playWhenReady) { @@ -815,10 +804,12 @@ import java.util.concurrent.atomic.AtomicBoolean; if (newPositionUs != playbackInfo.positionUs) { playbackInfo = handlePositionDiscontinuity( - periodId, newPositionUs, playbackInfo.requestedContentPositionUs); - if (sendDiscontinuity) { - playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); - } + periodId, + newPositionUs, + playbackInfo.requestedContentPositionUs, + playbackInfo.discontinuityStartPositionUs, + sendDiscontinuity, + Player.DISCONTINUITY_REASON_INTERNAL); } } @@ -841,6 +832,10 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + private void attemptRendererErrorRecovery() throws ExoPlaybackException { + seekToCurrentPosition(/* sendDiscontinuity= */ true); + } + private void updatePlaybackPositions() throws ExoPlaybackException { MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); if (playingPeriodHolder == null) { @@ -860,9 +855,11 @@ import java.util.concurrent.atomic.AtomicBoolean; playbackInfo = handlePositionDiscontinuity( playbackInfo.periodId, - discontinuityPositionUs, - playbackInfo.requestedContentPositionUs); - playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); + /* positionUs= */ discontinuityPositionUs, + playbackInfo.requestedContentPositionUs, + /* discontinuityStartPositionUs= */ discontinuityPositionUs, + /* reportDiscontinuity= */ true, + Player.DISCONTINUITY_REASON_INTERNAL); } } else { rendererPositionUs = @@ -985,7 +982,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } else if (playbackInfo.playbackState == Player.STATE_BUFFERING && shouldTransitionToReadyState(renderersAllowPlayback)) { setState(Player.STATE_READY); - pendingRecoverableError = null; // Any pending error was successfully recovered from. + pendingRecoverableRendererError = null; // Any pending error was successfully recovered from. if (shouldPlayWhenReady()) { startRenderers(); } @@ -1174,10 +1171,13 @@ import java.util.concurrent.atomic.AtomicBoolean; } } finally { playbackInfo = - handlePositionDiscontinuity(periodId, periodPositionUs, requestedContentPositionUs); - if (seekPositionAdjusted) { - playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); - } + handlePositionDiscontinuity( + periodId, + periodPositionUs, + requestedContentPositionUs, + /* discontinuityStartPositionUs= */ periodPositionUs, + /* reportDiscontinuity= */ seekPositionAdjusted, + Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); } } @@ -1342,6 +1342,7 @@ import java.util.concurrent.atomic.AtomicBoolean; boolean releaseMediaSourceList, boolean resetError) { handler.removeMessages(MSG_DO_SOME_WORK); + pendingRecoverableRendererError = null; isRebuffering = false; mediaClock.stop(); rendererPositionUs = 0; @@ -1368,7 +1369,7 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodId mediaPeriodId = playbackInfo.periodId; long startPositionUs = playbackInfo.positionUs; long requestedContentPositionUs = - shouldUseRequestedContentPosition(playbackInfo, period, window) + shouldUseRequestedContentPosition(playbackInfo, period) ? playbackInfo.requestedContentPositionUs : playbackInfo.positionUs; boolean resetTrackInfo = false; @@ -1392,6 +1393,7 @@ import java.util.concurrent.atomic.AtomicBoolean; playbackInfo.timeline, mediaPeriodId, requestedContentPositionUs, + /* discontinuityStartPositionUs= */ startPositionUs, playbackInfo.playbackState, resetError ? null : playbackInfo.playbackError, /* isLoading= */ false, @@ -1402,15 +1404,14 @@ import java.util.concurrent.atomic.AtomicBoolean; playbackInfo.playWhenReady, playbackInfo.playbackSuppressionReason, playbackInfo.playbackParameters, - startPositionUs, + /* bufferedPositionUs= */ startPositionUs, /* totalBufferedDurationUs= */ 0, - startPositionUs, + /* positionUs= */ startPositionUs, offloadSchedulingEnabled, /* sleepingForOffload= */ false); if (releaseMediaSourceList) { mediaSourceList.release(); } - pendingRecoverableError = null; } private Pair getPlaceholderFirstMediaPeriodPosition(Timeline timeline) { @@ -1642,12 +1643,18 @@ import java.util.concurrent.atomic.AtomicBoolean; long periodPositionUs = playingPeriodHolder.applyTrackSelection( newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags); + boolean hasDiscontinuity = + playbackInfo.playbackState != Player.STATE_ENDED + && periodPositionUs != playbackInfo.positionUs; playbackInfo = handlePositionDiscontinuity( - playbackInfo.periodId, periodPositionUs, playbackInfo.requestedContentPositionUs); - if (playbackInfo.playbackState != Player.STATE_ENDED - && periodPositionUs != playbackInfo.positionUs) { - playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); + playbackInfo.periodId, + periodPositionUs, + playbackInfo.requestedContentPositionUs, + playbackInfo.discontinuityStartPositionUs, + hasDiscontinuity, + Player.DISCONTINUITY_REASON_INTERNAL); + if (hasDiscontinuity) { resetRendererPosition(periodPositionUs); } @@ -1750,7 +1757,8 @@ import java.util.concurrent.atomic.AtomicBoolean; || !shouldPlayWhenReady()); } - private void handleMediaSourceListInfoRefreshed(Timeline timeline) throws ExoPlaybackException { + private void handleMediaSourceListInfoRefreshed(Timeline timeline, boolean isSourceRefresh) + throws ExoPlaybackException { PositionUpdateForPlaylistChange positionUpdate = resolvePositionForPlaylistChange( timeline, @@ -1767,7 +1775,6 @@ import java.util.concurrent.atomic.AtomicBoolean; long newPositionUs = positionUpdate.periodPositionUs; boolean periodPositionChanged = !playbackInfo.periodId.equals(newPeriodId) || newPositionUs != playbackInfo.positionUs; - try { if (positionUpdate.endPlayback) { if (playbackInfo.playbackState != Player.STATE_IDLE) { @@ -1808,8 +1815,23 @@ import java.util.concurrent.atomic.AtomicBoolean; : C.TIME_UNSET); if (periodPositionChanged || newRequestedContentPositionUs != playbackInfo.requestedContentPositionUs) { + Object oldPeriodUid = playbackInfo.periodId.periodUid; + Timeline oldTimeline = playbackInfo.timeline; + boolean reportDiscontinuity = + periodPositionChanged + && isSourceRefresh + && !oldTimeline.isEmpty() + && !oldTimeline.getPeriodByUid(oldPeriodUid, period).isPlaceholder; playbackInfo = - handlePositionDiscontinuity(newPeriodId, newPositionUs, newRequestedContentPositionUs); + handlePositionDiscontinuity( + newPeriodId, + newPositionUs, + newRequestedContentPositionUs, + playbackInfo.discontinuityStartPositionUs, + reportDiscontinuity, + timeline.getIndexOfPeriod(oldPeriodUid) == C.INDEX_UNSET + ? Player.DISCONTINUITY_REASON_REMOVE + : Player.DISCONTINUITY_REASON_SKIP); } resetPendingPauseAtEndOfPeriod(); resolvePendingMessagePositions( @@ -2000,7 +2022,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @Nullable MediaPeriodHolder readingPeriod = queue.getReadingPeriod(); if (readingPeriod == null || queue.getPlayingPeriod() == readingPeriod - || readingPeriod.allRenderersEnabled) { + || readingPeriod.allRenderersInCorrectState) { // Not reading ahead or all renderers updated. return; } @@ -2057,12 +2079,10 @@ import java.util.concurrent.atomic.AtomicBoolean; handlePositionDiscontinuity( newPlayingPeriodHolder.info.id, newPlayingPeriodHolder.info.startPositionUs, - newPlayingPeriodHolder.info.requestedContentPositionUs); - int discontinuityReason = - oldPlayingPeriodHolder.info.isLastInTimelinePeriod - ? Player.DISCONTINUITY_REASON_PERIOD_TRANSITION - : Player.DISCONTINUITY_REASON_AD_INSERTION; - playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason); + newPlayingPeriodHolder.info.requestedContentPositionUs, + /* discontinuityStartPositionUs= */ newPlayingPeriodHolder.info.startPositionUs, + /* reportDiscontinuity= */ true, + Player.DISCONTINUITY_REASON_AUTO_TRANSITION); updateLivePlaybackSpeedControl( /* newTimeline= */ playbackInfo.timeline, /* newPeriodId= */ newPlayingPeriodHolder.info.id, @@ -2095,7 +2115,7 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodHolder nextPlayingPeriodHolder = playingPeriodHolder.getNext(); return nextPlayingPeriodHolder != null && rendererPositionUs >= nextPlayingPeriodHolder.getStartPositionRendererTime() - && nextPlayingPeriodHolder.allRenderersEnabled; + && nextPlayingPeriodHolder.allRenderersInCorrectState; } private boolean hasReadingPeriodFinishedReading() { @@ -2148,7 +2168,10 @@ import java.util.concurrent.atomic.AtomicBoolean; handlePositionDiscontinuity( playbackInfo.periodId, loadingPeriodHolder.info.startPositionUs, - playbackInfo.requestedContentPositionUs); + playbackInfo.requestedContentPositionUs, + loadingPeriodHolder.info.startPositionUs, + /* reportDiscontinuity= */ false, + /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL); } maybeContinueLoading(); } @@ -2240,7 +2263,12 @@ import java.util.concurrent.atomic.AtomicBoolean; @CheckResult private PlaybackInfo handlePositionDiscontinuity( - MediaPeriodId mediaPeriodId, long positionUs, long contentPositionUs) { + MediaPeriodId mediaPeriodId, + long positionUs, + long contentPositionUs, + long discontinuityStartPositionUs, + boolean reportDiscontinuity, + @DiscontinuityReason int discontinuityReason) { deliverPendingMessageAtStartPositionRequired = deliverPendingMessageAtStartPositionRequired || positionUs != playbackInfo.positionUs @@ -2272,11 +2300,14 @@ import java.util.concurrent.atomic.AtomicBoolean; trackSelectorResult = emptyTrackSelectorResult; staticMetadata = ImmutableList.of(); } - + if (reportDiscontinuity) { + playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason); + } return playbackInfo.copyWithNewPosition( mediaPeriodId, positionUs, contentPositionUs, + discontinuityStartPositionUs, getTotalBufferedDurationUs(), trackGroupArray, trackSelectorResult, @@ -2321,7 +2352,7 @@ import java.util.concurrent.atomic.AtomicBoolean; enableRenderer(i, rendererWasEnabledFlags[i]); } } - readingMediaPeriod.allRenderersEnabled = true; + readingMediaPeriod.allRenderersInCorrectState = true; } private void enableRenderer(int rendererIndex, boolean wasRendererEnabled) @@ -2445,7 +2476,7 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodId oldPeriodId = playbackInfo.periodId; Object newPeriodUid = oldPeriodId.periodUid; boolean shouldUseRequestedContentPosition = - shouldUseRequestedContentPosition(playbackInfo, period, window); + shouldUseRequestedContentPosition(playbackInfo, period); long oldContentPositionUs = shouldUseRequestedContentPosition ? playbackInfo.requestedContentPositionUs @@ -2518,12 +2549,17 @@ import java.util.concurrent.atomic.AtomicBoolean; timeline.getPeriodByUid(newPeriodUid, period).windowIndex; } else { playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period); - long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs(); - int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex; - Pair periodPosition = - timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); - newPeriodUid = periodPosition.first; - newContentPositionUs = periodPosition.second; + if (playbackInfo.timeline.getWindow(period.windowIndex, window).firstPeriodIndex + == playbackInfo.timeline.getIndexOfPeriod(oldPeriodId.periodUid)) { + // Only need to resolve the first period in a window because subsequent periods must start + // at position 0 and don't need to be resolved. + long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs(); + int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex; + Pair periodPosition = + timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); + newPeriodUid = periodPosition.first; + newContentPositionUs = periodPosition.second; + } // Use an explicitly requested content position as new target live offset. setTargetLiveOffset = true; } @@ -2583,16 +2619,14 @@ import java.util.concurrent.atomic.AtomicBoolean; } private static boolean shouldUseRequestedContentPosition( - PlaybackInfo playbackInfo, Timeline.Period period, Timeline.Window window) { + PlaybackInfo playbackInfo, Timeline.Period period) { // Only use the actual position as content position if it's not an ad and we already have // prepared media information. Otherwise use the requested position. MediaPeriodId periodId = playbackInfo.periodId; Timeline timeline = playbackInfo.timeline; return periodId.isAd() || timeline.isEmpty() - || timeline.getWindow( - timeline.getPeriodByUid(periodId.periodUid, period).windowIndex, window) - .isPlaceholder; + || timeline.getPeriodByUid(periodId.periodUid, period).isPlaceholder; } /** @@ -2658,9 +2692,12 @@ import java.util.concurrent.atomic.AtomicBoolean; } pendingMessageInfo.resolvedPeriodIndex = index; previousTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period); - if (previousTimeline.getWindow(period.windowIndex, window).isPlaceholder) { + if (period.isPlaceholder + && previousTimeline.getWindow(period.windowIndex, window).firstPeriodIndex + == previousTimeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid)) { // The position needs to be re-resolved because the window in the previous timeline wasn't - // fully prepared. + // fully prepared. Only resolve the first period in a window because subsequent periods must + // start at position 0 and don't need to be resolved. long windowPositionUs = pendingMessageInfo.resolvedPeriodTimeUs + period.getPositionInWindowUs(); int windowIndex = @@ -2735,10 +2772,12 @@ import java.util.concurrent.atomic.AtomicBoolean; int periodIndex = timeline.getIndexOfPeriod(periodPosition.first); if (periodIndex != C.INDEX_UNSET) { // We successfully located the period in the internal timeline. - seekTimeline.getPeriodByUid(periodPosition.first, period); - if (seekTimeline.getWindow(period.windowIndex, window).isPlaceholder) { + if (seekTimeline.getPeriodByUid(periodPosition.first, period).isPlaceholder + && seekTimeline.getWindow(period.windowIndex, window).firstPeriodIndex + == seekTimeline.getIndexOfPeriod(periodPosition.first)) { // The seek timeline was using a placeholder, so we need to re-resolve using the updated - // timeline in case the resolved position changed. + // timeline in case the resolved position changed. Only resolve the first period in a window + // because subsequent periods must start at position 0 and don't need to be resolved. int newWindowIndex = timeline.getPeriodByUid(periodPosition.first, period).windowIndex; periodPosition = timeline.getPeriodPosition( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java index 13c0659ccc..67f3cf6b44 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2; import androidx.annotation.Nullable; import com.google.android.exoplayer2.drm.DrmSession; -/** - * Holds a {@link Format}. - */ +/** Holds a {@link Format}. */ public final class FormatHolder { /** An accompanying context for decrypting samples in the format. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index e8639e1f9a..d8569a544d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -53,12 +53,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; /** {@link MediaPeriodInfo} about this media period. */ public MediaPeriodInfo info; /** - * Whether all required renderers have been enabled with the {@link #sampleStreams} for this + * Whether all renderers are in the correct state for this {@link #mediaPeriod}. + * + *

    Renderers that are needed must have been enabled with the {@link #sampleStreams} for this * {@link #mediaPeriod}. This means either {@link Renderer#enable(RendererConfiguration, Format[], - * SampleStream, long, boolean, boolean, long)} or {@link Renderer#replaceStream(Format[], - * SampleStream, long)} has been called. + * SampleStream, long, boolean, boolean, long, long)} or {@link Renderer#replaceStream(Format[], + * SampleStream, long, long)} has been called. + * + *

    Renderers that are not needed must have been {@link Renderer#disable() disabled}. */ - public boolean allRenderersEnabled; + public boolean allRenderersInCorrectState; private final boolean[] mayRetainStreamFlags; private final RendererCapabilities[] rendererCapabilities; 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 7ba7589a56..d72eaa15db 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 @@ -706,10 +706,10 @@ import com.google.common.collect.ImmutableList; currentPeriodId.windowSequenceNumber); } } else { - // Play the next ad group if it's available. - int nextAdGroupIndex = period.getAdGroupIndexForPositionUs(mediaPeriodInfo.endPositionUs); - if (nextAdGroupIndex == C.INDEX_UNSET) { - // The next ad group can't be played. Play content from the previous end position instead. + // Play the next ad group if it's still available. + int adIndexInAdGroup = period.getFirstAdIndexToPlay(currentPeriodId.nextAdGroupIndex); + if (adIndexInAdGroup == period.getAdCountInAdGroup(currentPeriodId.nextAdGroupIndex)) { + // The next ad group has no ads left to play. Play content from the end position instead. return getMediaPeriodInfoForContent( timeline, currentPeriodId.periodUid, @@ -717,11 +717,10 @@ import com.google.common.collect.ImmutableList; /* requestedContentPositionUs= */ mediaPeriodInfo.durationUs, currentPeriodId.windowSequenceNumber); } - int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); return getMediaPeriodInfoForAd( timeline, currentPeriodId.periodUid, - nextAdGroupIndex, + currentPeriodId.nextAdGroupIndex, adIndexInAdGroup, /* contentPositionUs= */ mediaPeriodInfo.durationUs, currentPeriodId.windowSequenceNumber); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java index 1227dbb397..6a7d298955 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java @@ -21,6 +21,7 @@ import static java.lang.Math.min; import android.os.Handler; import androidx.annotation.Nullable; import com.google.android.exoplayer2.analytics.AnalyticsCollector; +import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MaskingMediaPeriod; @@ -339,6 +340,7 @@ import java.util.Set; Log.e(TAG, "Failed to release child source.", e); } childSource.mediaSource.removeEventListener(childSource.eventListener); + childSource.mediaSource.removeDrmEventListener(childSource.eventListener); } childSources.clear(); enabledMediaSourceHolders.clear(); @@ -448,6 +450,7 @@ import java.util.Set; Assertions.checkNotNull(childSources.remove(mediaSourceHolder)); removedChild.mediaSource.releaseSource(removedChild.caller); removedChild.mediaSource.removeEventListener(removedChild.eventListener); + removedChild.mediaSource.removeDrmEventListener(removedChild.eventListener); enabledMediaSourceHolders.remove(mediaSourceHolder); } } @@ -503,12 +506,12 @@ import java.util.Set; public final MediaSource mediaSource; public final MediaSource.MediaSourceCaller caller; - public final MediaSourceEventListener eventListener; + public final ForwardingEventListener eventListener; public MediaSourceAndListener( MediaSource mediaSource, MediaSource.MediaSourceCaller caller, - MediaSourceEventListener eventListener) { + ForwardingEventListener eventListener) { this.mediaSource = mediaSource; this.caller = caller; this.eventListener = eventListener; @@ -600,9 +603,11 @@ import java.util.Set; @Override public void onDrmSessionAcquired( - int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) { + int windowIndex, + @Nullable MediaSource.MediaPeriodId mediaPeriodId, + @DrmSession.State int state) { if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmSessionAcquired(); + drmEventDispatcher.drmSessionAcquired(state); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 96d14d0239..9ccc2a5a40 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -50,6 +50,8 @@ import java.util.List; * suspended content. */ public final long requestedContentPositionUs; + /** The start position after a reported position discontinuity, in microseconds. */ + public final long discontinuityStartPositionUs; /** The current playback state. One of the {@link Player}.STATE_ constants. */ @Player.State public final int playbackState; /** The current playback error, or null if this is not an error state. */ @@ -104,6 +106,7 @@ import java.util.List; Timeline.EMPTY, PLACEHOLDER_MEDIA_PERIOD_ID, /* requestedContentPositionUs= */ C.TIME_UNSET, + /* discontinuityStartPositionUs= */ 0, Player.STATE_IDLE, /* playbackError= */ null, /* isLoading= */ false, @@ -147,6 +150,7 @@ import java.util.List; Timeline timeline, MediaPeriodId periodId, long requestedContentPositionUs, + long discontinuityStartPositionUs, @Player.State int playbackState, @Nullable ExoPlaybackException playbackError, boolean isLoading, @@ -165,6 +169,7 @@ import java.util.List; this.timeline = timeline; this.periodId = periodId; this.requestedContentPositionUs = requestedContentPositionUs; + this.discontinuityStartPositionUs = discontinuityStartPositionUs; this.playbackState = playbackState; this.playbackError = playbackError; this.isLoading = isLoading; @@ -207,6 +212,7 @@ import java.util.List; MediaPeriodId periodId, long positionUs, long requestedContentPositionUs, + long discontinuityStartPositionUs, long totalBufferedDurationUs, TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult, @@ -215,6 +221,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -244,6 +251,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -273,6 +281,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -302,6 +311,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -331,6 +341,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -360,6 +371,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -393,6 +405,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -422,6 +435,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -452,6 +466,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, @@ -481,6 +496,7 @@ import java.util.List; timeline, periodId, requestedContentPositionUs, + discontinuityStartPositionUs, playbackState, playbackError, isLoading, 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 36f562f7cb..57c70cd5ca 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 @@ -246,7 +246,7 @@ public final class PlayerMessage { /** * Sends the message. If the target throws an {@link ExoPlaybackException} then it is propagated * out of the player as an error using {@link - * Player.EventListener#onPlayerError(ExoPlaybackException)}. + * Player.Listener#onPlayerError(ExoPlaybackException)}. * * @return This message. * @throws IllegalStateException If this message has already been sent. @@ -341,6 +341,7 @@ public final class PlayerMessage { long deadlineMs = clock.elapsedRealtime() + timeoutMs; long remainingMs = timeoutMs; while (!isProcessed && remainingMs > 0) { + clock.onThreadBlocked(); wait(remainingMs); remainingMs = deadlineMs - clock.elapsedRealtime(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index 8578a23929..f84fef5004 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -24,7 +24,6 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.DecoderVideoRenderer; import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; @@ -76,11 +75,14 @@ public interface Renderer extends PlayerMessage.Target { /** * The type of a message that can be passed to a video renderer via {@link - * ExoPlayer#createMessage(Target)}. The message payload should be the target {@link Surface}, or - * null. + * ExoPlayer#createMessage(Target)}. The message payload is normally a {@link Surface}, however + * some video renderers may accept other outputs (e.g., {@link VideoDecoderOutputBufferRenderer}). + * + *

    If the receiving renderer does not support the payload type as an output, then it will clear + * any existing output that it has. */ @SuppressWarnings("deprecation") - int MSG_SET_SURFACE = C.MSG_SET_SURFACE; + int MSG_SET_VIDEO_OUTPUT = C.MSG_SET_SURFACE; /** * A type of a message that can be passed to an audio renderer via {@link * ExoPlayer#createMessage(Target)}. The message payload should be a {@link Float} with 0 being @@ -142,17 +144,6 @@ public interface Renderer extends PlayerMessage.Target { */ @SuppressWarnings("deprecation") int MSG_SET_CAMERA_MOTION_LISTENER = C.MSG_SET_CAMERA_MOTION_LISTENER; - /** - * The type of a message that can be passed to a {@link DecoderVideoRenderer} via {@link - * ExoPlayer#createMessage(Target)}. The message payload should be the target {@link - * VideoDecoderOutputBufferRenderer}, or null. - * - *

    This message is intended only for use with extension renderers that expect a {@link - * VideoDecoderOutputBufferRenderer}. For other use cases, an output surface should be passed via - * {@link #MSG_SET_SURFACE} instead. - */ - @SuppressWarnings("deprecation") - int MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER = C.MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER; /** * The type of a message that can be passed to an audio renderer via {@link * ExoPlayer#createMessage(Target)}. The message payload should be a {@link Boolean} instance @@ -240,7 +231,7 @@ public interface Renderer extends PlayerMessage.Target { /** * Returns the track type that the renderer handles. * - * @see Player#getRendererType(int) + * @see ExoPlayer#getRendererType(int) * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. */ int getTrackType(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java index 657e1174e8..dc7a8f8642 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java @@ -21,9 +21,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * Defines the capabilities of a {@link Renderer}. - */ +/** Defines the capabilities of a {@link Renderer}. */ public interface RendererCapabilities { /** @deprecated Use {@link C.FormatSupport} instead. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java index e36cb0c71d..c5648a906d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2; import androidx.annotation.Nullable; -/** - * The configuration of a {@link Renderer}. - */ +/** The configuration of a {@link Renderer}. */ public final class RendererConfiguration { /** The default configuration. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java index 74ee923961..fba0ca247b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.video.VideoRendererEventListener; -/** - * Builds {@link Renderer} instances for use by a {@link SimpleExoPlayer}. - */ +/** Builds {@link Renderer} instances for use by a {@link SimpleExoPlayer}. */ public interface RenderersFactory { /** 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 67a12f05dc..6f8df8b996 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 @@ -15,12 +15,23 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES; +import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID; +import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; +import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; +import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE; +import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; +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 android.content.Context; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.media.AudioFormat; import android.media.AudioTrack; import android.media.MediaCodec; +import android.media.MediaFormat; import android.os.Handler; import android.os.Looper; import android.view.Surface; @@ -57,15 +68,17 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 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; import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView; import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -78,11 +91,11 @@ import java.util.concurrent.TimeoutException; */ public class SimpleExoPlayer extends BasePlayer implements ExoPlayer, - Player.AudioComponent, - Player.VideoComponent, - Player.TextComponent, - Player.MetadataComponent, - Player.DeviceComponent { + ExoPlayer.AudioComponent, + ExoPlayer.VideoComponent, + ExoPlayer.TextComponent, + ExoPlayer.MetadataComponent, + ExoPlayer.DeviceComponent { /** The default timeout for detaching a surface from the player, in milliseconds. */ public static final long DEFAULT_DETACH_SURFACE_TIMEOUT_MS = 2_000; @@ -98,6 +111,7 @@ public class SimpleExoPlayer extends BasePlayer private final RenderersFactory renderersFactory; private Clock clock; + private long foregroundModeTimeoutMs; private TrackSelector trackSelector; private MediaSourceFactory mediaSourceFactory; private LoadControl loadControl; @@ -252,6 +266,21 @@ public class SimpleExoPlayer extends BasePlayer detachSurfaceTimeoutMs = DEFAULT_DETACH_SURFACE_TIMEOUT_MS; } + /** + * Set a limit on the time a call to {@link #setForegroundMode} can spend. If a call to {@link + * #setForegroundMode} takes more than {@code timeoutMs} milliseconds to complete, the player + * will raise an error via {@link Player.Listener#onPlayerError}. + * + *

    This method is experimental, and will be renamed or removed in a future release. + * + * @param timeoutMs The time limit in milliseconds. + */ + public Builder experimentalSetForegroundModeTimeoutMs(long timeoutMs) { + Assertions.checkState(!buildCalled); + foregroundModeTimeoutMs = timeoutMs; + return this; + } + /** * Sets the {@link TrackSelector} that will be used by the player. * @@ -471,7 +500,7 @@ public class SimpleExoPlayer extends BasePlayer * *

    If a call to {@link #release} or {@link #setForegroundMode} takes more than {@code * timeoutMs} to complete, the player will report an error via {@link - * Player.EventListener#onPlayerError}. + * Player.Listener#onPlayerError}. * * @param releaseTimeoutMs The release timeout, in milliseconds. * @return This builder. @@ -488,7 +517,7 @@ public class SimpleExoPlayer extends BasePlayer * *

    If detaching a surface or replacing a surface takes more than {@code * detachSurfaceTimeoutMs} to complete, the player will report an error via {@link - * Player.EventListener#onPlayerError}. + * Player.Listener#onPlayerError}. * * @param detachSurfaceTimeoutMs The timeout for detaching a surface, in milliseconds. * @return This builder. @@ -505,7 +534,7 @@ public class SimpleExoPlayer extends BasePlayer * *

    This means the player will pause at the end of each window in the current {@link * #getCurrentTimeline() timeline}. Listeners will be informed by a call to {@link - * Player.EventListener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link + * Player.Listener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link * Player#PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM} when this happens. * * @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item. @@ -560,15 +589,14 @@ public class SimpleExoPlayer extends BasePlayer } private static final String TAG = "SimpleExoPlayer"; - private static final String WRONG_THREAD_ERROR_MESSAGE = - "Player is accessed on the wrong thread. See " - + "https://exoplayer.dev/issues/player-accessed-on-wrong-thread"; protected final Renderer[] renderers; + private final ConditionVariable constructorFinished; private final Context applicationContext; private final ExoPlayerImpl player; private final ComponentListener componentListener; + private final FrameMetadataListener frameMetadataListener; private final CopyOnWriteArraySet videoListeners; private final CopyOnWriteArraySet audioListeners; private final CopyOnWriteArraySet textOutputs; @@ -585,11 +613,13 @@ public class SimpleExoPlayer extends BasePlayer @Nullable private Format videoFormat; @Nullable private Format audioFormat; @Nullable private AudioTrack keepSessionIdAudioTrack; - @Nullable private Surface surface; - private boolean ownsSurface; - @C.VideoScalingMode private int videoScalingMode; + @Nullable private Object videoOutput; + @Nullable private Surface ownedSurface; @Nullable private SurfaceHolder surfaceHolder; + @Nullable private SphericalGLSurfaceView sphericalGLSurfaceView; + private boolean surfaceHolderSurfaceIsVideoOutput; @Nullable private TextureView textureView; + @C.VideoScalingMode private int videoScalingMode; private int surfaceWidth; private int surfaceHeight; @Nullable private DecoderCounters videoDecoderCounters; @@ -607,6 +637,7 @@ public class SimpleExoPlayer extends BasePlayer private boolean isPriorityTaskManagerRegistered; private boolean playerReleased; private DeviceInfo deviceInfo; + private VideoSize videoSize; /** @deprecated Use the {@link Builder} and pass it to {@link #SimpleExoPlayer(Builder)}. */ @Deprecated @@ -635,76 +666,104 @@ public class SimpleExoPlayer extends BasePlayer /** @param builder The {@link Builder} to obtain all construction parameters. */ protected SimpleExoPlayer(Builder builder) { - applicationContext = builder.context.getApplicationContext(); - analyticsCollector = builder.analyticsCollector; - priorityTaskManager = builder.priorityTaskManager; - audioAttributes = builder.audioAttributes; - videoScalingMode = builder.videoScalingMode; - skipSilenceEnabled = builder.skipSilenceEnabled; - detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; - componentListener = new ComponentListener(); - videoListeners = new CopyOnWriteArraySet<>(); - audioListeners = new CopyOnWriteArraySet<>(); - textOutputs = new CopyOnWriteArraySet<>(); - metadataOutputs = new CopyOnWriteArraySet<>(); - deviceListeners = new CopyOnWriteArraySet<>(); - Handler eventHandler = new Handler(builder.looper); - renderers = - builder.renderersFactory.createRenderers( - eventHandler, - componentListener, - componentListener, - componentListener, - componentListener); + constructorFinished = new ConditionVariable(); + try { + applicationContext = builder.context.getApplicationContext(); + analyticsCollector = builder.analyticsCollector; + priorityTaskManager = builder.priorityTaskManager; + audioAttributes = builder.audioAttributes; + videoScalingMode = builder.videoScalingMode; + skipSilenceEnabled = builder.skipSilenceEnabled; + detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; + componentListener = new ComponentListener(); + frameMetadataListener = new FrameMetadataListener(); + videoListeners = new CopyOnWriteArraySet<>(); + audioListeners = new CopyOnWriteArraySet<>(); + textOutputs = new CopyOnWriteArraySet<>(); + metadataOutputs = new CopyOnWriteArraySet<>(); + deviceListeners = new CopyOnWriteArraySet<>(); + Handler eventHandler = new Handler(builder.looper); + renderers = + builder.renderersFactory.createRenderers( + eventHandler, + componentListener, + componentListener, + componentListener, + componentListener); - // Set initial values. - audioVolume = 1; - if (Util.SDK_INT < 21) { - audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); - } else { - audioSessionId = C.generateAudioSessionIdV21(applicationContext); + // Set initial values. + audioVolume = 1; + if (Util.SDK_INT < 21) { + audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); + } else { + audioSessionId = C.generateAudioSessionIdV21(applicationContext); + } + currentCues = Collections.emptyList(); + throwsWhenUsingWrongThread = true; + + // Build the player and associated objects. + Commands additionalPermanentAvailableCommands = + new Commands.Builder() + .addAll( + COMMAND_GET_AUDIO_ATTRIBUTES, + COMMAND_GET_VOLUME, + COMMAND_GET_DEVICE_VOLUME, + COMMAND_SET_VOLUME, + COMMAND_SET_DEVICE_VOLUME, + COMMAND_ADJUST_DEVICE_VOLUME, + COMMAND_SET_VIDEO_SURFACE, + COMMAND_GET_TEXT) + .build(); + player = + new ExoPlayerImpl( + renderers, + builder.trackSelector, + builder.mediaSourceFactory, + builder.loadControl, + builder.bandwidthMeter, + analyticsCollector, + builder.useLazyPreparation, + builder.seekParameters, + builder.livePlaybackSpeedControl, + builder.releaseTimeoutMs, + builder.pauseAtEndOfMediaItems, + builder.clock, + builder.looper, + /* wrappingPlayer= */ this, + additionalPermanentAvailableCommands); + player.addListener(componentListener); + player.addAudioOffloadListener(componentListener); + if (builder.foregroundModeTimeoutMs > 0) { + player.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs); + } + + audioBecomingNoisyManager = + new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener); + audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy); + audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener); + audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null); + streamVolumeManager = + new StreamVolumeManager(builder.context, eventHandler, componentListener); + streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); + wakeLockManager = new WakeLockManager(builder.context); + wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE); + wifiLockManager = new WifiLockManager(builder.context); + wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); + deviceInfo = createDeviceInfo(streamVolumeManager); + videoSize = VideoSize.UNKNOWN; + + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); + sendRendererMessage(C.TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); + sendRendererMessage(C.TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); + sendRendererMessage( + C.TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener); + sendRendererMessage( + C.TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener); + } finally { + constructorFinished.open(); } - currentCues = Collections.emptyList(); - throwsWhenUsingWrongThread = true; - - // Build the player and associated objects. - player = - new ExoPlayerImpl( - renderers, - builder.trackSelector, - builder.mediaSourceFactory, - builder.loadControl, - builder.bandwidthMeter, - analyticsCollector, - builder.useLazyPreparation, - builder.seekParameters, - builder.livePlaybackSpeedControl, - builder.releaseTimeoutMs, - builder.pauseAtEndOfMediaItems, - builder.clock, - builder.looper, - /* wrappingPlayer= */ this); - player.addListener(componentListener); - - audioBecomingNoisyManager = - new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener); - audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy); - audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener); - audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null); - streamVolumeManager = new StreamVolumeManager(builder.context, eventHandler, componentListener); - streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); - wakeLockManager = new WakeLockManager(builder.context); - wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE); - wifiLockManager = new WifiLockManager(builder.context); - wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); - deviceInfo = createDeviceInfo(streamVolumeManager); - - sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); - sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); - sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); - sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_SCALING_MODE, videoScalingMode); - sendRendererMessage( - C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); } @Override @@ -761,7 +820,7 @@ public class SimpleExoPlayer extends BasePlayer public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { verifyApplicationThread(); this.videoScalingMode = videoScalingMode; - sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_SCALING_MODE, videoScalingMode); + sendRendererMessage(C.TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); } @Override @@ -770,18 +829,23 @@ public class SimpleExoPlayer extends BasePlayer return videoScalingMode; } + @Override + public VideoSize getVideoSize() { + return videoSize; + } + @Override public void clearVideoSurface() { verifyApplicationThread(); removeSurfaceCallbacks(); - setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ false); + setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } @Override public void clearVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); - if (surface != null && surface == this.surface) { + if (surface != null && surface == videoOutput) { clearVideoSurface(); } } @@ -790,10 +854,7 @@ public class SimpleExoPlayer extends BasePlayer public void setVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); removeSurfaceCallbacks(); - if (surface != null) { - setVideoDecoderOutputBufferRenderer(/* videoDecoderOutputBufferRenderer= */ null); - } - setVideoSurfaceInternal(surface, /* ownsSurface= */ false); + setVideoOutputInternal(surface); int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET; maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } @@ -801,23 +862,20 @@ public class SimpleExoPlayer extends BasePlayer @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); - removeSurfaceCallbacks(); - if (surfaceHolder != null) { - setVideoDecoderOutputBufferRenderer(/* videoDecoderOutputBufferRenderer= */ null); - } - this.surfaceHolder = surfaceHolder; if (surfaceHolder == null) { - setVideoSurfaceInternal(null, /* ownsSurface= */ false); - maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); + clearVideoSurface(); } else { + removeSurfaceCallbacks(); + this.surfaceHolderSurfaceIsVideoOutput = true; + this.surfaceHolder = surfaceHolder; surfaceHolder.addCallback(componentListener); Surface surface = surfaceHolder.getSurface(); if (surface != null && surface.isValid()) { - setVideoSurfaceInternal(surface, /* ownsSurface= */ false); + setVideoOutputInternal(surface); Rect surfaceSize = surfaceHolder.getSurfaceFrame(); maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height()); } else { - setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ false); + setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } } @@ -827,19 +885,28 @@ public class SimpleExoPlayer extends BasePlayer public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { - setVideoSurfaceHolder(null); + clearVideoSurface(); } } @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); - if (surfaceView instanceof VideoDecoderGLSurfaceView) { - VideoDecoderOutputBufferRenderer videoDecoderOutputBufferRenderer = - ((VideoDecoderGLSurfaceView) surfaceView).getVideoDecoderOutputBufferRenderer(); - clearVideoSurface(); - surfaceHolder = surfaceView.getHolder(); - setVideoDecoderOutputBufferRenderer(videoDecoderOutputBufferRenderer); + if (surfaceView instanceof VideoDecoderOutputBufferRenderer) { + removeSurfaceCallbacks(); + setVideoOutputInternal(surfaceView); + setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); + } else if (surfaceView instanceof SphericalGLSurfaceView) { + removeSurfaceCallbacks(); + sphericalGLSurfaceView = (SphericalGLSurfaceView) surfaceView; + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) + .setPayload(sphericalGLSurfaceView) + .send(); + sphericalGLSurfaceView.addVideoSurfaceListener(componentListener); + setVideoOutputInternal(sphericalGLSurfaceView.getVideoSurface()); + setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); } else { setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } @@ -848,39 +915,29 @@ public class SimpleExoPlayer extends BasePlayer @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); - if (surfaceView instanceof VideoDecoderGLSurfaceView) { - if (surfaceView.getHolder() == surfaceHolder) { - setVideoDecoderOutputBufferRenderer(null); - surfaceHolder = null; - } - } else { - clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); - } + clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } @Override public void setVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); - removeSurfaceCallbacks(); - if (textureView != null) { - setVideoDecoderOutputBufferRenderer(/* videoDecoderOutputBufferRenderer= */ null); - } - this.textureView = textureView; if (textureView == null) { - setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ true); - maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); + clearVideoSurface(); } else { + removeSurfaceCallbacks(); + this.textureView = textureView; if (textureView.getSurfaceTextureListener() != null) { Log.w(TAG, "Replacing existing SurfaceTextureListener."); } textureView.setSurfaceTextureListener(componentListener); + @Nullable SurfaceTexture surfaceTexture = textureView.isAvailable() ? textureView.getSurfaceTexture() : null; if (surfaceTexture == null) { - setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ true); + setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } else { - setVideoSurfaceInternal(new Surface(surfaceTexture), /* ownsSurface= */ true); + setSurfaceTextureInternal(surfaceTexture); maybeNotifySurfaceSizeChanged(textureView.getWidth(), textureView.getHeight()); } } @@ -890,10 +947,22 @@ public class SimpleExoPlayer extends BasePlayer public void clearVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView != null && textureView == this.textureView) { - setVideoTextureView(null); + clearVideoSurface(); } } + @Override + public void addAudioOffloadListener(AudioOffloadListener listener) { + // Don't verify application thread. We allow calls to this method from any thread. + player.addAudioOffloadListener(listener); + } + + @Override + public void removeAudioOffloadListener(AudioOffloadListener listener) { + // Don't verify application thread. We allow calls to this method from any thread. + player.removeAudioOffloadListener(listener); + } + @Override public void addAudioListener(AudioListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -915,7 +984,7 @@ public class SimpleExoPlayer extends BasePlayer } if (!Util.areEqual(this.audioAttributes, audioAttributes)) { this.audioAttributes = audioAttributes; - sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); analyticsCollector.onAudioAttributesChanged(audioAttributes); for (AudioListener audioListener : audioListeners) { @@ -954,8 +1023,8 @@ public class SimpleExoPlayer extends BasePlayer initializeKeepSessionIdAudioTrack(audioSessionId); } this.audioSessionId = audioSessionId; - sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); - sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); + sendRendererMessage(C.TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); analyticsCollector.onAudioSessionIdChanged(audioSessionId); for (AudioListener audioListener : audioListeners) { audioListener.onAudioSessionIdChanged(audioSessionId); @@ -970,7 +1039,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { verifyApplicationThread(); - sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); } @Override @@ -1010,8 +1079,7 @@ public class SimpleExoPlayer extends BasePlayer return; } this.skipSilenceEnabled = skipSilenceEnabled; - sendRendererMessage( - C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); notifySkipSilenceEnabledChanged(); } @@ -1124,8 +1192,11 @@ public class SimpleExoPlayer extends BasePlayer public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); videoFrameMetadataListener = listener; - sendRendererMessage( - C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER, listener); + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) + .setPayload(listener) + .send(); } @Override @@ -1134,16 +1205,22 @@ public class SimpleExoPlayer extends BasePlayer if (videoFrameMetadataListener != listener) { return; } - sendRendererMessage( - C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER, /* payload= */ null); + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) + .setPayload(null) + .send(); } @Override public void setCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); cameraMotionListener = listener; - sendRendererMessage( - C.TRACK_TYPE_CAMERA_MOTION, Renderer.MSG_SET_CAMERA_MOTION_LISTENER, listener); + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) + .setPayload(listener) + .send(); } @Override @@ -1152,8 +1229,11 @@ public class SimpleExoPlayer extends BasePlayer if (cameraMotionListener != listener) { return; } - sendRendererMessage( - C.TRACK_TYPE_CAMERA_MOTION, Renderer.MSG_SET_CAMERA_MOTION_LISTENER, /* payload= */ null); + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) + .setPayload(null) + .send(); } @Override @@ -1205,6 +1285,18 @@ public class SimpleExoPlayer extends BasePlayer return player.getClock(); } + @Override + public void addListener(Listener listener) { + Assertions.checkNotNull(listener); + addAudioListener(listener); + addVideoListener(listener); + addTextOutput(listener); + addMetadataOutput(listener); + addDeviceListener(listener); + EventListener eventListener = listener; + addListener(eventListener); + } + @Override public void addListener(Player.EventListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1212,6 +1304,18 @@ public class SimpleExoPlayer extends BasePlayer player.addListener(listener); } + @Override + public void removeListener(Listener listener) { + Assertions.checkNotNull(listener); + removeAudioListener(listener); + removeVideoListener(listener); + removeTextOutput(listener); + removeMetadataOutput(listener); + removeDeviceListener(listener); + EventListener eventListener = listener; + removeListener(eventListener); + } + @Override public void removeListener(Player.EventListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1232,14 +1336,6 @@ public class SimpleExoPlayer extends BasePlayer return player.getPlaybackSuppressionReason(); } - /** @deprecated Use {@link #getPlayerError()} instead. */ - @Deprecated - @Override - @Nullable - public ExoPlaybackException getPlaybackError() { - return getPlayerError(); - } - @Override @Nullable public ExoPlaybackException getPlayerError() { @@ -1255,6 +1351,12 @@ public class SimpleExoPlayer extends BasePlayer prepare(); } + @Override + public Commands getAvailableCommands() { + verifyApplicationThread(); + return player.getAvailableCommands(); + } + @Override public void prepare() { verifyApplicationThread(); @@ -1284,24 +1386,13 @@ public class SimpleExoPlayer extends BasePlayer @Override public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { verifyApplicationThread(); - setMediaSources( - Collections.singletonList(mediaSource), - /* startWindowIndex= */ resetPosition ? 0 : C.INDEX_UNSET, - /* startPositionMs= */ C.TIME_UNSET); + setMediaSources(Collections.singletonList(mediaSource), resetPosition); prepare(); } - @Override - public void setMediaItems(List mediaItems) { - verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); - player.setMediaItems(mediaItems); - } - @Override public void setMediaItems(List mediaItems, boolean resetPosition) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaItems(mediaItems, resetPosition); } @@ -1309,42 +1400,18 @@ public class SimpleExoPlayer extends BasePlayer public void setMediaItems( List mediaItems, int startWindowIndex, long startPositionMs) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaItems(mediaItems, startWindowIndex, startPositionMs); } - @Override - public void setMediaItem(MediaItem mediaItem) { - verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); - player.setMediaItem(mediaItem); - } - - @Override - public void setMediaItem(MediaItem mediaItem, boolean resetPosition) { - verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); - player.setMediaItem(mediaItem, resetPosition); - } - - @Override - public void setMediaItem(MediaItem mediaItem, long startPositionMs) { - verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); - player.setMediaItem(mediaItem, startPositionMs); - } - @Override public void setMediaSources(List mediaSources) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaSources(mediaSources); } @Override public void setMediaSources(List mediaSources, boolean resetPosition) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaSources(mediaSources, resetPosition); } @@ -1352,55 +1419,33 @@ public class SimpleExoPlayer extends BasePlayer public void setMediaSources( List mediaSources, int startWindowIndex, long startPositionMs) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaSources(mediaSources, startWindowIndex, startPositionMs); } @Override public void setMediaSource(MediaSource mediaSource) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaSource(mediaSource); } @Override public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaSource(mediaSource, resetPosition); } @Override public void setMediaSource(MediaSource mediaSource, long startPositionMs) { verifyApplicationThread(); - analyticsCollector.resetForNewPlaylist(); player.setMediaSource(mediaSource, startPositionMs); } - @Override - public void addMediaItems(List mediaItems) { - verifyApplicationThread(); - player.addMediaItems(mediaItems); - } - @Override public void addMediaItems(int index, List mediaItems) { verifyApplicationThread(); player.addMediaItems(index, mediaItems); } - @Override - public void addMediaItem(MediaItem mediaItem) { - verifyApplicationThread(); - player.addMediaItem(mediaItem); - } - - @Override - public void addMediaItem(int index, MediaItem mediaItem) { - verifyApplicationThread(); - player.addMediaItem(index, mediaItem); - } - @Override public void addMediaSource(MediaSource mediaSource) { verifyApplicationThread(); @@ -1425,36 +1470,18 @@ public class SimpleExoPlayer extends BasePlayer player.addMediaSources(index, mediaSources); } - @Override - public void moveMediaItem(int currentIndex, int newIndex) { - verifyApplicationThread(); - player.moveMediaItem(currentIndex, newIndex); - } - @Override public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { verifyApplicationThread(); player.moveMediaItems(fromIndex, toIndex, newIndex); } - @Override - public void removeMediaItem(int index) { - verifyApplicationThread(); - player.removeMediaItem(index); - } - @Override public void removeMediaItems(int fromIndex, int toIndex) { verifyApplicationThread(); player.removeMediaItems(fromIndex, toIndex); } - @Override - public void clearMediaItems() { - verifyApplicationThread(); - player.clearMediaItems(); - } - @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { verifyApplicationThread(); @@ -1526,7 +1553,7 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { + public void setPlaybackParameters(PlaybackParameters playbackParameters) { verifyApplicationThread(); player.setPlaybackParameters(playbackParameters); } @@ -1578,11 +1605,9 @@ public class SimpleExoPlayer extends BasePlayer player.release(); analyticsCollector.release(); removeSurfaceCallbacks(); - if (surface != null) { - if (ownsSurface) { - surface.release(); - } - surface = null; + if (ownedSurface != null) { + ownedSurface.release(); + ownedSurface = null; } if (isPriorityTaskManagerRegistered) { Assertions.checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); @@ -1635,6 +1660,11 @@ public class SimpleExoPlayer extends BasePlayer return player.getCurrentStaticMetadata(); } + @Override + public MediaMetadata getMediaMetadata() { + return player.getMediaMetadata(); + } + @Override public Timeline getCurrentTimeline() { verifyApplicationThread(); @@ -1822,7 +1852,10 @@ public class SimpleExoPlayer extends BasePlayer *

    The default is {@code true} and this method will be removed in the future. * * @param throwsWhenUsingWrongThread Whether to throw when methods are called from a wrong thread. + * @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. */ + @Deprecated public void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; } @@ -1830,6 +1863,15 @@ public class SimpleExoPlayer extends BasePlayer // Internal methods. private void removeSurfaceCallbacks() { + if (sphericalGLSurfaceView != null) { + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) + .setPayload(null) + .send(); + sphericalGLSurfaceView.removeVideoSurfaceListener(componentListener); + sphericalGLSurfaceView = null; + } if (textureView != null) { if (textureView.getSurfaceTextureListener() != componentListener) { Log.w(TAG, "SurfaceTextureListener already unset or replaced."); @@ -1844,22 +1886,29 @@ public class SimpleExoPlayer extends BasePlayer } } - private void setVideoSurfaceInternal(@Nullable Surface surface, boolean ownsSurface) { - // Note: We don't turn this method into a no-op if the surface is being replaced with itself - // so as to ensure onRenderedFirstFrame callbacks are still called in this case. + private void setSurfaceTextureInternal(SurfaceTexture surfaceTexture) { + Surface surface = new Surface(surfaceTexture); + setVideoOutputInternal(surface); + ownedSurface = surface; + } + + private void setVideoOutputInternal(@Nullable Object videoOutput) { + // Note: We don't turn this method into a no-op if the output is being replaced with itself so + // as to ensure onRenderedFirstFrame callbacks are still called in this case. List messages = new ArrayList<>(); for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { messages.add( player .createMessage(renderer) - .setType(Renderer.MSG_SET_SURFACE) - .setPayload(surface) + .setType(MSG_SET_VIDEO_OUTPUT) + .setPayload(videoOutput) .send()); } } - if (this.surface != null && this.surface != surface) { - // We're replacing a surface. Block to ensure that it's not accessed after the method returns. + if (this.videoOutput != null && this.videoOutput != videoOutput) { + // We're replacing an output. Block to ensure that this output will not be accessed by the + // renderers after this method returns. try { for (PlayerMessage message : messages) { message.blockUntilDelivered(detachSurfaceTimeoutMs); @@ -1873,21 +1922,37 @@ public class SimpleExoPlayer extends BasePlayer ExoPlaybackException.createForRenderer( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE))); } - // If we created the previous surface, we are responsible for releasing it. - if (this.ownsSurface) { - this.surface.release(); + if (this.videoOutput == ownedSurface) { + // We're replacing a surface that we are responsible for releasing. + ownedSurface.release(); + ownedSurface = null; } } - this.surface = surface; - this.ownsSurface = ownsSurface; + this.videoOutput = videoOutput; } - private void setVideoDecoderOutputBufferRenderer( - @Nullable VideoDecoderOutputBufferRenderer videoDecoderOutputBufferRenderer) { - sendRendererMessage( - C.TRACK_TYPE_VIDEO, - Renderer.MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER, - videoDecoderOutputBufferRenderer); + /** + * Sets the holder of the surface that will be displayed to the user, but which should + * not be the output for video renderers. This case occurs when video frames need to be + * rendered to an intermediate surface (which is not the one held by the provided holder). + * + * @param nonVideoOutputSurfaceHolder The holder of the surface that will eventually be displayed + * to the user. + */ + private void setNonVideoOutputSurfaceHolderInternal(SurfaceHolder nonVideoOutputSurfaceHolder) { + // Although we won't use the view's surface directly as the video output, still use the holder + // to query the surface size, to be informed in changes to the size via componentListener, and + // for equality checking in clearVideoSurfaceHolder. + surfaceHolderSurfaceIsVideoOutput = false; + surfaceHolder = nonVideoOutputSurfaceHolder; + surfaceHolder.addCallback(componentListener); + Surface surface = surfaceHolder.getSurface(); + if (surface != null && surface.isValid()) { + Rect surfaceSize = surfaceHolder.getSurfaceFrame(); + maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height()); + } else { + maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); + } } private void maybeNotifySurfaceSizeChanged(int width, int height) { @@ -1903,7 +1968,7 @@ public class SimpleExoPlayer extends BasePlayer private void sendVolumeToRenderers() { float scaledVolume = audioVolume * audioFocusManager.getVolumeMultiplier(); - sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_VOLUME, scaledVolume); + sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_VOLUME, scaledVolume); } @SuppressWarnings("SuspiciousMethodCalls") @@ -1948,14 +2013,21 @@ public class SimpleExoPlayer extends BasePlayer } private void verifyApplicationThread() { - if (Looper.myLooper() != getApplicationLooper()) { + // The constructor may be executed on a background thread. Wait with accessing the player from + // the app thread until the constructor finished executing. + constructorFinished.blockUninterruptible(); + if (Thread.currentThread() != getApplicationLooper().getThread()) { + String message = + Util.formatInvariant( + "Player is accessed on the wrong thread.\n" + + "Current thread: '%s'\n" + + "Expected thread: '%s'\n" + + "See https://exoplayer.dev/issues/player-accessed-on-wrong-thread", + Thread.currentThread().getName(), getApplicationLooper().getThread().getName()); if (throwsWhenUsingWrongThread) { - throw new IllegalStateException(WRONG_THREAD_ERROR_MESSAGE); + throw new IllegalStateException(message); } - Log.w( - TAG, - WRONG_THREAD_ERROR_MESSAGE, - hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException()); + Log.w(TAG, message, hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException()); hasNotifiedFullWrongThreadWarning = true; } } @@ -2022,10 +2094,12 @@ public class SimpleExoPlayer extends BasePlayer MetadataOutput, SurfaceHolder.Callback, TextureView.SurfaceTextureListener, + SphericalGLSurfaceView.VideoSurfaceListener, AudioFocusManager.PlayerControl, AudioBecomingNoisyManager.EventListener, StreamVolumeManager.Listener, - Player.EventListener { + Player.EventListener, + AudioOffloadListener { // VideoRendererEventListener implementation @@ -2055,20 +2129,23 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public void onVideoSizeChanged( - int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - analyticsCollector.onVideoSizeChanged( - width, height, unappliedRotationDegrees, pixelWidthHeightRatio); + public void onVideoSizeChanged(VideoSize videoSize) { + SimpleExoPlayer.this.videoSize = videoSize; + analyticsCollector.onVideoSizeChanged(videoSize); for (VideoListener videoListener : videoListeners) { + videoListener.onVideoSizeChanged(videoSize); videoListener.onVideoSizeChanged( - width, height, unappliedRotationDegrees, pixelWidthHeightRatio); + videoSize.width, + videoSize.height, + videoSize.unappliedRotationDegrees, + videoSize.pixelWidthHeightRatio); } } @Override - public void onRenderedFirstFrame(Surface surface) { - analyticsCollector.onRenderedFirstFrame(surface); - if (SimpleExoPlayer.this.surface == surface) { + public void onRenderedFirstFrame(Object output, long renderTimeMs) { + analyticsCollector.onRenderedFirstFrame(output, renderTimeMs); + if (videoOutput == output) { for (VideoListener videoListener : videoListeners) { videoListener.onRenderedFirstFrame(); } @@ -2092,6 +2169,11 @@ public class SimpleExoPlayer extends BasePlayer analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount); } + @Override + public void onVideoCodecError(Exception videoCodecError) { + analyticsCollector.onVideoCodecError(videoCodecError); + } + // AudioRendererEventListener implementation @Override @@ -2150,6 +2232,11 @@ public class SimpleExoPlayer extends BasePlayer analyticsCollector.onAudioSinkError(audioSinkError); } + @Override + public void onAudioCodecError(Exception audioCodecError) { + analyticsCollector.onAudioCodecError(audioCodecError); + } + // TextOutput implementation @Override @@ -2165,6 +2252,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public void onMetadata(Metadata metadata) { analyticsCollector.onMetadata(metadata); + player.onMetadata(metadata); for (MetadataOutput metadataOutput : metadataOutputs) { metadataOutput.onMetadata(metadata); } @@ -2174,7 +2262,9 @@ public class SimpleExoPlayer extends BasePlayer @Override public void surfaceCreated(SurfaceHolder holder) { - setVideoSurfaceInternal(holder.getSurface(), false); + if (surfaceHolderSurfaceIsVideoOutput) { + setVideoOutputInternal(holder.getSurface()); + } } @Override @@ -2184,7 +2274,9 @@ public class SimpleExoPlayer extends BasePlayer @Override public void surfaceDestroyed(SurfaceHolder holder) { - setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ false); + if (surfaceHolderSurfaceIsVideoOutput) { + setVideoOutputInternal(/* videoOutput= */ null); + } maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } @@ -2192,7 +2284,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { - setVideoSurfaceInternal(new Surface(surfaceTexture), /* ownsSurface= */ true); + setSurfaceTextureInternal(surfaceTexture); maybeNotifySurfaceSizeChanged(width, height); } @@ -2203,7 +2295,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { - setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ true); + setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); return true; } @@ -2213,6 +2305,18 @@ public class SimpleExoPlayer extends BasePlayer // Do nothing. } + // SphericalGLSurfaceView.VideoSurfaceListener + + @Override + public void onVideoSurfaceCreated(Surface surface) { + setVideoOutputInternal(surface); + } + + @Override + public void onVideoSurfaceDestroyed(Surface surface) { + setVideoOutputInternal(/* videoOutput= */ null); + } + // AudioFocusManager.PlayerControl implementation @Override @@ -2283,9 +2387,91 @@ public class SimpleExoPlayer extends BasePlayer updateWakeAndWifiLock(); } + // Player.AudioOffloadListener implementation. + @Override public void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) { updateWakeAndWifiLock(); } } + + /** Listeners that are called on the playback thread. */ + private static final class FrameMetadataListener + implements VideoFrameMetadataListener, CameraMotionListener, PlayerMessage.Target { + + public static final int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = + Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; + public static final int MSG_SET_CAMERA_MOTION_LISTENER = + Renderer.MSG_SET_CAMERA_MOTION_LISTENER; + public static final int MSG_SET_SPHERICAL_SURFACE_VIEW = Renderer.MSG_CUSTOM_BASE; + + @Nullable private VideoFrameMetadataListener videoFrameMetadataListener; + @Nullable private CameraMotionListener cameraMotionListener; + @Nullable private VideoFrameMetadataListener internalVideoFrameMetadataListener; + @Nullable private CameraMotionListener internalCameraMotionListener; + + @Override + public void handleMessage(int messageType, @Nullable Object payload) { + switch (messageType) { + case MSG_SET_VIDEO_FRAME_METADATA_LISTENER: + videoFrameMetadataListener = (VideoFrameMetadataListener) payload; + break; + case MSG_SET_CAMERA_MOTION_LISTENER: + cameraMotionListener = (CameraMotionListener) payload; + break; + case MSG_SET_SPHERICAL_SURFACE_VIEW: + SphericalGLSurfaceView surfaceView = (SphericalGLSurfaceView) payload; + if (surfaceView == null) { + internalVideoFrameMetadataListener = null; + internalCameraMotionListener = null; + } else { + internalVideoFrameMetadataListener = surfaceView.getVideoFrameMetadataListener(); + internalCameraMotionListener = surfaceView.getCameraMotionListener(); + } + break; + default: + break; + } + } + + // VideoFrameMetadataListener + + @Override + public void onVideoFrameAboutToBeRendered( + long presentationTimeUs, + long releaseTimeNs, + Format format, + @Nullable MediaFormat mediaFormat) { + if (internalVideoFrameMetadataListener != null) { + internalVideoFrameMetadataListener.onVideoFrameAboutToBeRendered( + presentationTimeUs, releaseTimeNs, format, mediaFormat); + } + if (videoFrameMetadataListener != null) { + videoFrameMetadataListener.onVideoFrameAboutToBeRendered( + presentationTimeUs, releaseTimeNs, format, mediaFormat); + } + } + + // CameraMotionListener + + @Override + public void onCameraMotion(long timeUs, float[] rotation) { + if (internalCameraMotionListener != null) { + internalCameraMotionListener.onCameraMotion(timeUs, rotation); + } + if (cameraMotionListener != null) { + cameraMotionListener.onCameraMotion(timeUs, rotation); + } + } + + @Override + public void onCameraMotionReset() { + if (internalCameraMotionListener != null) { + internalCameraMotionListener.onCameraMotionReset(); + } + if (cameraMotionListener != null) { + cameraMotionListener.onCameraMotionReset(); + } + } + } } 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 20bb920c57..5d1c0a9c2f 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 @@ -19,13 +19,13 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import android.os.Looper; import android.util.SparseArray; -import android.view.Surface; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; 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.Player.PlaybackSuppressionReason; @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; +import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.LoadEventInfo; @@ -51,6 +52,7 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -64,7 +66,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; * Data collector that forwards analytics events to {@link AnalyticsListener AnalyticsListeners}. */ public class AnalyticsCollector - implements Player.EventListener, + implements Player.Listener, AudioRendererEventListener, VideoRendererEventListener, MediaSourceEventListener, @@ -77,7 +79,7 @@ public class AnalyticsCollector private final MediaPeriodQueueTracker mediaPeriodQueueTracker; private final SparseArray eventTimes; - private ListenerSet listeners; + private ListenerSet listeners; private @MonotonicNonNull Player player; private boolean isSeeking; @@ -88,12 +90,7 @@ public class AnalyticsCollector */ public AnalyticsCollector(Clock clock) { this.clock = checkNotNull(clock); - listeners = - new ListenerSet<>( - Util.getCurrentOrMainLooper(), - clock, - AnalyticsListener.Events::new, - (listener, eventFlags) -> {}); + listeners = new ListenerSet<>(Util.getCurrentOrMainLooper(), clock, (listener, flags) -> {}); period = new Period(); window = new Window(); mediaPeriodQueueTracker = new MediaPeriodQueueTracker(period); @@ -136,10 +133,8 @@ public class AnalyticsCollector listeners = listeners.copy( looper, - (listener, events) -> { - events.setEventTimes(eventTimes); - listener.onEvents(player, events); - }); + (listener, flags) -> + listener.onEvents(player, new AnalyticsListener.Events(flags, eventTimes))); } /** @@ -176,6 +171,7 @@ public class AnalyticsCollector * Notify analytics collector that a seek operation will start. Should be called before the player * adjusts its state and position to the seek. */ + @SuppressWarnings("deprecation") // Calling deprecated listener method. public final void notifySeekStarted() { if (!isSeeking) { EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); @@ -185,11 +181,6 @@ public class AnalyticsCollector } } - /** Resets the analytics collector for a new playlist. */ - public final void resetForNewPlaylist() { - // TODO: remove method. - } - // MetadataOutput events. /** @@ -207,7 +198,7 @@ public class AnalyticsCollector // AudioRendererEventListener implementation. - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onAudioEnabled(DecoderCounters counters) { EventTime eventTime = generateReadingMediaPeriodEventTime(); @@ -220,7 +211,7 @@ public class AnalyticsCollector }); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onAudioDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) { @@ -230,12 +221,14 @@ public class AnalyticsCollector AnalyticsListener.EVENT_AUDIO_DECODER_INITIALIZED, listener -> { listener.onAudioDecoderInitialized(eventTime, decoderName, initializationDurationMs); + listener.onAudioDecoderInitialized( + eventTime, decoderName, initializedTimestampMs, initializationDurationMs); listener.onDecoderInitialized( eventTime, C.TRACK_TYPE_AUDIO, decoderName, initializationDurationMs); }); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onAudioInputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { @@ -244,6 +237,7 @@ public class AnalyticsCollector eventTime, AnalyticsListener.EVENT_AUDIO_INPUT_FORMAT_CHANGED, listener -> { + listener.onAudioInputFormatChanged(eventTime, format); listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation); listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format); }); @@ -278,7 +272,7 @@ public class AnalyticsCollector listener -> listener.onAudioDecoderReleased(eventTime, decoderName)); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onAudioDisabled(DecoderCounters counters) { EventTime eventTime = generatePlayingMediaPeriodEventTime(); @@ -309,6 +303,15 @@ public class AnalyticsCollector listener -> listener.onAudioSinkError(eventTime, audioSinkError)); } + @Override + public final void onAudioCodecError(Exception audioCodecError) { + EventTime eventTime = generateReadingMediaPeriodEventTime(); + sendEvent( + eventTime, + AnalyticsListener.EVENT_AUDIO_CODEC_ERROR, + listener -> listener.onAudioCodecError(eventTime, audioCodecError)); + } + // Additional audio events. /** @@ -352,7 +355,7 @@ public class AnalyticsCollector // VideoRendererEventListener implementation. - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onVideoEnabled(DecoderCounters counters) { EventTime eventTime = generateReadingMediaPeriodEventTime(); @@ -365,7 +368,7 @@ public class AnalyticsCollector }); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onVideoDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) { @@ -375,12 +378,14 @@ public class AnalyticsCollector AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED, listener -> { listener.onVideoDecoderInitialized(eventTime, decoderName, initializationDurationMs); + listener.onVideoDecoderInitialized( + eventTime, decoderName, initializedTimestampMs, initializationDurationMs); listener.onDecoderInitialized( eventTime, C.TRACK_TYPE_VIDEO, decoderName, initializationDurationMs); }); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onVideoInputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { @@ -389,6 +394,7 @@ public class AnalyticsCollector eventTime, AnalyticsListener.EVENT_VIDEO_INPUT_FORMAT_CHANGED, listener -> { + listener.onVideoInputFormatChanged(eventTime, format); listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation); listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format); }); @@ -412,7 +418,7 @@ public class AnalyticsCollector listener -> listener.onVideoDecoderReleased(eventTime, decoderName)); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onVideoDisabled(DecoderCounters counters) { EventTime eventTime = generatePlayingMediaPeriodEventTime(); @@ -425,25 +431,31 @@ public class AnalyticsCollector }); } + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override - public final void onVideoSizeChanged( - int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + public final void onVideoSizeChanged(VideoSize videoSize) { EventTime eventTime = generateReadingMediaPeriodEventTime(); sendEvent( eventTime, AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED, - listener -> - listener.onVideoSizeChanged( - eventTime, width, height, unappliedRotationDegrees, pixelWidthHeightRatio)); + listener -> { + listener.onVideoSizeChanged(eventTime, videoSize); + listener.onVideoSizeChanged( + eventTime, + videoSize.width, + videoSize.height, + videoSize.unappliedRotationDegrees, + videoSize.pixelWidthHeightRatio); + }); } @Override - public final void onRenderedFirstFrame(@Nullable Surface surface) { + public final void onRenderedFirstFrame(Object output, long renderTimeMs) { EventTime eventTime = generateReadingMediaPeriodEventTime(); sendEvent( eventTime, AnalyticsListener.EVENT_RENDERED_FIRST_FRAME, - listener -> listener.onRenderedFirstFrame(eventTime, surface)); + listener -> listener.onRenderedFirstFrame(eventTime, output, renderTimeMs)); } @Override @@ -456,6 +468,15 @@ public class AnalyticsCollector listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount)); } + @Override + public final void onVideoCodecError(Exception videoCodecError) { + EventTime eventTime = generateReadingMediaPeriodEventTime(); + sendEvent( + eventTime, + AnalyticsListener.EVENT_VIDEO_CODEC_ERROR, + listener -> listener.onVideoCodecError(eventTime, videoCodecError)); + } + // Additional video events. /** @@ -597,16 +618,20 @@ public class AnalyticsCollector listener -> listener.onStaticMetadataChanged(eventTime, metadataList)); } + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onIsLoadingChanged(boolean isLoading) { EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); sendEvent( eventTime, AnalyticsListener.EVENT_IS_LOADING_CHANGED, - listener -> listener.onIsLoadingChanged(eventTime, isLoading)); + listener -> { + listener.onLoadingChanged(eventTime, isLoading); + listener.onIsLoadingChanged(eventTime, isLoading); + }); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Implementing and calling deprecated listener method. @Override public final void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); @@ -685,8 +710,13 @@ public class AnalyticsCollector listener -> listener.onPlayerError(eventTime, error)); } + // Calling deprecated callback. + @SuppressWarnings("deprecation") @Override - public final void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { + public final void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @Player.DiscontinuityReason int reason) { if (reason == Player.DISCONTINUITY_REASON_SEEK) { isSeeking = false; } @@ -695,7 +725,10 @@ public class AnalyticsCollector sendEvent( eventTime, AnalyticsListener.EVENT_POSITION_DISCONTINUITY, - listener -> listener.onPositionDiscontinuity(eventTime, reason)); + listener -> { + listener.onPositionDiscontinuity(eventTime, reason); + listener.onPositionDiscontinuity(eventTime, oldPosition, newPosition, reason); + }); } @Override @@ -707,7 +740,16 @@ public class AnalyticsCollector listener -> listener.onPlaybackParametersChanged(eventTime, playbackParameters)); } - @SuppressWarnings("deprecation") + @Override + public void onMediaMetadataChanged(MediaMetadata mediaMetadata) { + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); + sendEvent( + eventTime, + AnalyticsListener.EVENT_MEDIA_METADATA_CHANGED, + listener -> listener.onMediaMetadataChanged(eventTime, mediaMetadata)); + } + + @SuppressWarnings("deprecation") // Implementing and calling deprecated listener method. @Override public final void onSeekProcessed() { EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); @@ -729,12 +771,17 @@ public class AnalyticsCollector // DefaultDrmSessionManager.EventListener implementation. @Override - public final void onDrmSessionAcquired(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { + @SuppressWarnings("deprecation") // Calls deprecated listener method. + public final void onDrmSessionAcquired( + int windowIndex, @Nullable MediaPeriodId mediaPeriodId, @DrmSession.State int state) { EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); sendEvent( eventTime, AnalyticsListener.EVENT_DRM_SESSION_ACQUIRED, - listener -> listener.onDrmSessionAcquired(eventTime)); + listener -> { + listener.onDrmSessionAcquired(eventTime); + listener.onDrmSessionAcquired(eventTime, state); + }); } @Override 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 7692ec47e2..6550c5ac05 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 @@ -17,7 +17,10 @@ package com.google.android.exoplayer2.analytics; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import android.media.MediaCodec; +import android.media.MediaCodec.CodecException; import android.os.Looper; +import android.os.SystemClock; import android.util.SparseArray; import android.view.Surface; import androidx.annotation.IntDef; @@ -26,6 +29,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; 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.Player.DiscontinuityReason; @@ -35,14 +39,19 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.decoder.DecoderException; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; +import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.util.MutableFlags; +import com.google.android.exoplayer2.util.ExoFlags; +import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.base.Objects; import java.io.IOException; import java.lang.annotation.Documented; @@ -65,13 +74,27 @@ import java.util.List; public interface AnalyticsListener { /** A set of {@link EventFlags}. */ - final class Events extends MutableFlags { + final class Events { + private final ExoFlags flags; private final SparseArray eventTimes; - /** Creates the set of event flags. */ - public Events() { - eventTimes = new SparseArray<>(/* initialCapacity= */ 0); + /** + * Creates an instance. + * + * @param flags The {@link ExoFlags} containing the {@link EventFlags} in the set. + * @param eventTimes A map from {@link EventFlags} to {@link EventTime}. Must at least contain + * all the events recorded in {@code flags}. Events that are not recorded in {@code flags} + * are ignored. + */ + public Events(ExoFlags flags, SparseArray eventTimes) { + this.flags = flags; + SparseArray flagsToTimes = new SparseArray<>(/* initialCapacity= */ flags.size()); + for (int i = 0; i < flags.size(); i++) { + @EventFlags int eventFlag = flags.get(i); + flagsToTimes.append(eventFlag, checkNotNull(eventTimes.get(eventFlag))); + } + this.eventTimes = flagsToTimes; } /** @@ -84,30 +107,14 @@ public interface AnalyticsListener { return checkNotNull(eventTimes.get(event)); } - /** - * Sets the {@link EventTime} values for events recorded in this set. - * - * @param eventTimes A map from {@link EventFlags} to {@link EventTime}. Must at least contain - * all the events recorded in this set. - */ - public void setEventTimes(SparseArray eventTimes) { - this.eventTimes.clear(); - for (int i = 0; i < size(); i++) { - @EventFlags int eventFlag = get(i); - this.eventTimes.append(eventFlag, checkNotNull(eventTimes.get(eventFlag))); - } - } - /** * Returns whether the given event occurred. * * @param event The {@link EventFlags event}. * @return Whether the event occurred. */ - @Override public boolean contains(@EventFlags int event) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.contains(event); + return flags.contains(event); } /** @@ -116,10 +123,13 @@ public interface AnalyticsListener { * @param events The {@link EventFlags events}. * @return Whether any of the events occurred. */ - @Override public boolean containsAny(@EventFlags int... events) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.containsAny(events); + return flags.containsAny(events); + } + + /** Returns the number of events in the set. */ + public int size() { + return flags.size(); } /** @@ -131,11 +141,9 @@ public interface AnalyticsListener { * @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive). * @return The {@link EventFlags event} at the given index. */ - @Override @EventFlags public int get(int index) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.get(index); + return flags.get(index); } } @@ -161,6 +169,7 @@ public interface AnalyticsListener { EVENT_PLAYER_ERROR, EVENT_POSITION_DISCONTINUITY, EVENT_PLAYBACK_PARAMETERS_CHANGED, + EVENT_MEDIA_METADATA_CHANGED, EVENT_LOAD_STARTED, EVENT_LOAD_COMPLETED, EVENT_LOAD_CANCELED, @@ -198,6 +207,8 @@ public interface AnalyticsListener { EVENT_DRM_KEYS_REMOVED, EVENT_DRM_SESSION_RELEASED, EVENT_PLAYER_RELEASED, + EVENT_AUDIO_CODEC_ERROR, + EVENT_VIDEO_CODEC_ERROR, }) @interface EventFlags {} /** {@link Player#getCurrentTimeline()} changed. */ @@ -230,11 +241,13 @@ public interface AnalyticsListener { int EVENT_PLAYER_ERROR = Player.EVENT_PLAYER_ERROR; /** * A position discontinuity occurred. See {@link - * Player.EventListener#onPositionDiscontinuity(int)}. + * Player.Listener#onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int)}. */ int EVENT_POSITION_DISCONTINUITY = Player.EVENT_POSITION_DISCONTINUITY; /** {@link Player#getPlaybackParameters()} changed. */ int EVENT_PLAYBACK_PARAMETERS_CHANGED = Player.EVENT_PLAYBACK_PARAMETERS_CHANGED; + /** {@link Player#getMediaMetadata()} changed. */ + int EVENT_MEDIA_METADATA_CHANGED = Player.EVENT_MEDIA_METADATA_CHANGED; /** A source started loading data. */ int EVENT_LOAD_STARTED = 1000; // Intentional gap to leave space for new Player events /** A source started completed loading data. */ @@ -312,6 +325,10 @@ public interface AnalyticsListener { int EVENT_DRM_SESSION_RELEASED = 1035; /** The player was released. */ int EVENT_PLAYER_RELEASED = 1036; + /** The audio codec encountered an error. */ + int EVENT_AUDIO_CODEC_ERROR = 1037; + /** The video codec encountered an error. */ + int EVENT_VIDEO_CODEC_ERROR = 1038; /** Time information of an event. */ final class EventTime { @@ -522,18 +539,32 @@ public interface AnalyticsListener { @Player.MediaItemTransitionReason int reason) {} /** - * Called when a position discontinuity occurred. - * - * @param eventTime The event time. - * @param reason The reason for the position discontinuity. + * @deprecated Use {@link #onPositionDiscontinuity(EventTime, Player.PositionInfo, + * Player.PositionInfo, int)} instead. */ + @Deprecated default void onPositionDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason) {} /** - * Called when a seek operation started. + * Called when a position discontinuity occurred. * * @param eventTime The event time. + * @param oldPosition The position before the discontinuity. + * @param newPosition The position after the discontinuity. + * @param reason The reason for the position discontinuity. */ + default void onPositionDiscontinuity( + EventTime eventTime, + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @DiscontinuityReason int reason) {} + + /** + * @deprecated Use {@link #onPositionDiscontinuity(EventTime, Player.PositionInfo, + * Player.PositionInfo, int)} instead, listening to changes with {@link + * Player#DISCONTINUITY_REASON_SEEK}. + */ + @Deprecated default void onSeekStarted(EventTime eventTime) {} /** @@ -574,10 +605,7 @@ public interface AnalyticsListener { * @param eventTime The event time. * @param isLoading Whether the player is loading. */ - @SuppressWarnings("deprecation") - default void onIsLoadingChanged(EventTime eventTime, boolean isLoading) { - onLoadingChanged(eventTime, isLoading); - } + default void onIsLoadingChanged(EventTime eventTime, boolean isLoading) {} /** @deprecated Use {@link #onIsLoadingChanged(EventTime, boolean)} instead. */ @Deprecated @@ -618,6 +646,18 @@ public interface AnalyticsListener { */ default void onStaticMetadataChanged(EventTime eventTime, List metadataList) {} + /** + * Called when the combined {@link MediaMetadata} changes. + * + *

    The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} + * and the static and dynamic metadata sourced from {@link + * Player.Listener#onStaticMetadataChanged(List)} and {@link MetadataOutput#onMetadata(Metadata)}. + * + * @param eventTime The event time. + * @param mediaMetadata The combined {@link MediaMetadata}. + */ + default void onMediaMetadataChanged(EventTime eventTime, MediaMetadata mediaMetadata) {} + /** * Called when a media source started loading data. * @@ -649,8 +689,14 @@ public interface AnalyticsListener { EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {} /** - * Called when a media source loading error occurred. These errors are just for informational - * purposes and the player may recover. + * Called when a media source loading error occurred. + * + *

    This method being called does not indicate that playback has failed, or that it will fail. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. * * @param eventTime The event time. * @param loadEventInfo The {@link LoadEventInfo} defining the load event. @@ -740,8 +786,18 @@ public interface AnalyticsListener { * * @param eventTime The event time. * @param decoderName The decoder that was created. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. */ + default void onAudioDecoderInitialized( + EventTime eventTime, + String decoderName, + long initializedTimestampMs, + long initializationDurationMs) {} + + /** @deprecated Use {@link #onAudioDecoderInitialized(EventTime, String, long, long)}. */ + @Deprecated default void onAudioDecoderInitialized( EventTime eventTime, String decoderName, long initializationDurationMs) {} @@ -760,11 +816,10 @@ public interface AnalyticsListener { * decoder instance can be reused for the new format, or {@code null} if the renderer did not * have a decoder. */ - @SuppressWarnings("deprecation") default void onAudioInputFormatChanged( - EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { - onAudioInputFormatChanged(eventTime, format); - } + EventTime eventTime, + Format format, + @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {} /** * Called when the audio position has increased for the first time since the last pause or @@ -829,15 +884,38 @@ public interface AnalyticsListener { default void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {} /** - * Called when {@link AudioSink} has encountered an error. These errors are just for informational - * purposes and the player may recover. + * Called when {@link AudioSink} has encountered an error. + * + *

    This method being called does not indicate that playback has failed, or that it will fail. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. * * @param eventTime The event time. - * @param audioSinkError Either a {@link AudioSink.InitializationException} or a {@link - * AudioSink.WriteException} describing the error. + * @param audioSinkError The error that occurred. Typically an {@link + * AudioSink.InitializationException}, a {@link AudioSink.WriteException}, or an {@link + * AudioSink.UnexpectedDiscontinuityException}. */ default void onAudioSinkError(EventTime eventTime, Exception audioSinkError) {} + /** + * Called when an audio decoder encounters an error. + * + *

    This method being called does not indicate that playback has failed, or that it will fail. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. + * + * @param eventTime The event time. + * @param audioCodecError The error. Typically a {@link CodecException} if the renderer uses + * {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder. + */ + default void onAudioCodecError(EventTime eventTime, Exception audioCodecError) {} + /** * Called when the volume changes. * @@ -860,8 +938,18 @@ public interface AnalyticsListener { * * @param eventTime The event time. * @param decoderName The decoder that was created. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. */ + default void onVideoDecoderInitialized( + EventTime eventTime, + String decoderName, + long initializedTimestampMs, + long initializationDurationMs) {} + + /** @deprecated Use {@link #onVideoDecoderInitialized(EventTime, String, long, long)}. */ + @Deprecated default void onVideoDecoderInitialized( EventTime eventTime, String decoderName, long initializationDurationMs) {} @@ -880,11 +968,10 @@ public interface AnalyticsListener { * decoder instance can be reused for the new format, or {@code null} if the renderer did not * have a decoder. */ - @SuppressWarnings("deprecation") default void onVideoInputFormatChanged( - EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { - onVideoInputFormatChanged(eventTime, format); - } + EventTime eventTime, + Format format, + @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {} /** * Called after video frames have been dropped. @@ -931,29 +1018,44 @@ public interface AnalyticsListener { default void onVideoFrameProcessingOffset( EventTime eventTime, long totalProcessingOffsetUs, int frameCount) {} + /** + * Called when a video decoder encounters an error. + * + *

    This method being called does not indicate that playback has failed, or that it will fail. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. + * + * @param eventTime The event time. + * @param videoCodecError The error. Typically a {@link CodecException} if the renderer uses + * {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder. + */ + default void onVideoCodecError(EventTime eventTime, Exception videoCodecError) {} + /** * 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. * * @param eventTime The event time. - * @param surface The {@link Surface} to which a frame has been rendered, or {@code null} if the - * renderer renders to something that isn't a {@link Surface}. + * @param output The output to which a frame has been rendered. Normally a {@link Surface}, + * however may also be other output types (e.g., a {@link VideoDecoderOutputBufferRenderer}). + * @param renderTimeMs {@link SystemClock#elapsedRealtime()} when the first frame was rendered. */ - default void onRenderedFirstFrame(EventTime eventTime, @Nullable Surface surface) {} + default void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {} /** * Called before a frame is rendered for the first time since setting the surface, and each time * there's a change in the size or pixel aspect ratio of the video being rendered. * * @param eventTime The event time. - * @param width The width of the video. - * @param height The height of the video. - * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise - * rotation in degrees that the application should apply for the video for it to be rendered - * in the correct orientation. This value will always be zero on API levels 21 and above, - * since the renderer will apply all necessary rotations internally. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. + * @param videoSize The new size of the video. */ + default void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) {} + + /** @deprecated Implement {@link #onVideoSizeChanged(EventTime eventTime, VideoSize)} instead. */ + @Deprecated default void onVideoSizeChanged( EventTime eventTime, int width, @@ -972,12 +1074,17 @@ public interface AnalyticsListener { */ default void onSurfaceSizeChanged(EventTime eventTime, int width, int height) {} + /** @deprecated Implement {@link #onDrmSessionAcquired(EventTime, int)} instead. */ + @Deprecated + default void onDrmSessionAcquired(EventTime eventTime) {} + /** * Called each time a drm session is acquired. * * @param eventTime The event time. + * @param state The {@link DrmSession.State} of the session when the acquisition completed. */ - default void onDrmSessionAcquired(EventTime eventTime) {} + default void onDrmSessionAcquired(EventTime eventTime, @DrmSession.State int state) {} /** * Called each time drm keys are loaded. @@ -987,8 +1094,14 @@ public interface AnalyticsListener { default void onDrmKeysLoaded(EventTime eventTime) {} /** - * Called when a drm error occurs. These errors are just for informational purposes and the player - * may recover. + * Called when a drm error occurs. + * + *

    This method being called does not indicate that playback has failed, or that it will fail. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. * * @param eventTime The event time. * @param error The error. 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 9746829107..7d4d6087dc 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 @@ -33,6 +33,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Random; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * Default {@link PlaybackSessionManager} which instantiates a new session for each window in the @@ -104,6 +105,10 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag @Override public synchronized void updateSessions(EventTime eventTime) { Assertions.checkNotNull(listener); + if (eventTime.timeline.isEmpty()) { + // Don't try to create new sessions for empty timelines. + return; + } @Nullable SessionDescriptor currentSession = sessions.get(currentSessionId); if (eventTime.mediaPeriodId != null && currentSession != null) { // If we receive an event associated with a media period, then it needs to be either part of @@ -184,16 +189,14 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag } } } - updateSessionsWithDiscontinuity(eventTime, Player.DISCONTINUITY_REASON_INTERNAL); + updateCurrentSession(eventTime); } @Override public synchronized void updateSessionsWithDiscontinuity( EventTime eventTime, @DiscontinuityReason int reason) { Assertions.checkNotNull(listener); - boolean hasAutomaticTransition = - reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION - || reason == Player.DISCONTINUITY_REASON_AD_INSERTION; + boolean hasAutomaticTransition = reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION; Iterator iterator = sessions.values().iterator(); while (iterator.hasNext()) { SessionDescriptor session = iterator.next(); @@ -210,6 +213,36 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag } } } + updateCurrentSession(eventTime); + } + + @Override + @Nullable + public synchronized String getActiveSessionId() { + return currentSessionId; + } + + @Override + public synchronized void finishAllSessions(EventTime eventTime) { + currentSessionId = null; + Iterator iterator = sessions.values().iterator(); + while (iterator.hasNext()) { + SessionDescriptor session = iterator.next(); + iterator.remove(); + if (session.isCreated && listener != null) { + listener.onSessionFinished( + eventTime, session.sessionId, /* automaticTransitionToNextPlayback= */ false); + } + } + } + + @RequiresNonNull("listener") + private void updateCurrentSession(EventTime eventTime) { + if (eventTime.timeline.isEmpty()) { + // Clear current session if the Timeline is empty. + currentSessionId = null; + return; + } @Nullable SessionDescriptor previousSessionDescriptor = sessions.get(currentSessionId); SessionDescriptor currentSessionDescriptor = getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId); @@ -236,20 +269,6 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag } } - @Override - public void finishAllSessions(EventTime eventTime) { - currentSessionId = null; - Iterator iterator = sessions.values().iterator(); - while (iterator.hasNext()) { - SessionDescriptor session = iterator.next(); - iterator.remove(); - if (session.isCreated && listener != null) { - listener.onSessionFinished( - eventTime, session.sessionId, /* automaticTransitionToNextPlayback= */ false); - } - } - } - private SessionDescriptor getOrAddSession( int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { // There should only be one matching session if mediaPeriodId is non-null. If mediaPeriodId is diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java index 1038f3b6e1..786da8c2fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.analytics; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime; @@ -128,6 +129,13 @@ public interface PlaybackSessionManager { */ void updateSessionsWithDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason); + /** + * Returns the session identifier of the session that is currently actively playing, or {@code + * null} if there no such session. + */ + @Nullable + String getActiveSessionId(); + /** * Finishes all existing sessions and calls their respective {@link * Listener#onSessionFinished(EventTime, String, boolean)} callback. 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 563af3bc37..2ca886a5e6 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 @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoSize; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -79,19 +80,17 @@ public final class PlaybackStatsListener private final Period period; private PlaybackStats finishedPlaybackStats; - @Nullable private String activeContentPlayback; - @Nullable private String activeAdPlayback; - @Nullable private EventTime onSeekStartedEventTime; - @Player.DiscontinuityReason int discontinuityReason; - int droppedFrames; - @Nullable Exception nonFatalException; - long bandwidthTimeMs; - long bandwidthBytes; - @Nullable Format videoFormat; - @Nullable Format audioFormat; - int videoHeight; - int videoWidth; + @Nullable private String discontinuityFromSession; + private long discontinuityFromPositionMs; + @Player.DiscontinuityReason private int discontinuityReason; + private int droppedFrames; + @Nullable private Exception nonFatalException; + private long bandwidthTimeMs; + private long bandwidthBytes; + @Nullable private Format videoFormat; + @Nullable private Format audioFormat; + private VideoSize videoSize; /** * Creates listener for playback stats. @@ -108,6 +107,7 @@ public final class PlaybackStatsListener sessionStartEventTimes = new HashMap<>(); finishedPlaybackStats = PlaybackStats.EMPTY; period = new Period(); + videoSize = VideoSize.UNKNOWN; sessionManager.setListener(this); } @@ -137,13 +137,10 @@ public final class PlaybackStatsListener */ @Nullable public PlaybackStats getPlaybackStats() { + @Nullable String activeSessionId = sessionManager.getActiveSessionId(); @Nullable PlaybackStatsTracker activeStatsTracker = - activeAdPlayback != null - ? playbackStatsTrackers.get(activeAdPlayback) - : activeContentPlayback != null - ? playbackStatsTrackers.get(activeContentPlayback) - : null; + activeSessionId == null ? null : playbackStatsTrackers.get(activeSessionId); return activeStatsTracker == null ? null : activeStatsTracker.build(/* isFinal= */ false); } @@ -159,11 +156,6 @@ public final class PlaybackStatsListener @Override public void onSessionActive(EventTime eventTime, String session) { checkNotNull(playbackStatsTrackers.get(session)).onForeground(); - if (eventTime.mediaPeriodId != null && eventTime.mediaPeriodId.isAd()) { - activeAdPlayback = session; - } else { - activeContentPlayback = session; - } } @Override @@ -173,14 +165,11 @@ public final class PlaybackStatsListener @Override public void onSessionFinished(EventTime eventTime, String session, boolean automaticTransition) { - if (session.equals(activeAdPlayback)) { - activeAdPlayback = null; - } else if (session.equals(activeContentPlayback)) { - activeContentPlayback = null; - } PlaybackStatsTracker tracker = checkNotNull(playbackStatsTrackers.remove(session)); EventTime startEventTime = checkNotNull(sessionStartEventTimes.remove(session)); - tracker.onFinished(eventTime, automaticTransition); + long discontinuityFromPositionMs = + session.equals(discontinuityFromSession) ? this.discontinuityFromPositionMs : C.TIME_UNSET; + tracker.onFinished(eventTime, automaticTransition, discontinuityFromPositionMs); PlaybackStats playbackStats = tracker.build(/* isFinal= */ true); finishedPlaybackStats = PlaybackStats.merge(finishedPlaybackStats, playbackStats); if (callback != null) { @@ -191,15 +180,18 @@ public final class PlaybackStatsListener // AnalyticsListener implementation. @Override - public void onPositionDiscontinuity(EventTime eventTime, @Player.DiscontinuityReason int reason) { + public void onPositionDiscontinuity( + EventTime eventTime, + Player.PositionInfo oldPositionInfo, + Player.PositionInfo newPositionInfo, + @Player.DiscontinuityReason int reason) { + if (discontinuityFromSession == null) { + discontinuityFromSession = sessionManager.getActiveSessionId(); + discontinuityFromPositionMs = oldPositionInfo.positionMs; + } discontinuityReason = reason; } - @Override - public void onSeekStarted(EventTime eventTime) { - onSeekStartedEventTime = eventTime; - } - @Override public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) { this.droppedFrames = droppedFrames; @@ -238,10 +230,8 @@ public final class PlaybackStatsListener } @Override - public void onVideoSizeChanged( - EventTime eventTime, int width, int height, int rotationDegrees, float pixelRatio) { - videoWidth = width; - videoHeight = height; + public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) { + this.videoSize = videoSize; } @Override @@ -249,13 +239,11 @@ public final class PlaybackStatsListener if (events.size() == 0) { return; } - maybeAddSessions(player, events); + maybeAddSessions(events); for (String session : playbackStatsTrackers.keySet()) { Pair eventTimeAndBelongsToPlayback = findBestEventTime(events, session); PlaybackStatsTracker tracker = playbackStatsTrackers.get(session); - boolean hasPositionDiscontinuity = - hasEvent(events, session, EVENT_POSITION_DISCONTINUITY) - || hasEvent(events, session, EVENT_TIMELINE_CHANGED); + boolean hasDiscontinuityToPlayback = hasEvent(events, session, EVENT_POSITION_DISCONTINUITY); boolean hasDroppedFrames = hasEvent(events, session, EVENT_DROPPED_VIDEO_FRAMES); boolean hasAudioUnderrun = hasEvent(events, session, EVENT_AUDIO_UNDERRUN); boolean startedLoading = hasEvent(events, session, EVENT_LOAD_STARTED); @@ -270,8 +258,8 @@ public final class PlaybackStatsListener player, /* eventTime= */ eventTimeAndBelongsToPlayback.first, /* belongsToPlayback= */ eventTimeAndBelongsToPlayback.second, - /* seeked= */ onSeekStartedEventTime != null, - hasPositionDiscontinuity, + session.equals(discontinuityFromSession) ? discontinuityFromPositionMs : C.TIME_UNSET, + hasDiscontinuityToPlayback, hasDroppedFrames ? droppedFrames : 0, hasAudioUnderrun, startedLoading, @@ -281,39 +269,33 @@ public final class PlaybackStatsListener hasBandwidthData ? bandwidthBytes : 0, hasFormatData ? videoFormat : null, hasFormatData ? audioFormat : null, - hasVideoSize ? videoHeight : Format.NO_VALUE, - hasVideoSize ? videoWidth : Format.NO_VALUE); + hasVideoSize ? videoSize : null); } - onSeekStartedEventTime = null; videoFormat = null; audioFormat = null; + discontinuityFromSession = null; if (events.contains(AnalyticsListener.EVENT_PLAYER_RELEASED)) { sessionManager.finishAllSessions(events.getEventTime(EVENT_PLAYER_RELEASED)); } } - private void maybeAddSessions(Player player, Events events) { - boolean isCompletelyIdle = - player.getCurrentTimeline().isEmpty() && player.getPlaybackState() == Player.STATE_IDLE; + private void maybeAddSessions(Events events) { for (int i = 0; i < events.size(); i++) { @EventFlags int event = events.get(i); EventTime eventTime = events.getEventTime(event); if (event == EVENT_TIMELINE_CHANGED) { sessionManager.updateSessionsWithTimelineChange(eventTime); - } else if (!isCompletelyIdle && event == EVENT_POSITION_DISCONTINUITY) { + } else if (event == EVENT_POSITION_DISCONTINUITY) { sessionManager.updateSessionsWithDiscontinuity(eventTime, discontinuityReason); - } else if (!isCompletelyIdle) { + } else { sessionManager.updateSessions(eventTime); } } } private Pair findBestEventTime(Events events, String session) { - // Check all event times of the events as well as the event time when a seek started. - @Nullable EventTime eventTime = onSeekStartedEventTime; - boolean belongsToPlayback = - onSeekStartedEventTime != null - && sessionManager.belongsToSession(onSeekStartedEventTime, session); + @Nullable EventTime eventTime = null; + boolean belongsToPlayback = false; for (int i = 0; i < events.size(); i++) { @EventFlags int event = events.get(i); EventTime newEventTime = events.getEventTime(event); @@ -461,15 +443,18 @@ public final class PlaybackStatsListener * @param eventTime The {@link EventTime}. Does not belong to this playback. * @param automaticTransition Whether the playback finished because of an automatic transition * to the next playback item. + * @param discontinuityFromPositionMs The position before the discontinuity from this playback, + * {@link C#TIME_UNSET} if no discontinuity started from this playback. */ - public void onFinished(EventTime eventTime, boolean automaticTransition) { + public void onFinished( + EventTime eventTime, boolean automaticTransition, long discontinuityFromPositionMs) { // Simulate state change to ENDED to record natural ending of playback. @PlaybackState int finalPlaybackState = currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED || automaticTransition ? PlaybackStats.PLAYBACK_STATE_ENDED : PlaybackStats.PLAYBACK_STATE_ABANDONED; - maybeUpdateMediaTimeHistory(eventTime.realtimeMs, /* mediaTimeMs= */ C.TIME_UNSET); + maybeUpdateMediaTimeHistory(eventTime.realtimeMs, discontinuityFromPositionMs); maybeRecordVideoFormatTime(eventTime.realtimeMs); maybeRecordAudioFormatTime(eventTime.realtimeMs); updatePlaybackState(finalPlaybackState, eventTime); @@ -481,8 +466,9 @@ public final class PlaybackStatsListener * @param player The {@link Player}. * @param eventTime The {@link EventTime} of the events. * @param belongsToPlayback Whether the {@code eventTime} belongs to this playback. - * @param seeked Whether a seek occurred. - * @param positionDiscontinuity Whether a position discontinuity occurred for this playback. + * @param discontinuityFromPositionMs The position before the discontinuity from this playback, + * or {@link C#TIME_UNSET} if no discontinuity started from this playback. + * @param hasDiscontinuity Whether a discontinuity to this playback occurred. * @param droppedFrameCount The number of newly dropped frames for this playback. * @param hasAudioUnderun Whether a new audio underrun occurred for this playback. * @param startedLoading Whether this playback started loading. @@ -492,15 +478,14 @@ public final class PlaybackStatsListener * @param bandwidthBytes The number of bytes loaded for this playback. * @param videoFormat A reported downstream video format for this playback, or null. * @param audioFormat A reported downstream audio format for this playback, or null. - * @param videoHeight The reported video height for this playback, or {@link Format#NO_VALUE}. - * @param videoWidth The reported video width for this playback, or {@link Format#NO_VALUE}. + * @param videoSize The reported video size for this playback, or null. */ public void onEvents( Player player, EventTime eventTime, boolean belongsToPlayback, - boolean seeked, - boolean positionDiscontinuity, + long discontinuityFromPositionMs, + boolean hasDiscontinuity, int droppedFrameCount, boolean hasAudioUnderun, boolean startedLoading, @@ -510,9 +495,9 @@ public final class PlaybackStatsListener long bandwidthBytes, @Nullable Format videoFormat, @Nullable Format audioFormat, - int videoHeight, - int videoWidth) { - if (seeked) { + @Nullable VideoSize videoSize) { + if (discontinuityFromPositionMs != C.TIME_UNSET) { + maybeUpdateMediaTimeHistory(eventTime.realtimeMs, discontinuityFromPositionMs); isSeeking = true; } if (player.getPlaybackState() != Player.STATE_BUFFERING) { @@ -521,7 +506,7 @@ public final class PlaybackStatsListener int playerPlaybackState = player.getPlaybackState(); if (playerPlaybackState == Player.STATE_IDLE || playerPlaybackState == Player.STATE_ENDED - || positionDiscontinuity) { + || hasDiscontinuity) { isInterruptedByAd = false; } if (fatalError != null) { @@ -561,9 +546,13 @@ public final class PlaybackStatsListener } if (currentVideoFormat != null && currentVideoFormat.height == Format.NO_VALUE - && videoHeight != Format.NO_VALUE) { + && videoSize != null) { Format formatWithHeightAndWidth = - currentVideoFormat.buildUpon().setWidth(videoWidth).setHeight(videoHeight).build(); + currentVideoFormat + .buildUpon() + .setWidth(videoSize.width) + .setHeight(videoSize.height) + .build(); maybeUpdateVideoFormat(eventTime, formatWithHeightAndWidth); } if (startedLoading) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 82ca6f4b9a..d948f0feb8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -18,15 +18,17 @@ package com.google.android.exoplayer2.audio; import static com.google.android.exoplayer2.util.Util.castNonNull; import android.media.AudioTrack; +import android.media.MediaCodec; +import android.media.MediaCodec.CodecException; import android.os.Handler; import android.os.SystemClock; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.decoder.DecoderException; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.util.Assertions; @@ -67,11 +69,8 @@ public interface AudioRendererEventListener { * decoder instance can be reused for the new format, or {@code null} if the renderer did not * have a decoder. */ - @SuppressWarnings("deprecation") default void onAudioInputFormatChanged( - Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { - onAudioInputFormatChanged(format); - } + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {} /** * Called when the audio position has increased for the first time since the last pause or @@ -113,6 +112,21 @@ public interface AudioRendererEventListener { */ default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {} + /** + * Called when an audio decoder encounters an error. + * + *

    This method being called does not indicate that playback has failed, or that it will fail. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. + * + * @param audioCodecError The error. Typically a {@link CodecException} if the renderer uses + * {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder. + */ + default void onAudioCodecError(Exception audioCodecError) {} + /** * Called when {@link AudioSink} has encountered an error. * @@ -120,18 +134,15 @@ public interface AudioRendererEventListener { * AudioTrack} errors. * *

    This method being called does not indicate that playback has failed, or that it will fail. - * The player may be able to recover from the error (for example by recreating the AudioTrack, - * possibly with different settings) and continue. Hence applications should not - * implement this method to display a user visible error or initiate an application level retry - * ({@link Player.EventListener#onPlayerError} is the appropriate place to implement such - * behavior). This method is called to provide the application with an opportunity to log the - * error if it wishes to do so. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. * - *

    Fatal errors that cannot be recovered will be reported wrapped in a {@link - * ExoPlaybackException} by {@link Player.EventListener#onPlayerError(ExoPlaybackException)}. - * - * @param audioSinkError Either an {@link AudioSink.InitializationException} or a {@link - * AudioSink.WriteException} describing the error. + * @param audioSinkError The error that occurred. Typically an {@link + * AudioSink.InitializationException}, a {@link AudioSink.WriteException}, or an {@link + * AudioSink.UnexpectedDiscontinuityException}. */ default void onAudioSinkError(Exception audioSinkError) {} @@ -172,11 +183,15 @@ public interface AudioRendererEventListener { } /** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */ + @SuppressWarnings("deprecation") // Calling deprecated listener method. public void inputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { if (handler != null) { handler.post( - () -> castNonNull(listener).onAudioInputFormatChanged(format, decoderReuseEvaluation)); + () -> { + castNonNull(listener).onAudioInputFormatChanged(format); + castNonNull(listener).onAudioInputFormatChanged(format, decoderReuseEvaluation); + }); } } @@ -230,5 +245,12 @@ public interface AudioRendererEventListener { handler.post(() -> castNonNull(listener).onAudioSinkError(audioSinkError)); } } + + /** Invokes {@link AudioRendererEventListener#onAudioCodecError(Exception)}. */ + public void audioCodecError(Exception audioCodecError) { + if (handler != null) { + handler.post(() -> castNonNull(listener).onAudioCodecError(audioCodecError)); + } + } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index 463461916f..adf475ef35 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -119,15 +119,15 @@ public interface AudioSink { * The player may be able to recover from the error (for example by recreating the AudioTrack, * possibly with different settings) and continue. Hence applications should not * implement this method to display a user visible error or initiate an application level retry - * ({@link Player.EventListener#onPlayerError} is the appropriate place to implement such - * behavior). This method is called to provide the application with an opportunity to log the - * error if it wishes to do so. + * ({@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior). + * This method is called to provide the application with an opportunity to log the error if it + * wishes to do so. * *

    Fatal errors that cannot be recovered will be reported wrapped in a {@link - * ExoPlaybackException} by {@link Player.EventListener#onPlayerError(ExoPlaybackException)}. + * ExoPlaybackException} by {@link Player.Listener#onPlayerError(ExoPlaybackException)}. * - * @param audioSinkError Either an {@link AudioSink.InitializationException} or a {@link - * AudioSink.WriteException} describing the error. + * @param audioSinkError The error that occurred. Typically an {@link InitializationException}, + * a {@link WriteException}, or an {@link UnexpectedDiscontinuityException}. */ default void onAudioSinkError(Exception audioSinkError) {} } @@ -226,6 +226,32 @@ public interface AudioSink { } + /** Thrown when the sink encounters an unexpected timestamp discontinuity. */ + final class UnexpectedDiscontinuityException extends Exception { + /** The actual presentation time of a sample, in microseconds. */ + public final long actualPresentationTimeUs; + /** The expected presentation time of a sample, in microseconds. */ + public final long expectedPresentationTimeUs; + + /** + * Creates an instance. + * + * @param actualPresentationTimeUs The actual presentation time of a sample, in microseconds. + * @param expectedPresentationTimeUs The expected presentation time of a sample, in + * microseconds. + */ + public UnexpectedDiscontinuityException( + long actualPresentationTimeUs, long expectedPresentationTimeUs) { + super( + "Unexpected audio track timestamp discontinuity: expected " + + expectedPresentationTimeUs + + ", got " + + actualPresentationTimeUs); + this.actualPresentationTimeUs = actualPresentationTimeUs; + this.expectedPresentationTimeUs = expectedPresentationTimeUs; + } + } + /** * The level of support the sink provides for a format. One of {@link * #SINK_FORMAT_SUPPORTED_DIRECTLY}, {@link #SINK_FORMAT_SUPPORTED_WITH_TRANSCODING} or {@link @@ -402,7 +428,7 @@ public interface AudioSink { /** * Sets the playback volume. * - * @param volume A volume in the range [0.0, 1.0]. + * @param volume Linear output gain to apply to all channels. Should be in the range [0.0, 1.0]. */ void setVolume(float volume); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java index 12962a786a..4367883df9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.audio; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static java.lang.Math.max; import android.os.Handler; @@ -45,8 +46,9 @@ import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.ExoMediaCrypto; -import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; @@ -82,6 +84,8 @@ public abstract class DecoderAudioRenderer< Decoder> extends BaseRenderer implements MediaClock { + private static final String TAG = "DecoderAudioRenderer"; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -186,7 +190,7 @@ public abstract class DecoderAudioRenderer< eventDispatcher = new EventDispatcher(eventHandler, eventListener); this.audioSink = audioSink; audioSink.setListener(new AudioSinkListener()); - flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); + flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance(); decoderReinitializationState = REINITIALIZATION_STATE_NONE; audioTrackNeedsConfigure = true; } @@ -270,7 +274,7 @@ public abstract class DecoderAudioRenderer< // We don't have a format yet, so try and read one. FormatHolder formatHolder = getFormatHolder(); flagsOnlyBuffer.clear(); - @SampleStream.ReadDataResult int result = readSource(formatHolder, flagsOnlyBuffer, true); + @ReadDataResult int result = readSource(formatHolder, flagsOnlyBuffer, FLAG_REQUIRE_FORMAT); if (result == C.RESULT_FORMAT_READ) { onInputFormatChanged(formatHolder); } else if (result == C.RESULT_BUFFER_READ) { @@ -300,6 +304,8 @@ public abstract class DecoderAudioRenderer< while (feedInputBuffer()) {} TraceUtil.endSection(); } catch (DecoderException e) { + Log.e(TAG, "Audio codec error", e); + eventDispatcher.audioCodecError(e); throw createRendererException(e, inputFormat); } catch (AudioSink.ConfigurationException e) { throw createRendererException(e, e.format); @@ -433,7 +439,7 @@ public abstract class DecoderAudioRenderer< } FormatHolder formatHolder = getFormatHolder(); - switch (readSource(formatHolder, inputBuffer, /* formatRequired= */ false)) { + switch (readSource(formatHolder, inputBuffer, /* readFlags= */ 0)) { case C.RESULT_NOTHING_READ: return false; case C.RESULT_FORMAT_READ: @@ -618,7 +624,11 @@ public abstract class DecoderAudioRenderer< eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, codecInitializedTimestamp - codecInitializingTimestamp); decoderCounters.decoderInitCount++; - } catch (DecoderException | OutOfMemoryError e) { + } catch (DecoderException e) { + Log.e(TAG, "Audio codec error", e); + eventDispatcher.audioCodecError(e); + throw createRendererException(e, inputFormat); + } catch (OutOfMemoryError e) { throw createRendererException(e, inputFormat); } } @@ -688,7 +698,7 @@ public abstract class DecoderAudioRenderer< eventDispatcher.inputFormatChanged(inputFormat, evaluation); } - private void onQueueInputBuffer(DecoderInputBuffer buffer) { + protected void onQueueInputBuffer(DecoderInputBuffer buffer) { if (allowFirstBufferPositionDiscontinuity && !buffer.isDecodeOnly()) { // TODO: Remove this hack once we have a proper fix for [Internal: b/71876314]. // Allow the position to jump if the first presentable input buffer has a timestamp that @@ -735,6 +745,7 @@ public abstract class DecoderAudioRenderer< @Override public void onAudioSinkError(Exception audioSinkError) { + Log.e(TAG, "Audio sink error", audioSinkError); eventDispatcher.audioSinkError(audioSinkError); } } 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 0fd0c4fb29..47bfba9efe 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 @@ -215,6 +215,35 @@ public final class DefaultAudioSink implements AudioSink { /** The default skip silence flag. */ private static final boolean DEFAULT_SKIP_SILENCE = false; + /** Audio offload mode configuration. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + OFFLOAD_MODE_DISABLED, + OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED, + OFFLOAD_MODE_ENABLED_GAPLESS_NOT_REQUIRED + }) + public @interface OffloadMode {} + + /** The audio sink will never play in offload mode. */ + public static final int OFFLOAD_MODE_DISABLED = 0; + /** + * The audio sink will prefer offload playback except if the track is gapless and the device does + * not advertise support for gapless playback in offload. + * + *

    Use this option to prioritize seamless transitions between tracks of the same album to power + * savings. + */ + public static final int OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED = 1; + /** + * The audio sink will prefer offload playback even if this might result in silence gaps between + * tracks. + * + *

    Use this option to prioritize battery saving at the cost of a possible non seamless + * transitions between tracks of the same album. + */ + public static final int OFFLOAD_MODE_ENABLED_GAPLESS_NOT_REQUIRED = 2; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({OUTPUT_MODE_PCM, OUTPUT_MODE_OFFLOAD, OUTPUT_MODE_PASSTHROUGH}) @@ -281,7 +310,7 @@ public final class DefaultAudioSink implements AudioSink { private final AudioTrackPositionTracker audioTrackPositionTracker; private final ArrayDeque mediaPositionParametersCheckpoints; private final boolean enableAudioTrackPlaybackParams; - private final boolean enableOffload; + @OffloadMode private final int offloadMode; @MonotonicNonNull private StreamEventCallbackV29 offloadStreamEventCallbackV29; private final PendingExceptionHolder initializationExceptionPendingExceptionHolder; @@ -364,7 +393,7 @@ public final class DefaultAudioSink implements AudioSink { new DefaultAudioProcessorChain(audioProcessors), enableFloatOutput, /* enableAudioTrackPlaybackParams= */ false, - /* enableOffload= */ false); + OFFLOAD_MODE_DISABLED); } /** @@ -382,8 +411,8 @@ public final class DefaultAudioSink implements AudioSink { * use. * @param enableAudioTrackPlaybackParams Whether to enable setting playback speed using {@link * android.media.AudioTrack#setPlaybackParams(PlaybackParams)}, if supported. - * @param enableOffload Whether to enable audio offload. If an audio format can be both played - * with offload and encoded audio passthrough, it will be played in offload. Audio offload is + * @param offloadMode Audio offload configuration. If an audio format can be both played with + * offload and encoded audio passthrough, it will be played in offload. Audio offload is * supported from API level 29. Most Android devices can only support one offload {@link * android.media.AudioTrack} at a time and can invalidate it at any time. Thus an app can * never be guaranteed that it will be able to play in offload. Audio processing (for example, @@ -394,12 +423,12 @@ public final class DefaultAudioSink implements AudioSink { AudioProcessorChain audioProcessorChain, boolean enableFloatOutput, boolean enableAudioTrackPlaybackParams, - boolean enableOffload) { + @OffloadMode int offloadMode) { this.audioCapabilities = audioCapabilities; this.audioProcessorChain = Assertions.checkNotNull(audioProcessorChain); this.enableFloatOutput = Util.SDK_INT >= 21 && enableFloatOutput; this.enableAudioTrackPlaybackParams = Util.SDK_INT >= 23 && enableAudioTrackPlaybackParams; - this.enableOffload = Util.SDK_INT >= 29 && enableOffload; + this.offloadMode = Util.SDK_INT >= 29 ? offloadMode : OFFLOAD_MODE_DISABLED; releasingConditionVariable = new ConditionVariable(true); audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener()); channelMappingAudioProcessor = new ChannelMappingAudioProcessor(); @@ -462,9 +491,7 @@ public final class DefaultAudioSink implements AudioSink { // guaranteed to support. return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING; } - if (enableOffload - && !offloadDisabledUntilNextConfiguration - && isOffloadedPlaybackSupported(format, audioAttributes)) { + if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) { return SINK_FORMAT_SUPPORTED_DIRECTLY; } if (isPassthroughPlaybackSupported(format, audioCapabilities)) { @@ -541,7 +568,7 @@ public final class DefaultAudioSink implements AudioSink { availableAudioProcessors = new AudioProcessor[0]; outputSampleRate = inputFormat.sampleRate; outputPcmFrameSize = C.LENGTH_UNSET; - if (enableOffload && isOffloadedPlaybackSupported(inputFormat, audioAttributes)) { + if (useOffloadedPlayback(inputFormat, audioAttributes)) { outputMode = OUTPUT_MODE_OFFLOAD; outputEncoding = MimeTypes.getEncoding( @@ -762,13 +789,9 @@ public final class DefaultAudioSink implements AudioSink { getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount()); if (!startMediaTimeUsNeedsSync && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) { - Log.e( - TAG, - "Discontinuity detected [expected " - + expectedPresentationTimeUs - + ", got " - + presentationTimeUs - + "]"); + listener.onAudioSinkError( + new AudioSink.UnexpectedDiscontinuityException( + presentationTimeUs, expectedPresentationTimeUs)); startMediaTimeUsNeedsSync = true; } if (startMediaTimeUsNeedsSync) { @@ -1569,9 +1592,8 @@ public final class DefaultAudioSink implements AudioSink { return Util.getAudioTrackChannelConfig(channelCount); } - private static boolean isOffloadedPlaybackSupported( - Format format, AudioAttributes audioAttributes) { - if (Util.SDK_INT < 29) { + private boolean useOffloadedPlayback(Format format, AudioAttributes audioAttributes) { + if (Util.SDK_INT < 29 || offloadMode == OFFLOAD_MODE_DISABLED) { return false; } @C.Encoding @@ -1589,8 +1611,12 @@ public final class DefaultAudioSink implements AudioSink { audioFormat, audioAttributes.getAudioAttributesV21())) { return false; } - boolean notGapless = format.encoderDelay == 0 && format.encoderPadding == 0; - return notGapless || isOffloadedGaplessPlaybackSupported(); + boolean isGapless = format.encoderDelay != 0 || format.encoderPadding != 0; + boolean offloadRequiresGaplessSupport = offloadMode == OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED; + if (isGapless && offloadRequiresGaplessSupport && !isOffloadedGaplessPlaybackSupported()) { + return false; + } + return true; } private static boolean isOffloadedPlayback(AudioTrack audioTrack) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 5786af503d..2fc8667bc5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -49,8 +49,9 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; -import com.google.android.exoplayer2.mediacodec.MediaFormatUtil; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MediaClock; +import com.google.android.exoplayer2.util.MediaFormatUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; @@ -346,9 +347,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - protected void configureCodec( + protected MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, - MediaCodecAdapter codec, Format format, @Nullable MediaCrypto crypto, float codecOperatingRate) { @@ -356,12 +356,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); MediaFormat mediaFormat = getMediaFormat(format, codecInfo.codecMimeType, codecMaxInputSize, codecOperatingRate); - codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0); // Store the input MIME type if we're only using the codec for decryption. boolean decryptOnlyCodecEnabled = MimeTypes.AUDIO_RAW.equals(codecInfo.mimeType) && !MimeTypes.AUDIO_RAW.equals(format.sampleMimeType); decryptOnlyCodecFormat = decryptOnlyCodecEnabled ? format : null; + return new MediaCodecAdapter.Configuration( + codecInfo, mediaFormat, format, /* surface= */ null, crypto, /* flags= */ 0); } @Override @@ -414,6 +415,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media eventDispatcher.decoderReleased(name); } + @Override + protected void onCodecError(Exception codecError) { + Log.e(TAG, "Audio codec error", codecError); + eventDispatcher.audioCodecError(codecError); + } + @Override @Nullable protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) @@ -848,6 +855,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media @Override public void onAudioSinkError(Exception audioSinkError) { + Log.e(TAG, "Audio sink error", audioSinkError); eventDispatcher.audioSinkError(audioSinkError); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java index ca431bf77e..073f6ecca7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.decoder; -/** - * Output buffer decoded by a {@link Decoder}. - */ +/** Output buffer decoded by a {@link Decoder}. */ public abstract class OutputBuffer extends Buffer { /** Buffer owner. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java index 22cff021de..12d3c8ca26 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java @@ -19,9 +19,7 @@ import androidx.annotation.Nullable; import java.nio.ByteBuffer; import java.nio.ByteOrder; -/** - * Buffer for {@link SimpleDecoder} output. - */ +/** Buffer for {@link SimpleDecoder} output. */ public class SimpleOutputBuffer extends OutputBuffer { private final Owner owner; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java index 81cfc26393..f144cc1ac2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.drm; -/** - * Thrown when a non-platform component fails to decrypt data. - */ +/** Thrown when a non-platform component fails to decrypt data. */ public class DecryptionException extends Exception { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index f7d7a097a0..4325e677de 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -293,11 +293,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; if (openInternal(true)) { doLicense(true); } - } else if (eventDispatcher != null && isOpen()) { - // If the session is already open then send the acquire event only to the provided dispatcher. - // TODO: Add a parameter to onDrmSessionAcquired to indicate whether the session is being - // re-used or not. - eventDispatcher.drmSessionAcquired(); + } else if (eventDispatcher != null + && isOpen() + && eventDispatchers.count(eventDispatcher) == 1) { + // If the session is already open and this is the first instance of eventDispatcher we've + // seen, then send the acquire event only to the provided dispatcher. + eventDispatcher.drmSessionAcquired(state); } referenceCountListener.onReferenceCountIncremented(this, referenceCount); } @@ -321,15 +322,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; mediaDrm.closeSession(sessionId); sessionId = null; } - dispatchEvent(DrmSessionEventListener.EventDispatcher::drmSessionReleased); } if (eventDispatcher != null) { - if (isOpen()) { - // If the session is still open then send the release event only to the provided dispatcher - // before removing it. + eventDispatchers.remove(eventDispatcher); + if (eventDispatchers.count(eventDispatcher) == 0) { + // Release events are only sent to the last-attached instance of each EventDispatcher. eventDispatcher.drmSessionReleased(); } - eventDispatchers.remove(eventDispatcher); } referenceCountListener.onReferenceCountDecremented(this, referenceCount); } @@ -353,8 +352,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; try { sessionId = mediaDrm.openSession(); mediaCrypto = mediaDrm.createMediaCrypto(sessionId); - dispatchEvent(DrmSessionEventListener.EventDispatcher::drmSessionAcquired); state = STATE_OPENED; + // Capture state into a local so a consistent value is seen by the lambda. + int localState = state; + dispatchEvent(eventDispatcher -> eventDispatcher.drmSessionAcquired(localState)); Assertions.checkNotNull(sessionId); return true; } catch (NotProvisionedException e) { @@ -446,7 +447,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); return true; } catch (Exception e) { - Log.e(TAG, "Error trying to restore keys.", e); onError(e); } return false; @@ -522,6 +522,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private void onError(final Exception e) { lastException = new DrmSessionException(e); + Log.e(TAG, "DRM session error", e); dispatchEvent(eventDispatcher -> eventDispatcher.drmSessionManagerError(e)); if (state != STATE_OPENED_WITH_KEYS) { state = STATE_ERROR; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index d24d5ce847..ebc08641fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -15,6 +15,10 @@ */ package com.google.android.exoplayer2.drm; +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.annotation.SuppressLint; import android.media.ResourceBusyException; import android.os.Handler; @@ -31,7 +35,6 @@ import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener; import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; 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; @@ -47,9 +50,15 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */ +/** + * A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. + * + *

    This implementation supports pre-acquisition of sessions using {@link + * #preacquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}. + */ @RequiresApi(18) public class DefaultDrmSessionManager implements DrmSessionManager { @@ -120,8 +129,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager { */ public Builder setUuidAndExoMediaDrmProvider( UUID uuid, ExoMediaDrm.Provider exoMediaDrmProvider) { - this.uuid = Assertions.checkNotNull(uuid); - this.exoMediaDrmProvider = Assertions.checkNotNull(exoMediaDrmProvider); + this.uuid = checkNotNull(uuid); + this.exoMediaDrmProvider = checkNotNull(exoMediaDrmProvider); return this; } @@ -157,8 +166,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { public Builder setUseDrmSessionsForClearContent( int... useDrmSessionsForClearContentTrackTypes) { for (int trackType : useDrmSessionsForClearContentTrackTypes) { - Assertions.checkArgument( - trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_AUDIO); + checkArgument(trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_AUDIO); } this.useDrmSessionsForClearContentTrackTypes = useDrmSessionsForClearContentTrackTypes.clone(); @@ -185,7 +193,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { * @return This builder. */ public Builder setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) { - this.loadErrorHandlingPolicy = Assertions.checkNotNull(loadErrorHandlingPolicy); + this.loadErrorHandlingPolicy = checkNotNull(loadErrorHandlingPolicy); return this; } @@ -205,7 +213,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { * @return This builder. */ public Builder setSessionKeepaliveMs(long sessionKeepaliveMs) { - Assertions.checkArgument(sessionKeepaliveMs > 0 || sessionKeepaliveMs == C.TIME_UNSET); + checkArgument(sessionKeepaliveMs > 0 || sessionKeepaliveMs == C.TIME_UNSET); this.sessionKeepaliveMs = sessionKeepaliveMs; return this; } @@ -282,14 +290,15 @@ public class DefaultDrmSessionManager implements DrmSessionManager { private final List sessions; private final List provisioningSessions; + private final Set preacquiredSessionReferences; private final Set keepaliveSessions; private int prepareCallsCount; @Nullable private ExoMediaDrm exoMediaDrm; @Nullable private DefaultDrmSession placeholderDrmSession; @Nullable private DefaultDrmSession noMultiSessionDrmSession; - @Nullable private Looper playbackLooper; - private @MonotonicNonNull Handler sessionReleasingHandler; + private @MonotonicNonNull Looper playbackLooper; + private @MonotonicNonNull Handler playbackHandler; private int mode; @Nullable private byte[] offlineLicenseKeySetId; @@ -388,8 +397,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager { boolean playClearSamplesWithoutKeys, LoadErrorHandlingPolicy loadErrorHandlingPolicy, long sessionKeepaliveMs) { - Assertions.checkNotNull(uuid); - Assertions.checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead"); + checkNotNull(uuid); + checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead"); this.uuid = uuid; this.exoMediaDrmProvider = exoMediaDrmProvider; this.callback = callback; @@ -403,6 +412,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { mode = MODE_PLAYBACK; sessions = new ArrayList<>(); provisioningSessions = new ArrayList<>(); + preacquiredSessionReferences = Sets.newIdentityHashSet(); keepaliveSessions = Sets.newIdentityHashSet(); this.sessionKeepaliveMs = sessionKeepaliveMs; } @@ -432,9 +442,9 @@ public class DefaultDrmSessionManager implements DrmSessionManager { * @param offlineLicenseKeySetId The key set id of the license to be used with the given mode. */ public void setMode(@Mode int mode, @Nullable byte[] offlineLicenseKeySetId) { - Assertions.checkState(sessions.isEmpty()); + checkState(sessions.isEmpty()); if (mode == MODE_QUERY || mode == MODE_RELEASE) { - Assertions.checkNotNull(offlineLicenseKeySetId); + checkNotNull(offlineLicenseKeySetId); } this.mode = mode; this.offlineLicenseKeySetId = offlineLicenseKeySetId; @@ -447,7 +457,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { if (prepareCallsCount++ != 0) { return; } - Assertions.checkState(exoMediaDrm == null); + checkState(exoMediaDrm == null); exoMediaDrm = exoMediaDrmProvider.acquireExoMediaDrm(uuid); exoMediaDrm.setOnEventListener(new MediaDrmEventListener()); } @@ -466,10 +476,24 @@ public class DefaultDrmSessionManager implements DrmSessionManager { sessions.get(i).release(/* eventDispatcher= */ null); } } - Assertions.checkNotNull(exoMediaDrm).release(); + releaseAllPreacquiredSessions(); + + checkNotNull(exoMediaDrm).release(); exoMediaDrm = null; } + @Override + public DrmSessionReference preacquireSession( + Looper playbackLooper, + @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, + Format format) { + initPlaybackLooper(playbackLooper); + PreacquiredSessionReference preacquiredSessionReference = + new PreacquiredSessionReference(eventDispatcher); + preacquiredSessionReference.acquire(format); + return preacquiredSessionReference; + } + @Override @Nullable public DrmSession acquireSession( @@ -477,18 +501,35 @@ public class DefaultDrmSessionManager implements DrmSessionManager { @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, Format format) { initPlaybackLooper(playbackLooper); + return acquireSession( + playbackLooper, + eventDispatcher, + format, + /* shouldReleasePreacquiredSessionsBeforeRetrying= */ true); + } + + // Must be called on the playback thread. + @Nullable + private DrmSession acquireSession( + Looper playbackLooper, + @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, + Format format, + boolean shouldReleasePreacquiredSessionsBeforeRetrying) { maybeCreateMediaDrmHandler(playbackLooper); if (format.drmInitData == null) { // Content is not encrypted. - return maybeAcquirePlaceholderSession(MimeTypes.getTrackType(format.sampleMimeType)); + return maybeAcquirePlaceholderSession( + MimeTypes.getTrackType(format.sampleMimeType), + shouldReleasePreacquiredSessionsBeforeRetrying); } @Nullable List schemeDatas = null; if (offlineLicenseKeySetId == null) { - schemeDatas = getSchemeDatas(Assertions.checkNotNull(format.drmInitData), uuid, false); + schemeDatas = getSchemeDatas(checkNotNull(format.drmInitData), uuid, false); if (schemeDatas.isEmpty()) { final MissingSchemeDataException error = new MissingSchemeDataException(uuid); + Log.e(TAG, "DRM error", error); if (eventDispatcher != null) { eventDispatcher.drmSessionManagerError(error); } @@ -514,7 +555,10 @@ public class DefaultDrmSessionManager implements DrmSessionManager { // Create a new session. session = createAndAcquireSessionWithRetry( - schemeDatas, /* isPlaceholderSession= */ false, eventDispatcher); + schemeDatas, + /* isPlaceholderSession= */ false, + eventDispatcher, + shouldReleasePreacquiredSessionsBeforeRetrying); if (!multiSession) { noMultiSessionDrmSession = session; } @@ -530,7 +574,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { @Nullable public Class getExoMediaCryptoType(Format format) { Class exoMediaCryptoType = - Assertions.checkNotNull(exoMediaDrm).getExoMediaCryptoType(); + checkNotNull(exoMediaDrm).getExoMediaCryptoType(); if (format.drmInitData == null) { int trackType = MimeTypes.getTrackType(format.sampleMimeType); return Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) != C.INDEX_UNSET @@ -546,8 +590,9 @@ public class DefaultDrmSessionManager implements DrmSessionManager { // Internal methods. @Nullable - private DrmSession maybeAcquirePlaceholderSession(int trackType) { - ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm); + private DrmSession maybeAcquirePlaceholderSession( + int trackType, boolean shouldReleasePreacquiredSessionsBeforeRetrying) { + ExoMediaDrm exoMediaDrm = checkNotNull(this.exoMediaDrm); boolean avoidPlaceholderDrmSessions = FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType()) && FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC; @@ -562,7 +607,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager { createAndAcquireSessionWithRetry( /* schemeDatas= */ ImmutableList.of(), /* isPlaceholderSession= */ true, - /* eventDispatcher= */ null); + /* eventDispatcher= */ null, + shouldReleasePreacquiredSessionsBeforeRetrying); sessions.add(placeholderDrmSession); this.placeholderDrmSession = placeholderDrmSession; } else { @@ -606,12 +652,14 @@ public class DefaultDrmSessionManager implements DrmSessionManager { return true; } - private void initPlaybackLooper(Looper playbackLooper) { + @EnsuresNonNull({"this.playbackLooper", "this.playbackHandler"}) + private synchronized void initPlaybackLooper(Looper playbackLooper) { if (this.playbackLooper == null) { this.playbackLooper = playbackLooper; - this.sessionReleasingHandler = new Handler(playbackLooper); + this.playbackHandler = new Handler(playbackLooper); } else { - Assertions.checkState(this.playbackLooper == playbackLooper); + checkState(this.playbackLooper == playbackLooper); + checkNotNull(playbackHandler); } } @@ -624,35 +672,67 @@ public class DefaultDrmSessionManager implements DrmSessionManager { private DefaultDrmSession createAndAcquireSessionWithRetry( @Nullable List schemeDatas, boolean isPlaceholderSession, - @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) { + @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, + boolean shouldReleasePreacquiredSessionsBeforeRetrying) { DefaultDrmSession session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); - if (session.getState() == DrmSession.STATE_ERROR - && (Util.SDK_INT < 19 - || Assertions.checkNotNull(session.getError()).getCause() - instanceof ResourceBusyException)) { - // We're short on DRM session resources, so eagerly release all our keepalive sessions. - // ResourceBusyException is only available at API 19, so on earlier versions we always - // eagerly release regardless of the underlying error. - if (!keepaliveSessions.isEmpty()) { - // Make a local copy, because sessions are removed from this.keepaliveSessions during - // release (via callback). - ImmutableSet keepaliveSessions = - ImmutableSet.copyOf(this.keepaliveSessions); - for (DrmSession keepaliveSession : keepaliveSessions) { - keepaliveSession.release(/* eventDispatcher= */ null); - } - // Undo the acquisitions from createAndAcquireSession(). - session.release(eventDispatcher); - if (sessionKeepaliveMs != C.TIME_UNSET) { - session.release(/* eventDispatcher= */ null); - } - session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); + // If we're short on DRM session resources, first try eagerly releasing all our keepalive + // sessions and then retry the acquisition. + if (acquisitionFailedIndicatingResourceShortage(session) && !keepaliveSessions.isEmpty()) { + // Make a local copy, because sessions are removed from this.keepaliveSessions during + // release (via callback). + ImmutableSet keepaliveSessions = + ImmutableSet.copyOf(this.keepaliveSessions); + for (DrmSession keepaliveSession : keepaliveSessions) { + keepaliveSession.release(/* eventDispatcher= */ null); } + undoAcquisition(session, eventDispatcher); + session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); + } + + // If the acquisition failed again due to continued resource shortage, and + // shouldReleasePreacquiredSessionsBeforeRetrying is true, try releasing all pre-acquired + // sessions and then retry the acquisition. + if (acquisitionFailedIndicatingResourceShortage(session) + && shouldReleasePreacquiredSessionsBeforeRetrying + && !preacquiredSessionReferences.isEmpty()) { + releaseAllPreacquiredSessions(); + undoAcquisition(session, eventDispatcher); + session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); } return session; } + private static boolean acquisitionFailedIndicatingResourceShortage(DrmSession session) { + // ResourceBusyException is only available at API 19, so on earlier versions we + // assume any error indicates resource shortage (ensuring we retry). + return session.getState() == DrmSession.STATE_ERROR + && (Util.SDK_INT < 19 + || checkNotNull(session.getError()).getCause() instanceof ResourceBusyException); + } + + /** + * Undoes the acquisitions from {@link #createAndAcquireSession(List, boolean, + * DrmSessionEventListener.EventDispatcher)}. + */ + private void undoAcquisition( + DrmSession session, @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) { + session.release(eventDispatcher); + if (sessionKeepaliveMs != C.TIME_UNSET) { + session.release(/* eventDispatcher= */ null); + } + } + + private void releaseAllPreacquiredSessions() { + // Make a local copy, because sessions are removed from this.preacquiredSessionReferences + // during release (via callback). + ImmutableSet preacquiredSessionReferences = + ImmutableSet.copyOf(this.preacquiredSessionReferences); + for (PreacquiredSessionReference preacquiredSessionReference : preacquiredSessionReferences) { + preacquiredSessionReference.release(); + } + } + /** * Creates a new {@link DefaultDrmSession} and acquires it on behalf of the caller (passing in * {@code eventDispatcher}). @@ -664,7 +744,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { @Nullable List schemeDatas, boolean isPlaceholderSession, @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) { - Assertions.checkNotNull(exoMediaDrm); + checkNotNull(exoMediaDrm); // Placeholder sessions should always play clear samples without keys. boolean playClearSamplesWithoutKeys = this.playClearSamplesWithoutKeys | isPlaceholderSession; DefaultDrmSession session = @@ -680,7 +760,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { offlineLicenseKeySetId, keyRequestParameters, callback, - Assertions.checkNotNull(playbackLooper), + checkNotNull(playbackLooper), loadErrorHandlingPolicy); // Acquire the session once on behalf of the caller to DrmSessionManager - this is the // reference 'assigned' to the caller which they're responsible for releasing. Do this first, @@ -781,7 +861,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { if (sessionKeepaliveMs != C.TIME_UNSET) { // The session has been acquired elsewhere so we want to cancel our timeout. keepaliveSessions.remove(session); - Assertions.checkNotNull(sessionReleasingHandler).removeCallbacksAndMessages(session); + checkNotNull(playbackHandler).removeCallbacksAndMessages(session); } } @@ -790,7 +870,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { if (newReferenceCount == 1 && sessionKeepaliveMs != C.TIME_UNSET) { // Only the internal keep-alive reference remains, so we can start the timeout. keepaliveSessions.add(session); - Assertions.checkNotNull(sessionReleasingHandler) + checkNotNull(playbackHandler) .postAtTime( () -> session.release(/* eventDispatcher= */ null), session, @@ -811,7 +891,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { } provisioningSessions.remove(session); if (sessionKeepaliveMs != C.TIME_UNSET) { - Assertions.checkNotNull(sessionReleasingHandler).removeCallbacksAndMessages(session); + checkNotNull(playbackHandler).removeCallbacksAndMessages(session); keepaliveSessions.remove(session); } } @@ -823,7 +903,78 @@ public class DefaultDrmSessionManager implements DrmSessionManager { @Override public void onEvent( ExoMediaDrm md, @Nullable byte[] sessionId, int event, int extra, @Nullable byte[] data) { - Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget(); + checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget(); + } + } + + /** + * An implementation of {@link DrmSessionReference} that lazily acquires the underlying {@link + * DrmSession}. + * + *

    A new instance is needed for each reference (compared to maintaining exactly one instance + * for each {@link DrmSession}) because each associated {@link + * DrmSessionEventListener.EventDispatcher} might be different. The {@link + * DrmSessionEventListener.EventDispatcher} is required to implement the zero-arg {@link + * DrmSessionReference#release()} method. + */ + private class PreacquiredSessionReference implements DrmSessionReference { + + @Nullable private final DrmSessionEventListener.EventDispatcher eventDispatcher; + + @Nullable private DrmSession session; + private boolean isReleased; + + /** + * Constructs an instance. + * + * @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} passed to {@link + * #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}. + */ + public PreacquiredSessionReference( + @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) { + this.eventDispatcher = eventDispatcher; + } + + /** + * Acquires the underlying session. + * + *

    Must be called at most once. Can be called from any thread. + */ + public void acquire(Format format) { + checkNotNull(playbackHandler) + .post( + () -> { + if (prepareCallsCount == 0 || isReleased) { + // The manager has been fully released or this reference has already been + // released. Abort the acquisition attempt. + return; + } + this.session = + acquireSession( + checkNotNull(playbackLooper), + eventDispatcher, + format, + /* shouldReleasePreacquiredSessionsBeforeRetrying= */ false); + preacquiredSessionReferences.add(this); + }); + } + + @Override + public void release() { + // Ensure the underlying session is released immediately if we're already on the playback + // thread, to allow a failed session opening to be immediately retried. + Util.postOrRun( + checkNotNull(playbackHandler), + () -> { + if (isReleased) { + return; + } + if (session != null) { + session.release(eventDispatcher); + } + preacquiredSessionReferences.remove(this); + isReleased = true; + }); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java index 0720d9677f..98a5c2db3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java @@ -28,13 +28,19 @@ import java.util.concurrent.CopyOnWriteArrayList; /** Listener of {@link DrmSessionManager} events. */ public interface DrmSessionEventListener { + /** @deprecated Implement {@link #onDrmSessionAcquired(int, MediaPeriodId, int)} instead. */ + @Deprecated + default void onDrmSessionAcquired(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {} + /** * Called each time a drm session is acquired. * * @param windowIndex The window index in the timeline this media period belongs to. * @param mediaPeriodId The {@link MediaPeriodId} associated with the drm session. + * @param state The {@link DrmSession.State} of the session when the acquisition completed. */ - default void onDrmSessionAcquired(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {} + default void onDrmSessionAcquired( + int windowIndex, @Nullable MediaPeriodId mediaPeriodId, @DrmSession.State int state) {} /** * Called each time keys are loaded. @@ -50,8 +56,8 @@ public interface DrmSessionEventListener { *

    This method being called does not indicate that playback has failed, or that it will fail. * The player may be able to recover from the error and continue. Hence applications should * not implement this method to display a user visible error or initiate an application - * level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement - * such behavior). This method is called to provide the application with an opportunity to log the + * level retry ({@link Player.Listener#onPlayerError} is the appropriate place to implement such + * behavior). This method is called to provide the application with an opportunity to log the * error if it wishes to do so. * * @param windowIndex The window index in the timeline this media period belongs to. @@ -149,13 +155,20 @@ public interface DrmSessionEventListener { } } - /** Dispatches {@link #onDrmSessionAcquired(int, MediaPeriodId)}. */ - public void drmSessionAcquired() { + /** + * Dispatches {@link #onDrmSessionAcquired(int, MediaPeriodId, int)} and {@link + * #onDrmSessionAcquired(int, MediaPeriodId)}. + */ + @SuppressWarnings("deprecation") // Calls deprecated listener method. + public void drmSessionAcquired(@DrmSession.State int state) { for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) { DrmSessionEventListener listener = listenerAndHandler.listener; postOrRun( listenerAndHandler.handler, - () -> listener.onDrmSessionAcquired(windowIndex, mediaPeriodId)); + () -> { + listener.onDrmSessionAcquired(windowIndex, mediaPeriodId); + listener.onDrmSessionAcquired(windowIndex, mediaPeriodId, state); + }); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java index 70dc4fa7f5..4b3ee553d8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java @@ -22,6 +22,23 @@ import com.google.android.exoplayer2.Format; /** Manages a DRM session. */ public interface DrmSessionManager { + /** + * Represents a single reference count of a {@link DrmSession}, while deliberately not giving + * access to the underlying session. + */ + interface DrmSessionReference { + /** A reference that is never populated with an underlying {@link DrmSession}. */ + DrmSessionReference EMPTY = () -> {}; + + /** + * Releases the underlying session at most once. + * + *

    Can be called from any thread. Calling this method more than once will only release the + * underlying session once. + */ + void release(); + } + /** An instance that supports no DRM schemes. */ DrmSessionManager DRM_UNSUPPORTED = new DrmSessionManager() { @@ -81,6 +98,51 @@ public interface DrmSessionManager { // Do nothing. } + /** + * Pre-acquires a DRM session for the specified {@link Format}. + * + *

    This notifies the manager that a subsequent call to {@link #acquireSession(Looper, + * DrmSessionEventListener.EventDispatcher, Format)} with the same {@link Format} is likely, + * allowing a manager that supports pre-acquisition to get the required {@link DrmSession} ready + * in the background. + * + *

    The caller must call {@link DrmSessionReference#release()} on the returned instance when + * they no longer require the pre-acquisition (i.e. they know they won't be making a matching call + * to {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)} in the near + * future). + * + *

    This manager may silently release the underlying session in order to allow another operation + * to complete. This will result in a subsequent call to {@link #acquireSession(Looper, + * DrmSessionEventListener.EventDispatcher, Format)} re-initializing a new session, including + * repeating key loads and other async initialization steps. + * + *

    The caller must separately call {@link #acquireSession(Looper, + * DrmSessionEventListener.EventDispatcher, Format)} in order to obtain a session suitable for + * playback. The pre-acquired {@link DrmSessionReference} and full {@link DrmSession} instances + * are distinct. The caller must release both, and can release the {@link DrmSessionReference} + * before the {@link DrmSession} without affecting playback. + * + *

    This can be called from any thread. + * + *

    Implementations that do not support pre-acquisition always return an empty {@link + * DrmSessionReference} instance. + * + * @param playbackLooper The looper associated with the media playback thread. + * @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute + * events, and passed on to {@link + * DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}. + * @param format The {@link Format} for which to pre-acquire a {@link DrmSession}. + * @return A releaser for the pre-acquired session. Guaranteed to be non-null even if the matching + * {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)} would + * return null. + */ + default DrmSessionReference preacquireSession( + Looper playbackLooper, + @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, + Format format) { + return DrmSessionReference.EMPTY; + } + /** * Returns a {@link DrmSession} for the specified {@link Format}, with an incremented reference * count. May return null if the {@link Format#drmInitData} is null and the DRM session manager is diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java index e5e1089fa9..b203a58c64 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java @@ -15,8 +15,5 @@ */ package com.google.android.exoplayer2.drm; -/** - * Thrown when the drm keys loaded into an open session expire. - */ -public final class KeysExpiredException extends Exception { -} +/** Thrown when the drm keys loaded into an open session expire. */ +public final class KeysExpiredException extends Exception {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java index 14b817e713..d367a773c5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java @@ -19,9 +19,7 @@ import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; import java.util.UUID; -/** - * Performs {@link ExoMediaDrm} key and provisioning requests. - */ +/** Performs {@link ExoMediaDrm} key and provisioning requests. */ public interface MediaDrmCallback { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java index a89196dc04..cd728df1b2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java @@ -20,9 +20,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * Thrown when the requested DRM scheme is not supported. - */ +/** Thrown when the requested DRM scheme is not supported. */ public final class UnsupportedDrmException extends Exception { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java index 5f60ad690e..a5998488f2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java @@ -20,9 +20,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import java.util.Map; -/** - * Utility methods for Widevine. - */ +/** Utility methods for Widevine. */ public final class WidevineUtil { /** Widevine specific key status field name for the remaining license duration, in seconds. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java index e5c50c1f8e..6d77cdac8d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java @@ -29,7 +29,9 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; +import com.google.android.exoplayer2.util.TraceUtil; import com.google.common.base.Supplier; +import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -96,13 +98,41 @@ import java.nio.ByteBuffer; } @Override - public AsynchronousMediaCodecAdapter createAdapter(MediaCodec codec) { - return new AsynchronousMediaCodecAdapter( - codec, - callbackThreadSupplier.get(), - queueingThreadSupplier.get(), - forceQueueingSynchronizationWorkaround, - synchronizeCodecInteractionsWithQueueing); + public AsynchronousMediaCodecAdapter createAdapter(Configuration configuration) + throws IOException { + String codecName = configuration.codecInfo.name; + @Nullable AsynchronousMediaCodecAdapter codecAdapter = null; + @Nullable MediaCodec codec = null; + try { + TraceUtil.beginSection("createCodec:" + codecName); + codec = MediaCodec.createByCodecName(codecName); + codecAdapter = + new AsynchronousMediaCodecAdapter( + codec, + callbackThreadSupplier.get(), + queueingThreadSupplier.get(), + forceQueueingSynchronizationWorkaround, + synchronizeCodecInteractionsWithQueueing); + TraceUtil.endSection(); + TraceUtil.beginSection("configureCodec"); + codecAdapter.configure( + configuration.mediaFormat, + configuration.surface, + configuration.crypto, + configuration.flags); + TraceUtil.endSection(); + TraceUtil.beginSection("startCodec"); + codecAdapter.start(); + TraceUtil.endSection(); + return codecAdapter; + } catch (Exception e) { + if (codecAdapter != null) { + codecAdapter.release(); + } else if (codec != null) { + codec.release(); + } + throw e; + } } } @@ -138,8 +168,7 @@ import java.nio.ByteBuffer; this.state = STATE_CREATED; } - @Override - public void configure( + private void configure( @Nullable MediaFormat mediaFormat, @Nullable Surface surface, @Nullable MediaCrypto crypto, @@ -149,8 +178,7 @@ import java.nio.ByteBuffer; state = STATE_CONFIGURED; } - @Override - public void start() { + private void start() { bufferEnqueuer.start(); codec.start(); state = STATE_STARTED; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java index 21f79a78a2..8a2bb9a9bc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java @@ -30,6 +30,7 @@ import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import java.util.ArrayDeque; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; @@ -303,7 +304,7 @@ class AsynchronousMediaCodecBufferEnqueuer { * buffers (see [Internal: b/149908061]). */ private static boolean needsSynchronizationWorkaround() { - String manufacturer = Util.toLowerInvariant(Util.MANUFACTURER); + String manufacturer = Ascii.toLowerCase(Util.MANUFACTURER); return manufacturer.contains("samsung") || manufacturer.contains("motorola"); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java index 53b7928b47..368444f81d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java @@ -25,7 +25,9 @@ import android.view.Surface; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.decoder.CryptoInfo; +import java.io.IOException; import java.nio.ByteBuffer; /** @@ -35,6 +37,40 @@ import java.nio.ByteBuffer; * regardless of the mode the {@link MediaCodec} is operating in. */ public interface MediaCodecAdapter { + /** Configuration parameters for a {@link MediaCodecAdapter}. */ + final class Configuration { + /** Information about the {@link MediaCodec} being configured. */ + public final MediaCodecInfo codecInfo; + /** The {@link MediaFormat} for which the codec is being configured. */ + public final MediaFormat mediaFormat; + /** The {@link Format} for which the codec is being configured. */ + public final Format format; + /** For video playbacks, the output where the object will render the decoded frames. */ + @Nullable public final Surface surface; + /** For DRM protected playbacks, a {@link MediaCrypto} to use for decryption. */ + @Nullable public final MediaCrypto crypto; + /** + * Specify CONFIGURE_FLAG_ENCODE to configure the component as an encoder. + * + * @see MediaCodec#configure + */ + public final int flags; + + public Configuration( + MediaCodecInfo codecInfo, + MediaFormat mediaFormat, + Format format, + @Nullable Surface surface, + @Nullable MediaCrypto crypto, + int flags) { + this.codecInfo = codecInfo; + this.mediaFormat = mediaFormat; + this.format = format; + this.surface = surface; + this.crypto = crypto; + this.flags = flags; + } + } /** A factory for {@link MediaCodecAdapter} instances. */ interface Factory { @@ -42,8 +78,8 @@ public interface MediaCodecAdapter { /** Default factory used in most cases. */ Factory DEFAULT = new SynchronousMediaCodecAdapter.Factory(); - /** Creates an instance wrapping the provided {@link MediaCodec} instance. */ - MediaCodecAdapter createAdapter(MediaCodec codec); + /** Creates a {@link MediaCodecAdapter} instance. */ + MediaCodecAdapter createAdapter(Configuration configuration) throws IOException; } /** @@ -55,25 +91,6 @@ public interface MediaCodecAdapter { void onFrameRendered(MediaCodecAdapter codec, long presentationTimeUs, long nanoTime); } - /** - * Configures this adapter and the underlying {@link MediaCodec}. Needs to be called before {@link - * #start()}. - * - * @see MediaCodec#configure(MediaFormat, Surface, MediaCrypto, int) - */ - void configure( - @Nullable MediaFormat mediaFormat, - @Nullable Surface surface, - @Nullable MediaCrypto crypto, - int flags); - - /** - * Starts this instance. Needs to be called after {@link #configure}. - * - * @see MediaCodec#start() - */ - void start(); - /** * Returns the next available input buffer index from the underlying {@link MediaCodec} or {@link * MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists. @@ -192,8 +209,7 @@ public interface MediaCodecAdapter { void setParameters(Bundle params); /** - * Specifies the scaling mode to use, if a surface has been specified in a previous call to {@link - * #configure}. + * Specifies the scaling mode to use, if a surface was specified when the codec was created. * * @see MediaCodec#setVideoScalingMode(int) */ 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 1e4506c795..2190e1fcd5 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 @@ -23,6 +23,9 @@ import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_OMIT_SAMPLE_DATA; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_PEEK; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static java.lang.Math.max; @@ -48,6 +51,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons; import com.google.android.exoplayer2.drm.DrmSession; @@ -57,6 +61,8 @@ import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; +import com.google.android.exoplayer2.source.SampleStream.ReadFlags; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; @@ -73,9 +79,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; -/** - * An abstract renderer that uses {@link MediaCodec} to decode samples for rendering. - */ +/** An abstract renderer that uses {@link MediaCodec} to decode samples for rendering. */ public abstract class MediaCodecRenderer extends BaseRenderer { /** Thrown when a failure occurs instantiating a decoder. */ @@ -294,7 +298,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private final MediaCodecSelector mediaCodecSelector; private final boolean enableDecoderFallback; private final float assumedMinimumCodecOperatingRate; - private final DecoderInputBuffer flagsOnlyBuffer; + private final DecoderInputBuffer noDataBuffer; private final DecoderInputBuffer buffer; private final DecoderInputBuffer bypassSampleBuffer; private final BatchBuffer bypassBatchBuffer; @@ -359,6 +363,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean enableAsynchronousBufferQueueing; private boolean forceAsyncQueueingSynchronizationWorkaround; private boolean enableSynchronizeCodecInteractionsWithQueueing; + private boolean enableSkipAndContinueIfSampleTooLarge; @Nullable private ExoPlaybackException pendingPlaybackException; protected DecoderCounters decoderCounters; private long outputStreamStartPositionUs; @@ -387,7 +392,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { this.mediaCodecSelector = checkNotNull(mediaCodecSelector); this.enableDecoderFallback = enableDecoderFallback; this.assumedMinimumCodecOperatingRate = assumedMinimumCodecOperatingRate; - flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); + noDataBuffer = DecoderInputBuffer.newNoDataInstance(); buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); bypassSampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); bypassBatchBuffer = new BatchBuffer(); @@ -409,7 +414,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // endianness. bypassBatchBuffer.ensureSpaceForWrite(/* length= */ 0); bypassBatchBuffer.data.order(ByteOrder.nativeOrder()); - resetCodecStateForRelease(); + + codecOperatingRate = CODEC_OPERATING_RATE_UNSET; + codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + codecHotswapDeadlineMs = C.TIME_UNSET; + largestQueuedPresentationTimeUs = C.TIME_UNSET; + lastBufferInStreamPresentationTimeUs = C.TIME_UNSET; + codecDrainState = DRAIN_STATE_NONE; + codecDrainAction = DRAIN_ACTION_NONE; } /** @@ -426,7 +441,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /** - * Enable asynchronous input buffer queueing. + * Enables asynchronous input buffer queueing. * *

    Operates the underlying {@link MediaCodec} in asynchronous mode and submits input buffers * from a separate thread to unblock the playback thread. @@ -439,7 +454,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /** - * Enable the asynchronous queueing synchronization workaround. + * Enables the asynchronous queueing synchronization workaround. * *

    When enabled, the queueing threads for {@link MediaCodec} instance will synchronize on a * shared lock when submitting buffers to the respective {@link MediaCodec}. @@ -452,7 +467,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /** - * Enable synchronizing codec interactions with asynchronous buffer queueing. + * Enables synchronizing codec interactions with asynchronous buffer queueing. * *

    When enabled, codec interactions will wait until all input buffers pending for asynchronous * queueing are submitted to the {@link MediaCodec} first. This method is effective only if {@link @@ -465,6 +480,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { enableSynchronizeCodecInteractionsWithQueueing = enabled; } + /** + * Enables skipping and continuing playback from the next key frame if a sample is encountered + * that's too large to fit into one of the decoder's input buffers. When not enabled, playback + * will fail in this case. + * + *

    This method is experimental, and will be renamed or removed in a future release. It should + * only be called before the renderer is used. + */ + public void experimentalSetSkipAndContinueIfSampleTooLarge(boolean enabled) { + enableSkipAndContinueIfSampleTooLarge = enabled; + } + @Override @AdaptiveSupport public final int supportsMixedMimeTypeAdaptation() { @@ -509,18 +536,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { throws DecoderQueryException; /** - * Configures a newly created {@link MediaCodec}. + * Returns the {@link MediaCodecAdapter.Configuration} that will be used to create and configure a + * {@link MediaCodec} to decode the given {@link Format} for a playback. * * @param codecInfo Information about the {@link MediaCodec} being configured. - * @param codec The {@link MediaCodecAdapter} to configure. * @param format The {@link Format} for which the codec is being configured. * @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption. * @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if * no codec operating rate should be set. + * @return The parameters needed to call {@link MediaCodec#configure}. */ - protected abstract void configureCodec( + @Nullable + protected abstract MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, - MediaCodecAdapter codec, Format format, @Nullable MediaCrypto crypto, float codecOperatingRate); @@ -721,11 +749,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { throws ExoPlaybackException { this.currentPlaybackSpeed = currentPlaybackSpeed; this.targetPlaybackSpeed = targetPlaybackSpeed; - if (codec != null - && codecDrainAction != DRAIN_ACTION_REINITIALIZE - && getState() != STATE_DISABLED) { - updateCodecOperatingRate(codecInputFormat); - } + updateCodecOperatingRate(codecInputFormat); } @Override @@ -808,7 +832,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { renderToEndOfStream(); return; } - if (inputFormat == null && !readToFlagsOnlyBuffer(/* requireFormat= */ true)) { + if (inputFormat == null && !readSourceOmittingSampleData(FLAG_REQUIRE_FORMAT)) { // We still don't have a format and can't make progress without one. return; } @@ -829,14 +853,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer { decoderCounters.skippedInputBufferCount += skipSource(positionUs); // We need to read any format changes despite not having a codec so that drmSession can be // updated, and so that we have the most recent format should the codec be initialized. We - // may also reach the end of the stream. Note that readSource will not read a sample into a - // flags-only buffer. - readToFlagsOnlyBuffer(/* requireFormat= */ false); + // may also reach the end of the stream. FLAG_PEEK is used because we don't want to advance + // the source further than skipSource has already done. + readSourceOmittingSampleData(FLAG_PEEK); } decoderCounters.ensureUpdated(); } catch (IllegalStateException e) { if (isMediaCodecException(e)) { - throw createRendererException(createDecoderException(e, getCodecInfo()), inputFormat); + onCodecError(e); + boolean isRecoverable = Util.SDK_INT >= 21 && isRecoverableMediaCodecExceptionV21(e); + if (isRecoverable) { + releaseCodec(); + } + throw createRendererException( + createDecoderException(e, getCodecInfo()), inputFormat, isRecoverable); } throw e; } @@ -955,16 +985,24 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return new MediaCodecDecoderException(cause, codecInfo); } - /** Reads into {@link #flagsOnlyBuffer} and returns whether a {@link Format} was read. */ - private boolean readToFlagsOnlyBuffer(boolean requireFormat) throws ExoPlaybackException { + /** + * Reads from the source when sample data is not required. If a format or an end of stream buffer + * is read, it will be handled before the call returns. + * + * @param readFlags Additional {@link ReadFlags}. {@link SampleStream#FLAG_OMIT_SAMPLE_DATA} is + * added internally, and so does not need to be passed. + * @return Whether a format was read and processed. + */ + private boolean readSourceOmittingSampleData(@SampleStream.ReadFlags int readFlags) + throws ExoPlaybackException { FormatHolder formatHolder = getFormatHolder(); - flagsOnlyBuffer.clear(); - @SampleStream.ReadDataResult - int result = readSource(formatHolder, flagsOnlyBuffer, requireFormat); + noDataBuffer.clear(); + @ReadDataResult + int result = readSource(formatHolder, noDataBuffer, readFlags | FLAG_OMIT_SAMPLE_DATA); if (result == C.RESULT_FORMAT_READ) { onInputFormatChanged(formatHolder); return true; - } else if (result == C.RESULT_BUFFER_READ && flagsOnlyBuffer.isEndOfStream()) { + } else if (result == C.RESULT_BUFFER_READ && noDataBuffer.isEndOfStream()) { inputStreamEnded = true; processEndOfStream(); } @@ -1082,7 +1120,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { long codecInitializedTimestamp; @Nullable MediaCodecAdapter codecAdapter = null; String codecName = codecInfo.name; - float codecOperatingRate = Util.SDK_INT < 23 ? CODEC_OPERATING_RATE_UNSET @@ -1090,35 +1127,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (codecOperatingRate <= assumedMinimumCodecOperatingRate) { codecOperatingRate = CODEC_OPERATING_RATE_UNSET; } - - try { - codecInitializingTimestamp = SystemClock.elapsedRealtime(); - TraceUtil.beginSection("createCodec:" + codecName); - MediaCodec codec = MediaCodec.createByCodecName(codecName); - if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) { - codecAdapter = - new AsynchronousMediaCodecAdapter.Factory( - getTrackType(), - forceAsyncQueueingSynchronizationWorkaround, - enableSynchronizeCodecInteractionsWithQueueing) - .createAdapter(codec); - } else { - codecAdapter = codecAdapterFactory.createAdapter(codec); - } - TraceUtil.endSection(); - TraceUtil.beginSection("configureCodec"); - configureCodec(codecInfo, codecAdapter, inputFormat, crypto, codecOperatingRate); - TraceUtil.endSection(); - TraceUtil.beginSection("startCodec"); - codecAdapter.start(); - TraceUtil.endSection(); - codecInitializedTimestamp = SystemClock.elapsedRealtime(); - } catch (Exception e) { - if (codecAdapter != null) { - codecAdapter.release(); - } - throw e; + codecInitializingTimestamp = SystemClock.elapsedRealtime(); + TraceUtil.beginSection("createCodec:" + codecName); + MediaCodecAdapter.Configuration configuration = + getMediaCodecConfiguration(codecInfo, inputFormat, crypto, codecOperatingRate); + if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) { + codecAdapter = + new AsynchronousMediaCodecAdapter.Factory( + getTrackType(), + forceAsyncQueueingSynchronizationWorkaround, + enableSynchronizeCodecInteractionsWithQueueing) + .createAdapter(configuration); + } else { + codecAdapter = codecAdapterFactory.createAdapter(configuration); } + codecInitializedTimestamp = SystemClock.elapsedRealtime(); this.codec = codecAdapter; this.codecInfo = codecInfo; @@ -1231,8 +1254,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer { int adaptiveReconfigurationBytes = buffer.data.position(); FormatHolder formatHolder = getFormatHolder(); - @SampleStream.ReadDataResult - int result = readSource(formatHolder, buffer, /* formatRequired= */ false); + + @SampleStream.ReadDataResult int result; + try { + result = readSource(formatHolder, buffer, /* readFlags= */ 0); + } catch (InsufficientCapacityException e) { + onCodecError(e); + if (enableSkipAndContinueIfSampleTooLarge) { + // Skip the sample that's too large by reading it without its data. Then flush the codec so + // that rendering will resume from the next key frame. + readSourceOmittingSampleData(/* readFlags= */ 0); + flushCodec(); + return true; + } else { + throw createRendererException( + createDecoderException(e, getCodecInfo()), inputFormat, /* isRecoverable= */ false); + } + } if (hasReadStreamToEnd()) { // Notify output queue of the last buffer's timestamp. @@ -1290,7 +1328,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // during normal consumption of samples from the source (i.e., without a corresponding // Renderer.enable or Renderer.resetPosition call). This is necessary for certain legacy and // workaround behaviors, for example when switching the output Surface on API levels prior to - // the introduction of MediaCodec.setOutputSurface. + // the introduction of MediaCodec.setOutputSurface, and when it's necessary to skip past a + // sample that's too large to be held in one of the decoder's input buffers. if (!codecReceivedBuffers && !buffer.isKeyFrame()) { buffer.clear(); if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { @@ -1386,6 +1425,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // Do nothing. } + /** + * Called when a codec error has occurred. + * + *

    The default implementation is a no-op. + * + * @param codecError The error. + */ + protected void onCodecError(Exception codecError) { + // Do nothing. + } + /** * Called when a new {@link Format} is read from the upstream {@link MediaPeriod}. * @@ -1657,6 +1707,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return CODEC_OPERATING_RATE_UNSET; } + /** + * Updates the codec operating rate, or triggers codec release and re-initialization if a + * previously set operating rate needs to be cleared. + * + * @throws ExoPlaybackException If an error occurs releasing or initializing a codec. + * @return False if codec release and re-initialization was triggered. True in all other cases. + */ + protected final boolean updateCodecOperatingRate() throws ExoPlaybackException { + return updateCodecOperatingRate(codecInputFormat); + } + /** * Updates the codec operating rate, or triggers codec release and re-initialization if a * previously set operating rate needs to be cleared. @@ -1670,6 +1731,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return true; } + if (codec == null + || codecDrainAction == DRAIN_ACTION_REINITIALIZE + || getState() == STATE_DISABLED) { + // No need to update the operating rate. + return true; + } + float newCodecOperatingRate = getCodecOperatingRateV23(targetPlaybackSpeed, format, getStreamFormats()); if (codecOperatingRate == newCodecOperatingRate) { @@ -2218,8 +2286,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { bypassSampleBuffer.clear(); while (true) { bypassSampleBuffer.clear(); - @SampleStream.ReadDataResult - int result = readSource(formatHolder, bypassSampleBuffer, /* formatRequired= */ false); + @ReadDataResult int result = readSource(formatHolder, bypassSampleBuffer, /* readFlags= */ 0); switch (result) { case C.RESULT_FORMAT_READ: onInputFormatChanged(formatHolder); @@ -2263,6 +2330,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return error instanceof MediaCodec.CodecException; } + @RequiresApi(21) + private static boolean isRecoverableMediaCodecExceptionV21(IllegalStateException error) { + if (error instanceof MediaCodec.CodecException) { + return ((MediaCodec.CodecException) error).isRecoverable(); + } + return false; + } + /** * Returns whether the decoder is known to fail when flushed. *

    diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java index 7f39dced61..6e3893cb48 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java @@ -19,9 +19,7 @@ import android.media.MediaCodec; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import java.util.List; -/** - * Selector of {@link MediaCodec} instances. - */ +/** Selector of {@link MediaCodec} instances. */ public interface MediaCodecSelector { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index bcaf8a07b1..36eea4276e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -33,6 +33,7 @@ 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.ColorInfo; +import com.google.common.base.Ascii; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -41,9 +42,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -/** - * A utility class for querying the available codecs. - */ +/** A utility class for querying the available codecs. */ @SuppressLint("InlinedApi") public final class MediaCodecUtil { @@ -624,7 +623,7 @@ public final class MediaCodecUtil { if (Util.SDK_INT >= 29) { return isSoftwareOnlyV29(codecInfo); } - String codecName = Util.toLowerInvariant(codecInfo.getName()); + String codecName = Ascii.toLowerCase(codecInfo.getName()); if (codecName.startsWith("arc.")) { // App Runtime for Chrome (ARC) codecs return false; } @@ -650,7 +649,7 @@ public final class MediaCodecUtil { if (Util.SDK_INT >= 29) { return isVendorV29(codecInfo); } - String codecName = Util.toLowerInvariant(codecInfo.getName()); + String codecName = Ascii.toLowerCase(codecInfo.getName()); return !codecName.startsWith("omx.google.") && !codecName.startsWith("c2.android.") && !codecName.startsWith("c2.google."); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaFormatUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaFormatUtil.java deleted file mode 100644 index 0ed58db266..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaFormatUtil.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2018 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.mediacodec; - -import android.media.MediaFormat; -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.video.ColorInfo; -import java.nio.ByteBuffer; -import java.util.List; - -/** Helper class for configuring {@link MediaFormat} instances. */ -public final class MediaFormatUtil { - - private MediaFormatUtil() {} - - /** - * Sets a {@link MediaFormat} {@link String} value. - * - * @param format The {@link MediaFormat} being configured. - * @param key The key to set. - * @param value The value to set. - */ - public static void setString(MediaFormat format, String key, String value) { - format.setString(key, value); - } - - /** - * Sets a {@link MediaFormat}'s codec specific data buffers. - * - * @param format The {@link MediaFormat} being configured. - * @param csdBuffers The csd buffers to set. - */ - public static void setCsdBuffers(MediaFormat format, List csdBuffers) { - for (int i = 0; i < csdBuffers.size(); i++) { - format.setByteBuffer("csd-" + i, ByteBuffer.wrap(csdBuffers.get(i))); - } - } - - /** - * Sets a {@link MediaFormat} integer value. Does nothing if {@code value} is {@link - * Format#NO_VALUE}. - * - * @param format The {@link MediaFormat} being configured. - * @param key The key to set. - * @param value The value to set. - */ - public static void maybeSetInteger(MediaFormat format, String key, int value) { - if (value != Format.NO_VALUE) { - format.setInteger(key, value); - } - } - - /** - * Sets a {@link MediaFormat} float value. Does nothing if {@code value} is {@link - * Format#NO_VALUE}. - * - * @param format The {@link MediaFormat} being configured. - * @param key The key to set. - * @param value The value to set. - */ - public static void maybeSetFloat(MediaFormat format, String key, float value) { - if (value != Format.NO_VALUE) { - format.setFloat(key, value); - } - } - - /** - * Sets a {@link MediaFormat} {@link ByteBuffer} value. Does nothing if {@code value} is null. - * - * @param format The {@link MediaFormat} being configured. - * @param key The key to set. - * @param value The byte array that will be wrapped to obtain the value. - */ - public static void maybeSetByteBuffer(MediaFormat format, String key, @Nullable byte[] value) { - if (value != null) { - format.setByteBuffer(key, ByteBuffer.wrap(value)); - } - } - - /** - * Sets a {@link MediaFormat}'s color information. Does nothing if {@code colorInfo} is null. - * - * @param format The {@link MediaFormat} being configured. - * @param colorInfo The color info to set. - */ - @SuppressWarnings("InlinedApi") - public static void maybeSetColorInfo(MediaFormat format, @Nullable ColorInfo colorInfo) { - if (colorInfo != null) { - maybeSetInteger(format, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); - maybeSetInteger(format, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace); - maybeSetInteger(format, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange); - maybeSetByteBuffer(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo); - } - } -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java index db1401cd52..67fb24f1dd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java @@ -16,10 +16,10 @@ package com.google.android.exoplayer2.mediacodec; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; import android.media.MediaCodec; -import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Bundle; import android.os.Handler; @@ -28,19 +28,51 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; +import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import java.io.IOException; import java.nio.ByteBuffer; /** * A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode. */ -public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { +public class SynchronousMediaCodecAdapter implements MediaCodecAdapter { /** A factory for {@link SynchronousMediaCodecAdapter} instances. */ - public static final class Factory implements MediaCodecAdapter.Factory { + public static class Factory implements MediaCodecAdapter.Factory { + @Override - public MediaCodecAdapter createAdapter(MediaCodec codec) { - return new SynchronousMediaCodecAdapter(codec); + public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException { + @Nullable MediaCodec codec = null; + try { + codec = createCodec(configuration); + TraceUtil.beginSection("configureCodec"); + codec.configure( + configuration.mediaFormat, + configuration.surface, + configuration.crypto, + configuration.flags); + TraceUtil.endSection(); + TraceUtil.beginSection("startCodec"); + codec.start(); + TraceUtil.endSection(); + return new SynchronousMediaCodecAdapter(codec); + } catch (IOException | RuntimeException e) { + if (codec != null) { + codec.release(); + } + throw e; + } + } + + /** Creates a new {@link MediaCodec} instance. */ + protected MediaCodec createCodec(Configuration configuration) throws IOException { + checkNotNull(configuration.codecInfo); + String codecName = configuration.codecInfo.name; + TraceUtil.beginSection("createCodec:" + codecName); + MediaCodec mediaCodec = MediaCodec.createByCodecName(codecName); + TraceUtil.endSection(); + return mediaCodec; } } @@ -50,20 +82,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { this.codec = mediaCodec; - } - - @Override - public void configure( - @Nullable MediaFormat mediaFormat, - @Nullable Surface surface, - @Nullable MediaCrypto crypto, - int flags) { - codec.configure(mediaFormat, surface, crypto, flags); - } - - @Override - public void start() { - codec.start(); if (Util.SDK_INT < 21) { inputByteBuffers = codec.getInputBuffers(); outputByteBuffers = codec.getOutputBuffers(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java index fae53a5d09..94ab6c4232 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java @@ -24,9 +24,7 @@ import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.scte35.SpliceInfoDecoder; import com.google.android.exoplayer2.util.MimeTypes; -/** - * A factory for {@link MetadataDecoder} instances. - */ +/** A factory for {@link MetadataDecoder} instances. */ public interface MetadataDecoderFactory { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java index a74eb456b5..182e4e5303 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java @@ -27,39 +27,29 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.RendererCapabilities; -import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableType; -/** - * A renderer for metadata. - */ +/** A renderer for metadata. */ public final class MetadataRenderer extends BaseRenderer implements Callback { private static final String TAG = "MetadataRenderer"; private static final int MSG_INVOKE_RENDERER = 0; - // TODO: Holding multiple pending metadata objects is temporary mitigation against - // https://github.com/google/ExoPlayer/issues/1874. It should be removed once this issue has been - // addressed. - private static final int MAX_PENDING_METADATA_COUNT = 5; private final MetadataDecoderFactory decoderFactory; private final MetadataOutput output; @Nullable private final Handler outputHandler; private final MetadataInputBuffer buffer; - private final @NullableType Metadata[] pendingMetadata; - private final long[] pendingMetadataTimestamps; - private int pendingMetadataIndex; - private int pendingMetadataCount; @Nullable private MetadataDecoder decoder; private boolean inputStreamEnded; private boolean outputStreamEnded; private long subsampleOffsetUs; + private long pendingMetadataTimestampUs; + @Nullable private Metadata pendingMetadata; /** * @param output The output. @@ -90,8 +80,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this); this.decoderFactory = Assertions.checkNotNull(decoderFactory); buffer = new MetadataInputBuffer(); - pendingMetadata = new Metadata[MAX_PENDING_METADATA_COUNT]; - pendingMetadataTimestamps = new long[MAX_PENDING_METADATA_COUNT]; + pendingMetadataTimestampUs = C.TIME_UNSET; } @Override @@ -117,51 +106,18 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { @Override protected void onPositionReset(long positionUs, boolean joining) { - flushPendingMetadata(); + pendingMetadata = null; + pendingMetadataTimestampUs = C.TIME_UNSET; inputStreamEnded = false; outputStreamEnded = false; } @Override public void render(long positionUs, long elapsedRealtimeUs) { - if (!inputStreamEnded && pendingMetadataCount < MAX_PENDING_METADATA_COUNT) { - buffer.clear(); - FormatHolder formatHolder = getFormatHolder(); - @SampleStream.ReadDataResult int result = readSource(formatHolder, buffer, false); - if (result == C.RESULT_BUFFER_READ) { - if (buffer.isEndOfStream()) { - inputStreamEnded = true; - } else { - buffer.subsampleOffsetUs = subsampleOffsetUs; - buffer.flip(); - @Nullable Metadata metadata = castNonNull(decoder).decode(buffer); - if (metadata != null) { - List entries = new ArrayList<>(metadata.length()); - decodeWrappedMetadata(metadata, entries); - if (!entries.isEmpty()) { - Metadata expandedMetadata = new Metadata(entries); - int index = - (pendingMetadataIndex + pendingMetadataCount) % MAX_PENDING_METADATA_COUNT; - pendingMetadata[index] = expandedMetadata; - pendingMetadataTimestamps[index] = buffer.timeUs; - pendingMetadataCount++; - } - } - } - } else if (result == C.RESULT_FORMAT_READ) { - subsampleOffsetUs = Assertions.checkNotNull(formatHolder.format).subsampleOffsetUs; - } - } - - if (pendingMetadataCount > 0 && pendingMetadataTimestamps[pendingMetadataIndex] <= positionUs) { - Metadata metadata = castNonNull(pendingMetadata[pendingMetadataIndex]); - invokeRenderer(metadata); - pendingMetadata[pendingMetadataIndex] = null; - pendingMetadataIndex = (pendingMetadataIndex + 1) % MAX_PENDING_METADATA_COUNT; - pendingMetadataCount--; - } - if (inputStreamEnded && pendingMetadataCount == 0) { - outputStreamEnded = true; + boolean working = true; + while (working) { + readMetadata(); + working = outputMetadata(positionUs); } } @@ -197,7 +153,8 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { @Override protected void onDisabled() { - flushPendingMetadata(); + pendingMetadata = null; + pendingMetadataTimestampUs = C.TIME_UNSET; decoder = null; } @@ -211,20 +168,6 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { return true; } - private void invokeRenderer(Metadata metadata) { - if (outputHandler != null) { - outputHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget(); - } else { - invokeRendererInternal(metadata); - } - } - - private void flushPendingMetadata() { - Arrays.fill(pendingMetadata, null); - pendingMetadataIndex = 0; - pendingMetadataCount = 0; - } - @Override public boolean handleMessage(Message msg) { switch (msg.what) { @@ -237,6 +180,56 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { } } + private void readMetadata() { + if (!inputStreamEnded && pendingMetadata == null) { + buffer.clear(); + FormatHolder formatHolder = getFormatHolder(); + @ReadDataResult int result = readSource(formatHolder, buffer, /* readFlags= */ 0); + if (result == C.RESULT_BUFFER_READ) { + if (buffer.isEndOfStream()) { + inputStreamEnded = true; + } else { + buffer.subsampleOffsetUs = subsampleOffsetUs; + buffer.flip(); + @Nullable Metadata metadata = castNonNull(decoder).decode(buffer); + if (metadata != null) { + List entries = new ArrayList<>(metadata.length()); + decodeWrappedMetadata(metadata, entries); + if (!entries.isEmpty()) { + Metadata expandedMetadata = new Metadata(entries); + pendingMetadata = expandedMetadata; + pendingMetadataTimestampUs = buffer.timeUs; + } + } + } + } else if (result == C.RESULT_FORMAT_READ) { + subsampleOffsetUs = Assertions.checkNotNull(formatHolder.format).subsampleOffsetUs; + } + } + } + + private boolean outputMetadata(long positionUs) { + boolean didOutput = false; + if (pendingMetadata != null && pendingMetadataTimestampUs <= positionUs) { + invokeRenderer(pendingMetadata); + pendingMetadata = null; + pendingMetadataTimestampUs = C.TIME_UNSET; + didOutput = true; + } + if (inputStreamEnded && pendingMetadata == null) { + outputStreamEnded = true; + } + return didOutput; + } + + private void invokeRenderer(Metadata metadata) { + if (outputHandler != null) { + outputHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget(); + } else { + invokeRendererInternal(metadata); + } + } + private void invokeRendererInternal(Metadata metadata) { output.onMetadata(metadata); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java index 8f0254d83f..a97cb6a2ce 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java @@ -19,7 +19,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataInputBuffer; import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder; -import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.base.Charsets; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; @@ -57,10 +57,10 @@ public final class IcyDecoder extends SimpleMetadataDecoder { int index = 0; Matcher matcher = METADATA_ELEMENT.matcher(icyString); while (matcher.find(index)) { - @Nullable String key = Util.toLowerInvariant(matcher.group(1)); + @Nullable String key = matcher.group(1); @Nullable String value = matcher.group(2); if (key != null) { - switch (key) { + switch (Ascii.toLowerCase(key)) { case STREAM_KEY_NAME: name = value; break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyInfo.java index 1a3ed2ea6d..9fda4ac725 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyInfo.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.metadata.icy; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.Assertions; import java.util.Arrays; @@ -52,6 +53,13 @@ public final class IcyInfo implements Metadata.Entry { url = in.readString(); } + @Override + public void populateMediaMetadata(MediaMetadata.Builder builder) { + if (title != null) { + builder.setTitle(title); + } + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java index 44850b720f..f1ceaa3bb4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java @@ -20,9 +20,7 @@ import android.os.Parcelable; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; -/** - * Represents a private command as defined in SCTE35, Section 9.3.6. - */ +/** Represents a private command as defined in SCTE35, Section 9.3.6. */ public final class PrivateCommand extends SpliceCommand { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java index b0c3e34cde..8cb4dcec2b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.metadata.scte35; import com.google.android.exoplayer2.metadata.Metadata; -/** - * Superclass for SCTE35 splice commands. - */ +/** Superclass for SCTE35 splice commands. */ public abstract class SpliceCommand implements Metadata.Entry { @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java index 6f56d3b68c..8c8c6a1ffb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java @@ -24,9 +24,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** - * Represents a splice insert command defined in SCTE35, Section 9.3.3. - */ +/** Represents a splice insert command defined in SCTE35, Section 9.3.3. */ public final class SpliceInsertCommand extends SpliceCommand { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java index 461d49ebb4..aed0a0e2bc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.metadata.scte35; import android.os.Parcel; -/** - * Represents a splice null command as defined in SCTE35, Section 9.3.1. - */ +/** Represents a splice null command as defined in SCTE35, Section 9.3.1. */ public final class SpliceNullCommand extends SpliceCommand { // Parcelable implementation. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java index 8696909c97..e252ae7354 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java @@ -23,9 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** - * Represents a splice schedule command as defined in SCTE35, Section 9.3.2. - */ +/** Represents a splice schedule command as defined in SCTE35, Section 9.3.2. */ public final class SpliceScheduleCommand extends SpliceCommand { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java index e233a276ed..5115b7a722 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java @@ -20,9 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; -/** - * Represents a time signal command as defined in SCTE35, Section 9.3.4. - */ +/** Represents a time signal command as defined in SCTE35, Section 9.3.4. */ public final class TimeSignalCommand extends SpliceCommand { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java index a4cbe17b82..35a3e788f5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java @@ -108,7 +108,6 @@ public final class ProgressiveDownloader implements Downloader { new CacheWriter( dataSource, dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, progressListener); priorityTaskManager = cacheDataSourceFactory.getUpstreamPriorityTaskManager(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index 7cf31bc030..74df93d82f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -472,7 +472,6 @@ public abstract class SegmentDownloader> impleme new CacheWriter( dataSource, segment.dataSpec, - /* allowShortContent= */ false, temporaryBuffer, progressNotifier); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java index 96ef4b0c6d..8b4cddd8f7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java @@ -87,7 +87,7 @@ public abstract class BaseMediaSource implements MediaSource { /** * Returns a {@link MediaSourceEventListener.EventDispatcher} which dispatches all events to the - * registered listeners with the specified media period id. + * registered listeners with the specified {@link MediaPeriodId}. * * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events. May be null, if * the events do not belong to a specific media period. @@ -101,7 +101,7 @@ public abstract class BaseMediaSource implements MediaSource { /** * Returns a {@link MediaSourceEventListener.EventDispatcher} which dispatches all events to the - * registered listeners with the specified media period id and time offset. + * registered listeners with the specified {@link MediaPeriodId} and time offset. * * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events. * @param mediaTimeOffsetMs The offset to be added to all media times, in milliseconds. @@ -115,7 +115,7 @@ public abstract class BaseMediaSource implements MediaSource { /** * Returns a {@link MediaSourceEventListener.EventDispatcher} which dispatches all events to the - * registered listeners with the specified window index, media period id and time offset. + * registered listeners with the specified window index, {@link MediaPeriodId} and time offset. * * @param windowIndex The timeline window index to be reported with the events. * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events. May be null, if @@ -130,7 +130,7 @@ public abstract class BaseMediaSource implements MediaSource { /** * Returns a {@link DrmSessionEventListener.EventDispatcher} which dispatches all events to the - * registered listeners with the specified media period id. + * registered listeners with the specified {@link MediaPeriodId} * * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events. May be null, if * the events do not belong to a specific media period. @@ -143,7 +143,7 @@ public abstract class BaseMediaSource implements MediaSource { /** * Returns a {@link DrmSessionEventListener.EventDispatcher} which dispatches all events to the - * registered listeners with the specified window index and media period id. + * registered listeners with the specified window index and {@link MediaPeriodId}. * * @param windowIndex The timeline window index to be reported with the events. * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events. May be null, if diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java index 8e0441dfcf..2d56762175 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.source; import java.io.IOException; -/** - * Thrown when a live playback falls behind the available media window. - */ +/** Thrown when a live playback falls behind the available media window. */ public final class BehindLiveWindowException extends IOException { public BehindLiveWindowException() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java index 7e770d4e39..edb16e53d6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java @@ -37,7 +37,7 @@ import java.util.Map; * {@link ProgressiveMediaExtractor} built on top of {@link Extractor} instances, whose * implementation classes are bundled in the app. */ -/* package */ final class BundledExtractorsAdapter implements ProgressiveMediaExtractor { +public final class BundledExtractorsAdapter implements ProgressiveMediaExtractor { private final ExtractorsFactory extractorsFactory; 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 650e055f0b..464bf497fe 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 @@ -299,7 +299,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public int readData( - FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) { + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { if (isPendingInitialDiscontinuity()) { return C.RESULT_NOTHING_READ; } @@ -307,7 +307,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); return C.RESULT_BUFFER_READ; } - @ReadDataResult int result = childStream.readData(formatHolder, buffer, requireFormat); + @ReadDataResult int result = childStream.readData(formatHolder, buffer, readFlags); if (result == C.RESULT_FORMAT_READ) { Format format = Assertions.checkNotNull(formatHolder.format); if (format.encoderDelay != 0 || format.encoderPadding != 0) { 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 581a0b17e3..6a58605480 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 @@ -293,19 +293,6 @@ public final class ClippingMediaSource extends CompositeMediaSource { refreshSourceInfo(clippingTimeline); } - @Override - protected long getMediaTimeForChildMediaTime(Void id, long mediaTimeMs) { - if (mediaTimeMs == C.TIME_UNSET) { - return C.TIME_UNSET; - } - long startMs = C.usToMs(startUs); - long clippedTimeMs = max(0, mediaTimeMs - startMs); - if (endUs != C.TIME_END_OF_SOURCE) { - clippedTimeMs = min(C.usToMs(endUs) - startMs, clippedTimeMs); - } - return clippedTimeMs; - } - /** * Provides a clipped view of a specified timeline. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java index 5f1464721c..a19504ed7d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java @@ -19,6 +19,7 @@ import android.os.Handler; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; @@ -34,7 +35,7 @@ import java.util.HashMap; */ public abstract class CompositeMediaSource extends BaseMediaSource { - private final HashMap childSources; + private final HashMap> childSources; @Nullable private Handler eventHandler; @Nullable private TransferListener mediaTransferListener; @@ -54,7 +55,7 @@ public abstract class CompositeMediaSource extends BaseMediaSource { @Override @CallSuper public void maybeThrowSourceInfoRefreshError() throws IOException { - for (MediaSourceAndListener childSource : childSources.values()) { + for (MediaSourceAndListener childSource : childSources.values()) { childSource.mediaSource.maybeThrowSourceInfoRefreshError(); } } @@ -62,7 +63,7 @@ public abstract class CompositeMediaSource extends BaseMediaSource { @Override @CallSuper protected void enableInternal() { - for (MediaSourceAndListener childSource : childSources.values()) { + for (MediaSourceAndListener childSource : childSources.values()) { childSource.mediaSource.enable(childSource.caller); } } @@ -70,7 +71,7 @@ public abstract class CompositeMediaSource extends BaseMediaSource { @Override @CallSuper protected void disableInternal() { - for (MediaSourceAndListener childSource : childSources.values()) { + for (MediaSourceAndListener childSource : childSources.values()) { childSource.mediaSource.disable(childSource.caller); } } @@ -78,9 +79,10 @@ public abstract class CompositeMediaSource extends BaseMediaSource { @Override @CallSuper protected void releaseSourceInternal() { - for (MediaSourceAndListener childSource : childSources.values()) { + for (MediaSourceAndListener childSource : childSources.values()) { childSource.mediaSource.releaseSource(childSource.caller); childSource.mediaSource.removeEventListener(childSource.eventListener); + childSource.mediaSource.removeDrmEventListener(childSource.eventListener); } childSources.clear(); } @@ -112,7 +114,7 @@ public abstract class CompositeMediaSource extends BaseMediaSource { MediaSourceCaller caller = (source, timeline) -> onChildSourceInfoRefreshed(id, source, timeline); ForwardingEventListener eventListener = new ForwardingEventListener(id); - childSources.put(id, new MediaSourceAndListener(mediaSource, caller, eventListener)); + childSources.put(id, new MediaSourceAndListener<>(mediaSource, caller, eventListener)); mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener); mediaSource.addDrmEventListener(Assertions.checkNotNull(eventHandler), eventListener); mediaSource.prepareSource(caller, mediaTransferListener); @@ -127,7 +129,7 @@ public abstract class CompositeMediaSource extends BaseMediaSource { * @param id The unique id used to prepare the child source. */ protected final void enableChildSource(@UnknownNull T id) { - MediaSourceAndListener enabledChild = Assertions.checkNotNull(childSources.get(id)); + MediaSourceAndListener enabledChild = Assertions.checkNotNull(childSources.get(id)); enabledChild.mediaSource.enable(enabledChild.caller); } @@ -137,7 +139,7 @@ public abstract class CompositeMediaSource extends BaseMediaSource { * @param id The unique id used to prepare the child source. */ protected final void disableChildSource(@UnknownNull T id) { - MediaSourceAndListener disabledChild = Assertions.checkNotNull(childSources.get(id)); + MediaSourceAndListener disabledChild = Assertions.checkNotNull(childSources.get(id)); disabledChild.mediaSource.disable(disabledChild.caller); } @@ -147,9 +149,10 @@ public abstract class CompositeMediaSource extends BaseMediaSource { * @param id The unique id used to prepare the child source. */ protected final void releaseChildSource(@UnknownNull T id) { - MediaSourceAndListener removedChild = Assertions.checkNotNull(childSources.remove(id)); + MediaSourceAndListener removedChild = Assertions.checkNotNull(childSources.remove(id)); removedChild.mediaSource.releaseSource(removedChild.caller); removedChild.mediaSource.removeEventListener(removedChild.eventListener); + removedChild.mediaSource.removeDrmEventListener(removedChild.eventListener); } /** @@ -181,25 +184,30 @@ public abstract class CompositeMediaSource extends BaseMediaSource { } /** - * Returns the media time in the composite source corresponding to the specified media time in a - * child source. The default implementation does not change the media time. + * Returns the media time in the {@link MediaPeriod} of the composite source corresponding to the + * specified media time in the {@link MediaPeriod} of the child source. The default implementation + * does not change the media time. * * @param id The unique id used to prepare the child source. - * @param mediaTimeMs A media time of the child source, in milliseconds. - * @return The corresponding media time in the composite source, in milliseconds. + * @param mediaTimeMs A media time in the {@link MediaPeriod} of the child source, in + * milliseconds. + * @return The corresponding media time in the {@link MediaPeriod} of the composite source, in + * milliseconds. */ protected long getMediaTimeForChildMediaTime(@UnknownNull T id, long mediaTimeMs) { return mediaTimeMs; } - private static final class MediaSourceAndListener { + private static final class MediaSourceAndListener { public final MediaSource mediaSource; public final MediaSourceCaller caller; - public final MediaSourceEventListener eventListener; + public final CompositeMediaSource.ForwardingEventListener eventListener; public MediaSourceAndListener( - MediaSource mediaSource, MediaSourceCaller caller, MediaSourceEventListener eventListener) { + MediaSource mediaSource, + MediaSourceCaller caller, + CompositeMediaSource.ForwardingEventListener eventListener) { this.mediaSource = mediaSource; this.caller = caller; this.eventListener = eventListener; @@ -290,9 +298,10 @@ public abstract class CompositeMediaSource extends BaseMediaSource { // DrmSessionEventListener implementation @Override - public void onDrmSessionAcquired(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { + public void onDrmSessionAcquired( + int windowIndex, @Nullable MediaPeriodId mediaPeriodId, @DrmSession.State int state) { if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmSessionAcquired(); + drmEventDispatcher.drmSessionAcquired(state); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java index ce5fb868f5..5afded58e2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java @@ -19,9 +19,7 @@ import static java.lang.Math.min; import com.google.android.exoplayer2.C; -/** - * A {@link SequenceableLoader} that encapsulates multiple other {@link SequenceableLoader}s. - */ +/** A {@link SequenceableLoader} that encapsulates multiple other {@link SequenceableLoader}s. */ public class CompositeSequenceableLoader implements SequenceableLoader { protected final SequenceableLoader[] loaders; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java index b4a266feef..1507068664 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.source; -/** - * A factory to create composite {@link SequenceableLoader}s. - */ +/** A factory to create composite {@link SequenceableLoader}s. */ public interface CompositeSequenceableLoaderFactory { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java index 759b0824af..d0a6340051 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.source; -/** - * Default implementation of {@link CompositeSequenceableLoaderFactory}. - */ +/** Default implementation of {@link CompositeSequenceableLoaderFactory}. */ public final class DefaultCompositeSequenceableLoaderFactory implements CompositeSequenceableLoaderFactory { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceEventListener.java deleted file mode 100644 index fbb3a86221..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceEventListener.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2018 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.source; - -/** - * @deprecated Use {@link MediaSourceEventListener} interface directly for selective overrides as - * all methods are implemented as no-op default methods. - */ -@Deprecated -public abstract class DefaultMediaSourceEventListener implements MediaSourceEventListener {} 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 31aad16b02..a7bd656294 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 @@ -29,8 +29,8 @@ import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.ads.AdsLoader; -import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider; import com.google.android.exoplayer2.source.ads.AdsMediaSource; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; @@ -468,6 +468,14 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { } catch (Exception e) { // Expected if the app was built without the hls module. } + try { + Class factoryClazz = + Class.forName("com.google.android.exoplayer2.source.rtsp.RtspMediaSource$Factory") + .asSubclass(MediaSourceFactory.class); + factories.put(C.TYPE_RTSP, factoryClazz.getConstructor().newInstance()); + } catch (Exception e) { + // Expected if the app was built without the RTSP module. + } // LINT.ThenChange(../../../../../../../../proguard-rules.txt) factories.put( C.TYPE_OTHER, new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java index fe574cf597..39ca0e97f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java @@ -19,9 +19,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -/** - * An empty {@link SampleStream}. - */ +/** An empty {@link SampleStream}. */ public final class EmptySampleStream implements SampleStream { @Override @@ -35,8 +33,8 @@ public final class EmptySampleStream implements SampleStream { } @Override - public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired) { + public int readData( + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); return C.RESULT_BUFFER_READ; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java deleted file mode 100644 index c2fa35275c..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (C) 2016 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.source; - -import android.net.Uri; -import android.os.Handler; -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.drm.DrmSessionManagerProvider; -import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; -import com.google.android.exoplayer2.extractor.Extractor; -import com.google.android.exoplayer2.extractor.ExtractorsFactory; -import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; -import com.google.android.exoplayer2.upstream.HttpDataSource; -import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; -import com.google.android.exoplayer2.upstream.TransferListener; -import com.google.android.exoplayer2.util.Assertions; -import java.io.IOException; - -/** @deprecated Use {@link ProgressiveMediaSource} instead. */ -@Deprecated -@SuppressWarnings("deprecation") -public final class ExtractorMediaSource extends CompositeMediaSource { - - /** @deprecated Use {@link MediaSourceEventListener} instead. */ - @Deprecated - public interface EventListener { - - /** - * Called when an error occurs loading media data. - *

    - * This method being called does not indicate that playback has failed, or that it will fail. - * The player may be able to recover from the error and continue. Hence applications should - * not implement this method to display a user visible error or initiate an application - * level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement - * such behavior). This method is called to provide the application with an opportunity to log - * the error if it wishes to do so. - * - * @param error The load error. - */ - void onLoadError(IOException error); - - } - - /** @deprecated Use {@link ProgressiveMediaSource.Factory} instead. */ - @Deprecated - public static final class Factory implements MediaSourceFactory { - - private final DataSource.Factory dataSourceFactory; - - private ExtractorsFactory extractorsFactory; - private LoadErrorHandlingPolicy loadErrorHandlingPolicy; - private int continueLoadingCheckIntervalBytes; - @Nullable private String customCacheKey; - @Nullable private Object tag; - - /** - * Creates a new factory for {@link ExtractorMediaSource}s. - * - * @param dataSourceFactory A factory for {@link DataSource}s to read the media. - */ - public Factory(DataSource.Factory dataSourceFactory) { - this.dataSourceFactory = dataSourceFactory; - extractorsFactory = new DefaultExtractorsFactory(); - loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); - continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES; - } - - /** - * Sets the factory for {@link Extractor}s to process the media stream. The default value is an - * instance of {@link DefaultExtractorsFactory}. - * - * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the - * possible formats are known, pass a factory that instantiates extractors for those - * formats. - * @return This factory, for convenience. - */ - public Factory setExtractorsFactory(@Nullable ExtractorsFactory extractorsFactory) { - this.extractorsFactory = - extractorsFactory != null ? extractorsFactory : new DefaultExtractorsFactory(); - return this; - } - - /** - * Sets the custom key that uniquely identifies the original stream. Used for cache indexing. - * The default value is {@code null}. - * - * @param customCacheKey A custom key that uniquely identifies the original stream. Used for - * cache indexing. - * @return This factory, for convenience. - */ - public Factory setCustomCacheKey(@Nullable String customCacheKey) { - this.customCacheKey = customCacheKey; - return this; - } - - /** - * @deprecated Use {@link MediaItem.Builder#setTag(Object)} and {@link - * #createMediaSource(MediaItem)} instead. - */ - @Deprecated - public Factory setTag(@Nullable Object tag) { - this.tag = tag; - return this; - } - - /** - * Sets the {@link LoadErrorHandlingPolicy}. The default value is created by calling {@link - * DefaultLoadErrorHandlingPolicy#DefaultLoadErrorHandlingPolicy()}. - * - * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. - * @return This factory, for convenience. - */ - @Override - public Factory setLoadErrorHandlingPolicy( - @Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) { - this.loadErrorHandlingPolicy = - loadErrorHandlingPolicy != null - ? loadErrorHandlingPolicy - : new DefaultLoadErrorHandlingPolicy(); - return this; - } - - /** - * Sets the number of bytes that should be loaded between each invocation of {@link - * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. The default value is - * {@link #DEFAULT_LOADING_CHECK_INTERVAL_BYTES}. - * - * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between - * each invocation of {@link - * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. - * @return This factory, for convenience. - */ - public Factory setContinueLoadingCheckIntervalBytes(int continueLoadingCheckIntervalBytes) { - this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; - return this; - } - - /** - * @deprecated Use {@link - * ProgressiveMediaSource.Factory#setDrmSessionManagerProvider(DrmSessionManagerProvider)} - * instead. - */ - @Deprecated - @Override - public Factory setDrmSessionManagerProvider( - @Nullable DrmSessionManagerProvider drmSessionManagerProvider) { - throw new UnsupportedOperationException(); - } - - /** @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmSessionManager} instead. */ - @Deprecated - @Override - public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) { - throw new UnsupportedOperationException(); - } - - /** - * @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmHttpDataSourceFactory} instead. - */ - @Deprecated - @Override - public MediaSourceFactory setDrmHttpDataSourceFactory( - @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) { - throw new UnsupportedOperationException(); - } - - /** @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmUserAgent} instead. */ - @Deprecated - @Override - public MediaSourceFactory setDrmUserAgent(@Nullable String userAgent) { - throw new UnsupportedOperationException(); - } - - /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */ - @SuppressWarnings("deprecation") - @Deprecated - @Override - public ExtractorMediaSource createMediaSource(Uri uri) { - return createMediaSource(new MediaItem.Builder().setUri(uri).build()); - } - - /** - * Returns a new {@link ExtractorMediaSource} using the current parameters. - * - * @param mediaItem The {@link MediaItem}. - * @return The new {@link ExtractorMediaSource}. - * @throws NullPointerException if {@link MediaItem#playbackProperties} is {@code null}. - */ - @Override - public ExtractorMediaSource createMediaSource(MediaItem mediaItem) { - Assertions.checkNotNull(mediaItem.playbackProperties); - return new ExtractorMediaSource( - mediaItem.playbackProperties.uri, - dataSourceFactory, - extractorsFactory, - loadErrorHandlingPolicy, - customCacheKey, - continueLoadingCheckIntervalBytes, - mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag); - } - - @Override - public int[] getSupportedTypes() { - return new int[] {C.TYPE_OTHER}; - } - } - - /** - * @deprecated Use {@link ProgressiveMediaSource#DEFAULT_LOADING_CHECK_INTERVAL_BYTES} instead. - */ - @Deprecated - public static final int DEFAULT_LOADING_CHECK_INTERVAL_BYTES = - ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES; - - private final ProgressiveMediaSource progressiveMediaSource; - - /** - * @param uri The {@link Uri} of the media stream. - * @param dataSourceFactory A factory for {@link DataSource}s to read the media. - * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the - * possible formats are known, pass a factory that instantiates extractors for those formats. - * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. - * @param eventHandler A handler for events. 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. - * @deprecated Use {@link Factory} instead. - */ - @Deprecated - public ExtractorMediaSource( - Uri uri, - DataSource.Factory dataSourceFactory, - ExtractorsFactory extractorsFactory, - @Nullable Handler eventHandler, - @Nullable EventListener eventListener) { - this(uri, dataSourceFactory, extractorsFactory, eventHandler, eventListener, null); - } - - /** - * @param uri The {@link Uri} of the media stream. - * @param dataSourceFactory A factory for {@link DataSource}s to read the media. - * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the - * possible formats are known, pass a factory that instantiates extractors for those formats. - * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. - * @param eventHandler A handler for events. 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 customCacheKey A custom key that uniquely identifies the original stream. Used for cache - * indexing. May be null. - * @deprecated Use {@link Factory} instead. - */ - @Deprecated - public ExtractorMediaSource( - Uri uri, - DataSource.Factory dataSourceFactory, - ExtractorsFactory extractorsFactory, - @Nullable Handler eventHandler, - @Nullable EventListener eventListener, - @Nullable String customCacheKey) { - this( - uri, - dataSourceFactory, - extractorsFactory, - eventHandler, - eventListener, - customCacheKey, - DEFAULT_LOADING_CHECK_INTERVAL_BYTES); - } - - /** - * @param uri The {@link Uri} of the media stream. - * @param dataSourceFactory A factory for {@link DataSource}s to read the media. - * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the - * possible formats are known, pass a factory that instantiates extractors for those formats. - * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. - * @param eventHandler A handler for events. 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 customCacheKey A custom key that uniquely identifies the original stream. Used for cache - * indexing. May be null. - * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each - * invocation of {@link MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. - * @deprecated Use {@link Factory} instead. - */ - @Deprecated - public ExtractorMediaSource( - Uri uri, - DataSource.Factory dataSourceFactory, - ExtractorsFactory extractorsFactory, - @Nullable Handler eventHandler, - @Nullable EventListener eventListener, - @Nullable String customCacheKey, - int continueLoadingCheckIntervalBytes) { - this( - uri, - dataSourceFactory, - extractorsFactory, - new DefaultLoadErrorHandlingPolicy(), - customCacheKey, - continueLoadingCheckIntervalBytes, - /* tag= */ null); - if (eventListener != null && eventHandler != null) { - addEventListener(eventHandler, new EventListenerWrapper(eventListener)); - } - } - - private ExtractorMediaSource( - Uri uri, - DataSource.Factory dataSourceFactory, - ExtractorsFactory extractorsFactory, - LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy, - @Nullable String customCacheKey, - int continueLoadingCheckIntervalBytes, - @Nullable Object tag) { - progressiveMediaSource = - new ProgressiveMediaSource( - new MediaItem.Builder() - .setUri(uri) - .setCustomCacheKey(customCacheKey) - .setTag(tag) - .build(), - dataSourceFactory, - extractorsFactory, - DrmSessionManager.DRM_UNSUPPORTED, - loadableLoadErrorHandlingPolicy, - continueLoadingCheckIntervalBytes); - } - - /** - * @deprecated Use {@link #getMediaItem()} and {@link MediaItem.PlaybackProperties#tag} instead. - */ - @SuppressWarnings("deprecation") - @Deprecated - @Override - @Nullable - public Object getTag() { - return progressiveMediaSource.getTag(); - } - - @Override - public MediaItem getMediaItem() { - return progressiveMediaSource.getMediaItem(); - } - - @Override - protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { - super.prepareSourceInternal(mediaTransferListener); - prepareChildSource(/* id= */ null, progressiveMediaSource); - } - - @Override - protected void onChildSourceInfoRefreshed( - @Nullable Void id, MediaSource mediaSource, Timeline timeline) { - refreshSourceInfo(timeline); - } - - @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { - return progressiveMediaSource.createPeriod(id, allocator, startPositionUs); - } - - @Override - public void releasePeriod(MediaPeriod mediaPeriod) { - progressiveMediaSource.releasePeriod(mediaPeriod); - } - - @Deprecated - private static final class EventListenerWrapper implements MediaSourceEventListener { - - private final EventListener eventListener; - - public EventListenerWrapper(EventListener eventListener) { - this.eventListener = Assertions.checkNotNull(eventListener); - } - - @Override - public void onLoadError( - int windowIndex, - @Nullable MediaPeriodId mediaPeriodId, - LoadEventInfo loadEventInfo, - MediaLoadData mediaLoadData, - IOException error, - boolean wasCanceled) { - eventListener.onLoadError(error); - } - } -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ForwardingTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ForwardingTimeline.java index 38b373b26c..fb732ad10d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ForwardingTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ForwardingTimeline.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; -/** - * An overridable {@link Timeline} implementation forwarding all methods to another timeline. - */ +/** An overridable {@link Timeline} implementation forwarding all methods to another timeline. */ public abstract class ForwardingTimeline extends Timeline { protected final Timeline timeline; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index 6d08147a63..d10651a575 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -32,9 +32,15 @@ import java.util.Map; /** * Loops a {@link MediaSource} a specified number of times. * - *

    Note: To loop a {@link MediaSource} indefinitely, it is usually better to use {@link - * ExoPlayer#setRepeatMode(int)} instead of this class. + * @deprecated To loop a {@link MediaSource} indefinitely, use {@link Player#setRepeatMode(int)} + * instead of this class. To add a {@link MediaSource} a specific number of times to the + * playlist, use {@link ExoPlayer#addMediaSource} in a loop with the same {@link MediaSource}. + * To combine repeated {@link MediaSource} instances into one {@link MediaSource}, for example + * to further wrap it in another {@link MediaSource}, use {@link ConcatenatingMediaSource} with + * the same {@link MediaSource} {@link ConcatenatingMediaSource#addMediaSource added} multiple + * times. */ +@Deprecated public final class LoopingMediaSource extends CompositeMediaSource { private final MaskingMediaSource maskingMediaSource; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java index fc26872385..e7c27ba95e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Window; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; @@ -395,12 +396,15 @@ public final class MaskingMediaSource extends CompositeMediaSource { @Override public Period getPeriod(int periodIndex, Period period, boolean setIds) { - return period.set( + period.set( /* id= */ setIds ? 0 : null, /* uid= */ setIds ? MaskingTimeline.MASKING_EXTERNAL_PERIOD_UID : null, /* windowIndex= */ 0, /* durationUs = */ C.TIME_UNSET, - /* positionInWindowUs= */ 0); + /* positionInWindowUs= */ 0, + /* adPlaybackState= */ AdPlaybackState.NONE, + /* isPlaceholder= */ true); + return period; } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaLoadData.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaLoadData.java index 0de79e9219..683912448d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaLoadData.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaLoadData.java @@ -19,7 +19,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -/** Descriptor for data being loaded or selected by a media source. */ +/** Descriptor for data being loaded or selected by a {@link MediaSource}. */ public final class MediaLoadData { /** One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data. */ @@ -45,13 +45,13 @@ public final class MediaLoadData { */ @Nullable public final Object trackSelectionData; /** - * The start time of the media, or {@link C#TIME_UNSET} if the data does not belong to a specific - * media period. + * The start time of the media in the {@link MediaPeriod}, or {@link C#TIME_UNSET} if the data + * does not belong to a specific {@link MediaPeriod}. */ public final long mediaStartTimeMs; /** - * The end time of the media, or {@link C#TIME_UNSET} if the data does not belong to a specific - * media period or the end time is unknown. + * The end time of the media in the {@link MediaPeriod}, or {@link C#TIME_UNSET} if the data does + * not belong to a specific {@link MediaPeriod} or the end time is unknown. */ public final long mediaEndTimeMs; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaParserExtractorAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaParserExtractorAdapter.java index 6cb20c9fbe..10fd2cdeba 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaParserExtractorAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaParserExtractorAdapter.java @@ -38,7 +38,13 @@ import java.util.Map; /** {@link ProgressiveMediaExtractor} implemented on top of the platform's {@link MediaParser}. */ @RequiresApi(30) -/* package */ final class MediaParserExtractorAdapter implements ProgressiveMediaExtractor { +public final class MediaParserExtractorAdapter implements ProgressiveMediaExtractor { + + /** + * A {@link ProgressiveMediaExtractor.Factory} for instances of this class, which rely on platform + * extractors through {@link MediaParser}. + */ + public static final ProgressiveMediaExtractor.Factory FACTORY = MediaParserExtractorAdapter::new; private final OutputConsumerAdapterV30 outputConsumerAdapter; private final InputReaderAdapterV30 inputReaderAdapter; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index f6f2ab496d..4b5229ba00 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -104,9 +104,19 @@ public interface MediaSource { } /** See {@link com.google.android.exoplayer2.source.MediaPeriodId#copyWithPeriodUid(Object)}. */ + @Override public MediaPeriodId copyWithPeriodUid(Object newPeriodUid) { return new MediaPeriodId(super.copyWithPeriodUid(newPeriodUid)); } + + /** + * See {@link + * com.google.android.exoplayer2.source.MediaPeriodId#copyWithWindowSequenceNumber(long)}. + */ + @Override + public MediaPeriodId copyWithWindowSequenceNumber(long windowSequenceNumber) { + return new MediaPeriodId(super.copyWithWindowSequenceNumber(windowSequenceNumber)); + } } /** 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 39fd6d53a9..c5859fecbc 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 @@ -92,11 +92,11 @@ public interface MediaSourceEventListener { * not be called in addition to this method. * *

    This method being called does not indicate that playback has failed, or that it will fail. - * The player may be able to recover from the error and continue. Hence applications should - * not implement this method to display a user visible error or initiate an application - * level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement - * such behavior). This method is called to provide the application with an opportunity to log the - * error if it wishes to do so. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. * * @param windowIndex The window index in the timeline of the media source this load belongs to. * @param mediaPeriodId The {@link MediaPeriodId} this load belongs to. Null if the load does not 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 7242c2a214..3a09452f24 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 @@ -31,25 +31,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import java.util.List; -/** - * Factory for creating {@link MediaSource}s from URIs. - * - *

    DrmSessionManager creation for protected content

    - * - *

    In case a {@link DrmSessionManager} is passed to {@link - * #setDrmSessionManager(DrmSessionManager)}, it will be used regardless of the drm configuration of - * the media item. - * - *

    For a media item with a {@link MediaItem.DrmConfiguration}, a {@link DefaultDrmSessionManager} - * is created based on that configuration. The following setter can be used to optionally configure - * the creation: - * - *

      - *
    • {@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory - * to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link - * DefaultHttpDataSourceFactory}). - *
    - */ +/** Factory for creating {@link MediaSource MediaSources} from {@link MediaItem MediaItems}. */ public interface MediaSourceFactory { /** @deprecated Use {@link MediaItem.PlaybackProperties#streamKeys} instead. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java index 860d9a3b95..2eac56f5fa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java @@ -440,8 +440,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public int readData( - FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) { - int readResult = sampleStream.readData(formatHolder, buffer, formatRequired); + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { + int readResult = sampleStream.readData(formatHolder, buffer, readFlags); if (readResult == C.RESULT_BUFFER_READ) { buffer.timeUs = max(0, buffer.timeUs + timeOffsetUs); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java index 9efe6acba1..ed4f6ac604 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java @@ -26,7 +26,14 @@ import java.util.List; import java.util.Map; /** Extracts the contents of a container file from a progressive media stream. */ -/* package */ interface ProgressiveMediaExtractor { +public interface ProgressiveMediaExtractor { + + /** Creates {@link ProgressiveMediaExtractor} instances. */ + interface Factory { + + /** Returns a new {@link ProgressiveMediaExtractor} instance. */ + ProgressiveMediaExtractor createProgressiveMediaExtractor(); + } /** * Initializes the underlying infrastructure for reading from the input. 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 f7b88fcab8..791dd72479 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 @@ -31,7 +31,6 @@ import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorOutput; -import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; @@ -40,6 +39,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.icy.IcyHeaders; import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; +import com.google.android.exoplayer2.source.SampleStream.ReadFlags; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; @@ -147,7 +147,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * @param uri The {@link Uri} of the media stream. * @param dataSource The data source to read the media. - * @param extractorsFactory The {@link ExtractorsFactory} to use to read the data source. + * @param progressiveMediaExtractor The {@link ProgressiveMediaExtractor} to use to read the data + * source. * @param drmSessionManager A {@link DrmSessionManager} to allow DRM interactions. * @param drmEventDispatcher A dispatcher to notify of {@link DrmSessionEventListener} events. * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. @@ -168,7 +169,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public ProgressiveMediaPeriod( Uri uri, DataSource dataSource, - ExtractorsFactory extractorsFactory, + ProgressiveMediaExtractor progressiveMediaExtractor, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, LoadErrorHandlingPolicy loadErrorHandlingPolicy, @@ -187,8 +188,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.allocator = allocator; this.customCacheKey = customCacheKey; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; - loader = new Loader("Loader:ProgressiveMediaPeriod"); - this.progressiveMediaExtractor = new BundledExtractorsAdapter(extractorsFactory); + loader = new Loader("ProgressiveMediaPeriod"); + this.progressiveMediaExtractor = progressiveMediaExtractor; loadCondition = new ConditionVariable(); maybeFinishPrepareRunnable = this::maybeFinishPrepare; onContinueLoadingRequestedRunnable = @@ -478,13 +479,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; int sampleQueueIndex, FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired) { + @ReadFlags int readFlags) { if (suppressRead()) { return C.RESULT_NOTHING_READ; } maybeNotifyDownstreamFormat(sampleQueueIndex); int result = - sampleQueues[sampleQueueIndex].read(formatHolder, buffer, formatRequired, loadingFinished); + sampleQueues[sampleQueueIndex].read(formatHolder, buffer, readFlags, loadingFinished); if (result == C.RESULT_NOTHING_READ) { maybeStartDeferredRetry(sampleQueueIndex); } @@ -947,9 +948,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } @Override - public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired) { - return ProgressiveMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired); + public int readData( + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { + return ProgressiveMediaPeriod.this.readData(track, formatHolder, buffer, readFlags); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java index fe249df6ff..4c9ce82495 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java @@ -54,7 +54,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource private final DataSource.Factory dataSourceFactory; - private ExtractorsFactory extractorsFactory; + private ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory; private boolean usingCustomDrmSessionManagerProvider; private DrmSessionManagerProvider drmSessionManagerProvider; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; @@ -72,15 +72,26 @@ public final class ProgressiveMediaSource extends BaseMediaSource this(dataSourceFactory, new DefaultExtractorsFactory()); } + /** + * Equivalent to {@link #Factory(DataSource.Factory, ProgressiveMediaExtractor.Factory) new + * Factory(dataSourceFactory, () -> new BundledExtractorsAdapter(extractorsFactory)}. + */ + public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { + this(dataSourceFactory, () -> new BundledExtractorsAdapter(extractorsFactory)); + } + /** * Creates a new factory for {@link ProgressiveMediaSource}s. * * @param dataSourceFactory A factory for {@link DataSource}s to read the media. - * @param extractorsFactory A factory for extractors used to extract media from its container. + * @param progressiveMediaExtractorFactory A factory for the {@link ProgressiveMediaExtractor} + * to extract media from its container. */ - public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { + public Factory( + DataSource.Factory dataSourceFactory, + ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory) { this.dataSourceFactory = dataSourceFactory; - this.extractorsFactory = extractorsFactory; + this.progressiveMediaExtractorFactory = progressiveMediaExtractorFactory; drmSessionManagerProvider = new DefaultDrmSessionManagerProvider(); loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES; @@ -93,8 +104,10 @@ public final class ProgressiveMediaSource extends BaseMediaSource */ @Deprecated public Factory setExtractorsFactory(@Nullable ExtractorsFactory extractorsFactory) { - this.extractorsFactory = - extractorsFactory != null ? extractorsFactory : new DefaultExtractorsFactory(); + this.progressiveMediaExtractorFactory = + () -> + new BundledExtractorsAdapter( + extractorsFactory != null ? extractorsFactory : new DefaultExtractorsFactory()); return this; } @@ -220,7 +233,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource return new ProgressiveMediaSource( mediaItem, dataSourceFactory, - extractorsFactory, + progressiveMediaExtractorFactory, drmSessionManagerProvider.get(mediaItem), loadErrorHandlingPolicy, continueLoadingCheckIntervalBytes); @@ -241,7 +254,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource private final MediaItem mediaItem; private final MediaItem.PlaybackProperties playbackProperties; private final DataSource.Factory dataSourceFactory; - private final ExtractorsFactory extractorsFactory; + private final ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory; private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy; private final int continueLoadingCheckIntervalBytes; @@ -252,18 +265,17 @@ public final class ProgressiveMediaSource extends BaseMediaSource private boolean timelineIsLive; @Nullable private TransferListener transferListener; - // TODO: Make private when ExtractorMediaSource is deleted. - /* package */ ProgressiveMediaSource( + private ProgressiveMediaSource( MediaItem mediaItem, DataSource.Factory dataSourceFactory, - ExtractorsFactory extractorsFactory, + ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy, int continueLoadingCheckIntervalBytes) { this.playbackProperties = checkNotNull(mediaItem.playbackProperties); this.mediaItem = mediaItem; this.dataSourceFactory = dataSourceFactory; - this.extractorsFactory = extractorsFactory; + this.progressiveMediaExtractorFactory = progressiveMediaExtractorFactory; this.drmSessionManager = drmSessionManager; this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; @@ -308,7 +320,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource return new ProgressiveMediaPeriod( playbackProperties.uri, dataSource, - extractorsFactory, + progressiveMediaExtractorFactory.createProgressiveMediaExtractor(), drmSessionManager, createDrmEventDispatcher(id), loadableLoadErrorHandlingPolicy, @@ -374,6 +386,13 @@ public final class ProgressiveMediaSource extends BaseMediaSource window.isPlaceholder = true; return window; } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + super.getPeriod(periodIndex, period, setIds); + period.isPlaceholder = true; + return period; + } }; } refreshSourceInfo(timeline); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java index 5bc1482e68..cbc8358a2f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java @@ -21,6 +21,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException; import com.google.android.exoplayer2.extractor.TrackOutput.CryptoData; import com.google.android.exoplayer2.source.SampleQueue.SampleExtrasHolder; import com.google.android.exoplayer2.upstream.Allocation; @@ -120,6 +121,8 @@ import java.util.Arrays; * * @param buffer The buffer to populate. * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted. + * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold + * the data being read. */ public void readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) { readAllocationNode = readSampleData(readAllocationNode, buffer, extrasHolder, scratch); @@ -131,6 +134,8 @@ import java.util.Arrays; * * @param buffer The buffer to populate. * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted. + * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold + * the data being peeked. */ public void peekToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) { readSampleData(readAllocationNode, buffer, extrasHolder, scratch); @@ -259,6 +264,8 @@ import java.util.Arrays; * @param scratch A scratch {@link ParsableByteArray}. * @return The first {@link AllocationNode} that contains unread bytes after the last byte that * the invocation read. + * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold + * the sample data. */ private static AllocationNode readSampleData( AllocationNode allocationNode, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java index 77e17c84b1..def7cb16bb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java @@ -15,26 +15,34 @@ */ package com.google.android.exoplayer2.source; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_OMIT_SAMPLE_DATA; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_PEEK; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static java.lang.Math.max; import android.os.Looper; -import android.util.Log; import androidx.annotation.CallSuper; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.DrmSessionManager.DrmSessionReference; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.source.SampleStream.ReadFlags; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataReader; 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.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -60,6 +68,7 @@ public class SampleQueue implements TrackOutput { private final SampleDataQueue sampleDataQueue; private final SampleExtrasHolder extrasHolder; + private final SpannedData sharedSampleMetadata; @Nullable private final DrmSessionManager drmSessionManager; @Nullable private final DrmSessionEventListener.EventDispatcher drmEventDispatcher; @Nullable private final Looper playbackLooper; @@ -75,7 +84,6 @@ public class SampleQueue implements TrackOutput { private int[] flags; private long[] timesUs; private @NullableType CryptoData[] cryptoDatas; - private Format[] formats; private int length; private int absoluteFirstIndex; @@ -91,7 +99,6 @@ public class SampleQueue implements TrackOutput { private boolean upstreamFormatAdjustmentRequired; @Nullable private Format unadjustedUpstreamFormat; @Nullable private Format upstreamFormat; - @Nullable private Format upstreamCommittedFormat; private int upstreamSourceId; private boolean upstreamAllSamplesAreSyncSamples; private boolean loggedUnexpectedNonSyncSample; @@ -154,7 +161,8 @@ public class SampleQueue implements TrackOutput { flags = new int[capacity]; sizes = new int[capacity]; cryptoDatas = new CryptoData[capacity]; - formats = new Format[capacity]; + sharedSampleMetadata = + new SpannedData<>(/* removeCallback= */ metadata -> metadata.drmSessionReference.release()); startTimeUs = Long.MIN_VALUE; largestDiscardedTimestampUs = Long.MIN_VALUE; largestQueuedTimestampUs = Long.MIN_VALUE; @@ -196,7 +204,7 @@ public class SampleQueue implements TrackOutput { largestDiscardedTimestampUs = Long.MIN_VALUE; largestQueuedTimestampUs = Long.MIN_VALUE; isLastSampleQueued = false; - upstreamCommittedFormat = null; + sharedSampleMetadata.clear(); if (resetUpstreamFormat) { unadjustedUpstreamFormat = null; upstreamFormat = null; @@ -369,26 +377,11 @@ public class SampleQueue implements TrackOutput { || isLastSampleQueued || (upstreamFormat != null && upstreamFormat != downstreamFormat); } - int relativeReadIndex = getRelativeIndex(readPosition); - if (formats[relativeReadIndex] != downstreamFormat) { + if (sharedSampleMetadata.get(getReadIndex()).format != downstreamFormat) { // A format can be read. return true; } - return mayReadSample(relativeReadIndex); - } - - /** Equivalent to {@link #read}, except it never advances the read position. */ - public final int peek( - FormatHolder formatHolder, - DecoderInputBuffer buffer, - boolean formatRequired, - boolean loadingFinished) { - int result = - peekSampleMetadata(formatHolder, buffer, formatRequired, loadingFinished, extrasHolder); - if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream() && !buffer.isFlagsOnly()) { - sampleDataQueue.peekToBuffer(buffer, extrasHolder); - } - return result; + return mayReadSample(getRelativeIndex(readPosition)); } /** @@ -400,27 +393,40 @@ public class SampleQueue implements TrackOutput { * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the * end of the stream. If the end of the stream has been reached, the {@link - * C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. If a {@link - * DecoderInputBuffer#isFlagsOnly() flags-only} buffer is passed, only the buffer flags may be - * populated by this method and the read position of the queue will not change. - * @param formatRequired Whether the caller requires that the format of the stream be read even if - * it's not changing. A sample will never be read if set to true, however it is still possible - * for the end of stream or nothing to be read. + * C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @param readFlags Flags controlling the behavior of this read operation. * @param loadingFinished True if an empty queue should be considered the end of the stream. * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or * {@link C#RESULT_BUFFER_READ}. + * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold + * the data of a sample being read. The buffer {@link DecoderInputBuffer#timeUs timestamp} and + * flags are populated if this exception is thrown, but the read position is not advanced. */ @CallSuper public int read( FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired, + @ReadFlags int readFlags, boolean loadingFinished) { int result = - peekSampleMetadata(formatHolder, buffer, formatRequired, loadingFinished, extrasHolder); - if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream() && !buffer.isFlagsOnly()) { - sampleDataQueue.readToBuffer(buffer, extrasHolder); - readPosition++; + peekSampleMetadata( + formatHolder, + buffer, + /* formatRequired= */ (readFlags & FLAG_REQUIRE_FORMAT) != 0, + loadingFinished, + extrasHolder); + if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) { + boolean peek = (readFlags & FLAG_PEEK) != 0; + if ((readFlags & FLAG_OMIT_SAMPLE_DATA) == 0) { + if (peek) { + sampleDataQueue.peekToBuffer(buffer, extrasHolder); + } else { + sampleDataQueue.readToBuffer(buffer, extrasHolder); + } + } + if (!peek) { + readPosition++; + } } return result; } @@ -684,12 +690,13 @@ public class SampleQueue implements TrackOutput { } } - int relativeReadIndex = getRelativeIndex(readPosition); - if (formatRequired || formats[relativeReadIndex] != downstreamFormat) { - onFormatResult(formats[relativeReadIndex], formatHolder); + Format format = sharedSampleMetadata.get(getReadIndex()).format; + if (formatRequired || format != downstreamFormat) { + onFormatResult(format, formatHolder); return C.RESULT_FORMAT_READ; } + int relativeReadIndex = getRelativeIndex(readPosition); if (!mayReadSample(relativeReadIndex)) { buffer.waitingForKeys = true; return C.RESULT_NOTHING_READ; @@ -715,11 +722,13 @@ public class SampleQueue implements TrackOutput { // referential quality. return false; } - if (Util.areEqual(format, upstreamCommittedFormat)) { + + if (!sharedSampleMetadata.isEmpty() + && sharedSampleMetadata.getEndValue().format.equals(format)) { // The format has changed back to the format of the last committed sample. If they are // different objects, we revert back to using upstreamCommittedFormat as the upstreamFormat // so we can detect format changes on the read side using cheap referential equality. - upstreamFormat = upstreamCommittedFormat; + upstreamFormat = sharedSampleMetadata.getEndValue().format; } else { upstreamFormat = format; } @@ -788,9 +797,20 @@ public class SampleQueue implements TrackOutput { sizes[relativeEndIndex] = size; flags[relativeEndIndex] = sampleFlags; cryptoDatas[relativeEndIndex] = cryptoData; - formats[relativeEndIndex] = upstreamFormat; sourceIds[relativeEndIndex] = upstreamSourceId; - upstreamCommittedFormat = upstreamFormat; + + if (sharedSampleMetadata.isEmpty() + || !sharedSampleMetadata.getEndValue().format.equals(upstreamFormat)) { + DrmSessionReference drmSessionReference = + drmSessionManager != null + ? drmSessionManager.preacquireSession( + checkNotNull(playbackLooper), drmEventDispatcher, upstreamFormat) + : DrmSessionReference.EMPTY; + + sharedSampleMetadata.appendSpan( + getWriteIndex(), + new SharedSampleMetadata(checkNotNull(upstreamFormat), drmSessionReference)); + } length++; if (length == capacity) { @@ -802,14 +822,12 @@ public class SampleQueue implements TrackOutput { int[] newFlags = new int[newCapacity]; int[] newSizes = new int[newCapacity]; CryptoData[] newCryptoDatas = new CryptoData[newCapacity]; - Format[] newFormats = new Format[newCapacity]; int beforeWrap = capacity - relativeFirstIndex; System.arraycopy(offsets, relativeFirstIndex, newOffsets, 0, beforeWrap); System.arraycopy(timesUs, relativeFirstIndex, newTimesUs, 0, beforeWrap); System.arraycopy(flags, relativeFirstIndex, newFlags, 0, beforeWrap); System.arraycopy(sizes, relativeFirstIndex, newSizes, 0, beforeWrap); System.arraycopy(cryptoDatas, relativeFirstIndex, newCryptoDatas, 0, beforeWrap); - System.arraycopy(formats, relativeFirstIndex, newFormats, 0, beforeWrap); System.arraycopy(sourceIds, relativeFirstIndex, newSourceIds, 0, beforeWrap); int afterWrap = relativeFirstIndex; System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap); @@ -817,14 +835,12 @@ public class SampleQueue implements TrackOutput { System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap); System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap); System.arraycopy(cryptoDatas, 0, newCryptoDatas, beforeWrap, afterWrap); - System.arraycopy(formats, 0, newFormats, beforeWrap, afterWrap); System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap); offsets = newOffsets; timesUs = newTimesUs; flags = newFlags; sizes = newSizes; cryptoDatas = newCryptoDatas; - formats = newFormats; sourceIds = newSourceIds; relativeFirstIndex = 0; capacity = newCapacity; @@ -856,6 +872,7 @@ public class SampleQueue implements TrackOutput { length -= discardCount; largestQueuedTimestampUs = max(largestDiscardedTimestampUs, getLargestTimestamp(length)); isLastSampleQueued = discardCount == 0 && isLastSampleQueued; + sharedSampleMetadata.discardFrom(discardFromIndex); if (length != 0) { int relativeLastWriteIndex = getRelativeIndex(length - 1); return offsets[relativeLastWriteIndex] + sizes[relativeLastWriteIndex]; @@ -981,6 +998,7 @@ public class SampleQueue implements TrackOutput { * @param discardCount The number of samples to discard. * @return The corresponding offset up to which data should be discarded. */ + @GuardedBy("this") private long discardSamples(int discardCount) { largestDiscardedTimestampUs = max(largestDiscardedTimestampUs, getLargestTimestamp(discardCount)); @@ -994,6 +1012,8 @@ public class SampleQueue implements TrackOutput { if (readPosition < 0) { readPosition = 0; } + sharedSampleMetadata.discardTo(absoluteFirstIndex); + if (length == 0) { int relativeLastDiscardIndex = (relativeFirstIndex == 0 ? capacity : relativeFirstIndex) - 1; return offsets[relativeLastDiscardIndex] + sizes[relativeLastDiscardIndex]; @@ -1046,4 +1066,15 @@ public class SampleQueue implements TrackOutput { public long offset; @Nullable public CryptoData cryptoData; } + + /** A holder for metadata that applies to a span of contiguous samples. */ + private static final class SharedSampleMetadata { + public final Format format; + public final DrmSessionReference drmSessionReference; + + private SharedSampleMetadata(Format format, DrmSessionReference drmSessionReference) { + this.format = format; + this.drmSessionReference = drmSessionReference; + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java index 0c5e5045ef..a5a05951d1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java @@ -19,17 +19,52 @@ import androidx.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * A stream of media samples (and associated format information). - */ +/** A stream of media samples (and associated format information). */ public interface SampleStream { - /** Return values of {@link #readData(FormatHolder, DecoderInputBuffer, boolean)}. */ + /** + * Flags that can be specified when calling {@link #readData}. Possible flag values are {@link + * #FLAG_PEEK}, {@link #FLAG_REQUIRE_FORMAT} and {@link #FLAG_OMIT_SAMPLE_DATA}. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = {FLAG_PEEK, FLAG_REQUIRE_FORMAT, FLAG_OMIT_SAMPLE_DATA}) + @interface ReadFlags {} + /** Specifies that the read position should not be advanced if a sample buffer is read. */ + int FLAG_PEEK = 1; + /** + * Specifies that if a sample buffer would normally be read next, the format of the stream should + * be read instead. In detail, the effect of this flag is as follows: + * + *
      + *
    • If a sample buffer would be read were the flag not set, then the stream format will be + * read instead. + *
    • If nothing would be read were the flag not set, then the stream format will be read if + * it's known. If the stream format is not known then behavior is unchanged. + *
    • If an end of stream buffer would be read were the flag not set, then behavior is + * unchanged. + *
    + */ + int FLAG_REQUIRE_FORMAT = 1 << 1; + /** + * Specifies that {@link DecoderInputBuffer#data}, {@link DecoderInputBuffer#supplementalData} and + * {@link DecoderInputBuffer#cryptoInfo} should not be populated when reading a sample buffer. + * + *

    This flag is useful for efficiently reading or (when combined with {@link #FLAG_PEEK}) + * peeking sample metadata. It can also be used for efficiency by a caller wishing to skip a + * sample buffer. + */ + int FLAG_OMIT_SAMPLE_DATA = 1 << 2; + + /** Return values of {@link #readData}. */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({C.RESULT_NOTHING_READ, C.RESULT_FORMAT_READ, C.RESULT_BUFFER_READ}) @@ -37,10 +72,9 @@ public interface SampleStream { /** * Returns whether data is available to be read. - *

    - * Note: If the stream has ended then a buffer with the end of stream flag can always be read from - * {@link #readData(FormatHolder, DecoderInputBuffer, boolean)}. Hence an ended stream is always - * ready. + * + *

    Note: If the stream has ended then a buffer with the end of stream flag can always be read + * from {@link #readData}. Hence an ended stream is always ready. * * @return Whether data is available to be read. */ @@ -65,17 +99,15 @@ public interface SampleStream { * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the * end of the stream. If the end of the stream has been reached, the {@link - * C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. If a {@link - * DecoderInputBuffer#isFlagsOnly() flags-only} buffer is passed, then no {@link - * DecoderInputBuffer#data} will be read and the read position of the stream will not change, - * but the flags of the buffer will be populated. - * @param formatRequired Whether the caller requires that the format of the stream be read even if - * it's not changing. A sample will never be read if set to true, however it is still possible - * for the end of stream or nothing to be read. - * @return The status of read, one of {@link ReadDataResult}. + * C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @param readFlags Flags controlling the behavior of this read operation. + * @return The {@link ReadDataResult result} of the read operation. + * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold + * the data of a sample being read. The buffer {@link DecoderInputBuffer#timeUs timestamp} and + * flags are populated if this exception is thrown, but the read position is not advanced. */ @ReadDataResult - int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired); + int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags); /** * Attempts to skip to the keyframe before the specified position, or to the end of the stream if @@ -85,5 +117,4 @@ public interface SampleStream { * @return The number of samples that were skipped. */ int skipData(long positionUs); - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java index fb6af1136a..e500d0520a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source; import com.google.android.exoplayer2.C; // TODO: Clarify the requirements for implementing this interface [Internal ref: b/36250203]. -/** - * A loader that can proceed in approximate synchronization with other loaders. - */ +/** A loader that can proceed in approximate synchronization with other loaders. */ public interface SequenceableLoader { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java index 447a3e3ad5..35e5e6f8c0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java @@ -292,8 +292,8 @@ public final class SilenceMediaSource extends BaseMediaSource { @Override public int readData( - FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) { - if (!sentFormat || formatRequired) { + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { + if (!sentFormat || (readFlags & FLAG_REQUIRE_FORMAT) != 0) { formatHolder.format = FORMAT; sentFormat = true; return C.RESULT_FORMAT_READ; @@ -307,14 +307,14 @@ public final class SilenceMediaSource extends BaseMediaSource { buffer.timeUs = getAudioPositionUs(positionBytes); buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); - if (buffer.isFlagsOnly()) { - return C.RESULT_BUFFER_READ; - } - int bytesToWrite = (int) min(SILENCE_SAMPLE.length, bytesRemaining); - buffer.ensureSpaceForWrite(bytesToWrite); - buffer.data.put(SILENCE_SAMPLE, /* offset= */ 0, bytesToWrite); - positionBytes += bytesToWrite; + if ((readFlags & FLAG_OMIT_SAMPLE_DATA) == 0) { + buffer.ensureSpaceForWrite(bytesToWrite); + buffer.data.put(SILENCE_SAMPLE, /* offset= */ 0, bytesToWrite); + } + if ((readFlags & FLAG_PEEK) == 0) { + positionBytes += bytesToWrite; + } return C.RESULT_BUFFER_READ; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java index 9c9f2265ad..8d70fdc102 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java @@ -24,9 +24,7 @@ import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Assertions; -/** - * A {@link Timeline} consisting of a single period and static window. - */ +/** A {@link Timeline} consisting of a single period and static window. */ public final class SinglePeriodTimeline extends Timeline { private static final Object UID = new Object(); 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 9e5d8aae54..26438dbb77 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 @@ -88,7 +88,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; tracks = new TrackGroupArray(new TrackGroup(format)); sampleStreams = new ArrayList<>(); - loader = new Loader("Loader:SingleSampleMediaPeriod"); + loader = new Loader("SingleSampleMediaPeriod"); } public void release() { @@ -349,31 +349,39 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public int readData( - FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) { + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { maybeNotifyDownstreamFormat(); if (streamState == STREAM_STATE_END_OF_STREAM) { buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); return C.RESULT_BUFFER_READ; - } else if (requireFormat || streamState == STREAM_STATE_SEND_FORMAT) { + } + + if ((readFlags & FLAG_REQUIRE_FORMAT) != 0 || streamState == STREAM_STATE_SEND_FORMAT) { formatHolder.format = format; streamState = STREAM_STATE_SEND_SAMPLE; return C.RESULT_FORMAT_READ; - } else if (loadingFinished) { - if (sampleData != null) { - buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); - buffer.timeUs = 0; - if (buffer.isFlagsOnly()) { - return C.RESULT_BUFFER_READ; - } - buffer.ensureSpaceForWrite(sampleSize); - buffer.data.put(sampleData, 0, sampleSize); - } else { - buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); - } + } + + if (!loadingFinished) { + return C.RESULT_NOTHING_READ; + } + + if (sampleData == null) { + buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); streamState = STREAM_STATE_END_OF_STREAM; return C.RESULT_BUFFER_READ; } - return C.RESULT_NOTHING_READ; + + buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); + buffer.timeUs = 0; + if ((readFlags & FLAG_OMIT_SAMPLE_DATA) == 0) { + buffer.ensureSpaceForWrite(sampleSize); + buffer.data.put(sampleData, 0, sampleSize); + } + if ((readFlags & FLAG_PEEK) == 0) { + streamState = STREAM_STATE_END_OF_STREAM; + } + return C.RESULT_BUFFER_READ; } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SpannedData.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SpannedData.java new file mode 100644 index 0000000000..3b303d887a --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SpannedData.java @@ -0,0 +1,157 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkState; +import static java.lang.Math.min; + +import android.util.SparseArray; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Consumer; + +/** + * Stores value objects associated with spans of integer keys. + * + *

    This implementation is optimised for consecutive {@link #get(int)} calls with keys that are + * close to each other in value. + * + *

    Spans are defined by their own {@code startKey} (inclusive) and the {@code startKey} of the + * next span (exclusive). The last span is open-ended. + * + * @param The type of values stored in this collection. + */ +/* package */ final class SpannedData { + + private int memoizedReadIndex; + + private final SparseArray spans; + private final Consumer removeCallback; + + /** Constructs an empty instance. */ + public SpannedData() { + this(/* removeCallback= */ value -> {}); + } + + /** + * Constructs an empty instance that invokes {@code removeCallback} on each value that is removed + * from the collection. + */ + public SpannedData(Consumer removeCallback) { + spans = new SparseArray<>(); + this.removeCallback = removeCallback; + memoizedReadIndex = C.INDEX_UNSET; + } + + /** + * Returns the value associated with the span covering {@code key}. + * + *

    The collection must not be {@link #isEmpty() empty}. + * + * @param key The key to lookup in the collection. Must be greater than or equal to the previous + * value passed to {@link #discardTo(int)} (or zero after {@link #clear()} has been called). + * @return The value associated with the provided key. + */ + public V get(int key) { + if (memoizedReadIndex == C.INDEX_UNSET) { + memoizedReadIndex = 0; + } + while (memoizedReadIndex > 0 && key < spans.keyAt(memoizedReadIndex)) { + memoizedReadIndex--; + } + while (memoizedReadIndex < spans.size() - 1 && key >= spans.keyAt(memoizedReadIndex + 1)) { + memoizedReadIndex++; + } + return spans.valueAt(memoizedReadIndex); + } + + /** + * Adds a new span to the end starting at {@code startKey} and containing {@code value}. + * + *

    {@code startKey} must be greater than or equal to the start key of the previous span. If + * they're equal, the previous span is overwritten and it's passed to {@code removeCallback} (if + * set). + */ + public void appendSpan(int startKey, V value) { + if (memoizedReadIndex == C.INDEX_UNSET) { + checkState(spans.size() == 0); + memoizedReadIndex = 0; + } + + if (spans.size() > 0) { + int lastStartKey = spans.keyAt(spans.size() - 1); + checkArgument(startKey >= lastStartKey); + if (lastStartKey == startKey) { + removeCallback.accept(spans.valueAt(spans.size() - 1)); + } + } + spans.append(startKey, value); + } + + /** + * Returns the value associated with the end span. This is either the last value passed to {@link + * #appendSpan(int, Object)}, or the value of the span covering the index passed to {@link + * #discardFrom(int)}. + * + *

    The collection must not be {@link #isEmpty() empty}. + */ + public V getEndValue() { + return spans.valueAt(spans.size() - 1); + } + + /** + * Discard the spans from the start up to {@code discardToKey}. + * + *

    The span associated with {@code discardToKey} is not discarded (which means the last span is + * never discarded). + */ + public void discardTo(int discardToKey) { + for (int i = 0; i < spans.size() - 1 && discardToKey >= spans.keyAt(i + 1); i++) { + removeCallback.accept(spans.valueAt(i)); + spans.removeAt(i); + if (memoizedReadIndex > 0) { + memoizedReadIndex--; + } + } + } + + /** + * Discard the spans from the end back to {@code discardFromKey}. + * + *

    The span associated with {@code discardFromKey} is not discarded. + */ + public void discardFrom(int discardFromKey) { + for (int i = spans.size() - 1; i >= 0 && discardFromKey < spans.keyAt(i); i--) { + removeCallback.accept(spans.valueAt(i)); + spans.removeAt(i); + } + memoizedReadIndex = spans.size() > 0 ? min(memoizedReadIndex, spans.size() - 1) : C.INDEX_UNSET; + } + + /** Remove all spans. */ + public void clear() { + for (int i = 0; i < spans.size(); i++) { + removeCallback.accept(spans.valueAt(i)); + } + memoizedReadIndex = C.INDEX_UNSET; + spans.clear(); + } + + /** Returns true if the collection is empty. */ + public boolean isEmpty() { + return spans.size() == 0; + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java b/library/core/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java index 508bf0e365..324f89794a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source; import android.net.Uri; import com.google.android.exoplayer2.ParserException; -/** - * Thrown if the input format was not recognized. - */ +/** Thrown if the input format was not recognized. */ public class UnrecognizedInputFormatException extends ParserException { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index 86a2cb6ca5..906139cf76 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -15,20 +15,13 @@ */ package com.google.android.exoplayer2.source.ads; -import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.common.collect.ImmutableList; import java.io.IOException; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; /** * Interface for loaders of ads, which can be used with {@link AdsMediaSource}. @@ -78,94 +71,6 @@ public interface AdsLoader { default void onAdTapped() {} } - /** Provides information about views for the ad playback UI. */ - interface AdViewProvider { - - /** - * Returns the {@link ViewGroup} on top of the player that will show any ad UI, or {@code null} - * if playing audio-only ads. Any views on top of the returned view group must be described by - * {@link OverlayInfo OverlayInfos} returned by {@link #getAdOverlayInfos()}, for accurate - * viewability measurement. - */ - @Nullable - ViewGroup getAdViewGroup(); - - /** @deprecated Use {@link #getAdOverlayInfos()} instead. */ - @Deprecated - default View[] getAdOverlayViews() { - return new View[0]; - } - - /** - * Returns a list of {@link OverlayInfo} instances describing views that are on top of the ad - * view group, but that are essential for controlling playback and should be excluded from ad - * viewability measurements by the {@link AdsLoader} (if it supports this). - * - *

    Each view must be either a fully transparent overlay (for capturing touch events), or a - * small piece of transient UI that is essential to the user experience of playback (such as a - * button to pause/resume playback or a transient full-screen or cast button). For more - * information see the documentation for your ads loader. - */ - @SuppressWarnings("deprecation") - default List getAdOverlayInfos() { - ImmutableList.Builder listBuilder = new ImmutableList.Builder<>(); - // Call through to deprecated version. - for (View view : getAdOverlayViews()) { - listBuilder.add(new OverlayInfo(view, OverlayInfo.PURPOSE_CONTROLS)); - } - return listBuilder.build(); - } - } - - /** Provides information about an overlay view shown on top of an ad view group. */ - final class OverlayInfo { - - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef({PURPOSE_CONTROLS, PURPOSE_CLOSE_AD, PURPOSE_OTHER, PURPOSE_NOT_VISIBLE}) - public @interface Purpose {} - /** Purpose for playback controls overlaying the player. */ - public static final int PURPOSE_CONTROLS = 0; - /** Purpose for ad close buttons overlaying the player. */ - public static final int PURPOSE_CLOSE_AD = 1; - /** Purpose for other overlays. */ - public static final int PURPOSE_OTHER = 2; - /** Purpose for overlays that are not visible. */ - public static final int PURPOSE_NOT_VISIBLE = 3; - - /** The overlay view. */ - public final View view; - /** The purpose of the overlay view. */ - @Purpose public final int purpose; - /** An optional, detailed reason that the overlay view is needed. */ - @Nullable public final String reasonDetail; - - /** - * Creates a new overlay info. - * - * @param view The view that is overlaying the player. - * @param purpose The purpose of the view. - */ - public OverlayInfo(View view, @Purpose int purpose) { - this(view, purpose, /* detailedReason= */ null); - } - - /** - * Creates a new overlay info. - * - * @param view The view that is overlaying the player. - * @param purpose The purpose of the view. - * @param detailedReason An optional, detailed reason that the view is on top of the player. See - * the documentation for the {@link AdsLoader} implementation for more information on this - * string's formatting. - */ - public OverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) { - this.view = view; - this.purpose = purpose; - this.reasonDetail = detailedReason; - } - } - // Methods called by the application. /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index f17ad2d8f5..f9a3daca86 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceFactory; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; @@ -128,7 +129,7 @@ public final class AdsMediaSource extends CompositeMediaSource { private final MediaSource contentMediaSource; private final MediaSourceFactory adMediaSourceFactory; private final AdsLoader adsLoader; - private final AdsLoader.AdViewProvider adViewProvider; + private final AdViewProvider adViewProvider; private final DataSpec adTagDataSpec; private final Object adsId; private final Handler mainHandler; @@ -160,7 +161,7 @@ public final class AdsMediaSource extends CompositeMediaSource { Object adsId, MediaSourceFactory adMediaSourceFactory, AdsLoader adsLoader, - AdsLoader.AdViewProvider adViewProvider) { + AdViewProvider adViewProvider) { this.contentMediaSource = contentMediaSource; this.adMediaSourceFactory = adMediaSourceFactory; this.adsLoader = adsLoader; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java index cc82510a29..c1a655e522 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java @@ -52,7 +52,8 @@ public final class SinglePeriodAdTimeline extends ForwardingTimeline { period.windowIndex, durationUs, period.getPositionInWindowUs(), - adPlaybackState); + adPlaybackState, + period.isPlaceholder); return period; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java index b508c1da1d..8693bb8912 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java @@ -23,9 +23,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Assertions; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * A base implementation of {@link MediaChunk} that outputs to a {@link BaseMediaChunkOutput}. - */ +/** A base implementation of {@link MediaChunk} that outputs to a {@link BaseMediaChunkOutput}. */ public abstract class BaseMediaChunk extends MediaChunk { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BundledChunkExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BundledChunkExtractor.java index f02329d5d5..ff19ee1d26 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BundledChunkExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BundledChunkExtractor.java @@ -29,8 +29,12 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; +import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.google.android.exoplayer2.extractor.rawcc.RawCcExtractor; import com.google.android.exoplayer2.upstream.DataReader; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -41,6 +45,41 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; */ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtractor { + /** {@link ChunkExtractor.Factory} for instances of this class. */ + public static final ChunkExtractor.Factory FACTORY = + (primaryTrackType, + format, + enableEventMessageTrack, + closedCaptionFormats, + playerEmsgTrackOutput) -> { + @Nullable String containerMimeType = format.containerMimeType; + Extractor extractor; + if (MimeTypes.isText(containerMimeType)) { + if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { + // RawCC is special because it's a text specific container format. + extractor = new RawCcExtractor(format); + } else { + // All other text types are raw formats that do not need an extractor. + return null; + } + } else if (MimeTypes.isMatroska(containerMimeType)) { + extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES); + } else { + int flags = 0; + if (enableEventMessageTrack) { + flags |= FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK; + } + extractor = + new FragmentedMp4Extractor( + flags, + /* timestampAdjuster= */ null, + /* sideloadedTrack= */ null, + closedCaptionFormats, + playerEmsgTrackOutput); + } + return new BundledChunkExtractor(extractor, primaryTrackType, format); + }; + private static final PositionHolder POSITION_HOLDER = new PositionHolder(); private final Extractor extractor; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java index 2d2d74718b..1c729b3f35 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java @@ -29,8 +29,8 @@ import java.util.List; import java.util.Map; /** - * An abstract base class for {@link Loadable} implementations that load chunks of data required - * for the playback of streams. + * An abstract base class for {@link Loadable} implementations that load chunks of data required for + * the playback of streams. */ public abstract class Chunk implements Loadable { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractor.java index 6bfe9590db..60774c3ea6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractor.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.TrackOutput; import java.io.IOException; +import java.util.List; /** * Extracts samples and track {@link Format Formats} from chunks. @@ -31,6 +32,27 @@ import java.io.IOException; */ public interface ChunkExtractor { + /** Creates {@link ChunkExtractor} instances. */ + interface Factory { + + /** + * Returns a new {@link ChunkExtractor} instance. + * + * @param primaryTrackType The type of the primary track. One of {@link C C.TRACK_TYPE_*}. + * @param representationFormat The format of the representation to extract from. + * @param enableEventMessageTrack Whether to enable the event message track. + * @param closedCaptionFormats The {@link Format Formats} of the Closed-Caption tracks. + * @return A new {@link ChunkExtractor} instance, or null if not applicable. + */ + @Nullable + ChunkExtractor createProgressiveMediaExtractor( + int primaryTrackType, + Format representationFormat, + boolean enableEventMessageTrack, + List closedCaptionFormats, + @Nullable TrackOutput playerEmsgTrackOutput); + } + /** Provides {@link TrackOutput} instances to be written to during extraction. */ interface TrackOutputProvider { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java index d6400c5165..da0a71e82d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.source.chunk; import androidx.annotation.Nullable; -/** - * Holds a chunk or an indication that the end of the stream has been reached. - */ +/** Holds a chunk or an indication that the end of the stream has been reached. */ public final class ChunkHolder { /** The chunk. */ 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 88a8a0d1ea..40c1b7f357 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 @@ -53,8 +53,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * A {@link SampleStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}. * May also be configured to expose additional embedded {@link SampleStream}s. */ -public class ChunkSampleStream implements SampleStream, SequenceableLoader, - Loader.Callback, Loader.ReleaseCallback { +public class ChunkSampleStream + implements SampleStream, SequenceableLoader, Loader.Callback, Loader.ReleaseCallback { /** A callback to be notified when a sample stream has finished being released. */ public interface ReleaseCallback { @@ -133,7 +133,7 @@ public class ChunkSampleStream implements SampleStream, S this.callback = callback; this.mediaSourceEventDispatcher = mediaSourceEventDispatcher; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; - loader = new Loader("Loader:ChunkSampleStream"); + loader = new Loader("ChunkSampleStream"); nextChunkHolder = new ChunkHolder(); mediaChunks = new ArrayList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); @@ -381,8 +381,8 @@ public class ChunkSampleStream implements SampleStream, S } @Override - public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired) { + public int readData( + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { if (isPendingReset()) { return C.RESULT_NOTHING_READ; } @@ -395,7 +395,7 @@ public class ChunkSampleStream implements SampleStream, S } maybeNotifyPrimaryTrackFormatChanged(); - return primarySampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished); + return primarySampleQueue.read(formatHolder, buffer, readFlags, loadingFinished); } @Override @@ -862,8 +862,8 @@ public class ChunkSampleStream implements SampleStream, S } @Override - public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired) { + public int readData( + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { if (isPendingReset()) { return C.RESULT_NOTHING_READ; } @@ -875,7 +875,7 @@ public class ChunkSampleStream implements SampleStream, S return C.RESULT_NOTHING_READ; } maybeNotifyDownstreamFormat(); - return sampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished); + return sampleQueue.read(formatHolder, buffer, readFlags, loadingFinished); } public void release() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java index 52756b378f..81ce2d63de 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java @@ -20,9 +20,7 @@ import com.google.android.exoplayer2.SeekParameters; import java.io.IOException; import java.util.List; -/** - * A provider of {@link Chunk}s for a {@link ChunkSampleStream} to load. - */ +/** A provider of {@link Chunk}s for a {@link ChunkSampleStream} to load. */ public interface ChunkSource { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java index b8938deac4..cd4fcbee77 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java @@ -27,9 +27,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; import java.io.IOException; -/** - * A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data. - */ +/** A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data. */ public class ContainerMediaChunk extends BaseMediaChunk { private final int chunkCount; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java index 6d97c1d92e..cec6c0ee02 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java @@ -25,8 +25,8 @@ import java.io.IOException; import java.util.Arrays; /** - * A base class for {@link Chunk} implementations where the data should be loaded into a - * {@code byte[]} before being consumed. + * A base class for {@link Chunk} implementations where the data should be loaded into a {@code + * byte[]} before being consumed. */ public abstract class DataChunk extends Chunk { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java index 39c097826f..690b9db4d8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java @@ -22,9 +22,7 @@ import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Assertions; -/** - * An abstract base class for {@link Chunk}s that contain media samples. - */ +/** An abstract base class for {@link Chunk}s that contain media samples. */ public abstract class MediaChunk extends Chunk { /** The chunk index, or {@link C#INDEX_UNSET} if it is not known. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.java index 7c440b46d7..fadf7ad6f7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaParserChunkExtractor.java @@ -40,6 +40,7 @@ import com.google.android.exoplayer2.source.mediaparser.InputReaderAdapterV30; import com.google.android.exoplayer2.source.mediaparser.MediaParserUtil; import com.google.android.exoplayer2.source.mediaparser.OutputConsumerAdapterV30; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; import java.util.ArrayList; @@ -49,6 +50,25 @@ import java.util.List; @RequiresApi(30) public final class MediaParserChunkExtractor implements ChunkExtractor { + // Maximum TAG length is 23 characters. + private static final String TAG = "MediaPrsrChunkExtractor"; + + public static final ChunkExtractor.Factory FACTORY = + (primaryTrackType, + format, + enableEventMessageTrack, + closedCaptionFormats, + playerEmsgTrackOutput) -> { + if (!MimeTypes.isText(format.containerMimeType)) { + // Container is either Matroska or Fragmented MP4. + return new MediaParserChunkExtractor(primaryTrackType, format, closedCaptionFormats); + } else { + // This is either RAWCC (unsupported) or a text track that does not require an extractor. + Log.w(TAG, "Ignoring an unsupported text track."); + return null; + } + }; + private final OutputConsumerAdapterV30 outputConsumerAdapter; private final InputReaderAdapterV30 inputReaderAdapter; private final MediaParser mediaParser; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java index 4e91e921d2..4c84a91be2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java @@ -26,9 +26,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; import java.io.IOException; -/** - * A {@link BaseMediaChunk} for chunks consisting of a single raw sample. - */ +/** A {@link BaseMediaChunk} for chunks consisting of a single raw sample. */ public final class SingleSampleMediaChunk extends BaseMediaChunk { private final int trackType; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java index 7987c8b5d6..fb5e1f198a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java @@ -21,12 +21,10 @@ import com.google.android.exoplayer2.decoder.SimpleDecoder; import com.google.android.exoplayer2.util.Assertions; import java.nio.ByteBuffer; -/** - * Base class for subtitle parsers that use their own decode thread. - */ -public abstract class SimpleSubtitleDecoder extends - SimpleDecoder implements - SubtitleDecoder { +/** Base class for subtitle parsers that use their own decode thread. */ +public abstract class SimpleSubtitleDecoder + extends SimpleDecoder + implements SubtitleDecoder { private final String name; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/Subtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Subtitle.java index 4dc5f61fb5..861faa73f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/Subtitle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/Subtitle.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.text; import com.google.android.exoplayer2.C; import java.util.List; -/** - * A subtitle consisting of timed {@link Cue}s. - */ +/** A subtitle consisting of timed {@link Cue}s. */ public interface Subtitle { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java index 2b080c6564..7571b0ffef 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java @@ -17,11 +17,9 @@ package com.google.android.exoplayer2.text; import com.google.android.exoplayer2.decoder.Decoder; -/** - * Decodes {@link Subtitle}s from {@link SubtitleInputBuffer}s. - */ -public interface SubtitleDecoder extends - Decoder { +/** Decodes {@link Subtitle}s from {@link SubtitleInputBuffer}s. */ +public interface SubtitleDecoder + extends Decoder { /** * Informs the decoder of the current playback position. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index bd652c6586..043c181225 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -29,9 +29,7 @@ import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder; import com.google.android.exoplayer2.text.webvtt.WebvttDecoder; import com.google.android.exoplayer2.util.MimeTypes; -/** - * A factory for {@link SubtitleDecoder} instances. - */ +/** A factory for {@link SubtitleDecoder} instances. */ public interface SubtitleDecoderFactory { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java index 63b997a613..1dfa9ffb45 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer2.decoder.OutputBuffer; import com.google.android.exoplayer2.util.Assertions; import java.util.List; -/** - * Base class for {@link SubtitleDecoder} output buffers. - */ +/** Base class for {@link SubtitleDecoder} output buffers. */ public abstract class SubtitleOutputBuffer extends OutputBuffer implements Subtitle { @Nullable private Subtitle subtitle; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java index cbfd43775f..c721fe8da0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java @@ -29,7 +29,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.RendererCapabilities; -import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -41,10 +41,10 @@ import java.util.List; /** * A renderer for text. - *

    - * {@link Subtitle}s are decoded from sample data using {@link SubtitleDecoder} instances obtained - * from a {@link SubtitleDecoderFactory}. The actual rendering of the subtitle {@link Cue}s is - * delegated to a {@link TextOutput}. + * + *

    {@link Subtitle}s are decoded from sample data using {@link SubtitleDecoder} instances + * obtained from a {@link SubtitleDecoderFactory}. The actual rendering of the subtitle {@link Cue}s + * is delegated to a {@link TextOutput}. */ public final class TextRenderer extends BaseRenderer implements Callback { @@ -273,7 +273,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { return; } // Try and read the next subtitle from the source. - @SampleStream.ReadDataResult int result = readSource(formatHolder, nextInputBuffer, false); + @ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0); if (result == C.RESULT_BUFFER_READ) { if (nextInputBuffer.isEndOfStream()) { inputStreamEnded = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java index 617be2b96f..e91edcd307 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java @@ -45,9 +45,7 @@ import java.util.Comparator; import java.util.List; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * A {@link SubtitleDecoder} for CEA-708 (also known as "EIA-708"). - */ +/** A {@link SubtitleDecoder} for CEA-708 (also known as "EIA-708"). */ public final class Cea708Decoder extends CeaDecoder { private static final String TAG = "Cea708Decoder"; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index 89366ca28c..3f764f9fb2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -22,7 +22,9 @@ import android.graphics.Typeface; import android.text.Layout; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; +import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; @@ -32,6 +34,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -154,7 +157,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { if (infoNameAndValue.length != 2) { continue; } - switch (Util.toLowerInvariant(infoNameAndValue[0].trim())) { + switch (Ascii.toLowerCase(infoNameAndValue[0].trim())) { case "playresx": try { screenWidth = Float.parseFloat(infoNameAndValue[1].trim()); @@ -339,6 +342,20 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { /* end= */ spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } + if (style.underline) { + spannableText.setSpan( + new UnderlineSpan(), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.strikeout) { + spannableText.setSpan( + new StrikethroughSpan(), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } } @SsaStyle.SsaAlignment int alignment; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDialogueFormat.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDialogueFormat.java index df3db09d73..82f3dd642c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDialogueFormat.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDialogueFormat.java @@ -22,7 +22,7 @@ import android.text.TextUtils; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; /** * Represents a {@code Format:} line from the {@code [Events]} section @@ -61,7 +61,7 @@ import com.google.android.exoplayer2.util.Util; Assertions.checkArgument(formatLine.startsWith(FORMAT_LINE_PREFIX)); String[] keys = TextUtils.split(formatLine.substring(FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { - switch (Util.toLowerInvariant(keys[i].trim())) { + switch (Ascii.toLowerCase(keys[i].trim())) { case "start": startTimeIndex = i; break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index 192838ad0d..3ae6bd133d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.primitives.Ints; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -94,6 +95,8 @@ import java.util.regex.Pattern; public final float fontSize; public final boolean bold; public final boolean italic; + public final boolean underline; + public final boolean strikeout; private SsaStyle( String name, @@ -101,13 +104,17 @@ import java.util.regex.Pattern; @Nullable @ColorInt Integer primaryColor, float fontSize, boolean bold, - boolean italic) { + boolean italic, + boolean underline, + boolean strikeout) { this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; this.fontSize = fontSize; this.bold = bold; this.italic = italic; + this.underline = underline; + this.strikeout = strikeout; } @Nullable @@ -135,10 +142,16 @@ import java.util.regex.Pattern; ? parseFontSize(styleValues[format.fontSizeIndex].trim()) : Cue.DIMEN_UNSET, format.boldIndex != C.INDEX_UNSET - ? parseBoldOrItalic(styleValues[format.boldIndex].trim()) + ? parseBooleanValue(styleValues[format.boldIndex].trim()) : false, format.italicIndex != C.INDEX_UNSET - ? parseBoldOrItalic(styleValues[format.italicIndex].trim()) + ? parseBooleanValue(styleValues[format.italicIndex].trim()) + : false, + format.underlineIndex != C.INDEX_UNSET + ? parseBooleanValue(styleValues[format.underlineIndex].trim()) + : false, + format.strikeoutIndex != C.INDEX_UNSET + ? parseBooleanValue(styleValues[format.strikeoutIndex].trim()) : false); } catch (RuntimeException e) { Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); @@ -225,12 +238,12 @@ import java.util.regex.Pattern; } } - private static boolean parseBoldOrItalic(String boldOrItalic) { + private static boolean parseBooleanValue(String booleanValue) { try { - int value = Integer.parseInt(boldOrItalic); + int value = Integer.parseInt(booleanValue); return value == 1 || value == -1; } catch (NumberFormatException e) { - Log.w(TAG, "Failed to parse bold/italic: '" + boldOrItalic + "'", e); + Log.w(TAG, "Failed to parse boolean value: '" + booleanValue + "'", e); return false; } } @@ -249,6 +262,8 @@ import java.util.regex.Pattern; public final int fontSizeIndex; public final int boldIndex; public final int italicIndex; + public final int underlineIndex; + public final int strikeoutIndex; public final int length; private Format( @@ -258,6 +273,8 @@ import java.util.regex.Pattern; int fontSizeIndex, int boldIndex, int italicIndex, + int underlineIndex, + int strikeoutIndex, int length) { this.nameIndex = nameIndex; this.alignmentIndex = alignmentIndex; @@ -265,6 +282,8 @@ import java.util.regex.Pattern; this.fontSizeIndex = fontSizeIndex; this.boldIndex = boldIndex; this.italicIndex = italicIndex; + this.underlineIndex = underlineIndex; + this.strikeoutIndex = strikeoutIndex; this.length = length; } @@ -281,10 +300,12 @@ import java.util.regex.Pattern; int fontSizeIndex = C.INDEX_UNSET; int boldIndex = C.INDEX_UNSET; int italicIndex = C.INDEX_UNSET; + int underlineIndex = C.INDEX_UNSET; + int strikeoutIndex = C.INDEX_UNSET; String[] keys = TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { - switch (Util.toLowerInvariant(keys[i].trim())) { + switch (Ascii.toLowerCase(keys[i].trim())) { case "name": nameIndex = i; break; @@ -303,6 +324,12 @@ import java.util.regex.Pattern; case "italic": italicIndex = i; break; + case "underline": + underlineIndex = i; + break; + case "strikeout": + strikeoutIndex = i; + break; } } return nameIndex != C.INDEX_UNSET @@ -313,6 +340,8 @@ import java.util.regex.Pattern; fontSizeIndex, boldIndex, italicIndex, + underlineIndex, + strikeoutIndex, keys.length) : null; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index efbf3ab64f..2ae22bacd2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -30,9 +30,7 @@ import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * A {@link SimpleSubtitleDecoder} for SubRip. - */ +/** A {@link SimpleSubtitleDecoder} for SubRip. */ public final class SubripDecoder extends SimpleSubtitleDecoder { // Fractional positions for use when alignment tags are present. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TextEmphasis.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TextEmphasis.java index a52d818c6e..ecfaeb27cd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TextEmphasis.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TextEmphasis.java @@ -23,6 +23,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.span.TextAnnotation; import com.google.android.exoplayer2.text.span.TextEmphasisSpan; +import com.google.common.base.Ascii; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; @@ -131,7 +132,7 @@ import java.util.regex.Pattern; return null; } - String parsingValue = value.trim(); + String parsingValue = Ascii.toLowerCase(value.trim()); if (parsingValue.isEmpty()) { return null; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index 898fd01576..1d84b7a6b3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.util.ColorParser; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.XmlPullParserUtil; +import com.google.common.base.Ascii; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayDeque; @@ -432,7 +433,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_DISPLAY_ALIGN); if (displayAlign != null) { - switch (Util.toLowerInvariant(displayAlign)) { + switch (Ascii.toLowerCase(displayAlign)) { case "center": lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; line += height / 2; @@ -454,7 +455,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { String writingDirection = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_WRITING_MODE); if (writingDirection != null) { - switch (Util.toLowerInvariant(writingDirection)) { + switch (Ascii.toLowerCase(writingDirection)) { // TODO: Support horizontal RTL modes. case TtmlNode.VERTICAL: case TtmlNode.VERTICAL_LR: @@ -533,25 +534,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { TtmlNode.ITALIC.equalsIgnoreCase(attributeValue)); break; case TtmlNode.ATTR_TTS_TEXT_ALIGN: - switch (Util.toLowerInvariant(attributeValue)) { - case TtmlNode.LEFT: - case TtmlNode.START: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); - break; - case TtmlNode.RIGHT: - case TtmlNode.END: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE); - break; - case TtmlNode.CENTER: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_CENTER); - break; - default: - // ignore - break; - } + style = createIfNull(style).setTextAlign(parseAlignment(attributeValue)); + break; + case TtmlNode.ATTR_EBUTTS_MULTI_ROW_ALIGN: + style = createIfNull(style).setMultiRowAlign(parseAlignment(attributeValue)); break; case TtmlNode.ATTR_TTS_TEXT_COMBINE: - switch (Util.toLowerInvariant(attributeValue)) { + switch (Ascii.toLowerCase(attributeValue)) { case TtmlNode.COMBINE_NONE: style = createIfNull(style).setTextCombine(false); break; @@ -564,7 +553,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } break; case TtmlNode.ATTR_TTS_RUBY: - switch (Util.toLowerInvariant(attributeValue)) { + switch (Ascii.toLowerCase(attributeValue)) { case TtmlNode.RUBY_CONTAINER: style = createIfNull(style).setRubyType(TtmlStyle.RUBY_TYPE_CONTAINER); break; @@ -585,7 +574,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } break; case TtmlNode.ATTR_TTS_RUBY_POSITION: - switch (Util.toLowerInvariant(attributeValue)) { + switch (Ascii.toLowerCase(attributeValue)) { case TtmlNode.ANNOTATION_POSITION_BEFORE: style = createIfNull(style).setRubyPosition(TextAnnotation.POSITION_BEFORE); break; @@ -598,7 +587,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } break; case TtmlNode.ATTR_TTS_TEXT_DECORATION: - switch (Util.toLowerInvariant(attributeValue)) { + switch (Ascii.toLowerCase(attributeValue)) { case TtmlNode.LINETHROUGH: style = createIfNull(style).setLinethrough(true); break; @@ -614,9 +603,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } break; case TtmlNode.ATTR_TTS_TEXT_EMPHASIS: - style = - createIfNull(style) - .setTextEmphasis(TextEmphasis.parse(Util.toLowerInvariant(attributeValue))); + style = createIfNull(style).setTextEmphasis(TextEmphasis.parse(attributeValue)); break; case TtmlNode.ATTR_TTS_SHEAR: style = createIfNull(style).setShearPercentage(parseShear(attributeValue)); @@ -633,6 +620,22 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { return style == null ? new TtmlStyle() : style; } + @Nullable + private static Layout.Alignment parseAlignment(String alignment) { + switch (Ascii.toLowerCase(alignment)) { + case TtmlNode.LEFT: + case TtmlNode.START: + return Layout.Alignment.ALIGN_NORMAL; + case TtmlNode.RIGHT: + case TtmlNode.END: + return Layout.Alignment.ALIGN_OPPOSITE; + case TtmlNode.CENTER: + return Layout.Alignment.ALIGN_CENTER; + default: + return null; + } + } + private static TtmlNode parseNode( XmlPullParser parser, @Nullable TtmlNode parent, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index 96c2dbe5f4..7e39d1e9f8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -72,6 +72,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public static final String ATTR_TTS_TEXT_EMPHASIS = "textEmphasis"; public static final String ATTR_TTS_WRITING_MODE = "writingMode"; public static final String ATTR_TTS_SHEAR = "shear"; + public static final String ATTR_EBUTTS_MULTI_ROW_ALIGN = "multiRowAlign"; // Values for ruby public static final String RUBY_CONTAINER = "container"; @@ -376,7 +377,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return; } String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId; - for (Map.Entry entry : nodeEndsByRegion.entrySet()) { String regionId = entry.getKey(); int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0; @@ -409,17 +409,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (resolvedStyle != null) { TtmlRenderUtil.applyStylesToSpan( text, start, end, resolvedStyle, parent, globalStyles, verticalType); - if (resolvedStyle.getShearPercentage() != TtmlStyle.UNSPECIFIED_SHEAR && TAG_P.equals(tag)) { - // Shear style should only be applied to P nodes - // https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear - // The spec doesn't specify the coordinate system to use for block shear - // however the spec shows examples of how different values are expected to be rendered. - // See: https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear - // https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-fontShear - // This maps the shear percentage to shear angle in graphics coordinates - regionOutput.setShearDegrees((resolvedStyle.getShearPercentage() * -90) / 100); + if (TAG_P.equals(tag)) { + if (resolvedStyle.getShearPercentage() != TtmlStyle.UNSPECIFIED_SHEAR) { + // Shear style should only be applied to P nodes + // https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear + // The spec doesn't specify the coordinate system to use for block shear + // however the spec shows examples of how different values are expected to be rendered. + // See: https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear + // https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-fontShear + // This maps the shear percentage to shear angle in graphics coordinates + regionOutput.setShearDegrees((resolvedStyle.getShearPercentage() * -90) / 100); + } + if (resolvedStyle.getTextAlign() != null) { + regionOutput.setTextAlignment(resolvedStyle.getTextAlign()); + } + if (resolvedStyle.getMultiRowAlign() != null) { + regionOutput.setMultiRowAlignment(resolvedStyle.getMultiRowAlign()); + } } - regionOutput.setTextAlignment(resolvedStyle.getTextAlign()); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java index d04fde3570..d1c7291652 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java @@ -86,6 +86,7 @@ import java.lang.annotation.RetentionPolicy; @RubyType private int rubyType; @TextAnnotation.Position private int rubyPosition; @Nullable private Layout.Alignment textAlign; + @Nullable private Layout.Alignment multiRowAlign; @OptionalBoolean private int textCombine; @Nullable private TextEmphasis textEmphasis; private float shearPercentage; @@ -244,6 +245,9 @@ import java.lang.annotation.RetentionPolicy; if (textAlign == null && ancestor.textAlign != null) { textAlign = ancestor.textAlign; } + if (multiRowAlign == null && ancestor.multiRowAlign != null) { + multiRowAlign = ancestor.multiRowAlign; + } if (textCombine == UNSPECIFIED) { textCombine = ancestor.textCombine; } @@ -308,6 +312,16 @@ import java.lang.annotation.RetentionPolicy; return this; } + @Nullable + public Layout.Alignment getMultiRowAlign() { + return multiRowAlign; + } + + public TtmlStyle setMultiRowAlign(@Nullable Layout.Alignment multiRowAlign) { + this.multiRowAlign = multiRowAlign; + return this; + } + /** Returns true if the source entity has {@code tts:textCombine=all}. */ public boolean getTextCombine() { return textCombine == ON; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index ad1abdc7bc..ea08c375ef 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -39,8 +39,8 @@ import java.util.List; /** * A {@link SimpleSubtitleDecoder} for tx3g. - *

    - * Currently supports parsing of a single text track with embedded styles. + * + *

    Currently supports parsing of a single text track with embedded styles. */ public final class Tx3gDecoder extends SimpleSubtitleDecoder { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java index 45c66302c8..b8f9e9b343 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java @@ -21,7 +21,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.text.span.TextAnnotation; -import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -216,7 +216,7 @@ public final class WebvttCssStyle { } public WebvttCssStyle setFontFamily(@Nullable String fontFamily) { - this.fontFamily = Util.toLowerInvariant(fontFamily); + this.fontFamily = fontFamily == null ? null : Ascii.toLowerCase(fontFamily); return this; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueInfo.java index 2119bd1c04..5c8ce58d33 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueInfo.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.text.webvtt; - import com.google.android.exoplayer2.text.Cue; /** A representation of a WebVTT cue. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java index fe36770aee..383c90af36 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java @@ -27,7 +27,9 @@ import java.util.List; /** * A {@link SimpleSubtitleDecoder} for WebVTT. + * *

    + * * @see WebVTT specification */ public final class WebvttDecoder extends SimpleSubtitleDecoder { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java index 9075083111..07b1fb7f9c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java @@ -22,9 +22,7 @@ import com.google.android.exoplayer2.util.Util; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * Utility methods for parsing WebVTT data. - */ +/** Utility methods for parsing WebVTT data. */ public final class WebvttParserUtil { private static final Pattern COMMENT = Pattern.compile("^NOTE([ \t].*)?$"); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java index bd2e18ad92..6dc3b2f636 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.trackselection; +import static java.lang.Math.max; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; @@ -27,6 +28,7 @@ import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -43,6 +45,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; */ public class AdaptiveTrackSelection extends BaseTrackSelection { + private static final String TAG = "AdaptiveTrackSelection"; + /** Factory for {@link AdaptiveTrackSelection} instances. */ public static class Factory implements ExoTrackSelection.Factory { @@ -74,7 +78,8 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher * quality, the selection may indicate that media already buffered at the lower quality can * be discarded to speed up the switch. This is the minimum duration of media that must be - * retained at the lower quality. + * retained at the lower quality. It must be at least {@code + * minDurationForQualityIncreaseMs}. * @param bandwidthFraction The fraction of the available bandwidth that the selection should * consider available for use. Setting to a value less than 1 is recommended to account for * inaccuracies in the bandwidth estimator. @@ -103,7 +108,8 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher * quality, the selection may indicate that media already buffered at the lower quality can * be discarded to speed up the switch. This is the minimum duration of media that must be - * retained at the lower quality. + * retained at the lower quality. It must be at least {@code + * minDurationForQualityIncreaseMs}. * @param bandwidthFraction The fraction of the available bandwidth that the selection should * consider available for use. Setting to a value less than 1 is recommended to account for * inaccuracies in the bandwidth estimator. @@ -148,11 +154,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { selections[i] = definition.tracks.length == 1 ? new FixedTrackSelection( - definition.group, definition.tracks[0], definition.reason, definition.data) + definition.group, + /* track= */ definition.tracks[0], + /* type= */ definition.type) : createAdaptiveTrackSelection( definition.group, - bandwidthMeter, definition.tracks, + definition.type, + bandwidthMeter, adaptationCheckpoints.get(i)); } return selections; @@ -162,20 +171,23 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { * Creates a single adaptive selection for the given group, bandwidth meter and tracks. * * @param group The {@link TrackGroup}. - * @param bandwidthMeter A {@link BandwidthMeter} which can be used to select tracks. * @param tracks The indices of the selected tracks in the track group. + * @param type The type that will be returned from {@link TrackSelection#getType()}. + * @param bandwidthMeter A {@link BandwidthMeter} which can be used to select tracks. * @param adaptationCheckpoints The {@link AdaptationCheckpoint checkpoints} that can be used to * calculate available bandwidth for this selection. * @return An {@link AdaptiveTrackSelection} for the specified tracks. */ protected AdaptiveTrackSelection createAdaptiveTrackSelection( TrackGroup group, - BandwidthMeter bandwidthMeter, int[] tracks, + int type, + BandwidthMeter bandwidthMeter, ImmutableList adaptationCheckpoints) { return new AdaptiveTrackSelection( group, tracks, + type, bandwidthMeter, minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs, @@ -220,6 +232,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { this( group, tracks, + TrackSelection.TYPE_UNSET, bandwidthMeter, DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, @@ -234,6 +247,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { * @param group The {@link TrackGroup}. * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be * empty. May be in any order. + * @param type The type that will be returned from {@link TrackSelection#getType()}. * @param bandwidthMeter Provides an estimate of the currently available bandwidth. * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the * selected track to switch to one of higher quality. @@ -242,7 +256,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher * quality, the selection may indicate that media already buffered at the lower quality can be * discarded to speed up the switch. This is the minimum duration of media that must be - * retained at the lower quality. + * retained at the lower quality. It must be at least {@code minDurationForQualityIncreaseMs}. * @param bandwidthFraction The fraction of the available bandwidth that the selection should * consider available for use. Setting to a value less than 1 is recommended to account for * inaccuracies in the bandwidth estimator. @@ -259,6 +273,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { protected AdaptiveTrackSelection( TrackGroup group, int[] tracks, + int type, BandwidthMeter bandwidthMeter, long minDurationForQualityIncreaseMs, long maxDurationForQualityDecreaseMs, @@ -267,7 +282,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { float bufferedFractionToLiveEdgeForQualityIncrease, List adaptationCheckpoints, Clock clock) { - super(group, tracks); + super(group, tracks, type); + if (minDurationToRetainAfterDiscardMs < minDurationForQualityIncreaseMs) { + Log.w( + TAG, + "Adjusting minDurationToRetainAfterDiscardMs to be at least" + + " minDurationForQualityIncreaseMs"); + minDurationToRetainAfterDiscardMs = minDurationForQualityIncreaseMs; + } this.bandwidthMeter = bandwidthMeter; this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L; this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L; @@ -309,11 +331,12 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { List queue, MediaChunkIterator[] mediaChunkIterators) { long nowMs = clock.elapsedRealtime(); + long chunkDurationUs = getNextChunkDurationUs(mediaChunkIterators, queue); // Make initial selection if (reason == C.SELECTION_REASON_UNKNOWN) { reason = C.SELECTION_REASON_INITIAL; - selectedIndex = determineIdealSelectedIndex(nowMs); + selectedIndex = determineIdealSelectedIndex(nowMs, chunkDurationUs); return; } @@ -325,7 +348,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { previousSelectedIndex = formatIndexOfPreviousChunk; previousReason = Iterables.getLast(queue).trackSelectionReason; } - int newSelectedIndex = determineIdealSelectedIndex(nowMs); + int newSelectedIndex = determineIdealSelectedIndex(nowMs, chunkDurationUs); if (!isBlacklisted(previousSelectedIndex, nowMs)) { // Revert back to the previous selection if conditions are not suitable for switching. Format currentFormat = getFormat(previousSelectedIndex); @@ -385,7 +408,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) { return queueSize; } - int idealSelectedIndex = determineIdealSelectedIndex(nowMs); + int idealSelectedIndex = determineIdealSelectedIndex(nowMs, getLastChunkDurationUs(queue)); Format idealFormat = getFormat(idealSelectedIndex); // If the chunks contain video, discard from the first SD chunk beyond // minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal @@ -413,14 +436,12 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { * @param format The {@link Format} of the candidate track. * @param trackBitrate The estimated bitrate of the track. May differ from {@link Format#bitrate} * if a more accurate estimate of the current track bitrate is available. - * @param playbackSpeed The current factor by which playback is sped up. * @param effectiveBitrate The bitrate available to this selection. * @return Whether this {@link Format} can be selected. */ @SuppressWarnings("unused") - protected boolean canSelectFormat( - Format format, int trackBitrate, float playbackSpeed, long effectiveBitrate) { - return Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate; + protected boolean canSelectFormat(Format format, int trackBitrate, long effectiveBitrate) { + return trackBitrate <= effectiveBitrate; } /** @@ -452,14 +473,16 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { * * @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link * Long#MIN_VALUE} to ignore track exclusion. + * @param chunkDurationUs The duration of a media chunk in microseconds, or {@link C#TIME_UNSET} + * if unknown. */ - private int determineIdealSelectedIndex(long nowMs) { - long effectiveBitrate = getAllocatedBandwidth(); + private int determineIdealSelectedIndex(long nowMs, long chunkDurationUs) { + long effectiveBitrate = getAllocatedBandwidth(chunkDurationUs); int lowestBitrateAllowedIndex = 0; for (int i = 0; i < length; i++) { if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) { Format format = getFormat(i); - if (canSelectFormat(format, format.bitrate, playbackSpeed, effectiveBitrate)) { + if (canSelectFormat(format, format.bitrate, effectiveBitrate)) { return i; } else { lowestBitrateAllowedIndex = i; @@ -477,8 +500,45 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { : minDurationForQualityIncreaseUs; } - private long getAllocatedBandwidth() { - long totalBandwidth = (long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction); + /** + * Returns a best estimate of the duration of the next chunk, in microseconds, or {@link + * C#TIME_UNSET} if an estimate could not be determined. + */ + private long getNextChunkDurationUs( + MediaChunkIterator[] mediaChunkIterators, List queue) { + // Try to get the next chunk duration for the currently selected format. + if (selectedIndex < mediaChunkIterators.length && mediaChunkIterators[selectedIndex].next()) { + MediaChunkIterator iterator = mediaChunkIterators[selectedIndex]; + return iterator.getChunkEndTimeUs() - iterator.getChunkStartTimeUs(); + } + // Try to get the next chunk duration for another format, on the assumption that chunks + // belonging to different formats are likely to have identical or similar durations. + for (MediaChunkIterator iterator : mediaChunkIterators) { + if (iterator.next()) { + return iterator.getChunkEndTimeUs() - iterator.getChunkStartTimeUs(); + } + } + // Try to get chunk duration for last chunk in the queue, on the assumption that the next chunk + // is likely to have a similar duration. + return getLastChunkDurationUs(queue); + } + + /** + * Returns the duration of the last chunk in the queue, in microseconds, or {@link C#TIME_UNSET} + * if the queue is empty or if the last chunk has an undefined start or end time. + */ + private long getLastChunkDurationUs(List queue) { + if (queue.isEmpty()) { + return C.TIME_UNSET; + } + MediaChunk lastChunk = Iterables.getLast(queue); + return lastChunk.startTimeUs != C.TIME_UNSET && lastChunk.endTimeUs != C.TIME_UNSET + ? lastChunk.endTimeUs - lastChunk.startTimeUs + : C.TIME_UNSET; + } + + private long getAllocatedBandwidth(long chunkDurationUs) { + long totalBandwidth = getTotalAllocatableBandwidth(chunkDurationUs); if (adaptationCheckpoints.isEmpty()) { return totalBandwidth; } @@ -497,6 +557,18 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { (fractionBetweenCheckpoints * (next.allocatedBandwidth - previous.allocatedBandwidth)); } + private long getTotalAllocatableBandwidth(long chunkDurationUs) { + long cautiousBandwidthEstimate = + (long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction); + long timeToFirstByteEstimateUs = bandwidthMeter.getTimeToFirstByteEstimateUs(); + if (timeToFirstByteEstimateUs == C.TIME_UNSET || chunkDurationUs == C.TIME_UNSET) { + return (long) (cautiousBandwidthEstimate / playbackSpeed); + } + float availableTimeToLoadUs = + max(chunkDurationUs / playbackSpeed - timeToFirstByteEstimateUs, 0); + return (long) (cautiousBandwidthEstimate * availableTimeToLoadUs / chunkDurationUs); + } + /** * Returns adaptation checkpoints for allocating bandwidth for adaptive track selections. * @@ -625,7 +697,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { } /** Checkpoint to determine allocated bandwidth. */ - protected static final class AdaptationCheckpoint { + public static final class AdaptationCheckpoint { /** Total bandwidth in bits per second at which this checkpoint applies. */ public final long totalBandwidth; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java index 17c486b45a..670f319f90 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java @@ -38,6 +38,8 @@ public abstract class BaseTrackSelection implements ExoTrackSelection { /** The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth. */ protected final int[] tracks; + /** The type of the selection. */ + private final int type; /** The {@link Format}s of the selected tracks, in order of decreasing bandwidth. */ private final Format[] formats; /** Selected track exclusion timestamps, in order of decreasing bandwidth. */ @@ -52,7 +54,18 @@ public abstract class BaseTrackSelection implements ExoTrackSelection { * null or empty. May be in any order. */ public BaseTrackSelection(TrackGroup group, int... tracks) { + this(group, tracks, TrackSelection.TYPE_UNSET); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @param type The type that will be returned from {@link TrackSelection#getType()}. + */ + public BaseTrackSelection(TrackGroup group, int[] tracks, int type) { Assertions.checkState(tracks.length > 0); + this.type = type; this.group = Assertions.checkNotNull(group); this.length = tracks.length; // Set the formats, sorted in order of decreasing bandwidth. @@ -70,14 +83,11 @@ public abstract class BaseTrackSelection implements ExoTrackSelection { excludeUntilTimes = new long[length]; } - @Override - public void enable() { - // Do nothing. - } + // TrackSelection implementation. @Override - public void disable() { - // Do nothing. + public final int getType() { + return type; } @Override @@ -121,6 +131,8 @@ public abstract class BaseTrackSelection implements ExoTrackSelection { return C.INDEX_UNSET; } + // ExoTrackSelection specific methods. + @Override public final Format getSelectedFormat() { return formats[getSelectedIndex()]; @@ -131,6 +143,16 @@ public abstract class BaseTrackSelection implements ExoTrackSelection { return tracks[getSelectedIndex()]; } + @Override + public void enable() { + // Do nothing. + } + + @Override + public void disable() { + // Do nothing. + } + @Override public void onPlaybackSpeed(float playbackSpeed) { // Do nothing. @@ -142,7 +164,7 @@ public abstract class BaseTrackSelection implements ExoTrackSelection { } @Override - public final boolean blacklist(int index, long exclusionDurationMs) { + public boolean blacklist(int index, long exclusionDurationMs) { long nowMs = SystemClock.elapsedRealtime(); boolean canExclude = isBlacklisted(index, nowMs); for (int i = 0; i < length && !canExclude; i++) { @@ -158,13 +180,8 @@ public abstract class BaseTrackSelection implements ExoTrackSelection { return true; } - /** - * Returns whether the track at the specified index in the selection is excluded. - * - * @param index The index of the track in the selection. - * @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}. - */ - protected final boolean isBlacklisted(int index, long nowMs) { + @Override + public boolean isBlacklisted(int index, long nowMs) { return excludeUntilTimes[index] > nowMs; } 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 627df86cf6..19b1b03e59 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 @@ -105,10 +105,10 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; * } * * If {@code rendererTrackGroups} is null then there aren't any currently mapped tracks, and so - * setting an override isn't possible. Note that a {@link Player.EventListener} registered on the - * player can be used to determine when the current tracks (and therefore the mapping) changes. If - * {@code rendererTrackGroups} is non-null then an override can be set. The next step is to query - * the properties of the available tracks to determine the {@code groupIndex} and the {@code + * setting an override isn't possible. Note that a {@link Player.Listener} registered on the player + * can be used to determine when the current tracks (and therefore the mapping) changes. If {@code + * rendererTrackGroups} is non-null then an override can be set. The next step is to query the + * properties of the available tracks to determine the {@code groupIndex} and the {@code * trackIndices} within the group it that should be selected. The override can then be specified * using {@link ParametersBuilder#setSelectionOverride}: * @@ -1507,29 +1507,26 @@ public class DefaultTrackSelector extends MappingTrackSelector { public final int groupIndex; public final int[] tracks; public final int length; - public final int reason; - public final int data; + public final int type; /** * @param groupIndex The overriding track group index. * @param tracks The overriding track indices within the track group. */ public SelectionOverride(int groupIndex, int... tracks) { - this(groupIndex, tracks, C.SELECTION_REASON_MANUAL, /* data= */ 0); + this(groupIndex, tracks, TrackSelection.TYPE_UNSET); } /** * @param groupIndex The overriding track group index. * @param tracks The overriding track indices within the track group. - * @param reason The reason for the override. One of the {@link C} SELECTION_REASON_ constants. - * @param data Optional data associated with this override. + * @param type The type that will be returned from {@link TrackSelection#getType()}. */ - public SelectionOverride(int groupIndex, int[] tracks, int reason, int data) { + public SelectionOverride(int groupIndex, int[] tracks, int type) { this.groupIndex = groupIndex; this.tracks = Arrays.copyOf(tracks, tracks.length); this.length = tracks.length; - this.reason = reason; - this.data = data; + this.type = type; Arrays.sort(this.tracks); } @@ -1538,8 +1535,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { length = in.readByte(); tracks = new int[length]; in.readIntArray(tracks); - reason = in.readInt(); - data = in.readInt(); + type = in.readInt(); } /** Returns whether this override contains the specified track index. */ @@ -1555,8 +1551,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { @Override public int hashCode() { int hash = 31 * groupIndex + Arrays.hashCode(tracks); - hash = 31 * hash + reason; - return 31 * hash + data; + return 31 * hash + type; } @Override @@ -1570,8 +1565,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { SelectionOverride other = (SelectionOverride) obj; return groupIndex == other.groupIndex && Arrays.equals(tracks, other.tracks) - && reason == other.reason - && data == other.data; + && type == other.type; } // Parcelable implementation. @@ -1586,8 +1580,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { dest.writeInt(groupIndex); dest.writeInt(tracks.length); dest.writeIntArray(tracks); - dest.writeInt(reason); - dest.writeInt(data); + dest.writeInt(type); } public static final Parcelable.Creator CREATOR = @@ -1729,10 +1722,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { override == null ? null : new ExoTrackSelection.Definition( - rendererTrackGroups.get(override.groupIndex), - override.tracks, - override.reason, - override.data); + rendererTrackGroups.get(override.groupIndex), override.tracks, override.type); } } @@ -2135,8 +2125,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { || (minVideoHeight <= format.height && format.height <= maxVideoHeight)) && (format.frameRate == Format.NO_VALUE || (minVideoFrameRate <= format.frameRate && format.frameRate <= maxVideoFrameRate)) - && (format.bitrate == Format.NO_VALUE - || (minVideoBitrate <= format.bitrate && format.bitrate <= maxVideoBitrate)); + && format.bitrate != Format.NO_VALUE + && minVideoBitrate <= format.bitrate + && format.bitrate <= maxVideoBitrate; } @Nullable diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/ExoTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/ExoTrackSelection.java index e6816ec884..ee1fa2dcfe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/ExoTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/ExoTrackSelection.java @@ -42,10 +42,8 @@ public interface ExoTrackSelection extends TrackSelection { public final TrackGroup group; /** The indices of the selected tracks in {@link #group}. */ public final int[] tracks; - /** The track selection reason. One of the {@link C} SELECTION_REASON_ constants. */ - public final int reason; - /** Optional data associated with this selection of tracks. */ - @Nullable public final Object data; + /** The type that will be returned from {@link TrackSelection#getType()}. */ + public final int type; /** * @param group The {@link TrackGroup}. Must not be null. @@ -53,20 +51,19 @@ public interface ExoTrackSelection extends TrackSelection { * null or empty. May be in any order. */ public Definition(TrackGroup group, int... tracks) { - this(group, tracks, C.SELECTION_REASON_UNKNOWN, /* data= */ null); + this(group, tracks, TrackSelection.TYPE_UNSET); } /** * @param group The {@link TrackGroup}. Must not be null. * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be - * @param reason The track selection reason. One of the {@link C} SELECTION_REASON_ constants. - * @param data Optional data associated with this selection of tracks. + * null or empty. May be in any order. + * @param type The type that will be returned from {@link TrackSelection#getType()}. */ - public Definition(TrackGroup group, int[] tracks, int reason, @Nullable Object data) { + public Definition(TrackGroup group, int[] tracks, int type) { this.group = group; this.tracks = tracks; - this.reason = reason; - this.data = data; + this.type = type; } } @@ -274,4 +271,13 @@ public interface ExoTrackSelection extends TrackSelection { * @return Whether exclusion was successful. */ boolean blacklist(int index, long exclusionDurationMs); + + /** + * Returns whether the track at the specified index in the selection is excluded. + * + * @param index The index of the track in the selection. + * @param nowMs The current time in the timebase of {@link + * android.os.SystemClock#elapsedRealtime()}. + */ + boolean isBlacklisted(int index, long nowMs); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java index 62e9a3dc71..b630c127ac 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java @@ -22,9 +22,7 @@ import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import java.util.List; -/** - * A {@link TrackSelection} consisting of a single track. - */ +/** A {@link TrackSelection} consisting of a single track. */ public final class FixedTrackSelection extends BaseTrackSelection { private final int reason; @@ -35,17 +33,33 @@ public final class FixedTrackSelection extends BaseTrackSelection { * @param track The index of the selected track within the {@link TrackGroup}. */ public FixedTrackSelection(TrackGroup group, int track) { - this(group, track, C.SELECTION_REASON_UNKNOWN, null); + this(group, /* track= */ track, /* type= */ TrackSelection.TYPE_UNSET); } /** * @param group The {@link TrackGroup}. Must not be null. * @param track The index of the selected track within the {@link TrackGroup}. + * @param type The type that will be returned from {@link TrackSelection#getType()}. + */ + public FixedTrackSelection(TrackGroup group, int track, int type) { + this( + group, + /* track= */ track, + /* type= */ type, + /* reason= */ C.SELECTION_REASON_UNKNOWN, + null); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param track The index of the selected track within the {@link TrackGroup}. + * @param type The type that will be returned from {@link TrackSelection#getType()}. * @param reason A reason for the track selection. * @param data Optional data associated with the track selection. */ - public FixedTrackSelection(TrackGroup group, int track, int reason, @Nullable Object data) { - super(group, track); + public FixedTrackSelection( + TrackGroup group, int track, int type, int reason, @Nullable Object data) { + super(group, /* tracks= */ new int[] {track}, /* type= */ type); this.reason = reason; this.data = data; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java index 3dcb73de21..49416f6556 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java @@ -53,7 +53,9 @@ public final class RandomTrackSelection extends BaseTrackSelection { Timeline timeline) { return TrackSelectionUtil.createTrackSelectionsForDefinitions( definitions, - definition -> new RandomTrackSelection(definition.group, definition.tracks, random)); + definition -> + new RandomTrackSelection( + definition.group, definition.tracks, definition.type, random)); } } @@ -61,35 +63,14 @@ public final class RandomTrackSelection extends BaseTrackSelection { private int selectedIndex; - /** - * @param group The {@link TrackGroup}. Must not be null. - * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be - * null or empty. May be in any order. - */ - public RandomTrackSelection(TrackGroup group, int... tracks) { - super(group, tracks); - random = new Random(); - selectedIndex = random.nextInt(length); - } - - /** - * @param group The {@link TrackGroup}. Must not be null. - * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be - * null or empty. May be in any order. - * @param seed A seed for the {@link Random} instance used to update the selected track. - */ - public RandomTrackSelection(TrackGroup group, int[] tracks, long seed) { - this(group, tracks, new Random(seed)); - } - /** * @param group The {@link TrackGroup}. Must not be null. * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be * null or empty. May be in any order. * @param random A source of random numbers. */ - public RandomTrackSelection(TrackGroup group, int[] tracks, Random random) { - super(group, tracks); + public RandomTrackSelection(TrackGroup group, int[] tracks, int type, Random random) { + super(group, tracks, type); this.random = random; selectedIndex = random.nextInt(length); } 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 0dac7259a3..53ae2e9cd6 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 @@ -19,6 +19,7 @@ import androidx.annotation.Nullable; 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.util.MimeTypes; import org.checkerframework.checker.nullness.compatqual.NullableType; /** Track selection related utility methods. */ @@ -64,7 +65,7 @@ public final class TrackSelectionUtil { } else { selections[i] = new FixedTrackSelection( - definition.group, definition.tracks[0], definition.reason, definition.data); + definition.group, definition.tracks[0], /* type= */ definition.type); } } return selections; @@ -97,4 +98,20 @@ public final class TrackSelectionUtil { } return builder.build(); } + + /** Returns if a {@link TrackSelectionArray} has at least one track of the given type. */ + public static boolean hasTrackOfType(TrackSelectionArray trackSelections, int trackType) { + for (int i = 0; i < trackSelections.length; i++) { + @Nullable TrackSelection trackSelection = trackSelections.get(i); + if (trackSelection == null) { + continue; + } + for (int j = 0; j < trackSelection.length(); j++) { + if (MimeTypes.getTrackType(trackSelection.getFormat(j).sampleMimeType) == trackType) { + return true; + } + } + } + return false; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java index f5aa81f325..4121a40875 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java @@ -17,9 +17,9 @@ package com.google.android.exoplayer2.upstream; /** * An allocation within a byte array. - *

    - * The allocation's length is obtained by calling {@link Allocator#getIndividualAllocationLength()} - * on the {@link Allocator} from which it was obtained. + * + *

    The allocation's length is obtained by calling {@link + * Allocator#getIndividualAllocationLength()} on the {@link Allocator} from which it was obtained. */ public final class Allocation { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java index 17b7dfd6e9..d508d375c3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.upstream; -/** - * A source of allocations. - */ +/** A source of allocations. */ public interface Allocator { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java index e529e28846..05838c8a2d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java @@ -24,7 +24,6 @@ import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -71,7 +70,7 @@ public final class AssetDataSource extends BaseDataSource { if (skipped < dataSpec.position) { // assetManager.open() returns an AssetInputStream, whose skip() implementation only skips // fewer bytes than requested if the skip is beyond the end of the asset's data. - throw new EOFException(); + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); } if (dataSpec.length != C.LENGTH_UNSET) { bytesRemaining = dataSpec.length; @@ -111,10 +110,6 @@ public final class AssetDataSource extends BaseDataSource { } if (bytesRead == -1) { - if (bytesRemaining != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new AssetDataSourceException(new EOFException()); - } return C.RESULT_END_OF_INPUT; } if (bytesRemaining != C.LENGTH_UNSET) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java index f35d745892..c5b2893398 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java @@ -17,12 +17,11 @@ package com.google.android.exoplayer2.upstream; import android.os.Handler; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import java.util.concurrent.CopyOnWriteArrayList; -/** - * Provides estimates of the currently available bandwidth. - */ +/** Provides estimates of the currently available bandwidth. */ public interface BandwidthMeter { /** @@ -106,6 +105,14 @@ public interface BandwidthMeter { /** Returns the estimated bitrate. */ long getBitrateEstimate(); + /** + * Returns the estimated time to first byte, in microseconds, or {@link C#TIME_UNSET} if no + * estimate is available. + */ + default long getTimeToFirstByteEstimateUs() { + return C.TIME_UNSET; + } + /** * Returns the {@link TransferListener} that this instance uses to gather bandwidth information * from data transfers. May be null if the implementation does not listen to data transfers. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java index 2ba6ab4c69..63b8fd6e19 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java @@ -24,9 +24,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * A {@link DataSink} for writing to a byte array. - */ +/** A {@link DataSink} for writing to a byte array. */ public final class ByteArrayDataSink implements DataSink { private @MonotonicNonNull ByteArrayOutputStream stream; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java index 17e9073128..24d2c728a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java @@ -47,16 +47,17 @@ public final class ByteArrayDataSource extends BaseDataSource { public long open(DataSpec dataSpec) throws IOException { uri = dataSpec.uri; transferInitializing(dataSpec); + if (dataSpec.position > data.length) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } readPosition = (int) dataSpec.position; - bytesRemaining = (int) ((dataSpec.length == C.LENGTH_UNSET) - ? (data.length - dataSpec.position) : dataSpec.length); - if (bytesRemaining <= 0 || readPosition + bytesRemaining > data.length) { - throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length - + "], length: " + data.length); + bytesRemaining = data.length - (int) dataSpec.position; + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = (int) min(bytesRemaining, dataSpec.length); } opened = true; transferStarted(dataSpec); - return bytesRemaining; + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining; } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java index b659c5ca98..be93f883fe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java @@ -24,7 +24,6 @@ import android.content.res.AssetFileDescriptor; import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import java.io.EOFException; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -72,48 +71,61 @@ public final class ContentDataSource extends BaseDataSource { if (assetFileDescriptor == null) { throw new FileNotFoundException("Could not open file descriptor for: " + uri); } + + long assetFileDescriptorLength = assetFileDescriptor.getLength(); FileInputStream inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); this.inputStream = inputStream; - long assetStartOffset = assetFileDescriptor.getStartOffset(); - long skipped = inputStream.skip(assetStartOffset + dataSpec.position) - assetStartOffset; + // We can't rely only on the "skipped < dataSpec.position" check below to detect whether the + // position is beyond the end of the asset being read. This is because the file may contain + // multiple assets, and there's nothing to prevent InputStream.skip() from succeeding by + // skipping into the data of the next asset. Hence we also need to check against the asset + // length explicitly, which is guaranteed to be set unless the asset extends to the end of the + // file. + if (assetFileDescriptorLength != AssetFileDescriptor.UNKNOWN_LENGTH + && dataSpec.position > assetFileDescriptorLength) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + long assetFileDescriptorOffset = assetFileDescriptor.getStartOffset(); + long skipped = + inputStream.skip(assetFileDescriptorOffset + dataSpec.position) + - assetFileDescriptorOffset; if (skipped != dataSpec.position) { // We expect the skip to be satisfied in full. If it isn't then we're probably trying to - // skip beyond the end of the data. - throw new EOFException(); + // read beyond the end of the last resource in the file. + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); } - if (dataSpec.length != C.LENGTH_UNSET) { - bytesRemaining = dataSpec.length; - } else { - long assetFileDescriptorLength = assetFileDescriptor.getLength(); - if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) { - // The asset must extend to the end of the file. If FileInputStream.getChannel().size() - // returns 0 then the remaining length cannot be determined. - FileChannel channel = inputStream.getChannel(); - long channelSize = channel.size(); - if (channelSize == 0) { - bytesRemaining = C.LENGTH_UNSET; - } else { - bytesRemaining = channelSize - channel.position(); - if (bytesRemaining < 0) { - throw new EOFException(); - } - } + if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) { + // The asset must extend to the end of the file. We can try and resolve the length with + // FileInputStream.getChannel().size(). + FileChannel channel = inputStream.getChannel(); + long channelSize = channel.size(); + if (channelSize == 0) { + bytesRemaining = C.LENGTH_UNSET; } else { - bytesRemaining = assetFileDescriptorLength - skipped; + bytesRemaining = channelSize - channel.position(); if (bytesRemaining < 0) { - throw new EOFException(); + // The skip above was satisfied in full, but skipped beyond the end of the file. + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); } } + } else { + bytesRemaining = assetFileDescriptorLength - skipped; + if (bytesRemaining < 0) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } } } catch (IOException e) { throw new ContentDataSourceException(e); } + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = + bytesRemaining == C.LENGTH_UNSET ? dataSpec.length : min(bytesRemaining, dataSpec.length); + } opened = true; transferStarted(dataSpec); - - return bytesRemaining; + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining; } @Override @@ -134,10 +146,6 @@ public final class ContentDataSource extends BaseDataSource { } if (bytesRead == -1) { - if (bytesRemaining != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new ContentDataSourceException(new EOFException()); - } return C.RESULT_END_OF_INPUT; } if (bytesRemaining != C.LENGTH_UNSET) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java index 2b9cf00e47..0d1166e8a2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java @@ -35,8 +35,8 @@ public final class DataSchemeDataSource extends BaseDataSource { @Nullable private DataSpec dataSpec; @Nullable private byte[] data; - private int endPosition; private int readPosition; + private int bytesRemaining; public DataSchemeDataSource() { super(/* isNetwork= */ false); @@ -46,7 +46,6 @@ public final class DataSchemeDataSource extends BaseDataSource { public long open(DataSpec dataSpec) throws IOException { transferInitializing(dataSpec); this.dataSpec = dataSpec; - readPosition = (int) dataSpec.position; Uri uri = dataSpec.uri; String scheme = uri.getScheme(); if (!SCHEME_DATA.equals(scheme)) { @@ -67,14 +66,17 @@ public final class DataSchemeDataSource extends BaseDataSource { // TODO: Add support for other charsets. data = Util.getUtf8Bytes(URLDecoder.decode(dataString, Charsets.US_ASCII.name())); } - endPosition = - dataSpec.length != C.LENGTH_UNSET ? (int) dataSpec.length + readPosition : data.length; - if (endPosition > data.length || readPosition > endPosition) { + if (dataSpec.position > data.length) { data = null; throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); } + readPosition = (int) dataSpec.position; + bytesRemaining = data.length - readPosition; + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = (int) min(bytesRemaining, dataSpec.length); + } transferStarted(dataSpec); - return (long) endPosition - readPosition; + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining; } @Override @@ -82,13 +84,13 @@ public final class DataSchemeDataSource extends BaseDataSource { if (readLength == 0) { return 0; } - int remainingBytes = endPosition - readPosition; - if (remainingBytes == 0) { + if (bytesRemaining == 0) { return C.RESULT_END_OF_INPUT; } - readLength = min(readLength, remainingBytes); + readLength = min(readLength, bytesRemaining); System.arraycopy(castNonNull(data), readPosition, buffer, offset, readLength); readPosition += readLength; + bytesRemaining -= readLength; bytesTransferred(readLength); return readLength; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java index 4973bb71e8..594403c33d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.upstream; import java.io.IOException; -/** - * A component to which streams of data can be written. - */ +/** A component to which streams of data can be written. */ public interface DataSink { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java index bb4ce1d0c1..f354dc58ef 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java @@ -23,9 +23,7 @@ import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import org.checkerframework.checker.nullness.compatqual.NullableType; -/** - * Default implementation of {@link Allocator}. - */ +/** Default implementation of {@link Allocator}. */ public final class DefaultAllocator implements Allocator { private static final int AVAILABLE_EXTRA_CAPACITY = 100; 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 8a5c4447c3..1c0b3f2642 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 @@ -15,29 +15,23 @@ */ package com.google.android.exoplayer2.upstream; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; import android.os.Handler; -import android.os.Looper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.BandwidthMeter.EventListener.EventDispatcher; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.NetworkTypeObserver; import com.google.android.exoplayer2.util.SlidingPercentile; 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.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Estimates bandwidth by listening to data transfers. @@ -51,30 +45,34 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /** * 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]. + * 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(6_100_000L, 3_800_000L, 2_100_000L, 1_300_000L, 590_000L); + ImmutableList.of(6_200_000L, 3_900_000L, 2_300_000L, 1_300_000L, 620_000L); /** Default initial 2G bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = - ImmutableList.of(218_000L, 159_000L, 145_000L, 130_000L, 112_000L); + ImmutableList.of(248_000L, 160_000L, 142_000L, 127_000L, 113_000L); /** Default initial 3G bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = - ImmutableList.of(2_200_000L, 1_300_000L, 930_000L, 730_000L, 530_000L); + ImmutableList.of(2_200_000L, 1_300_000L, 950_000L, 760_000L, 520_000L); /** Default initial 4G bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = - ImmutableList.of(4_800_000L, 2_700_000L, 1_800_000L, 1_200_000L, 630_000L); + ImmutableList.of(4_400_000L, 2_300_000L, 1_500_000L, 1_100_000L, 640_000L); /** Default initial 5G-NSA bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA = - ImmutableList.of(12_000_000L, 8_800_000L, 5_900_000L, 3_500_000L, 1_800_000L); + ImmutableList.of(10_000_000L, 7_200_000L, 5_000_000L, 2_700_000L, 1_600_000L); + + /** Default initial 5G-SA bitrate estimates in bits per second. */ + public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA = + ImmutableList.of(2_600_000L, 2_200_000L, 2_000_000L, 1_500_000L, 470_000L); /** * Default initial bitrate estimate used when the device is offline or the network type cannot be @@ -95,6 +93,8 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private static final int COUNTRY_GROUP_INDEX_4G = 3; /** Index for the 5G-NSA group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ private static final int COUNTRY_GROUP_INDEX_5G_NSA = 4; + /** Index for the 5G-SA group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + private static final int COUNTRY_GROUP_INDEX_5G_SA = 5; @Nullable private static DefaultBandwidthMeter singletonInstance; @@ -171,7 +171,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList */ public Builder setInitialBitrateEstimate(String countryCode) { initialBitrateEstimates = - getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode)); + getInitialBitrateEstimatesForCountry(Ascii.toUpperCase(countryCode)); return this; } @@ -214,7 +214,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private static Map getInitialBitrateEstimatesForCountry(String countryCode) { List groupIndices = getCountryGroupIndices(countryCode); - Map result = new HashMap<>(/* initialCapacity= */ 6); + Map result = new HashMap<>(/* initialCapacity= */ 8); result.put(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); result.put( C.NETWORK_TYPE_WIFI, @@ -229,9 +229,12 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G.get(groupIndices.get(COUNTRY_GROUP_INDEX_4G))); result.put( - C.NETWORK_TYPE_5G, + C.NETWORK_TYPE_5G_NSA, DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA.get( groupIndices.get(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))); // Assume default Wifi speed for Ethernet to prevent using the slower fallback. result.put( C.NETWORK_TYPE_ETHERNET, @@ -242,7 +245,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList 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) : groupIndices; + return groupIndices.isEmpty() ? ImmutableList.of(2, 2, 2, 2, 2, 2) : groupIndices; } } @@ -262,11 +265,11 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; - @Nullable private final Context context; private final ImmutableMap initialBitrateEstimates; private final EventDispatcher eventDispatcher; private final SlidingPercentile slidingPercentile; private final Clock clock; + private final boolean resetOnNetworkTypeChange; private int streamCount; private long sampleStartTimeMs; @@ -298,19 +301,19 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList int maxWeight, Clock clock, boolean resetOnNetworkTypeChange) { - this.context = context == null ? null : context.getApplicationContext(); this.initialBitrateEstimates = ImmutableMap.copyOf(initialBitrateEstimates); this.eventDispatcher = new EventDispatcher(); this.slidingPercentile = new SlidingPercentile(maxWeight); this.clock = clock; - // Set the initial network type and bitrate estimate - networkType = context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context); - bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); - // Register to receive connectivity actions if possible. - if (context != null && resetOnNetworkTypeChange) { - ConnectivityActionReceiver connectivityActionReceiver = - ConnectivityActionReceiver.getInstance(context); - connectivityActionReceiver.register(/* bandwidthMeter= */ this); + this.resetOnNetworkTypeChange = resetOnNetworkTypeChange; + if (context != null) { + NetworkTypeObserver networkTypeObserver = NetworkTypeObserver.getInstance(context); + networkType = networkTypeObserver.getNetworkType(); + bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); + networkTypeObserver.register(/* listener= */ this::onNetworkTypeChanged); + } else { + networkType = C.NETWORK_TYPE_UNKNOWN; + bitrateEstimate = getInitialBitrateEstimateForNetworkType(C.NETWORK_TYPE_UNKNOWN); } } @@ -325,7 +328,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList public synchronized void setNetworkTypeOverride(@C.NetworkType int networkType) { networkTypeOverride = networkType; networkTypeOverrideSet = true; - onConnectivityAction(); + onNetworkTypeChanged(networkType); } @Override @@ -400,11 +403,15 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList streamCount--; } - private synchronized void onConnectivityAction() { - int networkType = - networkTypeOverrideSet - ? networkTypeOverride - : (context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); + private synchronized void onNetworkTypeChanged(@C.NetworkType int networkType) { + if (this.networkType != C.NETWORK_TYPE_UNKNOWN && !resetOnNetworkTypeChange) { + // Reset on network change disabled. Ignore all updates except the initial one. + return; + } + + if (networkTypeOverrideSet) { + networkType = networkTypeOverride; + } if (this.networkType == networkType) { return; } @@ -455,312 +462,248 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList return isNetwork && !dataSpec.isFlagSet(DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED); } - /* - * Note: This class only holds a weak reference to DefaultBandwidthMeter instances. It should not - * be made non-static, since doing so adds a strong reference (i.e. DefaultBandwidthMeter.this). - */ - private static class ConnectivityActionReceiver extends BroadcastReceiver { - - private static @MonotonicNonNull ConnectivityActionReceiver staticInstance; - - private final Handler mainHandler; - private final ArrayList> bandwidthMeters; - - public static synchronized ConnectivityActionReceiver getInstance(Context context) { - if (staticInstance == null) { - staticInstance = new ConnectivityActionReceiver(); - IntentFilter filter = new IntentFilter(); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - context.registerReceiver(staticInstance, filter); - } - return staticInstance; - } - - private ConnectivityActionReceiver() { - mainHandler = new Handler(Looper.getMainLooper()); - bandwidthMeters = new ArrayList<>(); - } - - public synchronized void register(DefaultBandwidthMeter bandwidthMeter) { - removeClearedReferences(); - bandwidthMeters.add(new WeakReference<>(bandwidthMeter)); - // Simulate an initial update on the main thread (like the sticky broadcast we'd receive if - // we were to register a separate broadcast receiver for each bandwidth meter). - mainHandler.post(() -> updateBandwidthMeter(bandwidthMeter)); - } - - @Override - public synchronized void onReceive(Context context, Intent intent) { - if (isInitialStickyBroadcast()) { - return; - } - removeClearedReferences(); - for (int i = 0; i < bandwidthMeters.size(); i++) { - WeakReference bandwidthMeterReference = bandwidthMeters.get(i); - DefaultBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); - if (bandwidthMeter != null) { - updateBandwidthMeter(bandwidthMeter); - } - } - } - - private void updateBandwidthMeter(DefaultBandwidthMeter bandwidthMeter) { - bandwidthMeter.onConnectivityAction(); - } - - private void removeClearedReferences() { - for (int i = bandwidthMeters.size() - 1; i >= 0; i--) { - WeakReference bandwidthMeterReference = bandwidthMeters.get(i); - DefaultBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); - if (bandwidthMeter == null) { - bandwidthMeters.remove(i); - } - } - } - } - private static ImmutableListMultimap createInitialBitrateCountryGroupAssignment() { - ImmutableListMultimap.Builder countryGroupAssignment = - ImmutableListMultimap.builder(); - countryGroupAssignment.putAll("AD", 1, 2, 0, 0, 2); - countryGroupAssignment.putAll("AE", 1, 4, 4, 4, 1); - countryGroupAssignment.putAll("AF", 4, 4, 3, 4, 2); - countryGroupAssignment.putAll("AG", 2, 2, 1, 1, 2); - countryGroupAssignment.putAll("AI", 1, 2, 2, 2, 2); - countryGroupAssignment.putAll("AL", 1, 1, 0, 1, 2); - countryGroupAssignment.putAll("AM", 2, 2, 1, 2, 2); - countryGroupAssignment.putAll("AO", 3, 4, 4, 2, 2); - countryGroupAssignment.putAll("AR", 2, 4, 2, 2, 2); - countryGroupAssignment.putAll("AS", 2, 2, 4, 3, 2); - countryGroupAssignment.putAll("AT", 0, 3, 0, 0, 2); - countryGroupAssignment.putAll("AU", 0, 2, 0, 1, 1); - countryGroupAssignment.putAll("AW", 1, 2, 0, 4, 2); - countryGroupAssignment.putAll("AX", 0, 2, 2, 2, 2); - countryGroupAssignment.putAll("AZ", 3, 3, 3, 4, 2); - countryGroupAssignment.putAll("BA", 1, 1, 0, 1, 2); - countryGroupAssignment.putAll("BB", 0, 2, 0, 0, 2); - countryGroupAssignment.putAll("BD", 2, 0, 3, 3, 2); - countryGroupAssignment.putAll("BE", 0, 1, 2, 3, 2); - countryGroupAssignment.putAll("BF", 4, 4, 4, 2, 2); - countryGroupAssignment.putAll("BG", 0, 1, 0, 0, 2); - countryGroupAssignment.putAll("BH", 1, 0, 2, 4, 2); - countryGroupAssignment.putAll("BI", 4, 4, 4, 4, 2); - countryGroupAssignment.putAll("BJ", 4, 4, 3, 4, 2); - countryGroupAssignment.putAll("BL", 1, 2, 2, 2, 2); - countryGroupAssignment.putAll("BM", 1, 2, 0, 0, 2); - countryGroupAssignment.putAll("BN", 4, 0, 1, 1, 2); - countryGroupAssignment.putAll("BO", 2, 3, 3, 2, 2); - countryGroupAssignment.putAll("BQ", 1, 2, 1, 2, 2); - countryGroupAssignment.putAll("BR", 2, 4, 2, 1, 2); - countryGroupAssignment.putAll("BS", 3, 2, 2, 3, 2); - countryGroupAssignment.putAll("BT", 3, 0, 3, 2, 2); - countryGroupAssignment.putAll("BW", 3, 4, 2, 2, 2); - countryGroupAssignment.putAll("BY", 1, 0, 2, 1, 2); - countryGroupAssignment.putAll("BZ", 2, 2, 2, 1, 2); - countryGroupAssignment.putAll("CA", 0, 3, 1, 2, 3); - countryGroupAssignment.putAll("CD", 4, 3, 2, 2, 2); - countryGroupAssignment.putAll("CF", 4, 2, 2, 2, 2); - countryGroupAssignment.putAll("CG", 3, 4, 1, 1, 2); - countryGroupAssignment.putAll("CH", 0, 1, 0, 0, 0); - countryGroupAssignment.putAll("CI", 3, 3, 3, 3, 2); - countryGroupAssignment.putAll("CK", 3, 2, 1, 0, 2); - countryGroupAssignment.putAll("CL", 1, 1, 2, 3, 2); - countryGroupAssignment.putAll("CM", 3, 4, 3, 2, 2); - countryGroupAssignment.putAll("CN", 2, 2, 2, 1, 3); - countryGroupAssignment.putAll("CO", 2, 4, 3, 2, 2); - countryGroupAssignment.putAll("CR", 2, 3, 4, 4, 2); - countryGroupAssignment.putAll("CU", 4, 4, 2, 1, 2); - countryGroupAssignment.putAll("CV", 2, 3, 3, 3, 2); - countryGroupAssignment.putAll("CW", 1, 2, 0, 0, 2); - countryGroupAssignment.putAll("CY", 1, 2, 0, 0, 2); - countryGroupAssignment.putAll("CZ", 0, 1, 0, 0, 2); - countryGroupAssignment.putAll("DE", 0, 1, 1, 2, 0); - countryGroupAssignment.putAll("DJ", 4, 1, 4, 4, 2); - countryGroupAssignment.putAll("DK", 0, 0, 1, 0, 2); - countryGroupAssignment.putAll("DM", 1, 2, 2, 2, 2); - countryGroupAssignment.putAll("DO", 3, 4, 4, 4, 2); - countryGroupAssignment.putAll("DZ", 3, 2, 4, 4, 2); - countryGroupAssignment.putAll("EC", 2, 4, 3, 2, 2); - countryGroupAssignment.putAll("EE", 0, 0, 0, 0, 2); - countryGroupAssignment.putAll("EG", 3, 4, 2, 1, 2); - countryGroupAssignment.putAll("EH", 2, 2, 2, 2, 2); - countryGroupAssignment.putAll("ER", 4, 2, 2, 2, 2); - countryGroupAssignment.putAll("ES", 0, 1, 2, 1, 2); - countryGroupAssignment.putAll("ET", 4, 4, 4, 1, 2); - countryGroupAssignment.putAll("FI", 0, 0, 1, 0, 0); - countryGroupAssignment.putAll("FJ", 3, 0, 3, 3, 2); - countryGroupAssignment.putAll("FK", 2, 2, 2, 2, 2); - countryGroupAssignment.putAll("FM", 4, 2, 4, 3, 2); - countryGroupAssignment.putAll("FO", 0, 2, 0, 0, 2); - countryGroupAssignment.putAll("FR", 1, 0, 2, 1, 2); - countryGroupAssignment.putAll("GA", 3, 3, 1, 0, 2); - countryGroupAssignment.putAll("GB", 0, 0, 1, 2, 2); - countryGroupAssignment.putAll("GD", 1, 2, 2, 2, 2); - countryGroupAssignment.putAll("GE", 1, 0, 1, 3, 2); - countryGroupAssignment.putAll("GF", 2, 2, 2, 4, 2); - countryGroupAssignment.putAll("GG", 0, 2, 0, 0, 2); - countryGroupAssignment.putAll("GH", 3, 2, 3, 2, 2); - countryGroupAssignment.putAll("GI", 0, 2, 0, 0, 2); - countryGroupAssignment.putAll("GL", 1, 2, 2, 1, 2); - countryGroupAssignment.putAll("GM", 4, 3, 2, 4, 2); - countryGroupAssignment.putAll("GN", 4, 3, 4, 2, 2); - countryGroupAssignment.putAll("GP", 2, 2, 3, 4, 2); - countryGroupAssignment.putAll("GQ", 4, 2, 3, 4, 2); - countryGroupAssignment.putAll("GR", 1, 1, 0, 1, 2); - countryGroupAssignment.putAll("GT", 3, 2, 3, 2, 2); - countryGroupAssignment.putAll("GU", 1, 2, 4, 4, 2); - countryGroupAssignment.putAll("GW", 3, 4, 4, 3, 2); - countryGroupAssignment.putAll("GY", 3, 3, 1, 0, 2); - countryGroupAssignment.putAll("HK", 0, 2, 3, 4, 2); - countryGroupAssignment.putAll("HN", 3, 0, 3, 3, 2); - countryGroupAssignment.putAll("HR", 1, 1, 0, 1, 2); - countryGroupAssignment.putAll("HT", 4, 3, 4, 4, 2); - countryGroupAssignment.putAll("HU", 0, 1, 0, 0, 2); - countryGroupAssignment.putAll("ID", 3, 2, 2, 3, 2); - countryGroupAssignment.putAll("IE", 0, 0, 1, 1, 2); - countryGroupAssignment.putAll("IL", 1, 0, 2, 3, 2); - countryGroupAssignment.putAll("IM", 0, 2, 0, 1, 2); - countryGroupAssignment.putAll("IN", 2, 1, 3, 3, 2); - countryGroupAssignment.putAll("IO", 4, 2, 2, 4, 2); - countryGroupAssignment.putAll("IQ", 3, 2, 4, 3, 2); - countryGroupAssignment.putAll("IR", 4, 2, 3, 4, 2); - countryGroupAssignment.putAll("IS", 0, 2, 0, 0, 2); - countryGroupAssignment.putAll("IT", 0, 0, 1, 1, 2); - countryGroupAssignment.putAll("JE", 2, 2, 0, 2, 2); - countryGroupAssignment.putAll("JM", 3, 3, 4, 4, 2); - countryGroupAssignment.putAll("JO", 1, 2, 1, 1, 2); - countryGroupAssignment.putAll("JP", 0, 2, 0, 1, 3); - countryGroupAssignment.putAll("KE", 3, 4, 2, 2, 2); - countryGroupAssignment.putAll("KG", 1, 0, 2, 2, 2); - countryGroupAssignment.putAll("KH", 2, 0, 4, 3, 2); - countryGroupAssignment.putAll("KI", 4, 2, 3, 1, 2); - countryGroupAssignment.putAll("KM", 4, 2, 2, 3, 2); - countryGroupAssignment.putAll("KN", 1, 2, 2, 2, 2); - countryGroupAssignment.putAll("KP", 4, 2, 2, 2, 2); - countryGroupAssignment.putAll("KR", 0, 2, 1, 1, 1); - countryGroupAssignment.putAll("KW", 2, 3, 1, 1, 1); - countryGroupAssignment.putAll("KY", 1, 2, 0, 0, 2); - countryGroupAssignment.putAll("KZ", 1, 2, 2, 3, 2); - countryGroupAssignment.putAll("LA", 2, 2, 1, 1, 2); - countryGroupAssignment.putAll("LB", 3, 2, 0, 0, 2); - countryGroupAssignment.putAll("LC", 1, 1, 0, 0, 2); - countryGroupAssignment.putAll("LI", 0, 2, 2, 2, 2); - countryGroupAssignment.putAll("LK", 2, 0, 2, 3, 2); - countryGroupAssignment.putAll("LR", 3, 4, 3, 2, 2); - countryGroupAssignment.putAll("LS", 3, 3, 2, 3, 2); - countryGroupAssignment.putAll("LT", 0, 0, 0, 0, 2); - countryGroupAssignment.putAll("LU", 0, 0, 0, 0, 2); - countryGroupAssignment.putAll("LV", 0, 0, 0, 0, 2); - countryGroupAssignment.putAll("LY", 4, 2, 4, 3, 2); - countryGroupAssignment.putAll("MA", 2, 1, 2, 1, 2); - countryGroupAssignment.putAll("MC", 0, 2, 2, 2, 2); - countryGroupAssignment.putAll("MD", 1, 2, 0, 0, 2); - countryGroupAssignment.putAll("ME", 1, 2, 1, 2, 2); - countryGroupAssignment.putAll("MF", 1, 2, 1, 0, 2); - countryGroupAssignment.putAll("MG", 3, 4, 3, 3, 2); - countryGroupAssignment.putAll("MH", 4, 2, 2, 4, 2); - countryGroupAssignment.putAll("MK", 1, 0, 0, 0, 2); - countryGroupAssignment.putAll("ML", 4, 4, 1, 1, 2); - countryGroupAssignment.putAll("MM", 2, 3, 2, 2, 2); - countryGroupAssignment.putAll("MN", 2, 4, 1, 1, 2); - countryGroupAssignment.putAll("MO", 0, 2, 4, 4, 2); - countryGroupAssignment.putAll("MP", 0, 2, 2, 2, 2); - countryGroupAssignment.putAll("MQ", 2, 2, 2, 3, 2); - countryGroupAssignment.putAll("MR", 3, 0, 4, 2, 2); - countryGroupAssignment.putAll("MS", 1, 2, 2, 2, 2); - countryGroupAssignment.putAll("MT", 0, 2, 0, 1, 2); - countryGroupAssignment.putAll("MU", 3, 1, 2, 3, 2); - countryGroupAssignment.putAll("MV", 4, 3, 1, 4, 2); - countryGroupAssignment.putAll("MW", 4, 1, 1, 0, 2); - countryGroupAssignment.putAll("MX", 2, 4, 3, 3, 2); - countryGroupAssignment.putAll("MY", 2, 0, 3, 3, 2); - countryGroupAssignment.putAll("MZ", 3, 3, 2, 3, 2); - countryGroupAssignment.putAll("NA", 4, 3, 2, 2, 2); - countryGroupAssignment.putAll("NC", 2, 0, 4, 4, 2); - countryGroupAssignment.putAll("NE", 4, 4, 4, 4, 2); - countryGroupAssignment.putAll("NF", 2, 2, 2, 2, 2); - countryGroupAssignment.putAll("NG", 3, 3, 2, 2, 2); - countryGroupAssignment.putAll("NI", 3, 1, 4, 4, 2); - countryGroupAssignment.putAll("NL", 0, 2, 4, 2, 0); - countryGroupAssignment.putAll("NO", 0, 1, 1, 0, 2); - countryGroupAssignment.putAll("NP", 2, 0, 4, 3, 2); - countryGroupAssignment.putAll("NR", 4, 2, 3, 1, 2); - countryGroupAssignment.putAll("NU", 4, 2, 2, 2, 2); - countryGroupAssignment.putAll("NZ", 0, 2, 1, 2, 4); - countryGroupAssignment.putAll("OM", 2, 2, 0, 2, 2); - countryGroupAssignment.putAll("PA", 1, 3, 3, 4, 2); - countryGroupAssignment.putAll("PE", 2, 4, 4, 4, 2); - countryGroupAssignment.putAll("PF", 2, 2, 1, 1, 2); - countryGroupAssignment.putAll("PG", 4, 3, 3, 2, 2); - countryGroupAssignment.putAll("PH", 3, 0, 3, 4, 4); - countryGroupAssignment.putAll("PK", 3, 2, 3, 3, 2); - countryGroupAssignment.putAll("PL", 1, 0, 2, 2, 2); - countryGroupAssignment.putAll("PM", 0, 2, 2, 2, 2); - countryGroupAssignment.putAll("PR", 1, 2, 2, 3, 4); - countryGroupAssignment.putAll("PS", 3, 3, 2, 2, 2); - countryGroupAssignment.putAll("PT", 1, 1, 0, 0, 2); - countryGroupAssignment.putAll("PW", 1, 2, 3, 0, 2); - countryGroupAssignment.putAll("PY", 2, 0, 3, 3, 2); - countryGroupAssignment.putAll("QA", 2, 3, 1, 2, 2); - countryGroupAssignment.putAll("RE", 1, 0, 2, 1, 2); - countryGroupAssignment.putAll("RO", 1, 1, 1, 2, 2); - countryGroupAssignment.putAll("RS", 1, 2, 0, 0, 2); - countryGroupAssignment.putAll("RU", 0, 1, 0, 1, 2); - countryGroupAssignment.putAll("RW", 4, 3, 3, 4, 2); - countryGroupAssignment.putAll("SA", 2, 2, 2, 1, 2); - countryGroupAssignment.putAll("SB", 4, 2, 4, 2, 2); - countryGroupAssignment.putAll("SC", 4, 2, 0, 1, 2); - countryGroupAssignment.putAll("SD", 4, 4, 4, 3, 2); - countryGroupAssignment.putAll("SE", 0, 0, 0, 0, 2); - countryGroupAssignment.putAll("SG", 0, 0, 3, 3, 4); - countryGroupAssignment.putAll("SH", 4, 2, 2, 2, 2); - countryGroupAssignment.putAll("SI", 0, 1, 0, 0, 2); - countryGroupAssignment.putAll("SJ", 2, 2, 2, 2, 2); - countryGroupAssignment.putAll("SK", 0, 1, 0, 0, 2); - countryGroupAssignment.putAll("SL", 4, 3, 3, 1, 2); - countryGroupAssignment.putAll("SM", 0, 2, 2, 2, 2); - countryGroupAssignment.putAll("SN", 4, 4, 4, 3, 2); - countryGroupAssignment.putAll("SO", 3, 4, 4, 4, 2); - countryGroupAssignment.putAll("SR", 3, 2, 3, 1, 2); - countryGroupAssignment.putAll("SS", 4, 1, 4, 2, 2); - countryGroupAssignment.putAll("ST", 2, 2, 1, 2, 2); - countryGroupAssignment.putAll("SV", 2, 1, 4, 4, 2); - countryGroupAssignment.putAll("SX", 2, 2, 1, 0, 2); - countryGroupAssignment.putAll("SY", 4, 3, 2, 2, 2); - countryGroupAssignment.putAll("SZ", 3, 4, 3, 4, 2); - countryGroupAssignment.putAll("TC", 1, 2, 1, 0, 2); - countryGroupAssignment.putAll("TD", 4, 4, 4, 4, 2); - countryGroupAssignment.putAll("TG", 3, 2, 1, 0, 2); - countryGroupAssignment.putAll("TH", 1, 3, 4, 3, 0); - countryGroupAssignment.putAll("TJ", 4, 4, 4, 4, 2); - countryGroupAssignment.putAll("TL", 4, 1, 4, 4, 2); - countryGroupAssignment.putAll("TM", 4, 2, 1, 2, 2); - countryGroupAssignment.putAll("TN", 2, 1, 1, 1, 2); - countryGroupAssignment.putAll("TO", 3, 3, 4, 2, 2); - countryGroupAssignment.putAll("TR", 1, 2, 1, 1, 2); - countryGroupAssignment.putAll("TT", 1, 3, 1, 3, 2); - countryGroupAssignment.putAll("TV", 3, 2, 2, 4, 2); - countryGroupAssignment.putAll("TW", 0, 0, 0, 0, 1); - countryGroupAssignment.putAll("TZ", 3, 3, 3, 2, 2); - countryGroupAssignment.putAll("UA", 0, 3, 0, 0, 2); - countryGroupAssignment.putAll("UG", 3, 2, 2, 3, 2); - countryGroupAssignment.putAll("US", 0, 1, 3, 3, 3); - countryGroupAssignment.putAll("UY", 2, 1, 1, 1, 2); - countryGroupAssignment.putAll("UZ", 2, 0, 3, 2, 2); - countryGroupAssignment.putAll("VC", 2, 2, 2, 2, 2); - countryGroupAssignment.putAll("VE", 4, 4, 4, 4, 2); - countryGroupAssignment.putAll("VG", 2, 2, 1, 2, 2); - countryGroupAssignment.putAll("VI", 1, 2, 2, 4, 2); - countryGroupAssignment.putAll("VN", 0, 1, 4, 4, 2); - countryGroupAssignment.putAll("VU", 4, 1, 3, 1, 2); - countryGroupAssignment.putAll("WS", 3, 1, 4, 2, 2); - countryGroupAssignment.putAll("XK", 1, 1, 1, 0, 2); - countryGroupAssignment.putAll("YE", 4, 4, 4, 4, 2); - countryGroupAssignment.putAll("YT", 3, 2, 1, 3, 2); - countryGroupAssignment.putAll("ZA", 2, 3, 2, 2, 2); - countryGroupAssignment.putAll("ZM", 3, 2, 2, 3, 2); - countryGroupAssignment.putAll("ZW", 3, 3, 3, 3, 2); - return countryGroupAssignment.build(); + return ImmutableListMultimap.builder() + .putAll("AD", 1, 2, 0, 0, 2, 2) + .putAll("AE", 1, 4, 4, 4, 2, 2) + .putAll("AF", 4, 4, 3, 4, 2, 2) + .putAll("AG", 4, 2, 1, 4, 2, 2) + .putAll("AI", 1, 2, 2, 2, 2, 2) + .putAll("AL", 1, 1, 1, 1, 2, 2) + .putAll("AM", 2, 2, 1, 3, 2, 2) + .putAll("AO", 3, 4, 3, 1, 2, 2) + .putAll("AR", 2, 4, 2, 1, 2, 2) + .putAll("AS", 2, 2, 3, 3, 2, 2) + .putAll("AT", 0, 1, 0, 0, 0, 2) + .putAll("AU", 0, 2, 0, 1, 1, 2) + .putAll("AW", 1, 2, 0, 4, 2, 2) + .putAll("AX", 0, 2, 2, 2, 2, 2) + .putAll("AZ", 3, 3, 3, 4, 4, 2) + .putAll("BA", 1, 1, 0, 1, 2, 2) + .putAll("BB", 0, 2, 0, 0, 2, 2) + .putAll("BD", 2, 0, 3, 3, 2, 2) + .putAll("BE", 0, 0, 2, 3, 2, 2) + .putAll("BF", 4, 4, 4, 2, 2, 2) + .putAll("BG", 0, 1, 0, 0, 2, 2) + .putAll("BH", 1, 0, 2, 4, 2, 2) + .putAll("BI", 4, 4, 4, 4, 2, 2) + .putAll("BJ", 4, 4, 4, 4, 2, 2) + .putAll("BL", 1, 2, 2, 2, 2, 2) + .putAll("BM", 0, 2, 0, 0, 2, 2) + .putAll("BN", 3, 2, 1, 0, 2, 2) + .putAll("BO", 1, 2, 4, 2, 2, 2) + .putAll("BQ", 1, 2, 1, 2, 2, 2) + .putAll("BR", 2, 4, 3, 2, 2, 2) + .putAll("BS", 2, 2, 1, 3, 2, 2) + .putAll("BT", 3, 0, 3, 2, 2, 2) + .putAll("BW", 3, 4, 1, 1, 2, 2) + .putAll("BY", 1, 1, 1, 2, 2, 2) + .putAll("BZ", 2, 2, 2, 2, 2, 2) + .putAll("CA", 0, 3, 1, 2, 4, 2) + .putAll("CD", 4, 2, 2, 1, 2, 2) + .putAll("CF", 4, 2, 3, 2, 2, 2) + .putAll("CG", 3, 4, 2, 2, 2, 2) + .putAll("CH", 0, 0, 0, 0, 1, 2) + .putAll("CI", 3, 3, 3, 3, 2, 2) + .putAll("CK", 2, 2, 3, 0, 2, 2) + .putAll("CL", 1, 1, 2, 2, 2, 2) + .putAll("CM", 3, 4, 3, 2, 2, 2) + .putAll("CN", 2, 2, 2, 1, 3, 2) + .putAll("CO", 2, 3, 4, 2, 2, 2) + .putAll("CR", 2, 3, 4, 4, 2, 2) + .putAll("CU", 4, 4, 2, 2, 2, 2) + .putAll("CV", 2, 3, 1, 0, 2, 2) + .putAll("CW", 1, 2, 0, 0, 2, 2) + .putAll("CY", 1, 1, 0, 0, 2, 2) + .putAll("CZ", 0, 1, 0, 0, 1, 2) + .putAll("DE", 0, 0, 1, 1, 0, 2) + .putAll("DJ", 4, 0, 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", 3, 3, 4, 4, 2, 4) + .putAll("EC", 2, 4, 3, 1, 2, 2) + .putAll("EE", 0, 1, 0, 0, 2, 2) + .putAll("EG", 3, 4, 3, 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, 4, 1, 2, 2) + .putAll("FI", 0, 0, 0, 0, 0, 2) + .putAll("FJ", 3, 0, 2, 3, 2, 2) + .putAll("FK", 4, 2, 2, 2, 2, 2) + .putAll("FM", 3, 2, 4, 4, 2, 2) + .putAll("FO", 1, 2, 0, 1, 2, 2) + .putAll("FR", 1, 1, 2, 0, 1, 2) + .putAll("GA", 3, 4, 1, 1, 2, 2) + .putAll("GB", 0, 0, 1, 1, 1, 2) + .putAll("GD", 1, 2, 2, 2, 2, 2) + .putAll("GE", 1, 1, 1, 2, 2, 2) + .putAll("GF", 2, 2, 2, 3, 2, 2) + .putAll("GG", 1, 2, 0, 0, 2, 2) + .putAll("GH", 3, 1, 3, 2, 2, 2) + .putAll("GI", 0, 2, 0, 0, 2, 2) + .putAll("GL", 1, 2, 0, 0, 2, 2) + .putAll("GM", 4, 3, 2, 4, 2, 2) + .putAll("GN", 4, 3, 4, 2, 2, 2) + .putAll("GP", 2, 1, 2, 3, 2, 2) + .putAll("GQ", 4, 2, 2, 4, 2, 2) + .putAll("GR", 1, 2, 0, 0, 2, 2) + .putAll("GT", 3, 2, 3, 1, 2, 2) + .putAll("GU", 1, 2, 3, 4, 2, 2) + .putAll("GW", 4, 4, 4, 4, 2, 2) + .putAll("GY", 3, 3, 3, 4, 2, 2) + .putAll("HK", 0, 1, 2, 3, 2, 0) + .putAll("HN", 3, 1, 3, 3, 2, 2) + .putAll("HR", 1, 1, 0, 0, 3, 2) + .putAll("HT", 4, 4, 4, 4, 2, 2) + .putAll("HU", 0, 0, 0, 0, 0, 2) + .putAll("ID", 3, 2, 3, 3, 2, 2) + .putAll("IE", 0, 0, 1, 1, 3, 2) + .putAll("IL", 1, 0, 2, 3, 4, 2) + .putAll("IM", 0, 2, 0, 1, 2, 2) + .putAll("IN", 2, 1, 3, 3, 2, 2) + .putAll("IO", 4, 2, 2, 4, 2, 2) + .putAll("IQ", 3, 3, 4, 4, 2, 2) + .putAll("IR", 3, 2, 3, 2, 2, 2) + .putAll("IS", 0, 2, 0, 0, 2, 2) + .putAll("IT", 0, 4, 0, 1, 2, 2) + .putAll("JE", 2, 2, 1, 2, 2, 2) + .putAll("JM", 3, 3, 4, 4, 2, 2) + .putAll("JO", 2, 2, 1, 1, 2, 2) + .putAll("JP", 0, 0, 0, 0, 2, 1) + .putAll("KE", 3, 4, 2, 2, 2, 2) + .putAll("KG", 2, 0, 1, 1, 2, 2) + .putAll("KH", 1, 0, 4, 3, 2, 2) + .putAll("KI", 4, 2, 4, 3, 2, 2) + .putAll("KM", 4, 3, 2, 3, 2, 2) + .putAll("KN", 1, 2, 2, 2, 2, 2) + .putAll("KP", 4, 2, 2, 2, 2, 2) + .putAll("KR", 0, 0, 1, 3, 1, 2) + .putAll("KW", 1, 3, 1, 1, 1, 2) + .putAll("KY", 1, 2, 0, 2, 2, 2) + .putAll("KZ", 2, 2, 2, 3, 2, 2) + .putAll("LA", 1, 2, 1, 1, 2, 2) + .putAll("LB", 3, 2, 0, 0, 2, 2) + .putAll("LC", 1, 2, 0, 0, 2, 2) + .putAll("LI", 0, 2, 2, 2, 2, 2) + .putAll("LK", 2, 0, 2, 3, 2, 2) + .putAll("LR", 3, 4, 4, 3, 2, 2) + .putAll("LS", 3, 3, 2, 3, 2, 2) + .putAll("LT", 0, 0, 0, 0, 2, 2) + .putAll("LU", 1, 0, 1, 1, 2, 2) + .putAll("LV", 0, 0, 0, 0, 2, 2) + .putAll("LY", 4, 2, 4, 3, 2, 2) + .putAll("MA", 3, 2, 2, 1, 2, 2) + .putAll("MC", 0, 2, 0, 0, 2, 2) + .putAll("MD", 1, 2, 0, 0, 2, 2) + .putAll("ME", 1, 2, 0, 1, 2, 2) + .putAll("MF", 2, 2, 1, 1, 2, 2) + .putAll("MG", 3, 4, 2, 2, 2, 2) + .putAll("MH", 4, 2, 2, 4, 2, 2) + .putAll("MK", 1, 1, 0, 0, 2, 2) + .putAll("ML", 4, 4, 2, 2, 2, 2) + .putAll("MM", 2, 3, 3, 3, 2, 2) + .putAll("MN", 2, 4, 2, 2, 2, 2) + .putAll("MO", 0, 2, 4, 4, 2, 2) + .putAll("MP", 0, 2, 2, 2, 2, 2) + .putAll("MQ", 2, 2, 2, 3, 2, 2) + .putAll("MR", 3, 0, 4, 3, 2, 2) + .putAll("MS", 1, 2, 2, 2, 2, 2) + .putAll("MT", 0, 2, 0, 0, 2, 2) + .putAll("MU", 2, 1, 1, 2, 2, 2) + .putAll("MV", 4, 3, 2, 4, 2, 2) + .putAll("MW", 4, 2, 1, 0, 2, 2) + .putAll("MX", 2, 4, 4, 4, 4, 2) + .putAll("MY", 1, 0, 3, 2, 2, 2) + .putAll("MZ", 3, 3, 2, 1, 2, 2) + .putAll("NA", 4, 3, 3, 2, 2, 2) + .putAll("NC", 3, 0, 4, 4, 2, 2) + .putAll("NE", 4, 4, 4, 4, 2, 2) + .putAll("NF", 2, 2, 2, 2, 2, 2) + .putAll("NG", 3, 3, 2, 3, 2, 2) + .putAll("NI", 2, 1, 4, 4, 2, 2) + .putAll("NL", 0, 2, 3, 2, 0, 2) + .putAll("NO", 0, 1, 2, 0, 0, 2) + .putAll("NP", 2, 0, 4, 2, 2, 2) + .putAll("NR", 3, 2, 3, 1, 2, 2) + .putAll("NU", 4, 2, 2, 2, 2, 2) + .putAll("NZ", 0, 2, 1, 2, 4, 2) + .putAll("OM", 2, 2, 1, 3, 3, 2) + .putAll("PA", 1, 3, 3, 3, 2, 2) + .putAll("PE", 2, 3, 4, 4, 2, 2) + .putAll("PF", 2, 2, 2, 1, 2, 2) + .putAll("PG", 4, 4, 3, 2, 2, 2) + .putAll("PH", 2, 1, 3, 3, 3, 2) + .putAll("PK", 3, 2, 3, 3, 2, 2) + .putAll("PL", 1, 0, 1, 2, 3, 2) + .putAll("PM", 0, 2, 2, 2, 2, 2) + .putAll("PR", 2, 1, 2, 2, 4, 3) + .putAll("PS", 3, 3, 2, 2, 2, 2) + .putAll("PT", 0, 1, 1, 0, 2, 2) + .putAll("PW", 1, 2, 4, 1, 2, 2) + .putAll("PY", 2, 0, 3, 2, 2, 2) + .putAll("QA", 2, 3, 1, 2, 3, 2) + .putAll("RE", 1, 0, 2, 2, 2, 2) + .putAll("RO", 0, 1, 0, 1, 0, 2) + .putAll("RS", 1, 2, 0, 0, 2, 2) + .putAll("RU", 0, 1, 0, 1, 4, 2) + .putAll("RW", 3, 3, 3, 1, 2, 2) + .putAll("SA", 2, 2, 2, 1, 1, 2) + .putAll("SB", 4, 2, 3, 2, 2, 2) + .putAll("SC", 4, 2, 1, 3, 2, 2) + .putAll("SD", 4, 4, 4, 4, 2, 2) + .putAll("SE", 0, 0, 0, 0, 0, 2) + .putAll("SG", 1, 0, 1, 2, 3, 2) + .putAll("SH", 4, 2, 2, 2, 2, 2) + .putAll("SI", 0, 0, 0, 0, 2, 2) + .putAll("SJ", 2, 2, 2, 2, 2, 2) + .putAll("SK", 0, 1, 0, 0, 2, 2) + .putAll("SL", 4, 3, 4, 0, 2, 2) + .putAll("SM", 0, 2, 2, 2, 2, 2) + .putAll("SN", 4, 4, 4, 4, 2, 2) + .putAll("SO", 3, 3, 3, 4, 2, 2) + .putAll("SR", 3, 2, 2, 2, 2, 2) + .putAll("SS", 4, 4, 3, 3, 2, 2) + .putAll("ST", 2, 2, 1, 2, 2, 2) + .putAll("SV", 2, 1, 4, 3, 2, 2) + .putAll("SX", 2, 2, 1, 0, 2, 2) + .putAll("SY", 4, 3, 3, 2, 2, 2) + .putAll("SZ", 3, 3, 2, 4, 2, 2) + .putAll("TC", 2, 2, 2, 0, 2, 2) + .putAll("TD", 4, 3, 4, 4, 2, 2) + .putAll("TG", 3, 2, 2, 4, 2, 2) + .putAll("TH", 0, 3, 2, 3, 2, 2) + .putAll("TJ", 4, 4, 4, 4, 2, 2) + .putAll("TL", 4, 0, 4, 4, 2, 2) + .putAll("TM", 4, 2, 4, 3, 2, 2) + .putAll("TN", 2, 1, 1, 2, 2, 2) + .putAll("TO", 3, 3, 4, 3, 2, 2) + .putAll("TR", 1, 2, 1, 1, 2, 2) + .putAll("TT", 1, 4, 0, 1, 2, 2) + .putAll("TV", 3, 2, 2, 4, 2, 2) + .putAll("TW", 0, 0, 0, 0, 1, 0) + .putAll("TZ", 3, 3, 3, 2, 2, 2) + .putAll("UA", 0, 3, 1, 1, 2, 2) + .putAll("UG", 3, 2, 3, 3, 2, 2) + .putAll("US", 1, 1, 2, 2, 4, 2) + .putAll("UY", 2, 2, 1, 1, 2, 2) + .putAll("UZ", 2, 1, 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, 2, 2, 2) + .putAll("VN", 0, 1, 3, 4, 2, 2) + .putAll("VU", 4, 0, 3, 1, 2, 2) + .putAll("WF", 4, 2, 2, 4, 2, 2) + .putAll("WS", 3, 1, 3, 1, 2, 2) + .putAll("XK", 0, 1, 1, 0, 2, 2) + .putAll("YE", 4, 4, 4, 3, 2, 2) + .putAll("YT", 4, 2, 2, 3, 2, 2) + .putAll("ZA", 3, 3, 2, 1, 2, 2) + .putAll("ZM", 3, 2, 3, 3, 2, 2) + .putAll("ZW", 3, 2, 4, 3, 2, 2) + .build(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java index 0c6d210517..fb3df995ae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java @@ -20,8 +20,8 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.upstream.DataSource.Factory; /** - * A {@link Factory} that produces {@link DefaultDataSource} instances that delegate to - * {@link DefaultHttpDataSource}s for non-file/asset/content URIs. + * A {@link Factory} that produces {@link DefaultDataSource} instances that delegate to {@link + * DefaultHttpDataSource}s for non-file/asset/content URIs. */ public final class DefaultDataSourceFactory implements Factory { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java index c23e569c6c..88b9112af8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.upstream; - import androidx.annotation.Nullable; import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java index d34e43eb46..7fba170f36 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java @@ -23,7 +23,6 @@ import android.text.TextUtils; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; -import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; @@ -91,7 +90,7 @@ public final class FileDataSource extends BaseDataSource { bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position : dataSpec.length; if (bytesRemaining < 0) { - throw new EOFException(); + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); } } catch (IOException e) { throw new FileDataSourceException(e); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java index cab9d003c4..d731455e51 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java @@ -36,9 +36,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; -/** - * Manages the background loading of {@link Loadable}s. - */ +/** Manages the background loading of {@link Loadable}s. */ public final class Loader implements LoaderErrorThrower { /** @@ -152,6 +150,8 @@ public final class Loader implements LoaderErrorThrower { } + private static final String THREAD_NAME_PREFIX = "ExoPlayer:Loader:"; + /** Types of action that can be taken in response to a load error. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -210,10 +210,12 @@ public final class Loader implements LoaderErrorThrower { @Nullable private IOException fatalError; /** - * @param threadName A name for the loader's thread. + * @param threadNameSuffix A name suffix for the loader's thread. This should be the name of the + * component using the loader. */ - public Loader(String threadName) { - this.downloadExecutorService = Util.newSingleThreadExecutor(threadName); + public Loader(String threadNameSuffix) { + this.downloadExecutorService = + Util.newSingleThreadExecutor(THREAD_NAME_PREFIX + threadNameSuffix); } /** @@ -431,24 +433,24 @@ public final class Loader implements LoaderErrorThrower { } } catch (Exception e) { // This should never happen, but handle it anyway. - Log.e(TAG, "Unexpected exception loading stream", e); if (!released) { + Log.e(TAG, "Unexpected exception loading stream", e); obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); } } catch (OutOfMemoryError e) { // This can occur if a stream is malformed in a way that causes an extractor to think it // needs to allocate a large amount of memory. We don't want the process to die in this // case, but we do want the playback to fail. - Log.e(TAG, "OutOfMemory error loading stream", e); if (!released) { + Log.e(TAG, "OutOfMemory error loading stream", e); obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); } } catch (Error e) { - // We'd hope that the platform would kill the process if an Error is thrown here, but the - // executor may catch the error (b/20616433). Throw it here, but also pass and throw it from - // the handler thread so that the process dies even if the executor behaves in this way. - Log.e(TAG, "Unexpected error loading stream", e); + // We'd hope that the platform would shut down the process if an Error is thrown here, but + // the executor may catch the error (b/20616433). Throw it here, but also pass and throw it + // from the handler thread so the process dies even if the executor behaves in this way. if (!released) { + Log.e(TAG, "Unexpected error loading stream", e); obtainMessage(MSG_FATAL_ERROR, e).sendToTarget(); } throw e; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java index 54c3d4cbe5..64d5938c89 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.upstream; import com.google.android.exoplayer2.upstream.Loader.Loadable; import java.io.IOException; -/** - * Conditionally throws errors affecting a {@link Loader}. - */ +/** Conditionally throws errors affecting a {@link Loader}. */ public interface LoaderErrorThrower { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java index e52e1db376..2f0d244307 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java @@ -24,14 +24,15 @@ import java.util.List; import java.util.Map; /** - * A {@link DataSource} that can be used as part of a task registered with a - * {@link PriorityTaskManager}. - *

    - * Calls to {@link #open(DataSpec)} and {@link #read(byte[], int, int)} are allowed to proceed only - * if there are no higher priority tasks registered to the {@link PriorityTaskManager}. If there - * exists a higher priority task then {@link PriorityTaskManager.PriorityTooLowException} is thrown. - *

    - * Instances of this class are intended to be used as parts of (possibly larger) tasks that are + * A {@link DataSource} that can be used as part of a task registered with a {@link + * PriorityTaskManager}. + * + *

    Calls to {@link #open(DataSpec)} and {@link #read(byte[], int, int)} are allowed to proceed + * only if there are no higher priority tasks registered to the {@link PriorityTaskManager}. If + * there exists a higher priority task then {@link PriorityTaskManager.PriorityTooLowException} is + * thrown. + * + *

    Instances of this class are intended to be used as parts of (possibly larger) tasks that are * registered with the {@link PriorityTaskManager}, and hence do not register as tasks * themselves. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java index daad41a9a6..cec2c9a79d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.upstream; import com.google.android.exoplayer2.upstream.DataSource.Factory; import com.google.android.exoplayer2.util.PriorityTaskManager; -/** - * A {@link DataSource.Factory} that produces {@link PriorityDataSource} instances. - */ +/** A {@link DataSource.Factory} that produces {@link PriorityDataSource} instances. */ public final class PriorityDataSourceFactory implements Factory { private final Factory upstreamFactory; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java index 7538cc67a4..ccdf7975a6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java @@ -31,6 +31,7 @@ import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.channels.FileChannel; /** * A {@link DataSource} for reading a raw resource inside the APK. @@ -60,7 +61,7 @@ public final class RawResourceDataSource extends BaseDataSource { super(message); } - public RawResourceDataSourceException(IOException e) { + public RawResourceDataSourceException(Throwable e) { super(e); } } @@ -133,41 +134,73 @@ public final class RawResourceDataSource extends BaseDataSource { } transferInitializing(dataSpec); - AssetFileDescriptor assetFileDescriptor = resources.openRawResourceFd(resourceId); + + AssetFileDescriptor assetFileDescriptor; + try { + assetFileDescriptor = resources.openRawResourceFd(resourceId); + } catch (Resources.NotFoundException e) { + throw new RawResourceDataSourceException(e); + } + this.assetFileDescriptor = assetFileDescriptor; if (assetFileDescriptor == null) { throw new RawResourceDataSourceException("Resource is compressed: " + uri); } + long assetFileDescriptorLength = assetFileDescriptor.getLength(); FileInputStream inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); this.inputStream = inputStream; + try { - inputStream.skip(assetFileDescriptor.getStartOffset()); - long skipped = inputStream.skip(dataSpec.position); - if (skipped < dataSpec.position) { + // We can't rely only on the "skipped < dataSpec.position" check below to detect whether the + // position is beyond the end of the resource being read. This is because the file will + // typically contain multiple resources, and there's nothing to prevent InputStream.skip() + // from succeeding by skipping into the data of the next resource. Hence we also need to check + // against the resource length explicitly, which is guaranteed to be set unless the resource + // extends to the end of the file. + if (assetFileDescriptorLength != AssetFileDescriptor.UNKNOWN_LENGTH + && dataSpec.position > assetFileDescriptorLength) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + long assetFileDescriptorOffset = assetFileDescriptor.getStartOffset(); + long skipped = + inputStream.skip(assetFileDescriptorOffset + dataSpec.position) + - assetFileDescriptorOffset; + if (skipped != dataSpec.position) { // We expect the skip to be satisfied in full. If it isn't then we're probably trying to - // skip beyond the end of the data. - throw new EOFException(); + // read beyond the end of the last resource in the file. + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) { + // The asset must extend to the end of the file. We can try and resolve the length with + // FileInputStream.getChannel().size(). + FileChannel channel = inputStream.getChannel(); + if (channel.size() == 0) { + bytesRemaining = C.LENGTH_UNSET; + } else { + bytesRemaining = channel.size() - channel.position(); + if (bytesRemaining < 0) { + // The skip above was satisfied in full, but skipped beyond the end of the file. + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + } + } else { + bytesRemaining = assetFileDescriptorLength - skipped; + if (bytesRemaining < 0) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } } } catch (IOException e) { throw new RawResourceDataSourceException(e); } if (dataSpec.length != C.LENGTH_UNSET) { - bytesRemaining = dataSpec.length; - } else { - long assetFileDescriptorLength = assetFileDescriptor.getLength(); - // If the length is UNKNOWN_LENGTH then the asset extends to the end of the file. bytesRemaining = - assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH - ? C.LENGTH_UNSET - : (assetFileDescriptorLength - dataSpec.position); + bytesRemaining == C.LENGTH_UNSET ? dataSpec.length : min(bytesRemaining, dataSpec.length); } - opened = true; transferStarted(dataSpec); - - return bytesRemaining; + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining; } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java index 689273d388..3ece5c1617 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java @@ -23,9 +23,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; -/** - * Tees data into a {@link DataSink} as the data is read. - */ +/** Tees data into a {@link DataSink} as the data is read. */ public final class TeeDataSource implements DataSource { private final DataSource upstream; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/TimeToFirstByteEstimator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/TimeToFirstByteEstimator.java new file mode 100644 index 0000000000..7a8b38018a --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/TimeToFirstByteEstimator.java @@ -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. + */ +package com.google.android.exoplayer2.upstream; + +import com.google.android.exoplayer2.C; + +/** Provides an estimate of the time to first byte of a transfer. */ +public interface TimeToFirstByteEstimator { + /** + * Returns the estimated time to first byte of the response body, in microseconds, or {@link + * C#TIME_UNSET} if no estimate is available. + */ + long getTimeToFirstByteEstimateUs(); + + /** Resets the estimator. */ + void reset(); + + /** + * Called when a transfer is being initialized. + * + * @param dataSpec Describes the data for which the transfer is initialized. + */ + void onTransferInitializing(DataSpec dataSpec); + + /** + * Called when a transfer starts. + * + * @param dataSpec Describes the data being transferred. + */ + void onTransferStart(DataSpec dataSpec); +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index c2bbdbb893..efccb0cf78 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -388,8 +388,9 @@ public final class CacheDataSource implements DataSource { @Nullable private Uri actualUri; @Nullable private DataSpec requestDataSpec; + @Nullable private DataSpec currentDataSpec; @Nullable private DataSource currentDataSource; - private boolean currentDataSpecLengthUnset; + private long currentDataSourceBytesRead; private long readPosition; private long bytesRemaining; @Nullable private CacheSpan currentHoleSpan; @@ -565,19 +566,27 @@ public final class CacheDataSource implements DataSource { notifyCacheIgnored(reason); } - if (dataSpec.length != C.LENGTH_UNSET || currentRequestIgnoresCache) { - bytesRemaining = dataSpec.length; + if (currentRequestIgnoresCache) { + bytesRemaining = C.LENGTH_UNSET; } else { bytesRemaining = ContentMetadata.getContentLength(cache.getContentMetadata(key)); if (bytesRemaining != C.LENGTH_UNSET) { bytesRemaining -= dataSpec.position; - if (bytesRemaining <= 0) { + if (bytesRemaining < 0) { throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); } } } - openNextSource(requestDataSpec, false); - return bytesRemaining; + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = + bytesRemaining == C.LENGTH_UNSET + ? dataSpec.length + : min(bytesRemaining, dataSpec.length); + } + if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { + openNextSource(requestDataSpec, false); + } + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining; } catch (Throwable e) { handleBeforeThrow(e); throw e; @@ -587,6 +596,7 @@ public final class CacheDataSource implements DataSource { @Override public int read(byte[] buffer, int offset, int readLength) throws IOException { DataSpec requestDataSpec = checkNotNull(this.requestDataSpec); + DataSpec currentDataSpec = checkNotNull(this.currentDataSpec); if (readLength == 0) { return 0; } @@ -603,10 +613,16 @@ public final class CacheDataSource implements DataSource { totalCachedBytesRead += bytesRead; } readPosition += bytesRead; + currentDataSourceBytesRead += bytesRead; if (bytesRemaining != C.LENGTH_UNSET) { bytesRemaining -= bytesRead; } - } else if (currentDataSpecLengthUnset) { + } else if (isReadingFromUpstream() + && (currentDataSpec.length == C.LENGTH_UNSET + || currentDataSourceBytesRead < currentDataSpec.length)) { + // We've encountered RESULT_END_OF_INPUT from the upstream DataSource at a position not + // imposed by the current DataSpec. This must mean that we've reached the end of the + // resource. setNoBytesRemainingAndMaybeStoreLength(castNonNull(requestDataSpec.key)); } else if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { closeCurrentSource(); @@ -614,13 +630,6 @@ public final class CacheDataSource implements DataSource { return read(buffer, offset, readLength); } return bytesRead; - } catch (IOException e) { - if (currentDataSpecLengthUnset && DataSourceException.isCausedByPositionOutOfRange(e)) { - setNoBytesRemainingAndMaybeStoreLength(castNonNull(requestDataSpec.key)); - return C.RESULT_END_OF_INPUT; - } - handleBeforeThrow(e); - throw e; } catch (Throwable e) { handleBeforeThrow(e); throw e; @@ -760,12 +769,13 @@ public final class CacheDataSource implements DataSource { currentHoleSpan = nextSpan; } currentDataSource = nextDataSource; - currentDataSpecLengthUnset = nextDataSpec.length == C.LENGTH_UNSET; + currentDataSpec = nextDataSpec; + currentDataSourceBytesRead = 0; long resolvedLength = nextDataSource.open(nextDataSpec); // Update bytesRemaining, actualUri and (if writing to cache) the cache metadata. ContentMetadataMutations mutations = new ContentMetadataMutations(); - if (currentDataSpecLengthUnset && resolvedLength != C.LENGTH_UNSET) { + if (nextDataSpec.length == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) { bytesRemaining = resolvedLength; ContentMetadataMutations.setContentLength(mutations, readPosition + bytesRemaining); } @@ -816,8 +826,8 @@ public final class CacheDataSource implements DataSource { try { currentDataSource.close(); } finally { + currentDataSpec = null; currentDataSource = null; - currentDataSpecLengthUnset = false; if (currentHoleSpan != null) { cache.releaseHoleSpan(currentHoleSpan); currentHoleSpan = null; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java index 492681e7fc..cba8f35009 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java @@ -19,9 +19,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import java.io.File; -/** - * Defines a span of data that may or may not be cached (as indicated by {@link #isCached}). - */ +/** Defines a span of data that may or may not be cached (as indicated by {@link #isCached}). */ public class CacheSpan implements Comparable { /** The cache key that uniquely identifies the resource. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java index 8ea2b4e280..7e9b3794b9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.upstream.cache; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.PriorityTaskManager.PriorityTooLowException; @@ -50,12 +49,10 @@ public final class CacheWriter { private final CacheDataSource dataSource; private final Cache cache; private final DataSpec dataSpec; - private final boolean allowShortContent; private final String cacheKey; private final byte[] temporaryBuffer; @Nullable private final ProgressListener progressListener; - private boolean initialized; private long nextPosition; private long endPosition; private long bytesCached; @@ -65,10 +62,6 @@ public final class CacheWriter { /** * @param dataSource A {@link CacheDataSource} that writes to the target cache. * @param dataSpec Defines the data to be written. - * @param allowShortContent Whether it's allowed for the content to end before the request as - * defined by the {@link DataSpec}. If {@code true} and the request exceeds the length of the - * content, then the content will be cached to the end. If {@code false} and the request - * exceeds the length of the content, {@link #cache} will throw an {@link IOException}. * @param temporaryBuffer A temporary buffer to be used during caching, or {@code null} if the * writer should instantiate its own internal temporary buffer. * @param progressListener An optional progress listener. @@ -76,13 +69,11 @@ public final class CacheWriter { public CacheWriter( CacheDataSource dataSource, DataSpec dataSpec, - boolean allowShortContent, @Nullable byte[] temporaryBuffer, @Nullable ProgressListener progressListener) { this.dataSource = dataSource; this.cache = dataSource.getCache(); this.dataSpec = dataSpec; - this.allowShortContent = allowShortContent; this.temporaryBuffer = temporaryBuffer == null ? new byte[DEFAULT_BUFFER_SIZE_BYTES] : temporaryBuffer; this.progressListener = progressListener; @@ -118,18 +109,15 @@ public final class CacheWriter { public void cache() throws IOException { throwIfCanceled(); - if (!initialized) { - if (dataSpec.length != C.LENGTH_UNSET) { - endPosition = dataSpec.position + dataSpec.length; - } else { - long contentLength = ContentMetadata.getContentLength(cache.getContentMetadata(cacheKey)); - endPosition = contentLength == C.LENGTH_UNSET ? C.POSITION_UNSET : contentLength; - } - bytesCached = cache.getCachedBytes(cacheKey, dataSpec.position, dataSpec.length); - if (progressListener != null) { - progressListener.onProgress(getLength(), bytesCached, /* newBytesCached= */ 0); - } - initialized = true; + bytesCached = cache.getCachedBytes(cacheKey, dataSpec.position, dataSpec.length); + if (dataSpec.length != C.LENGTH_UNSET) { + endPosition = dataSpec.position + dataSpec.length; + } else { + long contentLength = ContentMetadata.getContentLength(cache.getContentMetadata(cacheKey)); + endPosition = contentLength == C.LENGTH_UNSET ? C.POSITION_UNSET : contentLength; + } + if (progressListener != null) { + progressListener.onProgress(getLength(), bytesCached, /* newBytesCached= */ 0); } while (endPosition == C.POSITION_UNSET || nextPosition < endPosition) { @@ -158,42 +146,41 @@ public final class CacheWriter { */ private long readBlockToCache(long position, long length) throws IOException { boolean isLastBlock = position + length == endPosition || length == C.LENGTH_UNSET; - try { - long resolvedLength = C.LENGTH_UNSET; - boolean isDataSourceOpen = false; - if (length != C.LENGTH_UNSET) { - // If the length is specified, try to open the data source with a bounded request to avoid - // the underlying network stack requesting more data than required. - try { - DataSpec boundedDataSpec = - dataSpec.buildUpon().setPosition(position).setLength(length).build(); - resolvedLength = dataSource.open(boundedDataSpec); - isDataSourceOpen = true; - } catch (IOException exception) { - if (allowShortContent - && isLastBlock - && DataSourceException.isCausedByPositionOutOfRange(exception)) { - // The length of the request exceeds the length of the content. If we allow shorter - // content and are reading the last block, fall through and try again with an unbounded - // request to read up to the end of the content. - Util.closeQuietly(dataSource); - } else { - throw exception; - } - } + + long resolvedLength = C.LENGTH_UNSET; + boolean isDataSourceOpen = false; + if (length != C.LENGTH_UNSET) { + // If the length is specified, try to open the data source with a bounded request to avoid + // the underlying network stack requesting more data than required. + DataSpec boundedDataSpec = + dataSpec.buildUpon().setPosition(position).setLength(length).build(); + try { + resolvedLength = dataSource.open(boundedDataSpec); + isDataSourceOpen = true; + } catch (IOException e) { + Util.closeQuietly(dataSource); } - if (!isDataSourceOpen) { - // Either the length was unspecified, or we allow short content and our attempt to open the - // DataSource with the specified length failed. - throwIfCanceled(); - DataSpec unboundedDataSpec = - dataSpec.buildUpon().setPosition(position).setLength(C.LENGTH_UNSET).build(); + } + + if (!isDataSourceOpen) { + // Either the length was unspecified, or we allow short content and our attempt to open the + // DataSource with the specified length failed. + throwIfCanceled(); + DataSpec unboundedDataSpec = + dataSpec.buildUpon().setPosition(position).setLength(C.LENGTH_UNSET).build(); + try { resolvedLength = dataSource.open(unboundedDataSpec); + } catch (IOException e) { + Util.closeQuietly(dataSource); + throw e; } + } + + int totalBytesRead = 0; + try { if (isLastBlock && resolvedLength != C.LENGTH_UNSET) { onRequestEndPosition(position + resolvedLength); } - int totalBytesRead = 0; int bytesRead = 0; while (bytesRead != C.RESULT_END_OF_INPUT) { throwIfCanceled(); @@ -206,10 +193,16 @@ public final class CacheWriter { if (isLastBlock) { onRequestEndPosition(position + totalBytesRead); } - return totalBytesRead; - } finally { + } catch (IOException e) { Util.closeQuietly(dataSource); + throw e; } + + // Util.closeQuietly(dataSource) is not used here because it's important that an exception is + // thrown if DataSource.close fails. This is because there's no way of knowing whether the block + // was successfully cached in this case. + dataSource.close(); + return totalBytesRead; } private void onRequestEndPosition(long endPosition) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java index 9f4fcd4c11..ce69d5a46c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java @@ -82,7 +82,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * *

    [1] (key1, id1) is removed from the in-memory index ... the index is not stored to disk ... * [2] id1 is reused for a different key2 ... the index is not stored to disk ... [3] A file for - * key2 is partially written using a path corresponding to id1 ... the process is killed before + * key2 is partially written using a path corresponding to id1 ... the process is shut down before * the index is stored to disk ... [4] The index is read from disk, causing the partially written * file to be incorrectly associated to key1 * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java index c3fa9e3b08..d26e8036d7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java @@ -25,8 +25,8 @@ import java.util.NavigableSet; import java.util.TreeSet; /** - * Utility class for efficiently tracking regions of data that are stored in a {@link Cache} - * for a given cache key. + * Utility class for efficiently tracking regions of data that are stored in a {@link Cache} for a + * given cache key. */ public final class CachedRegionTracker implements Cache.Listener { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java index 26b6d83a43..3a68458866 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java @@ -19,9 +19,7 @@ import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -/** - * Interface for an immutable snapshot of keyed metadata. - */ +/** Interface for an immutable snapshot of keyed metadata. */ public interface ContentMetadata { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java index da89dc1cb3..db3cd2ef34 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java @@ -15,12 +15,11 @@ */ package com.google.android.exoplayer2.upstream.cache; - /** * Evictor that doesn't ever evict cache files. * - * Warning: Using this evictor might have unforeseeable consequences if cache - * size is not managed elsewhere. + *

    Warning: Using this evictor might have unforeseeable consequences if cache size is not managed + * elsewhere. */ public final class NoOpCacheEvictor implements CacheEvictor { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java index c1118c01a9..b29c5fac70 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java @@ -24,9 +24,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import java.io.IOException; import javax.crypto.Cipher; -/** - * A wrapping {@link DataSink} that encrypts the data being consumed. - */ +/** A wrapping {@link DataSink} that encrypts the data being consumed. */ public final class AesCipherDataSink implements DataSink { private final DataSink wrappedDataSink; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java index 5abe42b937..43a100ca69 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java @@ -29,9 +29,7 @@ import java.util.List; import java.util.Map; import javax.crypto.Cipher; -/** - * A {@link DataSource} that decrypts the data read from an upstream source. - */ +/** A {@link DataSource} that decrypts the data read from an upstream source. */ public final class AesCipherDataSource implements DataSource { private final DataSource upstream; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java index 1721b1d8b7..6df114442b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java @@ -30,8 +30,8 @@ import javax.crypto.spec.SecretKeySpec; /** * A flushing variant of a AES/CTR/NoPadding {@link Cipher}. * - * Unlike a regular {@link Cipher}, the update methods of this class are guaranteed to process all - * of the bytes input (and hence output the same number of bytes). + *

    Unlike a regular {@link Cipher}, the update methods of this class are guaranteed to process + * all of the bytes input (and hence output the same number of bytes). */ public final class AesFlushingCipher { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java index 85ef43f669..6f35168d08 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util; import android.graphics.Color; import android.text.TextUtils; import androidx.annotation.ColorInt; +import com.google.common.base.Ascii; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -106,7 +107,7 @@ public final class ColorParser { } } else { // we use our own color map - Integer color = COLOR_MAP.get(Util.toLowerInvariant(colorExpression)); + Integer color = COLOR_MAP.get(Ascii.toLowerCase(colorExpression)); if (color != null) { return color; } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java similarity index 95% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java index 39ea45aec8..f7aac3bdae 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/DebugTextViewHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui; +package com.google.android.exoplayer2.util; import android.annotation.SuppressLint; import android.os.Looper; @@ -22,14 +22,13 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.decoder.DecoderCounters; -import com.google.android.exoplayer2.util.Assertions; import java.util.Locale; /** * A helper class for periodically updating a {@link TextView} with debug information obtained from * a {@link SimpleExoPlayer}. */ -public class DebugTextViewHelper implements Player.EventListener, Runnable { +public class DebugTextViewHelper implements Player.Listener, Runnable { private static final int REFRESH_INTERVAL_MS = 1000; @@ -76,7 +75,7 @@ public class DebugTextViewHelper implements Player.EventListener, Runnable { textView.removeCallbacks(this); } - // Player.EventListener implementation. + // Player.Listener implementation. @Override public final void onPlaybackStateChanged(@Player.State int playbackState) { @@ -90,7 +89,10 @@ public class DebugTextViewHelper implements Player.EventListener, Runnable { } @Override - public final void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { + public final void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @Player.DiscontinuityReason int reason) { updateAndPost(); } 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 6e25f1f0a2..12513f597a 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 @@ -19,7 +19,6 @@ import static java.lang.Math.min; import android.os.SystemClock; import android.text.TextUtils; -import android.view.Surface; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -35,6 +34,7 @@ import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; +import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaLoadData; @@ -44,6 +44,7 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.video.VideoSize; import java.io.IOException; import java.text.NumberFormat; import java.util.List; @@ -140,13 +141,50 @@ public class EventLogger implements AnalyticsListener { } @Override - public void onPositionDiscontinuity(EventTime eventTime, @Player.DiscontinuityReason int reason) { - logd(eventTime, "positionDiscontinuity", getDiscontinuityReasonString(reason)); - } - - @Override - public void onSeekStarted(EventTime eventTime) { - logd(eventTime, "seekStarted"); + public void onPositionDiscontinuity( + EventTime eventTime, + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @Player.DiscontinuityReason int reason) { + StringBuilder builder = new StringBuilder(); + builder + .append("reason=") + .append(getDiscontinuityReasonString(reason)) + .append(", PositionInfo:old [") + .append("window=") + .append(oldPosition.windowIndex) + .append(", period=") + .append(oldPosition.periodIndex) + .append(", pos=") + .append(oldPosition.positionMs); + if (oldPosition.adGroupIndex != C.INDEX_UNSET) { + builder + .append(", contentPos=") + .append(oldPosition.contentPositionMs) + .append(", adGroup=") + .append(oldPosition.adGroupIndex) + .append(", ad=") + .append(oldPosition.adIndexInAdGroup); + } + builder + .append("], PositionInfo:new [") + .append("window=") + .append(newPosition.windowIndex) + .append(", period=") + .append(newPosition.periodIndex) + .append(", pos=") + .append(newPosition.positionMs); + if (newPosition.adGroupIndex != C.INDEX_UNSET) { + builder + .append(", contentPos=") + .append(newPosition.contentPositionMs) + .append(", adGroup=") + .append(newPosition.adGroupIndex) + .append(", ad=") + .append(newPosition.adIndexInAdGroup); + } + builder.append("]"); + logd(eventTime, "positionDiscontinuity", builder.toString()); } @Override @@ -415,18 +453,13 @@ public class EventLogger implements AnalyticsListener { } @Override - public void onRenderedFirstFrame(EventTime eventTime, @Nullable Surface surface) { - logd(eventTime, "renderedFirstFrame", String.valueOf(surface)); + public void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) { + logd(eventTime, "renderedFirstFrame", String.valueOf(output)); } @Override - public void onVideoSizeChanged( - EventTime eventTime, - int width, - int height, - int unappliedRotationDegrees, - float pixelWidthHeightRatio) { - logd(eventTime, "videoSize", width + ", " + height); + public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) { + logd(eventTime, "videoSize", videoSize.width + ", " + videoSize.height); } @Override @@ -479,8 +512,8 @@ public class EventLogger implements AnalyticsListener { } @Override - public void onDrmSessionAcquired(EventTime eventTime) { - logd(eventTime, "drmSessionAcquired"); + public void onDrmSessionAcquired(EventTime eventTime, @DrmSession.State int state) { + logd(eventTime, "drmSessionAcquired", "state=" + state); } @Override @@ -657,14 +690,16 @@ public class EventLogger implements AnalyticsListener { private static String getDiscontinuityReasonString(@Player.DiscontinuityReason int reason) { switch (reason) { - case Player.DISCONTINUITY_REASON_PERIOD_TRANSITION: - return "PERIOD_TRANSITION"; + case Player.DISCONTINUITY_REASON_AUTO_TRANSITION: + return "AUTO_TRANSITION"; case Player.DISCONTINUITY_REASON_SEEK: return "SEEK"; case Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT: return "SEEK_ADJUSTMENT"; - case Player.DISCONTINUITY_REASON_AD_INSERTION: - return "AD_INSERTION"; + case Player.DISCONTINUITY_REASON_REMOVE: + return "REMOVE"; + case Player.DISCONTINUITY_REASON_SKIP: + return "SKIP"; case Player.DISCONTINUITY_REASON_INTERNAL: return "INTERNAL"; default: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java index 7ee88d8f0f..be8ebe2f5c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.util; import java.util.Arrays; -/** - * Configurable loader for native libraries. - */ +/** Configurable loader for native libraries. */ public final class LibraryLoader { private static final String TAG = "LibraryLoader"; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java index df335908c0..48954cb9df 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.util; import com.google.android.exoplayer2.PlaybackParameters; -/** - * Tracks the progression of media time. - */ +/** Tracks the progression of media time. */ public interface MediaClock { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NetworkTypeObserver.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NetworkTypeObserver.java new file mode 100644 index 0000000000..fb43396156 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NetworkTypeObserver.java @@ -0,0 +1,269 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.Looper; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; +import androidx.annotation.GuardedBy; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.VisibleForTesting; +import com.google.android.exoplayer2.C; +import java.lang.ref.WeakReference; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Observer for network type changes. + * + *

    {@link #register Registered} listeners are informed at registration and whenever the network + * type changes. + * + *

    The current network type can also be {@link #getNetworkType queried} without registration. + */ +public final class NetworkTypeObserver { + + /** A listener for network type changes. */ + public interface Listener { + + /** + * Called when the network type changed or when the listener is first registered. + * + *

    This method is always called on the main thread. + */ + void onNetworkTypeChanged(@C.NetworkType int networkType); + } + + @Nullable private static NetworkTypeObserver staticInstance; + + private final Handler mainHandler; + // This class needs to hold weak references as it doesn't require listeners to unregister. + private final CopyOnWriteArrayList> listeners; + private final Object networkTypeLock; + + @GuardedBy("networkTypeLock") + @C.NetworkType + private int networkType; + + /** + * Returns a network type observer instance. + * + * @param context A {@link Context}. + */ + public static synchronized NetworkTypeObserver getInstance(Context context) { + if (staticInstance == null) { + staticInstance = new NetworkTypeObserver(context); + } + return staticInstance; + } + + /** Resets the network type observer for tests. */ + @VisibleForTesting + public static synchronized void resetForTests() { + staticInstance = null; + } + + private NetworkTypeObserver(Context context) { + mainHandler = new Handler(Looper.getMainLooper()); + listeners = new CopyOnWriteArrayList<>(); + networkTypeLock = new Object(); + networkType = C.NETWORK_TYPE_UNKNOWN; + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(/* receiver= */ new Receiver(), filter); + } + + /** + * Registers a listener. + * + *

    The current network type will be reported to the listener after registration. + * + * @param listener The {@link Listener}. + */ + public void register(Listener listener) { + removeClearedReferences(); + listeners.add(new WeakReference<>(listener)); + // Simulate an initial update on the main thread (like the sticky broadcast we'd receive if + // we were to register a separate broadcast receiver for each listener). + mainHandler.post(() -> listener.onNetworkTypeChanged(getNetworkType())); + } + + /** Returns the current network type. */ + @C.NetworkType + public int getNetworkType() { + synchronized (networkTypeLock) { + return networkType; + } + } + + private void removeClearedReferences() { + for (WeakReference listenerReference : listeners) { + if (listenerReference.get() == null) { + listeners.remove(listenerReference); + } + } + } + + private void updateNetworkType(@C.NetworkType int networkType) { + synchronized (networkTypeLock) { + if (this.networkType == networkType) { + return; + } + this.networkType = networkType; + } + for (WeakReference listenerReference : listeners) { + @Nullable Listener listener = listenerReference.get(); + if (listener != null) { + listener.onNetworkTypeChanged(networkType); + } else { + listeners.remove(listenerReference); + } + } + } + + @C.NetworkType + private static int getNetworkTypeFromConnectivityManager(Context context) { + NetworkInfo networkInfo; + @Nullable + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager == null) { + return C.NETWORK_TYPE_UNKNOWN; + } + try { + networkInfo = connectivityManager.getActiveNetworkInfo(); + } catch (SecurityException e) { + // Expected if permission was revoked. + return C.NETWORK_TYPE_UNKNOWN; + } + if (networkInfo == null || !networkInfo.isConnected()) { + return C.NETWORK_TYPE_OFFLINE; + } + switch (networkInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: + return C.NETWORK_TYPE_WIFI; + case ConnectivityManager.TYPE_WIMAX: + return C.NETWORK_TYPE_4G; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_MOBILE_DUN: + case ConnectivityManager.TYPE_MOBILE_HIPRI: + return getMobileNetworkType(networkInfo); + case ConnectivityManager.TYPE_ETHERNET: + return C.NETWORK_TYPE_ETHERNET; + default: + return C.NETWORK_TYPE_OTHER; + } + } + + @C.NetworkType + private static int getMobileNetworkType(NetworkInfo networkInfo) { + switch (networkInfo.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_GPRS: + return C.NETWORK_TYPE_2G; + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_IDEN: + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: + return C.NETWORK_TYPE_3G; + case TelephonyManager.NETWORK_TYPE_LTE: + return C.NETWORK_TYPE_4G; + case TelephonyManager.NETWORK_TYPE_NR: + return Util.SDK_INT >= 29 ? C.NETWORK_TYPE_5G_SA : C.NETWORK_TYPE_UNKNOWN; + case TelephonyManager.NETWORK_TYPE_IWLAN: + return C.NETWORK_TYPE_WIFI; + case TelephonyManager.NETWORK_TYPE_GSM: + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + default: // Future mobile network types. + return C.NETWORK_TYPE_CELLULAR_UNKNOWN; + } + } + + private final class Receiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + @C.NetworkType int networkType = getNetworkTypeFromConnectivityManager(context); + if (networkType == C.NETWORK_TYPE_4G && Util.SDK_INT >= 29) { + // Delay update of the network type to check whether this is actually 5G-NSA. + try { + // We can't access TelephonyManager getters like getServiceState() directly as they + // require special permissions. Attaching a listener is permission-free because the + // callback data is censored to not include sensitive information. + TelephonyManager telephonyManager = + checkNotNull((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)); + TelephonyManagerListener listener = new TelephonyManagerListener(); + if (Util.SDK_INT < 31) { + telephonyManager.listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE); + } else { + // Display info information can only be requested without permission from API 31. + telephonyManager.listen(listener, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED); + } + // We are only interested in the initial response with the current state, so unregister + // the listener immediately. + telephonyManager.listen(listener, PhoneStateListener.LISTEN_NONE); + return; + } catch (RuntimeException e) { + // Ignore problems with listener registration and keep reporting as 4G. + } + } + updateNetworkType(networkType); + } + } + + private class TelephonyManagerListener extends PhoneStateListener { + + @Override + public void onServiceStateChanged(@Nullable ServiceState serviceState) { + // This workaround to check the toString output of ServiceState only works on API 29 and 30. + String serviceStateString = serviceState == null ? "" : serviceState.toString(); + boolean is5gNsa = + serviceStateString.contains("nrState=CONNECTED") + || serviceStateString.contains("nrState=NOT_RESTRICTED"); + updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G); + } + + @RequiresApi(31) + @Override + public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { + int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType(); + boolean is5gNsa = + overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA + || overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE; + updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G); + } + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java index 6c2b337344..9f6274afd6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java @@ -63,14 +63,6 @@ public final class NotificationUtil { /** @see NotificationManager#IMPORTANCE_HIGH */ public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH; - /** @deprecated Use {@link #createNotificationChannel(Context, String, int, int, int)}. */ - @Deprecated - public static void createNotificationChannel( - Context context, String id, @StringRes int nameResourceId, @Importance int importance) { - createNotificationChannel( - context, id, nameResourceId, /* descriptionResourceId= */ 0, importance); - } - /** * Creates a notification channel that notifications can be posted to. See {@link * NotificationChannel} and {@link diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java b/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java index bf03c5f229..9021e26211 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java @@ -23,8 +23,8 @@ import java.util.PriorityQueue; /** * Allows tasks with associated priorities to control how they proceed relative to one another. - *

    - * A task should call {@link #add(int)} to register with the manager and {@link #remove(int)} to + * + *

    A task should call {@link #add(int)} to register with the manager and {@link #remove(int)} to * unregister. A registered task will prevent tasks of lower priority from proceeding, and should * call {@link #proceed(int)}, {@link #proceedNonBlocking(int)} or {@link #proceedOrThrow(int)} each * time it wishes to check whether it is itself allowed to proceed. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java index c9c21023c3..829f252c22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java @@ -24,10 +24,10 @@ import java.util.Comparator; * configured. Once the total weight of the values reaches the maximum weight, the oldest value is * reduced in weight until it reaches zero and is removed. This maintains a constant total weight, * equal to the maximum allowed, at the steady state. - *

    - * This class can be used for bandwidth estimation based on a sliding window of past transfer rate - * observations. This is an alternative to sliding mean and exponential averaging which suffer from - * susceptibility to outliers and slow adaptation to step functions. + * + *

    This class can be used for bandwidth estimation based on a sliding window of past transfer + * rate observations. This is an alternative to sliding mean and exponential averaging which suffer + * from susceptibility to outliers and slow adaptation to step functions. * * @see Wiki: Moving average * @see Wiki: Selection algorithm diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java index 90be8660c6..ecc5c91ae3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java @@ -19,9 +19,7 @@ import android.net.Uri; import android.text.TextUtils; import androidx.annotation.Nullable; -/** - * Utility methods for manipulating URIs. - */ +/** Utility methods for manipulating URIs. */ public final class UriUtil { /** 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 5310bfd624..d351442192 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 @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.video; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static java.lang.Math.max; import android.os.Handler; @@ -28,6 +29,7 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.C.VideoOutputMode; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; @@ -41,8 +43,9 @@ import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.ExoMediaCrypto; -import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; 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.video.VideoRendererEventListener.EventDispatcher; @@ -57,11 +60,9 @@ import java.lang.annotation.RetentionPolicy; * on the playback thread: * *

      - *
    • Message with type {@link #MSG_SET_SURFACE} to set the output surface. The message payload - * should be the target {@link Surface}, or null. - *
    • Message with type {@link #MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER} to set the output - * buffer renderer. The message payload should be the target {@link - * VideoDecoderOutputBufferRenderer}, or null. + *
    • Message with type {@link #MSG_SET_VIDEO_OUTPUT} to set the output surface. The message + * payload should be the target {@link Surface} or {@link VideoDecoderOutputBufferRenderer}, + * or null. Other non-null payloads have the effect of clearing the output. *
    • Message with type {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER} to set a listener for * metadata associated with frames being rendered. The message payload should be the {@link * VideoFrameMetadataListener}, or null. @@ -69,6 +70,8 @@ import java.lang.annotation.RetentionPolicy; */ public abstract class DecoderVideoRenderer extends BaseRenderer { + private static final String TAG = "DecoderVideoRenderer"; + /** Decoder reinitialization states. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -109,10 +112,11 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { private VideoDecoderInputBuffer inputBuffer; private VideoDecoderOutputBuffer outputBuffer; - @Nullable private Surface surface; + @VideoOutputMode private int outputMode; + @Nullable private Object output; + @Nullable private Surface outputSurface; @Nullable private VideoDecoderOutputBufferRenderer outputBufferRenderer; @Nullable private VideoFrameMetadataListener frameMetadataListener; - @C.VideoOutputMode private int outputMode; @Nullable private DrmSession decoderDrmSession; @Nullable private DrmSession sourceDrmSession; @@ -129,8 +133,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { private boolean inputStreamEnded; private boolean outputStreamEnded; - private int reportedWidth; - private int reportedHeight; + @Nullable private VideoSize reportedVideoSize; private long droppedFrameAccumulationStartTimeMs; private int droppedFrames; @@ -162,7 +165,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { joiningDeadlineMs = C.TIME_UNSET; clearReportedVideoSize(); formatQueue = new TimedValueQueue<>(); - flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); + flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance(); eventDispatcher = new EventDispatcher(eventHandler, eventListener); decoderReinitializationState = REINITIALIZATION_STATE_NONE; outputMode = C.VIDEO_OUTPUT_MODE_NONE; @@ -180,7 +183,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { // We don't have a format yet, so try and read one. FormatHolder formatHolder = getFormatHolder(); flagsOnlyBuffer.clear(); - @SampleStream.ReadDataResult int result = readSource(formatHolder, flagsOnlyBuffer, true); + @ReadDataResult int result = readSource(formatHolder, flagsOnlyBuffer, FLAG_REQUIRE_FORMAT); if (result == C.RESULT_FORMAT_READ) { onInputFormatChanged(formatHolder); } else if (result == C.RESULT_BUFFER_READ) { @@ -206,6 +209,8 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { while (feedInputBuffer()) {} TraceUtil.endSection(); } catch (DecoderException e) { + Log.e(TAG, "Video codec error", e); + eventDispatcher.videoCodecError(e); throw createRendererException(e, inputFormat); } decoderCounters.ensureUpdated(); @@ -242,10 +247,8 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { @Override public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException { - if (messageType == MSG_SET_SURFACE) { - setOutputSurface((Surface) message); - } else if (messageType == MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER) { - setOutputBufferRenderer((VideoDecoderOutputBufferRenderer) message); + if (messageType == MSG_SET_VIDEO_OUTPUT) { + setOutput(message); } else if (messageType == MSG_SET_VIDEO_FRAME_METADATA_LISTENER) { frameMetadataListener = (VideoFrameMetadataListener) message; } else { @@ -554,7 +557,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { } lastRenderTimeUs = C.msToUs(SystemClock.elapsedRealtime() * 1000); int bufferMode = outputBuffer.mode; - boolean renderSurface = bufferMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && surface != null; + boolean renderSurface = bufferMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && outputSurface != null; boolean renderYuv = bufferMode == C.VIDEO_OUTPUT_MODE_YUV && outputBufferRenderer != null; if (!renderYuv && !renderSurface) { dropOutputBuffer(outputBuffer); @@ -563,7 +566,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { if (renderYuv) { outputBufferRenderer.setOutputBuffer(outputBuffer); } else { - renderOutputBufferToSurface(outputBuffer, surface); + renderOutputBufferToSurface(outputBuffer, outputSurface); } consecutiveDroppedFrameCount = 0; decoderCounters.renderedOutputBufferCount++; @@ -584,47 +587,26 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { protected abstract void renderOutputBufferToSurface( VideoDecoderOutputBuffer outputBuffer, Surface surface) throws DecoderException; - /** - * Sets output surface. - * - * @param surface Surface. - */ - protected final void setOutputSurface(@Nullable Surface surface) { - if (this.surface != surface) { - // The output has changed. - this.surface = surface; - if (surface != null) { - outputBufferRenderer = null; - outputMode = C.VIDEO_OUTPUT_MODE_SURFACE_YUV; - if (decoder != null) { - setDecoderOutputMode(outputMode); - } - onOutputChanged(); - } else { - // The output has been removed. We leave the outputMode of the underlying decoder unchanged - // in anticipation that a subsequent output will likely be of the same type. - outputMode = C.VIDEO_OUTPUT_MODE_NONE; - onOutputRemoved(); - } - } else if (surface != null) { - // The output is unchanged and non-null. - onOutputReset(); + /** Sets the video output. */ + protected final void setOutput(@Nullable Object output) { + if (output instanceof Surface) { + outputSurface = (Surface) output; + outputBufferRenderer = null; + outputMode = C.VIDEO_OUTPUT_MODE_SURFACE_YUV; + } else if (output instanceof VideoDecoderOutputBufferRenderer) { + outputSurface = null; + outputBufferRenderer = (VideoDecoderOutputBufferRenderer) output; + outputMode = C.VIDEO_OUTPUT_MODE_YUV; + } else { + // Handle unsupported outputs by clearing the output. + output = null; + outputSurface = null; + outputBufferRenderer = null; + outputMode = C.VIDEO_OUTPUT_MODE_NONE; } - } - - /** - * Sets output buffer renderer. - * - * @param outputBufferRenderer Output buffer renderer. - */ - protected final void setOutputBufferRenderer( - @Nullable VideoDecoderOutputBufferRenderer outputBufferRenderer) { - if (this.outputBufferRenderer != outputBufferRenderer) { - // The output has changed. - this.outputBufferRenderer = outputBufferRenderer; - if (outputBufferRenderer != null) { - surface = null; - outputMode = C.VIDEO_OUTPUT_MODE_YUV; + if (this.output != output) { + this.output = output; + if (output != null) { if (decoder != null) { setDecoderOutputMode(outputMode); } @@ -632,10 +614,9 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { } else { // The output has been removed. We leave the outputMode of the underlying decoder unchanged // in anticipation that a subsequent output will likely be of the same type. - outputMode = C.VIDEO_OUTPUT_MODE_NONE; onOutputRemoved(); } - } else if (outputBufferRenderer != null) { + } else if (output != null) { // The output is unchanged and non-null. onOutputReset(); } @@ -646,7 +627,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { * * @param outputMode Output mode. */ - protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode); + protected abstract void setDecoderOutputMode(@VideoOutputMode int outputMode); /** * Evaluates whether the existing decoder can be reused for a new {@link Format}. @@ -707,7 +688,11 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { decoderInitializedTimestamp, decoderInitializedTimestamp - decoderInitializingTimestamp); decoderCounters.decoderInitCount++; - } catch (DecoderException | OutOfMemoryError e) { + } catch (DecoderException e) { + Log.e(TAG, "Video codec error", e); + eventDispatcher.videoCodecError(e); + throw createRendererException(e, inputFormat); + } catch (OutOfMemoryError e) { throw createRendererException(e, inputFormat); } } @@ -736,7 +721,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { } FormatHolder formatHolder = getFormatHolder(); - switch (readSource(formatHolder, inputBuffer, /* formatRequired= */ false)) { + switch (readSource(formatHolder, inputBuffer, /* readFlags= */ 0)) { case C.RESULT_NOTHING_READ: return false; case C.RESULT_FORMAT_READ: @@ -917,37 +902,32 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { renderedFirstFrameAfterEnable = true; if (!renderedFirstFrameAfterReset) { renderedFirstFrameAfterReset = true; - eventDispatcher.renderedFirstFrame(surface); + eventDispatcher.renderedFirstFrame(output); } } private void maybeRenotifyRenderedFirstFrame() { if (renderedFirstFrameAfterReset) { - eventDispatcher.renderedFirstFrame(surface); + eventDispatcher.renderedFirstFrame(output); } } private void clearReportedVideoSize() { - reportedWidth = Format.NO_VALUE; - reportedHeight = Format.NO_VALUE; + reportedVideoSize = null; } private void maybeNotifyVideoSizeChanged(int width, int height) { - if (reportedWidth != width || reportedHeight != height) { - reportedWidth = width; - reportedHeight = height; - eventDispatcher.videoSizeChanged( - width, height, /* unappliedRotationDegrees= */ 0, /* pixelWidthHeightRatio= */ 1); + if (reportedVideoSize == null + || reportedVideoSize.width != width + || reportedVideoSize.height != height) { + reportedVideoSize = new VideoSize(width, height); + eventDispatcher.videoSizeChanged(reportedVideoSize); } } private void maybeRenotifyVideoSizeChanged() { - if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { - eventDispatcher.videoSizeChanged( - reportedWidth, - reportedHeight, - /* unappliedRotationDegrees= */ 0, - /* pixelWidthHeightRatio= */ 1); + if (reportedVideoSize != null) { + eventDispatcher.videoSizeChanged(reportedVideoSize); } } 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 175f72fee3..09bb6fb570 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 @@ -58,9 +58,9 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; -import com.google.android.exoplayer2.mediacodec.MediaFormatUtil; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.MediaFormatUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; @@ -76,8 +76,9 @@ import java.util.List; * on the playback thread: * *
        - *
      • Message with type {@link #MSG_SET_SURFACE} to set the output surface. The message payload - * should be the target {@link Surface}, or null. + *
      • Message with type {@link #MSG_SET_VIDEO_OUTPUT} to set the output. The message payload + * should be the target {@link Surface}, or null to clear the output. Other non-null payloads + * have the effect of clearing the output. *
      • Message with type {@link #MSG_SET_SCALING_MODE} to set the video scaling mode. The message * payload should be one of the integer scaling modes in {@link C.VideoScalingMode}. Note that * the scaling mode only applies if the {@link Surface} targeted by this renderer is owned by @@ -144,10 +145,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private int currentHeight; private int currentUnappliedRotationDegrees; private float currentPixelWidthHeightRatio; - private int reportedWidth; - private int reportedHeight; - private int reportedUnappliedRotationDegrees; - private float reportedPixelWidthHeightRatio; + @Nullable private VideoSize reportedVideoSize; private boolean tunneling; private int tunnelingAudioSessionId; @@ -176,7 +174,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { allowedJoiningTimeMs, /* eventHandler= */ null, /* eventListener= */ null, - /* maxDroppedFramesToNotify= */ -1); + /* maxDroppedFramesToNotify= */ 0); } /** @@ -377,7 +375,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { mimeType, requiresSecureDecoder, requiresTunnelingDecoder); decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format); if (MimeTypes.VIDEO_DOLBY_VISION.equals(mimeType)) { - // Fall back to H.264/AVC or H.265/HEVC for the relevant DV profiles. + // Fall back to H.264/AVC or H.265/HEVC for the relevant DV profiles. This can't be done for + // profile CodecProfileLevel.DolbyVisionProfileDvheStn and profile + // CodecProfileLevel.DolbyVisionProfileDvheDtb because the first one is not backward + // compatible and the second one is deprecated and is not always backward compatible. @Nullable Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); if (codecProfileAndLevel != null) { @@ -503,8 +504,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException { switch (messageType) { - case MSG_SET_SURFACE: - setSurface((Surface) message); + case MSG_SET_VIDEO_OUTPUT: + setOutput(message); break; case MSG_SET_SCALING_MODE: scalingMode = (Integer) message; @@ -530,7 +531,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } - private void setSurface(Surface surface) throws ExoPlaybackException { + private void setOutput(@Nullable Object output) throws ExoPlaybackException { + // Handle unsupported (i.e., non-Surface) outputs by clearing the surface. + @Nullable Surface surface = output instanceof Surface ? (Surface) output : null; + if (surface == null) { // Use a dummy surface if possible. if (dummySurface != null) { @@ -543,6 +547,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } } + // We only need to update the codec if the surface has changed. if (this.surface != surface) { this.surface = surface; @@ -592,9 +597,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected void configureCodec( + protected MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, - MediaCodecAdapter codec, Format format, @Nullable MediaCrypto crypto, float codecOperatingRate) { @@ -617,10 +621,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } surface = dummySurface; } - codec.configure(mediaFormat, surface, crypto, 0); - if (Util.SDK_INT >= 23 && tunneling) { - tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); - } + return new MediaCodecAdapter.Configuration( + codecInfo, mediaFormat, format, surface, crypto, /* flags= */ 0); } @Override @@ -680,6 +682,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name); codecHandlesHdr10PlusOutOfBandMetadata = Assertions.checkNotNull(getCodecInfo()).isHdr10PlusOutOfBandMetadataSupported(); + if (Util.SDK_INT >= 23 && tunneling) { + tunnelingOnFrameRenderedListener = + new OnFrameRenderedListenerV23(Assertions.checkNotNull(getCodec())); + } } @Override @@ -687,6 +693,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { eventDispatcher.decoderReleased(name); } + @Override + protected void onCodecError(Exception codecError) { + Log.e(TAG, "Video codec error", codecError); + eventDispatcher.videoCodecError(codecError); + } + @Override @Nullable protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) @@ -1185,30 +1197,29 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } private void clearReportedVideoSize() { - reportedWidth = Format.NO_VALUE; - reportedHeight = Format.NO_VALUE; - reportedPixelWidthHeightRatio = Format.NO_VALUE; - reportedUnappliedRotationDegrees = Format.NO_VALUE; + reportedVideoSize = null; } private void maybeNotifyVideoSizeChanged() { if ((currentWidth != Format.NO_VALUE || currentHeight != Format.NO_VALUE) - && (reportedWidth != currentWidth || reportedHeight != currentHeight - || reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees - || reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio)) { - eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, - currentPixelWidthHeightRatio); - reportedWidth = currentWidth; - reportedHeight = currentHeight; - reportedUnappliedRotationDegrees = currentUnappliedRotationDegrees; - reportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; + && (reportedVideoSize == null + || reportedVideoSize.width != currentWidth + || reportedVideoSize.height != currentHeight + || reportedVideoSize.unappliedRotationDegrees != currentUnappliedRotationDegrees + || reportedVideoSize.pixelWidthHeightRatio != currentPixelWidthHeightRatio)) { + reportedVideoSize = + new VideoSize( + currentWidth, + currentHeight, + currentUnappliedRotationDegrees, + currentPixelWidthHeightRatio); + eventDispatcher.videoSizeChanged(reportedVideoSize); } } private void maybeRenotifyVideoSizeChanged() { - if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { - eventDispatcher.videoSizeChanged(reportedWidth, reportedHeight, - reportedUnappliedRotationDegrees, reportedPixelWidthHeightRatio); + if (reportedVideoSize != null) { + eventDispatcher.videoSizeChanged(reportedVideoSize); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderGLFrameRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderGLFrameRenderer.java deleted file mode 100644 index 18453bae9b..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderGLFrameRenderer.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2016 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.video; - -import android.opengl.GLES20; -import android.opengl.GLSurfaceView; -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.GlUtil; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.concurrent.atomic.AtomicReference; -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; - -/** - * GLSurfaceView.Renderer implementation that can render YUV Frames returned by a video decoder - * after decoding. It does the YUV to RGB color conversion in the Fragment Shader. - */ -/* package */ class VideoDecoderGLFrameRenderer - implements GLSurfaceView.Renderer, VideoDecoderOutputBufferRenderer { - - private static final float[] kColorConversion601 = { - 1.164f, 1.164f, 1.164f, - 0.0f, -0.392f, 2.017f, - 1.596f, -0.813f, 0.0f, - }; - - private static final float[] kColorConversion709 = { - 1.164f, 1.164f, 1.164f, - 0.0f, -0.213f, 2.112f, - 1.793f, -0.533f, 0.0f, - }; - - private static final float[] kColorConversion2020 = { - 1.168f, 1.168f, 1.168f, - 0.0f, -0.188f, 2.148f, - 1.683f, -0.652f, 0.0f, - }; - - private static final String VERTEX_SHADER = - "varying vec2 interp_tc_y;\n" - + "varying vec2 interp_tc_u;\n" - + "varying vec2 interp_tc_v;\n" - + "attribute vec4 in_pos;\n" - + "attribute vec2 in_tc_y;\n" - + "attribute vec2 in_tc_u;\n" - + "attribute vec2 in_tc_v;\n" - + "void main() {\n" - + " gl_Position = in_pos;\n" - + " interp_tc_y = in_tc_y;\n" - + " interp_tc_u = in_tc_u;\n" - + " interp_tc_v = in_tc_v;\n" - + "}\n"; - private static final String[] TEXTURE_UNIFORMS = {"y_tex", "u_tex", "v_tex"}; - private static final String FRAGMENT_SHADER = - "precision mediump float;\n" - + "varying vec2 interp_tc_y;\n" - + "varying vec2 interp_tc_u;\n" - + "varying vec2 interp_tc_v;\n" - + "uniform sampler2D y_tex;\n" - + "uniform sampler2D u_tex;\n" - + "uniform sampler2D v_tex;\n" - + "uniform mat3 mColorConversion;\n" - + "void main() {\n" - + " vec3 yuv;\n" - + " yuv.x = texture2D(y_tex, interp_tc_y).r - 0.0625;\n" - + " yuv.y = texture2D(u_tex, interp_tc_u).r - 0.5;\n" - + " yuv.z = texture2D(v_tex, interp_tc_v).r - 0.5;\n" - + " gl_FragColor = vec4(mColorConversion * yuv, 1.0);\n" - + "}\n"; - - private static final FloatBuffer TEXTURE_VERTICES = - GlUtil.createBuffer(new float[] {-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f}); - private final GLSurfaceView surfaceView; - private final int[] yuvTextures = new int[3]; - private final AtomicReference<@NullableType VideoDecoderOutputBuffer> - pendingOutputBufferReference; - - // Kept in field rather than a local variable in order not to get garbage collected before - // glDrawArrays uses it. - private FloatBuffer[] textureCoords; - - private int program; - private int[] texLocations; - private int colorMatrixLocation; - private int[] previousWidths; - private int[] previousStrides; - - // Accessed only from the GL thread. - private @MonotonicNonNull VideoDecoderOutputBuffer renderedOutputBuffer; - - public VideoDecoderGLFrameRenderer(GLSurfaceView surfaceView) { - this.surfaceView = surfaceView; - pendingOutputBufferReference = new AtomicReference<>(); - textureCoords = new FloatBuffer[3]; - texLocations = new int[3]; - previousWidths = new int[3]; - previousStrides = new int[3]; - for (int i = 0; i < 3; i++) { - previousWidths[i] = previousStrides[i] = -1; - } - } - - @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"); - GLES20.glEnableVertexAttribArray(posLocation); - GLES20.glVertexAttribPointer( - posLocation, - 2, - GLES20.GL_FLOAT, - /* normalized= */ false, - /* stride= */ 0, - TEXTURE_VERTICES); - texLocations[0] = GLES20.glGetAttribLocation(program, "in_tc_y"); - GLES20.glEnableVertexAttribArray(texLocations[0]); - texLocations[1] = GLES20.glGetAttribLocation(program, "in_tc_u"); - GLES20.glEnableVertexAttribArray(texLocations[1]); - texLocations[2] = GLES20.glGetAttribLocation(program, "in_tc_v"); - GLES20.glEnableVertexAttribArray(texLocations[2]); - GlUtil.checkGlError(); - colorMatrixLocation = GLES20.glGetUniformLocation(program, "mColorConversion"); - GlUtil.checkGlError(); - setupTextures(); - GlUtil.checkGlError(); - } - - @Override - public void onSurfaceChanged(GL10 unused, int width, int height) { - GLES20.glViewport(0, 0, width, height); - } - - @Override - public void onDrawFrame(GL10 unused) { - @Nullable - VideoDecoderOutputBuffer pendingOutputBuffer = - pendingOutputBufferReference.getAndSet(/* newValue= */ null); - if (pendingOutputBuffer == null && renderedOutputBuffer == null) { - // There is no output buffer to render at the moment. - return; - } - if (pendingOutputBuffer != null) { - if (renderedOutputBuffer != null) { - renderedOutputBuffer.release(); - } - renderedOutputBuffer = pendingOutputBuffer; - } - - VideoDecoderOutputBuffer outputBuffer = Assertions.checkNotNull(renderedOutputBuffer); - - // Set color matrix. Assume BT709 if the color space is unknown. - float[] colorConversion = kColorConversion709; - switch (outputBuffer.colorspace) { - case VideoDecoderOutputBuffer.COLORSPACE_BT601: - colorConversion = kColorConversion601; - break; - case VideoDecoderOutputBuffer.COLORSPACE_BT2020: - colorConversion = kColorConversion2020; - break; - case VideoDecoderOutputBuffer.COLORSPACE_BT709: - default: - // Do nothing. - break; - } - GLES20.glUniformMatrix3fv( - colorMatrixLocation, - /* color= */ 1, - /* transpose= */ false, - colorConversion, - /* offset= */ 0); - - int[] yuvStrides = Assertions.checkNotNull(outputBuffer.yuvStrides); - ByteBuffer[] yuvPlanes = Assertions.checkNotNull(outputBuffer.yuvPlanes); - - for (int i = 0; i < 3; i++) { - int h = (i == 0) ? outputBuffer.height : (outputBuffer.height + 1) / 2; - GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); - GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); - GLES20.glTexImage2D( - GLES20.GL_TEXTURE_2D, - /* level= */ 0, - GLES20.GL_LUMINANCE, - yuvStrides[i], - h, - /* border= */ 0, - GLES20.GL_LUMINANCE, - GLES20.GL_UNSIGNED_BYTE, - yuvPlanes[i]); - } - - int[] widths = new int[3]; - widths[0] = outputBuffer.width; - // TODO: Handle streams where chroma channels are not stored at half width and height - // compared to luma channel. See [Internal: b/142097774]. - // U and V planes are being stored at half width compared to Y. - widths[1] = widths[2] = (widths[0] + 1) / 2; - for (int i = 0; i < 3; i++) { - // Set cropping of stride if either width or stride has changed. - if (previousWidths[i] != widths[i] || previousStrides[i] != yuvStrides[i]) { - Assertions.checkState(yuvStrides[i] != 0); - float widthRatio = (float) widths[i] / yuvStrides[i]; - // These buffers are consumed during each call to glDrawArrays. They need to be member - // variables rather than local variables in order not to get garbage collected. - textureCoords[i] = - GlUtil.createBuffer( - new float[] {0.0f, 0.0f, 0.0f, 1.0f, widthRatio, 0.0f, widthRatio, 1.0f}); - GLES20.glVertexAttribPointer( - texLocations[i], - /* size= */ 2, - GLES20.GL_FLOAT, - /* normalized= */ false, - /* stride= */ 0, - textureCoords[i]); - previousWidths[i] = widths[i]; - previousStrides[i] = yuvStrides[i]; - } - } - - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); - GlUtil.checkGlError(); - } - - @Override - public void setOutputBuffer(VideoDecoderOutputBuffer outputBuffer) { - @Nullable - VideoDecoderOutputBuffer oldPendingOutputBuffer = - pendingOutputBufferReference.getAndSet(outputBuffer); - if (oldPendingOutputBuffer != null) { - // The old pending output buffer will never be used for rendering, so release it now. - oldPendingOutputBuffer.release(); - } - surfaceView.requestRender(); - } - - 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.glActiveTexture(GLES20.GL_TEXTURE0 + i); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); - GLES20.glTexParameterf( - GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameterf( - GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); - } - GlUtil.checkGlError(); - } -} 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 b9d016b886..06d9e412e2 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 @@ -16,21 +16,32 @@ package com.google.android.exoplayer2.video; import android.content.Context; +import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.util.AttributeSet; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.GlUtil; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.concurrent.atomic.AtomicReference; +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; /** - * GLSurfaceView for rendering video output. To render video in this view, call {@link - * #getVideoDecoderOutputBufferRenderer()} to get a {@link VideoDecoderOutputBufferRenderer} that - * will render video decoder output buffers in this view. + * GLSurfaceView implementing {@link VideoDecoderOutputBufferRenderer} for rendering {@link + * VideoDecoderOutputBuffer VideoDecoderOutputBuffers}. * - *

        This view is intended for use only with extension renderers. For other use cases a {@link - * android.view.SurfaceView} or {@link android.view.TextureView} should be used instead. + *

        This view is intended for use only with decoders that produce {@link VideoDecoderOutputBuffer + * VideoDecoderOutputBuffers}. For other use cases a {@link android.view.SurfaceView} or {@link + * android.view.TextureView} should be used instead. */ -public class VideoDecoderGLSurfaceView extends GLSurfaceView { +public final class VideoDecoderGLSurfaceView extends GLSurfaceView + implements VideoDecoderOutputBufferRenderer { - private final VideoDecoderGLFrameRenderer renderer; + private final Renderer renderer; /** @param context A {@link Context}. */ public VideoDecoderGLSurfaceView(Context context) { @@ -48,15 +59,260 @@ public class VideoDecoderGLSurfaceView extends GLSurfaceView { }) public VideoDecoderGLSurfaceView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - renderer = new VideoDecoderGLFrameRenderer(/* surfaceView= */ this); + renderer = new Renderer(/* surfaceView= */ this); setPreserveEGLContextOnPause(true); setEGLContextClientVersion(2); setRenderer(renderer); setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); } - /** Returns the {@link VideoDecoderOutputBufferRenderer} that will render frames in this view. */ + @Override + public void setOutputBuffer(VideoDecoderOutputBuffer outputBuffer) { + renderer.setOutputBuffer(outputBuffer); + } + + /** @deprecated This class implements {@link VideoDecoderOutputBufferRenderer} directly. */ + @Deprecated public VideoDecoderOutputBufferRenderer getVideoDecoderOutputBufferRenderer() { - return renderer; + return this; + } + + private static final class Renderer implements GLSurfaceView.Renderer { + + private static final float[] kColorConversion601 = { + 1.164f, 1.164f, 1.164f, + 0.0f, -0.392f, 2.017f, + 1.596f, -0.813f, 0.0f, + }; + + private static final float[] kColorConversion709 = { + 1.164f, 1.164f, 1.164f, + 0.0f, -0.213f, 2.112f, + 1.793f, -0.533f, 0.0f, + }; + + private static final float[] kColorConversion2020 = { + 1.168f, 1.168f, 1.168f, + 0.0f, -0.188f, 2.148f, + 1.683f, -0.652f, 0.0f, + }; + + private static final String VERTEX_SHADER = + "varying vec2 interp_tc_y;\n" + + "varying vec2 interp_tc_u;\n" + + "varying vec2 interp_tc_v;\n" + + "attribute vec4 in_pos;\n" + + "attribute vec2 in_tc_y;\n" + + "attribute vec2 in_tc_u;\n" + + "attribute vec2 in_tc_v;\n" + + "void main() {\n" + + " gl_Position = in_pos;\n" + + " interp_tc_y = in_tc_y;\n" + + " interp_tc_u = in_tc_u;\n" + + " interp_tc_v = in_tc_v;\n" + + "}\n"; + private static final String[] TEXTURE_UNIFORMS = {"y_tex", "u_tex", "v_tex"}; + private static final String FRAGMENT_SHADER = + "precision mediump float;\n" + + "varying vec2 interp_tc_y;\n" + + "varying vec2 interp_tc_u;\n" + + "varying vec2 interp_tc_v;\n" + + "uniform sampler2D y_tex;\n" + + "uniform sampler2D u_tex;\n" + + "uniform sampler2D v_tex;\n" + + "uniform mat3 mColorConversion;\n" + + "void main() {\n" + + " vec3 yuv;\n" + + " yuv.x = texture2D(y_tex, interp_tc_y).r - 0.0625;\n" + + " yuv.y = texture2D(u_tex, interp_tc_u).r - 0.5;\n" + + " yuv.z = texture2D(v_tex, interp_tc_v).r - 0.5;\n" + + " gl_FragColor = vec4(mColorConversion * yuv, 1.0);\n" + + "}\n"; + + private static final FloatBuffer TEXTURE_VERTICES = + GlUtil.createBuffer(new float[] {-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f}); + + private final GLSurfaceView surfaceView; + private final int[] yuvTextures; + private final int[] texLocations; + private final int[] previousWidths; + private final int[] previousStrides; + private final AtomicReference<@NullableType VideoDecoderOutputBuffer> + pendingOutputBufferReference; + + // Kept in field rather than a local variable in order not to get garbage collected before + // glDrawArrays uses it. + private final FloatBuffer[] textureCoords; + + private int program; + private int colorMatrixLocation; + + // Accessed only from the GL thread. + private @MonotonicNonNull VideoDecoderOutputBuffer renderedOutputBuffer; + + public Renderer(GLSurfaceView surfaceView) { + this.surfaceView = surfaceView; + yuvTextures = new int[3]; + texLocations = new int[3]; + previousWidths = new int[3]; + previousStrides = new int[3]; + pendingOutputBufferReference = new AtomicReference<>(); + textureCoords = new FloatBuffer[3]; + for (int i = 0; i < 3; i++) { + previousWidths[i] = previousStrides[i] = -1; + } + } + + @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"); + GLES20.glEnableVertexAttribArray(posLocation); + GLES20.glVertexAttribPointer( + posLocation, + 2, + GLES20.GL_FLOAT, + /* normalized= */ false, + /* stride= */ 0, + TEXTURE_VERTICES); + texLocations[0] = GLES20.glGetAttribLocation(program, "in_tc_y"); + GLES20.glEnableVertexAttribArray(texLocations[0]); + texLocations[1] = GLES20.glGetAttribLocation(program, "in_tc_u"); + GLES20.glEnableVertexAttribArray(texLocations[1]); + texLocations[2] = GLES20.glGetAttribLocation(program, "in_tc_v"); + GLES20.glEnableVertexAttribArray(texLocations[2]); + GlUtil.checkGlError(); + colorMatrixLocation = GLES20.glGetUniformLocation(program, "mColorConversion"); + GlUtil.checkGlError(); + setupTextures(); + GlUtil.checkGlError(); + } + + @Override + public void onSurfaceChanged(GL10 unused, int width, int height) { + GLES20.glViewport(0, 0, width, height); + } + + @Override + public void onDrawFrame(GL10 unused) { + @Nullable + VideoDecoderOutputBuffer pendingOutputBuffer = + pendingOutputBufferReference.getAndSet(/* newValue= */ null); + if (pendingOutputBuffer == null && renderedOutputBuffer == null) { + // There is no output buffer to render at the moment. + return; + } + if (pendingOutputBuffer != null) { + if (renderedOutputBuffer != null) { + renderedOutputBuffer.release(); + } + renderedOutputBuffer = pendingOutputBuffer; + } + + VideoDecoderOutputBuffer outputBuffer = Assertions.checkNotNull(renderedOutputBuffer); + + // Set color matrix. Assume BT709 if the color space is unknown. + float[] colorConversion = kColorConversion709; + switch (outputBuffer.colorspace) { + case VideoDecoderOutputBuffer.COLORSPACE_BT601: + colorConversion = kColorConversion601; + break; + case VideoDecoderOutputBuffer.COLORSPACE_BT2020: + colorConversion = kColorConversion2020; + break; + case VideoDecoderOutputBuffer.COLORSPACE_BT709: + default: + // Do nothing. + break; + } + GLES20.glUniformMatrix3fv( + colorMatrixLocation, + /* color= */ 1, + /* transpose= */ false, + colorConversion, + /* offset= */ 0); + + int[] yuvStrides = Assertions.checkNotNull(outputBuffer.yuvStrides); + ByteBuffer[] yuvPlanes = Assertions.checkNotNull(outputBuffer.yuvPlanes); + + for (int i = 0; i < 3; i++) { + int h = (i == 0) ? outputBuffer.height : (outputBuffer.height + 1) / 2; + GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); + GLES20.glTexImage2D( + GLES20.GL_TEXTURE_2D, + /* level= */ 0, + GLES20.GL_LUMINANCE, + yuvStrides[i], + h, + /* border= */ 0, + GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, + yuvPlanes[i]); + } + + int[] widths = new int[3]; + widths[0] = outputBuffer.width; + // TODO: Handle streams where chroma channels are not stored at half width and height + // compared to luma channel. See [Internal: b/142097774]. + // U and V planes are being stored at half width compared to Y. + widths[1] = widths[2] = (widths[0] + 1) / 2; + for (int i = 0; i < 3; i++) { + // Set cropping of stride if either width or stride has changed. + if (previousWidths[i] != widths[i] || previousStrides[i] != yuvStrides[i]) { + Assertions.checkState(yuvStrides[i] != 0); + float widthRatio = (float) widths[i] / yuvStrides[i]; + // These buffers are consumed during each call to glDrawArrays. They need to be member + // variables rather than local variables in order not to get garbage collected. + textureCoords[i] = + GlUtil.createBuffer( + new float[] {0.0f, 0.0f, 0.0f, 1.0f, widthRatio, 0.0f, widthRatio, 1.0f}); + GLES20.glVertexAttribPointer( + texLocations[i], + /* size= */ 2, + GLES20.GL_FLOAT, + /* normalized= */ false, + /* stride= */ 0, + textureCoords[i]); + previousWidths[i] = widths[i]; + previousStrides[i] = yuvStrides[i]; + } + } + + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); + GlUtil.checkGlError(); + } + + public void setOutputBuffer(VideoDecoderOutputBuffer outputBuffer) { + @Nullable + VideoDecoderOutputBuffer oldPendingOutputBuffer = + pendingOutputBufferReference.getAndSet(outputBuffer); + if (oldPendingOutputBuffer != null) { + // The old pending output buffer will never be used for rendering, so release it now. + oldPendingOutputBuffer.release(); + } + surfaceView.requestRender(); + } + + 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.glActiveTexture(GLES20.GL_TEXTURE0 + i); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + } + GlUtil.checkGlError(); + } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java index 899f1a8d47..4a0a0fbe09 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java @@ -30,8 +30,8 @@ public class VideoDecoderOutputBuffer extends OutputBuffer { public static final int COLORSPACE_BT709 = 2; public static final int COLORSPACE_BT2020 = 3; // LINT.ThenChange( - // ../../../../../../../../../../extensions/av1/src/main/jni/gav1_jni.cc, - // ../../../../../../../../../../extensions/vp9/src/main/jni/vpx_jni.cc + // ../../../../../../../../../../../../media/libraries/decoder_av1/src/main/jni/gav1_jni.cc, + // ../../../../../../../../../../../../media/libraries/decoder_vp9/src/main/jni/vpx_jni.cc // ) /** Decoder private data. Used from native code. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java index aacd8ce8d4..63370e7e54 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java @@ -17,14 +17,17 @@ package com.google.android.exoplayer2.video; import static com.google.android.exoplayer2.util.Util.castNonNull; +import android.media.MediaCodec; +import android.media.MediaCodec.CodecException; import android.os.Handler; import android.os.SystemClock; import android.view.Surface; -import android.view.TextureView; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.decoder.DecoderException; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.util.Assertions; @@ -65,11 +68,8 @@ public interface VideoRendererEventListener { * decoder instance can be reused for the new format, or {@code null} if the renderer did not * have a decoder. */ - @SuppressWarnings("deprecation") default void onVideoInputFormatChanged( - Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { - onVideoInputFormatChanged(format); - } + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {} /** * Called to report the number of frames dropped by the renderer. Dropped frames are reported @@ -107,30 +107,19 @@ public interface VideoRendererEventListener { * Called before a frame is rendered for the first time since setting the surface, and each time * there's a change in the size, rotation or pixel aspect ratio of the video being rendered. * - * @param width The video width in pixels. - * @param height The video height in pixels. - * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise - * rotation in degrees that the application should apply for the video for it to be rendered - * in the correct orientation. This value will always be zero on API levels 21 and above, - * since the renderer will apply all necessary rotations internally. On earlier API levels - * this is not possible. Applications that use {@link TextureView} can apply the rotation by - * calling {@link TextureView#setTransform}. Applications that do not expect to encounter - * rotated videos can safely ignore this parameter. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of - * square pixels this will be equal to 1.0. Different values are indicative of anamorphic - * content. + * @param videoSize The new size of the video. */ - default void onVideoSizeChanged( - int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {} + default void onVideoSizeChanged(VideoSize videoSize) {} /** - * Called when a frame is rendered for the first time since setting the surface, or since the + * Called when a frame is rendered for the first time since setting the output, or since the * renderer was reset, or since the stream being rendered was changed. * - * @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if - * the renderer renders to something that isn't a {@link Surface}. + * @param output The output of the video renderer. Normally a {@link Surface}, however some video + * renderers may have other output types (e.g., a {@link VideoDecoderOutputBufferRenderer}). + * @param renderTimeMs The {@link SystemClock#elapsedRealtime()} when the frame was rendered. */ - default void onRenderedFirstFrame(@Nullable Surface surface) {} + default void onRenderedFirstFrame(Object output, long renderTimeMs) {} /** * Called when a decoder is released. @@ -147,8 +136,21 @@ public interface VideoRendererEventListener { default void onVideoDisabled(DecoderCounters counters) {} /** - * Dispatches events to a {@link VideoRendererEventListener}. + * Called when a video decoder encounters an error. + * + *

        This method being called does not indicate that playback has failed, or that it will fail. + * The player may be able to recover from the error. Hence applications should not + * implement this method to display a user visible error or initiate an application level retry. + * {@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior. This + * method is called to provide the application with an opportunity to log the error if it wishes + * to do so. + * + * @param videoCodecError The error. Typically a {@link CodecException} if the renderer uses + * {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder. */ + default void onVideoCodecError(Exception videoCodecError) {} + + /** Dispatches events to a {@link VideoRendererEventListener}. */ final class EventDispatcher { @Nullable private final Handler handler; @@ -188,11 +190,15 @@ public interface VideoRendererEventListener { * Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format, * DecoderReuseEvaluation)}. */ + @SuppressWarnings("deprecation") // Calling deprecated listener method. public void inputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { if (handler != null) { handler.post( - () -> castNonNull(listener).onVideoInputFormatChanged(format, decoderReuseEvaluation)); + () -> { + castNonNull(listener).onVideoInputFormatChanged(format); + castNonNull(listener).onVideoInputFormatChanged(format, decoderReuseEvaluation); + }); } } @@ -213,25 +219,19 @@ public interface VideoRendererEventListener { } } - /** Invokes {@link VideoRendererEventListener#onVideoSizeChanged(int, int, int, float)}. */ - public void videoSizeChanged( - int width, - int height, - final int unappliedRotationDegrees, - final float pixelWidthHeightRatio) { + /** Invokes {@link VideoRendererEventListener#onVideoSizeChanged(VideoSize)}. */ + public void videoSizeChanged(VideoSize videoSize) { if (handler != null) { - handler.post( - () -> - castNonNull(listener) - .onVideoSizeChanged( - width, height, unappliedRotationDegrees, pixelWidthHeightRatio)); + handler.post(() -> castNonNull(listener).onVideoSizeChanged(videoSize)); } } - /** Invokes {@link VideoRendererEventListener#onRenderedFirstFrame(Surface)}. */ - public void renderedFirstFrame(@Nullable Surface surface) { + /** Invokes {@link VideoRendererEventListener#onRenderedFirstFrame(Object, long)}. */ + public void renderedFirstFrame(Object output) { if (handler != null) { - handler.post(() -> castNonNull(listener).onRenderedFirstFrame(surface)); + // TODO: Replace this timestamp with the actual frame release time. + long renderTimeMs = SystemClock.elapsedRealtime(); + handler.post(() -> castNonNull(listener).onRenderedFirstFrame(output, renderTimeMs)); } } @@ -254,6 +254,12 @@ public interface VideoRendererEventListener { } } + /** Invokes {@link VideoRendererEventListener#onVideoCodecError(Exception)}. */ + public void videoCodecError(Exception videoCodecError) { + if (handler != null) { + handler.post(() -> castNonNull(listener).onVideoCodecError(videoCodecError)); + } + } } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java index 287c62521e..6eaa2812c9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -94,8 +94,7 @@ public final class CameraMotionRenderer extends BaseRenderer { while (!hasReadStreamToEnd() && lastTimestampUs < positionUs + SAMPLE_WINDOW_DURATION_US) { buffer.clear(); FormatHolder formatHolder = getFormatHolder(); - @SampleStream.ReadDataResult - int result = readSource(formatHolder, buffer, /* formatRequired= */ false); + @ReadDataResult int result = readSource(formatHolder, buffer, /* readFlags= */ 0); if (result != C.RESULT_BUFFER_READ || buffer.isEndOfStream()) { return; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java index d464bf04fa..84bdcb7277 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java @@ -27,7 +27,7 @@ import com.google.android.exoplayer2.util.TimedValueQueue; *

      • Recenters the rotations to componsate the yaw of the initial rotation. *
      */ -public final class FrameRotationQueue { +/* package */ final class FrameRotationQueue { private final float[] recenterMatrix; private final float[] rotationMatrix; private final TimedValueQueue rotations; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/OrientationListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/OrientationListener.java similarity index 97% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/OrientationListener.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/spherical/OrientationListener.java index 7276953cf5..d35c7531b1 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/OrientationListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/OrientationListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.video.spherical; import android.hardware.Sensor; import android.hardware.SensorEvent; @@ -23,7 +23,6 @@ import android.opengl.Matrix; import android.view.Display; import android.view.Surface; import androidx.annotation.BinderThread; -import com.google.android.exoplayer2.video.spherical.FrameRotationQueue; /** * Listens for orientation sensor events, converts event data to rotation matrix and roll value, and diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java index 8ba24bb06e..357299a5d2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java @@ -24,7 +24,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** The projection mesh used with 360/VR videos. */ -public final class Projection { +/* package */ final class Projection { /** Enforces allowed (sub) mesh draw modes. */ @Documented diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java index 9f7f2362e5..9f2e1e697e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java @@ -34,7 +34,7 @@ import java.util.zip.Inflater; * *

      The decoder does not perform CRC checks at the moment. */ -public final class ProjectionDecoder { +/* package */ final class ProjectionDecoder { private static final int TYPE_YTMP = 0x79746d70; private static final int TYPE_MSHP = 0x6d736870; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java similarity index 98% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java index 9a8c787e77..4ba5dcca08 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionRenderer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.video.spherical; import static com.google.android.exoplayer2.util.GlUtil.checkGlError; @@ -22,7 +22,6 @@ import android.opengl.GLES20; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.GlUtil; -import com.google.android.exoplayer2.video.spherical.Projection; import java.nio.FloatBuffer; /** diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SceneRenderer.java similarity index 95% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SceneRenderer.java index 674826e387..dcc15d1fed 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SceneRenderer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.video.spherical; import static com.google.android.exoplayer2.util.GlUtil.checkGlError; @@ -28,10 +28,6 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; -import com.google.android.exoplayer2.video.spherical.CameraMotionListener; -import com.google.android.exoplayer2.video.spherical.FrameRotationQueue; -import com.google.android.exoplayer2.video.spherical.Projection; -import com.google.android.exoplayer2.video.spherical.ProjectionDecoder; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalGLSurfaceView.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SphericalGLSurfaceView.java similarity index 79% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalGLSurfaceView.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SphericalGLSurfaceView.java index 1c96f41df5..5f1610350a 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalGLSurfaceView.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/SphericalGLSurfaceView.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.video.spherical; import android.content.Context; import android.graphics.PointF; @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.view.Display; +import android.view.MotionEvent; import android.view.Surface; import android.view.WindowManager; import androidx.annotation.AnyThread; @@ -35,9 +36,10 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; +import java.util.concurrent.CopyOnWriteArrayList; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; @@ -53,6 +55,16 @@ import javax.microedition.khronos.opengles.GL10; */ public final class SphericalGLSurfaceView extends GLSurfaceView { + /** Listener for the {@link Surface} to which video frames should be rendered. */ + public interface VideoSurfaceListener { + + /** Called when the {@link Surface} to which video frames should be rendered is created. */ + void onVideoSurfaceCreated(Surface surface); + + /** Called when the {@link Surface} to which video frames should be rendered is destroyed. */ + void onVideoSurfaceDestroyed(Surface surface); + } + // Arbitrary vertical field of view. private static final int FIELD_OF_VIEW_DEGREES = 90; private static final float Z_NEAR = 0.1f; @@ -63,6 +75,7 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { /* package */ static final float UPRIGHT_ROLL = (float) Math.PI; + private final CopyOnWriteArrayList videoSurfaceListeners; private final SensorManager sensorManager; @Nullable private final Sensor orientationSensor; private final OrientationListener orientationListener; @@ -71,7 +84,6 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { private final SceneRenderer scene; @Nullable private SurfaceTexture surfaceTexture; @Nullable private Surface surface; - @Nullable private Player.VideoComponent videoComponent; private boolean useSensorRotation; private boolean isStarted; private boolean isOrientationListenerRegistered; @@ -82,6 +94,7 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { public SphericalGLSurfaceView(Context context, @Nullable AttributeSet attributeSet) { super(context, attributeSet); + videoSurfaceListeners = new CopyOnWriteArrayList<>(); mainHandler = new Handler(Looper.getMainLooper()); // Configure sensors and touch. @@ -114,6 +127,43 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { setOnTouchListener(touchTracker); } + /** + * Adds a {@link VideoSurfaceListener}. + * + * @param listener The listener to add. + */ + public void addVideoSurfaceListener(VideoSurfaceListener listener) { + videoSurfaceListeners.add(listener); + } + + /** + * Removes a {@link VideoSurfaceListener}. + * + * @param listener The listener to remove. + */ + public void removeVideoSurfaceListener(VideoSurfaceListener listener) { + videoSurfaceListeners.remove(listener); + } + + /** + * Returns the {@link Surface} to which video frames should be rendered, or {@code null} if it has + * not been created. + */ + @Nullable + public Surface getVideoSurface() { + return surface; + } + + /** Returns the {@link VideoFrameMetadataListener} that should be registered during playback. */ + public VideoFrameMetadataListener getVideoFrameMetadataListener() { + return scene; + } + + /** Returns the {@link CameraMotionListener} that should be registered during playback. */ + public CameraMotionListener getCameraMotionListener() { + return scene; + } + /** * Sets the default stereo mode. If the played video doesn't contain a stereo mode the default one * is used. @@ -124,31 +174,6 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { scene.setDefaultStereoMode(stereoMode); } - /** Sets the {@link Player.VideoComponent} to use. */ - public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) { - if (newVideoComponent == videoComponent) { - return; - } - if (videoComponent != null) { - if (surface != null) { - videoComponent.clearVideoSurface(surface); - } - videoComponent.clearVideoFrameMetadataListener(scene); - videoComponent.clearCameraMotionListener(scene); - } - videoComponent = newVideoComponent; - if (videoComponent != null) { - videoComponent.setVideoFrameMetadataListener(scene); - videoComponent.setCameraMotionListener(scene); - videoComponent.setVideoSurface(surface); - } - } - - /** Sets the {@link SingleTapListener} used to listen to single tap events on this view. */ - public void setSingleTapListener(@Nullable SingleTapListener listener) { - touchTracker.setSingleTapListener(listener); - } - /** Sets whether to use the orientation sensor for rotation (if available). */ public void setUseSensorRotation(boolean useSensorRotation) { this.useSensorRotation = useSensorRotation; @@ -177,14 +202,15 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { // Post to make sure we occur in order with any onSurfaceTextureAvailable calls. mainHandler.post( () -> { - if (surface != null) { - if (videoComponent != null) { - videoComponent.clearVideoSurface(surface); + @Nullable Surface oldSurface = surface; + if (oldSurface != null) { + for (VideoSurfaceListener videoSurfaceListener : videoSurfaceListeners) { + videoSurfaceListener.onVideoSurfaceDestroyed(oldSurface); } - releaseSurface(surfaceTexture, surface); - surfaceTexture = null; - surface = null; } + releaseSurface(surfaceTexture, oldSurface); + surfaceTexture = null; + surface = null; }); } @@ -203,15 +229,16 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { } // Called on GL thread. - private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) { + private void onSurfaceTextureAvailable(SurfaceTexture newSurfaceTexture) { mainHandler.post( () -> { - SurfaceTexture oldSurfaceTexture = this.surfaceTexture; - Surface oldSurface = this.surface; - this.surfaceTexture = surfaceTexture; - this.surface = new Surface(surfaceTexture); - if (videoComponent != null) { - videoComponent.setVideoSurface(surface); + @Nullable SurfaceTexture oldSurfaceTexture = surfaceTexture; + @Nullable Surface oldSurface = surface; + Surface newSurface = new Surface(newSurfaceTexture); + surfaceTexture = newSurfaceTexture; + surface = newSurface; + for (VideoSurfaceListener videoSurfaceListener : videoSurfaceListeners) { + videoSurfaceListener.onVideoSurfaceCreated(newSurface); } releaseSurface(oldSurfaceTexture, oldSurface); }); @@ -232,7 +259,7 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { * onDrawFrame and updatePitchMatrix. */ @VisibleForTesting - /* package */ class Renderer + /* package */ final class Renderer implements GLSurfaceView.Renderer, TouchTracker.Listener, OrientationListener.Listener { private final SceneRenderer scene; private final float[] projectionMatrix = new float[16]; @@ -325,6 +352,12 @@ public final class SphericalGLSurfaceView extends GLSurfaceView { Matrix.setRotateM(touchYawMatrix, 0, -scrollOffsetDegrees.x, 0, 1, 0); } + @Override + @UiThread + public boolean onSingleTapUp(MotionEvent event) { + return performClick(); + } + private float calculateFieldOfViewInYDirection(float aspect) { boolean landscapeMode = aspect > 1; if (landscapeMode) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/TouchTracker.java similarity index 91% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/spherical/TouchTracker.java index 20b7dc0319..c159fbee59 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/TouchTracker.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.video.spherical; import android.content.Context; import android.graphics.PointF; @@ -21,7 +21,6 @@ import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import androidx.annotation.BinderThread; -import androidx.annotation.Nullable; /** * Basic touch input system. @@ -44,11 +43,15 @@ import androidx.annotation.Nullable; * a nicer UI. An even more advanced UI would reproject the user's touch point into 3D and drag the * Mesh as the user moves their finger. However, that requires quaternion interpolation. */ -/* package */ class TouchTracker extends GestureDetector.SimpleOnGestureListener +/* package */ final class TouchTracker extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener, OrientationListener.Listener { - /* package */ interface Listener { + public interface Listener { void onScrollChange(PointF scrollOffsetDegrees); + + default boolean onSingleTapUp(MotionEvent event) { + return false; + } } // Touch input won't change the pitch beyond +/- 45 degrees. This reduces awkward situations @@ -65,7 +68,6 @@ import androidx.annotation.Nullable; // The conversion from touch to yaw & pitch requires compensating for device roll. This is set // on the sensor thread and read on the UI thread. private volatile float roll; - @Nullable private SingleTapListener singleTapListener; @SuppressWarnings({ "nullness:assignment.type.incompatible", @@ -78,10 +80,6 @@ import androidx.annotation.Nullable; roll = SphericalGLSurfaceView.UPRIGHT_ROLL; } - public void setSingleTapListener(@Nullable SingleTapListener listener) { - singleTapListener = listener; - } - /** * Converts ACTION_MOVE events to pitch & yaw events while compensating for device roll. * @@ -125,10 +123,7 @@ import androidx.annotation.Nullable; @Override public boolean onSingleTapUp(MotionEvent e) { - if (singleTapListener != null) { - return singleTapListener.onSingleTapUp(e); - } - return false; + return listener.onSingleTapUp(e); } @Override diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/spherical/package-info.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/package-info.java similarity index 100% rename from library/common/src/main/java/com/google/android/exoplayer2/video/spherical/package-info.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/spherical/package-info.java 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 b572729e02..5de5e866ea 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 @@ -15,6 +15,28 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.Player.COMMAND_ADJUST_DEVICE_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS; +import static com.google.android.exoplayer2.Player.COMMAND_GET_AUDIO_ATTRIBUTES; +import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_GET_DEVICE_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS; +import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; +import static com.google.android.exoplayer2.Player.COMMAND_GET_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE; +import static com.google.android.exoplayer2.Player.COMMAND_PREPARE_STOP; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME; +import static com.google.android.exoplayer2.Player.COMMAND_SET_REPEAT_MODE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_SHUFFLE_MODE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_SPEED_AND_PITCH; +import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_VOLUME; import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfWindow; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; @@ -28,6 +50,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; +import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -52,7 +75,6 @@ import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Player.DiscontinuityReason; -import com.google.android.exoplayer2.Player.EventListener; import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioAttributes; @@ -65,7 +87,6 @@ import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; import com.google.android.exoplayer2.source.ClippingMediaSource; import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.LoopingMediaSource; import com.google.android.exoplayer2.source.MaskingMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; @@ -80,7 +101,6 @@ import com.google.android.exoplayer2.testutil.Action; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; import com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet; import com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource; @@ -111,6 +131,7 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Range; import java.io.IOException; import java.util.ArrayList; @@ -118,6 +139,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -125,7 +147,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -133,7 +154,6 @@ import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import org.mockito.Mockito; import org.robolectric.shadows.ShadowAudioManager; -import org.robolectric.shadows.ShadowLooper; /** Unit test for {@link ExoPlayer}. */ @RunWith(AndroidJUnit4.class) @@ -171,25 +191,26 @@ public final class ExoPlayerTest { FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_UNKNOWN); SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build(); - EventListener mockEventListener = mock(EventListener.class); - player.addListener(mockEventListener); + Player.Listener mockListener = mock(Player.Listener.class); + player.addListener(mockListener); player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT)); player.prepare(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); - InOrder inOrder = inOrder(mockEventListener); + InOrder inOrder = inOrder(mockListener); inOrder - .verify(mockEventListener) + .verify(mockListener) .onTimelineChanged( argThat(noUid(expectedMaskingTimeline)), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); inOrder - .verify(mockEventListener) + .verify(mockListener) .onTimelineChanged( argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); - inOrder.verify(mockEventListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); assertThat(renderer.getFormatsRead()).isEmpty(); assertThat(renderer.sampleBufferReadCount).isEqualTo(0); assertThat(renderer.isEnded).isFalse(); @@ -201,29 +222,30 @@ public final class ExoPlayerTest { Timeline timeline = new FakeTimeline(); FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO); SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build(); - EventListener mockEventListener = mock(EventListener.class); - player.addListener(mockEventListener); + Player.Listener mockListener = mock(Player.Listener.class); + player.addListener(mockListener); player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT)); player.prepare(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); - InOrder inOrder = Mockito.inOrder(mockEventListener); + InOrder inOrder = Mockito.inOrder(mockListener); inOrder - .verify(mockEventListener) + .verify(mockListener) .onTimelineChanged( argThat(noUid(placeholderTimeline)), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); inOrder - .verify(mockEventListener) + .verify(mockListener) .onTimelineChanged( argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); inOrder - .verify(mockEventListener) + .verify(mockListener) .onTracksChanged( eq(new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT))), any()); - inOrder.verify(mockEventListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt()); + inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); assertThat(renderer.getFormatsRead()).containsExactly(ExoPlayerTestRunner.VIDEO_FORMAT); assertThat(renderer.sampleBufferReadCount).isEqualTo(1); assertThat(renderer.isEnded).isTrue(); @@ -235,27 +257,27 @@ public final class ExoPlayerTest { Timeline timeline = new FakeTimeline(/* windowCount= */ 3); FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO); SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build(); - EventListener mockEventListener = mock(EventListener.class); - player.addListener(mockEventListener); + Player.Listener mockPlayerListener = mock(Player.Listener.class); + player.addListener(mockPlayerListener); player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT)); player.prepare(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); - InOrder inOrder = Mockito.inOrder(mockEventListener); + InOrder inOrder = Mockito.inOrder(mockPlayerListener); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(new FakeMediaSource.InitialTimeline(timeline))), - eq(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION)); + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); inOrder - .verify(mockEventListener, times(2)) - .onPositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + .verify(mockPlayerListener, times(2)) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); assertThat(renderer.getFormatsRead()) .containsExactly( ExoPlayerTestRunner.VIDEO_FORMAT, @@ -273,27 +295,27 @@ public final class ExoPlayerTest { new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 100, /* id= */ 0)); FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO); SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build(); - EventListener mockEventListener = mock(EventListener.class); - player.addListener(mockEventListener); + Player.Listener mockPlayerListener = mock(Player.Listener.class); + player.addListener(mockPlayerListener); player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT)); player.prepare(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); - InOrder inOrder = inOrder(mockEventListener); + InOrder inOrder = inOrder(mockPlayerListener); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(placeholderTimeline)), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); inOrder - .verify(mockEventListener, times(99)) - .onPositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + .verify(mockPlayerListener, times(99)) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); assertThat(renderer.getFormatsRead()).hasSize(100); assertThat(renderer.sampleBufferReadCount).isEqualTo(100); assertThat(renderer.isEnded).isTrue(); @@ -355,8 +377,8 @@ public final class ExoPlayerTest { }; SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(videoRenderer, audioRenderer).build(); - EventListener mockEventListener = mock(EventListener.class); - player.addListener(mockEventListener); + Player.Listener mockPlayerListener = mock(Player.Listener.class); + player.addListener(mockPlayerListener); player.setMediaSource( new FakeMediaSource( @@ -365,19 +387,19 @@ public final class ExoPlayerTest { player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); - InOrder inOrder = inOrder(mockEventListener); + InOrder inOrder = inOrder(mockPlayerListener); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(new FakeMediaSource.InitialTimeline(timeline))), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); inOrder - .verify(mockEventListener, times(2)) - .onPositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + .verify(mockPlayerListener, times(2)) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); assertThat(audioRenderer.positionResetCount).isEqualTo(1); assertThat(videoRenderer.isEnded).isTrue(); assertThat(audioRenderer.isEnded).isTrue(); @@ -404,8 +426,8 @@ public final class ExoPlayerTest { Timeline thirdTimeline = new FakeTimeline(); MediaSource thirdSource = new FakeMediaSource(thirdTimeline, ExoPlayerTestRunner.VIDEO_FORMAT); SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build(); - EventListener mockEventListener = mock(EventListener.class); - player.addListener(mockEventListener); + Player.Listener mockPlayerListener = mock(Player.Listener.class); + player.addListener(mockPlayerListener); player.setMediaSource(firstSource); player.prepare(); @@ -420,28 +442,38 @@ public final class ExoPlayerTest { // prepared, it immediately exposed a placeholder timeline, but the source info refresh from the // second source was suppressed as we replace it with the third source before the update // arrives. - InOrder inOrder = inOrder(mockEventListener); - inOrder.verify(mockEventListener, never()).onPositionDiscontinuity(anyInt()); + InOrder inOrder = inOrder(mockPlayerListener); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(placeholderTimeline)), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(firstTimeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); inOrder - .verify(mockEventListener, times(2)) + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(placeholderTimeline)), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(mockPlayerListener) + .onTimelineChanged( + argThat(noUid(placeholderTimeline)), + eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); + inOrder + .verify(mockPlayerListener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(mockPlayerListener) .onTimelineChanged( argThat(noUid(thirdTimeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)); inOrder - .verify(mockEventListener) + .verify(mockPlayerListener) .onTracksChanged( eq(new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT))), any()); assertThat(renderer.isEnded).isTrue(); @@ -522,11 +554,11 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); testRunner.assertPlayedPeriodIndices(0, 1, 0, 2, 1, 2); testRunner.assertPositionDiscontinuityReasonsEqual( - Player.DISCONTINUITY_REASON_PERIOD_TRANSITION, - Player.DISCONTINUITY_REASON_PERIOD_TRANSITION, - Player.DISCONTINUITY_REASON_PERIOD_TRANSITION, - Player.DISCONTINUITY_REASON_PERIOD_TRANSITION, - Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + Player.DISCONTINUITY_REASON_AUTO_TRANSITION, + Player.DISCONTINUITY_REASON_AUTO_TRANSITION, + Player.DISCONTINUITY_REASON_AUTO_TRANSITION, + Player.DISCONTINUITY_REASON_AUTO_TRANSITION, + Player.DISCONTINUITY_REASON_AUTO_TRANSITION); assertThat(renderer.isEnded).isTrue(); } @@ -583,7 +615,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); // There is still one discontinuity from content to content for the failed ad insertion. - testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_AD_INSERTION); + testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_AUTO_TRANSITION); } @Test @@ -1006,11 +1038,15 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_BUFFERING) // Block until createPeriod has been called on the fake media source. .executeRunnable( - () -> { - try { - createPeriodCalledCountDownLatch.await(); - } catch (InterruptedException e) { - throw new IllegalStateException(e); + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + try { + player.getClock().onThreadBlocked(); + createPeriodCalledCountDownLatch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } } }) // Set playback speed (while the fake media period is not yet prepared). @@ -1243,7 +1279,8 @@ public final class ExoPlayerTest { Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); - testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); + testRunner.assertPositionDiscontinuityReasonsEqual( + Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_REMOVE); assertThat(currentWindowIndex[0]).isEqualTo(1); assertThat(currentPosition[0]).isGreaterThan(0); @@ -1652,7 +1689,7 @@ public final class ExoPlayerTest { @Test public void seekAndReprepareAfterPlaybackError_keepsSeekPositionAndTimeline() throws Exception { SimpleExoPlayer player = new TestExoPlayerBuilder(context).build(); - Player.EventListener mockListener = mock(Player.EventListener.class); + Player.Listener mockListener = mock(Player.Listener.class); player.addListener(mockListener); FakeMediaSource fakeMediaSource = new FakeMediaSource(); player.setMediaSource(fakeMediaSource); @@ -1690,40 +1727,6 @@ public final class ExoPlayerTest { assertThat(positionWhenFullyReadyAfterReprepare).isEqualTo(50); } - @Test - public void - testInvalidSeekPositionAfterSourceInfoRefreshWithShuffleModeEnabledUsesCorrectFirstPeriod() - throws Exception { - FakeMediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount= */ 2)); - AtomicInteger windowIndexAfterUpdate = new AtomicInteger(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .setShuffleOrder(new FakeShuffleOrder(/* length= */ 0)) - .setShuffleModeEnabled(true) - .waitForPlaybackState(Player.STATE_BUFFERING) - // Seeking to an invalid position will end playback. - .seek( - /* windowIndex= */ 100, /* positionMs= */ 0, /* catchIllegalSeekException= */ true) - .waitForPlaybackState(Player.STATE_ENDED) - .executeRunnable( - new PlayerRunnable() { - @Override - public void run(SimpleExoPlayer player) { - windowIndexAfterUpdate.set(player.getCurrentWindowIndex()); - } - }) - .build(); - new ExoPlayerTestRunner.Builder(context) - .setMediaSources(mediaSource) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilActionScheduleFinished(TIMEOUT_MS) - .blockUntilEnded(TIMEOUT_MS); - - assertThat(windowIndexAfterUpdate.get()).isEqualTo(1); - } - @Test public void restartAfterEmptyTimelineWithShuffleModeEnabledUsesCorrectFirstPeriod() throws Exception { @@ -2556,7 +2559,7 @@ public final class ExoPlayerTest { .start() .blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilEnded(TIMEOUT_MS); - assertThat(Collections.frequency(rendererMessages, Renderer.MSG_SET_SURFACE)).isEqualTo(2); + assertThat(Collections.frequency(rendererMessages, Renderer.MSG_SET_VIDEO_OUTPUT)).isEqualTo(2); } @Test @@ -2626,8 +2629,7 @@ public final class ExoPlayerTest { @Test public void timelineUpdateWithNewMidrollAdCuePoint_dropsPrebufferedPeriod() throws Exception { - Timeline timeline1 = - new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0)); + Timeline timeline1 = new FakeTimeline(TimelineWindowDefinition.createPlaceholder(/* tag= */ 0)); AdPlaybackState adPlaybackStateWithMidroll = FakeTimeline.createAdPlaybackState( /* adsPerAdGroup= */ 1, @@ -2663,9 +2665,10 @@ public final class ExoPlayerTest { testRunner.assertTimelineChangeReasonsEqual( Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, - Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); testRunner.assertPlayedPeriodIndices(0); + testRunner.assertPositionDiscontinuityReasonsEqual( + Player.DISCONTINUITY_REASON_AUTO_TRANSITION, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); assertThat(mediaSource.getCreatedMediaPeriods()).hasSize(4); assertThat(mediaSource.getCreatedMediaPeriods().get(0).nextAdGroupIndex) .isEqualTo(C.INDEX_UNSET); @@ -2728,7 +2731,7 @@ public final class ExoPlayerTest { // When the ad finishes, the player position should be at or after the requested seek position. TestPlayerRunHelper.runUntilPositionDiscontinuity( - player, Player.DISCONTINUITY_REASON_AD_INSERTION); + player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); assertThat(player.getCurrentPosition()).isAtLeast(seekPositionMs); } @@ -2810,6 +2813,7 @@ public final class ExoPlayerTest { // seek in the timeline which still has two windows in EPI, but when the seek // arrives in EPII the actual timeline has one window only. Hence it tries to // find the subsequent period of the removed period and finds it. + player.getClock().onThreadBlocked(); sourceReleasedCountDownLatch.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); @@ -2847,23 +2851,23 @@ public final class ExoPlayerTest { // We add two listeners to the player. The first stops the player as soon as it's ready and both // record the state change events they receive. final AtomicReference playerReference = new AtomicReference<>(); - final List eventListener1States = new ArrayList<>(); - final List eventListener2States = new ArrayList<>(); - final EventListener eventListener1 = - new EventListener() { + final List playerListener1States = new ArrayList<>(); + final List playerListener2States = new ArrayList<>(); + final Player.Listener playerListener1 = + new Player.Listener() { @Override public void onPlaybackStateChanged(@Player.State int playbackState) { - eventListener1States.add(playbackState); + playerListener1States.add(playbackState); if (playbackState == Player.STATE_READY) { playerReference.get().stop(/* reset= */ true); } } }; - final EventListener eventListener2 = - new EventListener() { + final Player.Listener playerListener2 = + new Player.Listener() { @Override public void onPlaybackStateChanged(@Player.State int playbackState) { - eventListener2States.add(playbackState); + playerListener2States.add(playbackState); } }; ActionSchedule actionSchedule = @@ -2873,8 +2877,8 @@ public final class ExoPlayerTest { @Override public void run(SimpleExoPlayer player) { playerReference.set(player); - player.addListener(eventListener1); - player.addListener(eventListener2); + player.addListener(playerListener1); + player.addListener(playerListener2); } }) .build(); @@ -2884,10 +2888,10 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(eventListener1States) + assertThat(playerListener1States) .containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE) .inOrder(); - assertThat(eventListener2States) + assertThat(playerListener2States) .containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE) .inOrder(); } @@ -2897,15 +2901,15 @@ public final class ExoPlayerTest { // The listener stops the player as soon as it's ready (which should report a timeline and state // change) and sets playWhenReady to false when the timeline callback is received. final AtomicReference playerReference = new AtomicReference<>(); - final List eventListenerPlayWhenReady = new ArrayList<>(); - final List eventListenerStates = new ArrayList<>(); + final List playerListenerPlayWhenReady = new ArrayList<>(); + final List playerListenerStates = new ArrayList<>(); List sequence = new ArrayList<>(); - final EventListener eventListener = - new EventListener() { + final Player.Listener playerListener = + new Player.Listener() { @Override public void onPlaybackStateChanged(@Player.State int playbackState) { - eventListenerStates.add(playbackState); + playerListenerStates.add(playbackState); if (playbackState == Player.STATE_READY) { playerReference.get().stop(/* reset= */ true); sequence.add(0); @@ -2923,7 +2927,7 @@ public final class ExoPlayerTest { @Override public void onPlayWhenReadyChanged( boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) { - eventListenerPlayWhenReady.add(playWhenReady); + playerListenerPlayWhenReady.add(playWhenReady); sequence.add(2); } }; @@ -2934,7 +2938,7 @@ public final class ExoPlayerTest { @Override public void run(SimpleExoPlayer player) { playerReference.set(player); - player.addListener(eventListener); + player.addListener(playerListener); } }) .build(); @@ -2944,10 +2948,10 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(eventListenerStates) + assertThat(playerListenerStates) .containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE) .inOrder(); - assertThat(eventListenerPlayWhenReady).containsExactly(false).inOrder(); + assertThat(playerListenerPlayWhenReady).containsExactly(false).inOrder(); assertThat(sequence).containsExactly(0, 1, 2).inOrder(); } @@ -2957,8 +2961,8 @@ public final class ExoPlayerTest { Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 3); final AtomicReference playerReference = new AtomicReference<>(); FakeMediaSource secondMediaSource = new FakeMediaSource(secondTimeline); - final EventListener eventListener = - new EventListener() { + final Player.Listener playerListener = + new Player.Listener() { @Override public void onPlaybackStateChanged(int state) { if (state == Player.STATE_IDLE) { @@ -2973,7 +2977,7 @@ public final class ExoPlayerTest { @Override public void run(SimpleExoPlayer player) { playerReference.set(player); - player.addListener(eventListener); + player.addListener(playerListener); } }) // Ensure there are no further pending callbacks. @@ -3012,13 +3016,13 @@ public final class ExoPlayerTest { MediaSource mediaSource = new ClippingMediaSource( new FakeMediaSource(), startPositionUs, startPositionUs + expectedDurationUs); - Clock clock = new AutoAdvancingFakeClock(); + Clock clock = new FakeClock(/* isAutoAdvancing= */ true); AtomicReference playerReference = new AtomicReference<>(); AtomicLong positionAtDiscontinuityMs = new AtomicLong(C.TIME_UNSET); AtomicLong clockAtStartMs = new AtomicLong(C.TIME_UNSET); AtomicLong clockAtDiscontinuityMs = new AtomicLong(C.TIME_UNSET); - EventListener eventListener = - new EventListener() { + Player.Listener playerListener = + new Player.Listener() { @Override public void onPlaybackStateChanged(@Player.State int playbackState) { if (playbackState == Player.STATE_READY && clockAtStartMs.get() == C.TIME_UNSET) { @@ -3028,7 +3032,7 @@ public final class ExoPlayerTest { @Override public void onPositionDiscontinuity(@DiscontinuityReason int reason) { - if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { positionAtDiscontinuityMs.set(playerReference.get().getCurrentPosition()); clockAtDiscontinuityMs.set(clock.elapsedRealtime()); } @@ -3041,7 +3045,7 @@ public final class ExoPlayerTest { @Override public void run(SimpleExoPlayer player) { playerReference.set(player); - player.addListener(eventListener); + player.addListener(playerListener); } }) .pause() @@ -3093,8 +3097,8 @@ public final class ExoPlayerTest { .setMediaSources(mediaSource) .setSupportedFormats(ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT) .setActionSchedule(actionSchedule) - .setEventListener( - new EventListener() { + .setPlayerListener( + new Player.Listener() { @Override public void onTracksChanged( TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { @@ -3247,11 +3251,11 @@ public final class ExoPlayerTest { windowDurationUs)); AtomicReference playerReference = new AtomicReference<>(); AtomicLong bufferedPositionAtFirstDiscontinuityMs = new AtomicLong(C.TIME_UNSET); - EventListener eventListener = - new EventListener() { + Player.Listener playerListener = + new Player.Listener() { @Override public void onPositionDiscontinuity(@DiscontinuityReason int reason) { - if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { if (bufferedPositionAtFirstDiscontinuityMs.get() == C.TIME_UNSET) { bufferedPositionAtFirstDiscontinuityMs.set( playerReference.get().getBufferedPosition()); @@ -3266,7 +3270,7 @@ public final class ExoPlayerTest { @Override public void run(SimpleExoPlayer player) { playerReference.set(player); - player.addListener(eventListener); + player.addListener(playerListener); } }) .pause() @@ -3301,11 +3305,11 @@ public final class ExoPlayerTest { FakeMediaSource fakeMediaSource = new FakeMediaSource(/* timeline= */ null); AtomicReference playerReference = new AtomicReference<>(); AtomicLong contentStartPositionMs = new AtomicLong(C.TIME_UNSET); - EventListener eventListener = - new EventListener() { + Player.Listener playerListener = + new Player.Listener() { @Override public void onPositionDiscontinuity(@DiscontinuityReason int reason) { - if (reason == Player.DISCONTINUITY_REASON_AD_INSERTION) { + if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { contentStartPositionMs.set(playerReference.get().getContentPosition()); } } @@ -3317,7 +3321,7 @@ public final class ExoPlayerTest { @Override public void run(SimpleExoPlayer player) { playerReference.set(player); - player.addListener(eventListener); + player.addListener(playerListener); } }) .seek(/* positionMs= */ 5_000) @@ -3354,11 +3358,11 @@ public final class ExoPlayerTest { FakeMediaSource fakeMediaSource = new FakeMediaSource(/* timeline= */ null); AtomicReference playerReference = new AtomicReference<>(); AtomicLong contentStartPositionMs = new AtomicLong(C.TIME_UNSET); - EventListener eventListener = - new EventListener() { + Player.Listener playerListener = + new Player.Listener() { @Override public void onPositionDiscontinuity(@DiscontinuityReason int reason) { - if (reason == Player.DISCONTINUITY_REASON_AD_INSERTION) { + if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { contentStartPositionMs.set(playerReference.get().getContentPosition()); } } @@ -3370,7 +3374,7 @@ public final class ExoPlayerTest { @Override public void run(SimpleExoPlayer player) { playerReference.set(player); - player.addListener(eventListener); + player.addListener(playerListener); } }) .waitForPlaybackState(Player.STATE_BUFFERING) @@ -3413,8 +3417,8 @@ public final class ExoPlayerTest { .play() .build(); List reportedPlaybackSpeeds = new ArrayList<>(); - EventListener listener = - new EventListener() { + Player.Listener listener = + new Player.Listener() { @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { reportedPlaybackSpeeds.add(playbackParameters.speed); @@ -3422,7 +3426,7 @@ public final class ExoPlayerTest { }; new ExoPlayerTestRunner.Builder(context) .setActionSchedule(actionSchedule) - .setEventListener(listener) + .setPlayerListener(listener) .build() .start() .blockUntilEnded(TIMEOUT_MS); @@ -3460,8 +3464,8 @@ public final class ExoPlayerTest { .play() .build(); List reportedPlaybackParameters = new ArrayList<>(); - EventListener listener = - new EventListener() { + Player.Listener listener = + new Player.Listener() { @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { reportedPlaybackParameters.add(playbackParameters); @@ -3471,7 +3475,7 @@ public final class ExoPlayerTest { .setSupportedFormats(ExoPlayerTestRunner.AUDIO_FORMAT) .setRenderers(renderer) .setActionSchedule(actionSchedule) - .setEventListener(listener) + .setPlayerListener(listener) .build() .start() .blockUntilEnded(TIMEOUT_MS); @@ -3495,8 +3499,8 @@ public final class ExoPlayerTest { .play() .build(); AtomicBoolean seenPlaybackSuppression = new AtomicBoolean(); - EventListener listener = - new EventListener() { + Player.Listener listener = + new Player.Listener() { @Override public void onPlaybackSuppressionReasonChanged( @Player.PlaybackSuppressionReason int playbackSuppressionReason) { @@ -3505,7 +3509,7 @@ public final class ExoPlayerTest { }; new ExoPlayerTestRunner.Builder(context) .setActionSchedule(actionSchedule) - .setEventListener(listener) + .setPlayerListener(listener) .build() .start() .blockUntilEnded(TIMEOUT_MS); @@ -3527,8 +3531,8 @@ public final class ExoPlayerTest { .executeRunnable(playerStateGrabber) .build(); AtomicBoolean seenPlaybackSuppression = new AtomicBoolean(); - EventListener listener = - new EventListener() { + Player.Listener listener = + new Player.Listener() { @Override public void onPlaybackSuppressionReasonChanged( @Player.PlaybackSuppressionReason int playbackSuppressionReason) { @@ -3537,7 +3541,7 @@ public final class ExoPlayerTest { }; new ExoPlayerTestRunner.Builder(context) .setActionSchedule(actionSchedule) - .setEventListener(listener) + .setPlayerListener(listener) .build() .start() .blockUntilActionScheduleFinished(TIMEOUT_MS); @@ -3637,7 +3641,6 @@ public final class ExoPlayerTest { public void seekTo_windowIndexIsReset_deprecated() throws Exception { FakeTimeline fakeTimeline = new FakeTimeline(); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); - LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2); final int[] windowIndex = {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}; @@ -3671,7 +3674,7 @@ public final class ExoPlayerTest { }) .build(); new ExoPlayerTestRunner.Builder(context) - .setMediaSources(loopingMediaSource) + .setMediaSources(mediaSource, mediaSource) .setActionSchedule(actionSchedule) .build() .start() @@ -3691,7 +3694,6 @@ public final class ExoPlayerTest { public void seekTo_windowIndexIsReset() throws Exception { FakeTimeline fakeTimeline = new FakeTimeline(); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); - LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2); final int[] windowIndex = {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}; @@ -3725,7 +3727,7 @@ public final class ExoPlayerTest { }) .build(); new ExoPlayerTestRunner.Builder(context) - .setMediaSources(loopingMediaSource) + .setMediaSources(mediaSource, mediaSource) .setActionSchedule(actionSchedule) .build() .start() @@ -4232,7 +4234,7 @@ public final class ExoPlayerTest { createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000)); assertThat(windowIndex[0]).isEqualTo(0); - assertThat(positionMs[0]).isGreaterThan(8000); + assertThat(positionMs[0]).isEqualTo(8000); assertThat(bufferedPositions[0]).isEqualTo(10_000); assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]); @@ -4459,7 +4461,7 @@ public final class ExoPlayerTest { assertThat(windowIndex[2]).isEqualTo(0); assertThat(isPlayingAd[2]).isFalse(); - assertThat(positionMs[2]).isGreaterThan(8000); + assertThat(positionMs[2]).isEqualTo(8000); assertThat(bufferedPositionMs[2]).isEqualTo(contentDurationMs); assertThat(totalBufferedDurationMs[2]).isAtLeast(contentDurationMs - positionMs[2]); } @@ -4583,65 +4585,30 @@ public final class ExoPlayerTest { @Test public void becomingNoisyIgnoredIfBecomingNoisyHandlingIsDisabled() throws Exception { - CountDownLatch becomingNoisyHandlingDisabled = new CountDownLatch(1); - CountDownLatch becomingNoisyDelivered = new CountDownLatch(1); - PlayerStateGrabber playerStateGrabber = new PlayerStateGrabber(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .executeRunnable( - new PlayerRunnable() { - @Override - public void run(SimpleExoPlayer player) { - player.setHandleAudioBecomingNoisy(false); - becomingNoisyHandlingDisabled.countDown(); + SimpleExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.play(); - // Wait for the broadcast to be delivered from the main thread. - try { - becomingNoisyDelivered.await(); - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - } - }) - .delay(1) // Handle pending messages on the playback thread. - .executeRunnable(playerStateGrabber) - .build(); - - ExoPlayerTestRunner testRunner = - new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start(); - becomingNoisyHandlingDisabled.await(); + player.setHandleAudioBecomingNoisy(false); deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - becomingNoisyDelivered.countDown(); + runUntilPendingCommandsAreFullyHandled(player); + boolean playWhenReadyAfterBroadcast = player.getPlayWhenReady(); + player.release(); - testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS); - assertThat(playerStateGrabber.playWhenReady).isTrue(); + assertThat(playWhenReadyAfterBroadcast).isTrue(); } @Test public void pausesWhenBecomingNoisyIfBecomingNoisyHandlingIsEnabled() throws Exception { - CountDownLatch becomingNoisyHandlingEnabled = new CountDownLatch(1); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .executeRunnable( - new PlayerRunnable() { - @Override - public void run(SimpleExoPlayer player) { - player.setHandleAudioBecomingNoisy(true); - becomingNoisyHandlingEnabled.countDown(); - } - }) - .waitForPlayWhenReady(false) // Becoming noisy should set playWhenReady = false - .play() - .build(); + SimpleExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.play(); - ExoPlayerTestRunner testRunner = - new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start(); - becomingNoisyHandlingEnabled.await(); + player.setHandleAudioBecomingNoisy(true); deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + runUntilPendingCommandsAreFullyHandled(player); + boolean playWhenReadyAfterBroadcast = player.getPlayWhenReady(); + player.release(); - // If the player fails to handle becoming noisy, blockUntilActionScheduleFinished will time out - // and throw, causing the test to fail. - testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS); + assertThat(playWhenReadyAfterBroadcast).isFalse(); } @Test @@ -4667,7 +4634,7 @@ public final class ExoPlayerTest { // Use chunked data to ensure the player actually needs to continue loading and playing. FakeAdaptiveDataSet.Factory dataSetFactory = new FakeAdaptiveDataSet.Factory( - /* chunkDurationUs= */ 500_000, /* bitratePercentStdDev= */ 10.0); + /* chunkDurationUs= */ 500_000, /* bitratePercentStdDev= */ 10.0, new Random(0)); MediaSource chunkedMediaSource = new FakeAdaptiveMediaSource( new FakeTimeline(), @@ -4764,7 +4731,7 @@ public final class ExoPlayerTest { // Wait until the MediaSource is prepared, i.e. returned its timeline, and at least one // iteration of doSomeWork after this was run. TestPlayerRunHelper.runUntilTimelineChanged(player); - TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); + runUntilPendingCommandsAreFullyHandled(player); assertThat(player.getPlayerError()).isNull(); } @@ -4792,7 +4759,7 @@ public final class ExoPlayerTest { // Use chunked data to ensure the player actually needs to continue loading and playing. FakeAdaptiveDataSet.Factory dataSetFactory = new FakeAdaptiveDataSet.Factory( - /* chunkDurationUs= */ 500_000, /* bitratePercentStdDev= */ 10.0); + /* chunkDurationUs= */ 500_000, /* bitratePercentStdDev= */ 10.0, new Random(0)); MediaSource chunkedMediaSource = new FakeAdaptiveMediaSource( new FakeTimeline(), @@ -6991,7 +6958,7 @@ public final class ExoPlayerTest { }, // buffers after set items with seek maskingPlaybackStates); assertArrayEquals(new int[] {2, 0, 0, 1, 1, 0, 0, 0, 0}, currentWindowIndices); - assertThat(currentPositions[0]).isGreaterThan(0); + assertThat(currentPositions[0]).isEqualTo(0); assertThat(currentPositions[1]).isEqualTo(0); assertThat(currentPositions[2]).isEqualTo(0); assertThat(bufferedPositions[0]).isGreaterThan(0); @@ -7412,7 +7379,7 @@ public final class ExoPlayerTest { drmSessionManager, drmEventDispatcher, /* deferOnPrepared= */ false) { - private Loader loader = new Loader("oomLoader"); + private final Loader loader = new Loader("ExoPlayerTest"); @Override public boolean continueLoading(long positionUs) { @@ -7474,15 +7441,15 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_ENDED) .build(); List onIsPlayingChanges = new ArrayList<>(); - Player.EventListener eventListener = - new Player.EventListener() { + Player.Listener playerListener = + new Player.Listener() { @Override public void onIsPlayingChanged(boolean isPlaying) { onIsPlayingChanges.add(isPlaying); } }; new ExoPlayerTestRunner.Builder(context) - .setEventListener(eventListener) + .setPlayerListener(playerListener) .setActionSchedule(actionSchedule) .build() .start() @@ -7499,8 +7466,8 @@ public final class ExoPlayerTest { String isPlayingChange1 = "isPlayingChange1"; String isPlayingChange2 = "isPlayingChange2"; ArrayList events = new ArrayList<>(); - Player.EventListener eventListener1 = - new Player.EventListener() { + Player.Listener playerListener1 = + new Player.Listener() { @Override public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { events.add(playWhenReadyChange1); @@ -7511,8 +7478,8 @@ public final class ExoPlayerTest { events.add(isPlayingChange1); } }; - Player.EventListener eventListener2 = - new Player.EventListener() { + Player.Listener playerListener2 = + new Player.Listener() { @Override public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { events.add(playWhenReadyChange2); @@ -7530,8 +7497,8 @@ public final class ExoPlayerTest { new PlayerRunnable() { @Override public void run(SimpleExoPlayer player) { - player.addListener(eventListener1); - player.addListener(eventListener2); + player.addListener(playerListener1); + player.addListener(playerListener2); } }) .waitForPlaybackState(Player.STATE_READY) @@ -7623,12 +7590,12 @@ public final class ExoPlayerTest { // Start playback and wait until player is idly waiting for an update of the first source. player.prepare(); player.play(); - TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); + runUntilPendingCommandsAreFullyHandled(player); // Update media with a non-zero default start position and window offset. firstMediaSource.setNewSourceInfo(timelineWithOffsets); // Wait until player transitions to second source (which also has non-zero offsets). TestPlayerRunHelper.runUntilPositionDiscontinuity( - player, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); assertThat(player.getCurrentWindowIndex()).isEqualTo(1); player.release(); @@ -7708,8 +7675,8 @@ public final class ExoPlayerTest { .build(); List currentMediaItems = new ArrayList<>(); List mediaItemsInTimeline = new ArrayList<>(); - Player.EventListener eventListener = - new Player.EventListener() { + Player.Listener playerListener = + new Player.Listener() { @Override public void onTimelineChanged(Timeline timeline, int reason) { if (reason != Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) { @@ -7724,13 +7691,13 @@ public final class ExoPlayerTest { @Override public void onPositionDiscontinuity(int reason) { if (reason == Player.DISCONTINUITY_REASON_SEEK - || reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + || reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { currentMediaItems.add(playerHolder[0].getCurrentMediaItem()); } } }; new ExoPlayerTestRunner.Builder(context) - .setEventListener(eventListener) + .setPlayerListener(playerListener) .setActionSchedule(actionSchedule) .setMediaSources(fakeMediaSource1, fakeMediaSource2, fakeMediaSource3) .build() @@ -8098,6 +8065,383 @@ public final class ExoPlayerTest { exoPlayerTestRunner.assertMediaItemsTransitionedSame(initialMediaItem); } + @Test + public void isCommandAvailable_isTrueForAvailableCommands() { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + + player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); + + assertThat(player.isCommandAvailable(COMMAND_PLAY_PAUSE)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_PREPARE_STOP)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_DEFAULT_POSITION)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)).isFalse(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)).isFalse(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SET_REPEAT_MODE)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_VOLUME)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SET_VOLUME)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_TEXT)).isTrue(); + } + + @Test + public void isCommandAvailable_duringAd_isFalseForSeekCommands() throws Exception { + AdPlaybackState adPlaybackState = + 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)}}); + Timeline adTimeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ C.msToUs(10_000), + adPlaybackState)); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + + player.addMediaSources( + ImmutableList.of( + new FakeMediaSource(), new FakeMediaSource(adTimeline), new FakeMediaSource())); + player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + + assertThat(player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)).isFalse(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)).isFalse(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)).isFalse(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)).isFalse(); + } + + @Test + public void isCommandAvailable_duringUnseekableItem_isFalseForSeekInCurrent() throws Exception { + Timeline timelineWithUnseekableWindow = + new FakeTimeline( + new TimelineWindowDefinition( + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationUs= */ C.msToUs(10_000))); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + + player.addMediaSource(new FakeMediaSource(timelineWithUnseekableWindow)); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + + assertThat(player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)).isFalse(); + } + + @Test + public void seekTo_nextWindow_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToNext = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithSeekToPrevious = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Commands commandsWithSeekToNextAndPrevious = + createWithDefaultCommands( + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSources( + ImmutableList.of( + new FakeMediaSource(), + new FakeMediaSource(), + new FakeMediaSource(), + new FakeMediaSource())); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextAndPrevious); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + } + + @Test + public void seekTo_previousWindow_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToNext = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithSeekToPrevious = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Commands commandsWithSeekToNextAndPrevious = + createWithDefaultCommands( + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0); + player.addMediaSources( + ImmutableList.of( + new FakeMediaSource(), + new FakeMediaSource(), + new FakeMediaSource(), + new FakeMediaSource())); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextAndPrevious); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + } + + @Test + public void seekTo_sameWindow_doesNotNotifyAvailableCommandsChanged() { + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSources(ImmutableList.of(new FakeMediaSource())); + player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200); + player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100); + verify(mockListener, never()).onAvailableCommandsChanged(any()); + } + + @Test + public void automaticWindowTransition_notifiesAvailableCommandsChanged() throws Exception { + Player.Commands commandsWithSeekToNext = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToNext = + createWithDefaultCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithSeekInCurrentAndToPrevious = + createWithDefaultCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Commands commandsWithSeekAnywhere = + createWithDefaultCommands( + COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSources( + ImmutableList.of( + new FakeMediaSource(), + new FakeMediaSource(), + new FakeMediaSource(), + new FakeMediaSource())); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + playUntilStartOfWindow(player, /* windowIndex= */ 1); + runUntilPendingCommandsAreFullyHandled(player); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + + playUntilStartOfWindow(player, /* windowIndex= */ 2); + runUntilPendingCommandsAreFullyHandled(player); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); + + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener, times(4)).onAvailableCommandsChanged(any()); + } + + @Test + public void addMediaSource_atTheEnd_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToNext = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSource(new FakeMediaSource()); + verify(mockListener, never()).onAvailableCommandsChanged(any()); + + player.addMediaSource(new FakeMediaSource()); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.addMediaSource(new FakeMediaSource()); + verify(mockListener).onAvailableCommandsChanged(any()); + } + + @Test + public void addMediaSource_atTheStart_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToPrevious = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSource(new FakeMediaSource()); + verify(mockListener, never()).onAvailableCommandsChanged(any()); + + player.addMediaSource(/* index= */ 0, new FakeMediaSource()); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.addMediaSource(/* index= */ 0, new FakeMediaSource()); + verify(mockListener).onAvailableCommandsChanged(any()); + } + + @Test + public void removeMediaItem_atTheEnd_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToNext = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithoutSeek = createWithDefaultCommands(); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSources( + ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.removeMediaItem(/* index= */ 2); + verify(mockListener).onAvailableCommandsChanged(any()); + + player.removeMediaItem(/* index= */ 1); + verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.removeMediaItem(/* index= */ 0); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + + @Test + public void removeMediaItem_atTheStart_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToPrevious = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Commands commandsWithoutSeek = createWithDefaultCommands(); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); + player.addMediaSources( + ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.removeMediaItem(/* index= */ 0); + verify(mockListener).onAvailableCommandsChanged(any()); + + player.removeMediaItem(/* index= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.removeMediaItem(/* index= */ 0); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + + @Test + public void removeMediaItem_current_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToNext = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithoutSeek = createWithDefaultCommands(); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.removeMediaItem(/* index= */ 0); + verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + + @Test + public void setRepeatMode_all_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToNextAndPrevious = + createWithDefaultCommands( + COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSource(new FakeMediaSource()); + verify(mockListener, never()).onAvailableCommandsChanged(any()); + + player.setRepeatMode(Player.REPEAT_MODE_ALL); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextAndPrevious); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + } + + @Test + public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() { + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + + player.addMediaSource(new FakeMediaSource()); + player.setRepeatMode(Player.REPEAT_MODE_ONE); + verify(mockListener, never()).onAvailableCommandsChanged(any()); + } + + @Test + public void setShuffleModeEnabled_notifiesAvailableCommandsChanged() { + Player.Commands commandsWithSeekToNext = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); + Player.Commands commandsWithSeekToPrevious = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); + Player.Listener mockListener = mock(Player.Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(mockListener); + MediaSource mediaSource = + new ConcatenatingMediaSource( + false, + new FakeShuffleOrder(/* length= */ 2), + new FakeMediaSource(), + new FakeMediaSource()); + + player.addMediaSource(mediaSource); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); + + player.setShuffleModeEnabled(true); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + } + @Test public void mediaSourceMaybeThrowSourceInfoRefreshError_isNotThrownUntilPlaybackReachedFailingItem() @@ -8300,7 +8644,6 @@ public final class ExoPlayerTest { assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse(); } - @Ignore // See [internal: b/170387438] @Test public void enableOffloadSchedulingWhileSleepingForOffload_isDisabled_isReported() throws Exception { @@ -8318,7 +8661,6 @@ public final class ExoPlayerTest { assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse(); } - @Ignore // See [internal: b/170387438] @Test public void enableOffloadScheduling_isEnable_playerSleeps() throws Exception { FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO); @@ -8335,7 +8677,6 @@ public final class ExoPlayerTest { assertThat(player.experimentalIsSleepingForOffload()).isTrue(); } - @Ignore // See [internal: b/170387438] @Test public void experimentalEnableOffloadSchedulingWhileSleepingForOffload_isDisabled_renderingResumes() @@ -8356,7 +8697,6 @@ public final class ExoPlayerTest { runUntilPlaybackState(player, Player.STATE_ENDED); } - @Ignore // See [internal: b/170387438] @Test public void wakeupListenerWhileSleepingForOffload_isWokenUp_renderingResumes() throws Exception { FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender(); @@ -8397,7 +8737,7 @@ public final class ExoPlayerTest { /* description= */ "Audio", /* value= */ "Audio track name"))) .build(); - EventListener eventListener = mock(EventListener.class); + Player.Listener playerListener = mock(Player.Listener.class); Timeline fakeTimeline = new FakeTimeline( new TimelineWindowDefinition( @@ -8405,7 +8745,7 @@ public final class ExoPlayerTest { SimpleExoPlayer player = new TestExoPlayerBuilder(context).build(); player.setMediaSource(new FakeMediaSource(fakeTimeline, videoFormat, audioFormat)); - player.addListener(eventListener); + player.addListener(playerListener); player.prepare(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); @@ -8413,7 +8753,7 @@ public final class ExoPlayerTest { player.release(); assertThat(metadata).containsExactly(videoFormat.metadata, audioFormat.metadata).inOrder(); - verify(eventListener) + verify(playerListener) .onStaticMetadataChanged(ImmutableList.of(videoFormat.metadata, audioFormat.metadata)); } @@ -8422,7 +8762,7 @@ public final class ExoPlayerTest { throws Exception { Format videoFormat = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(); Format audioFormat = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build(); - EventListener eventListener = mock(EventListener.class); + Player.Listener playerListener = mock(Player.Listener.class); Timeline fakeTimeline = new FakeTimeline( new TimelineWindowDefinition( @@ -8430,7 +8770,7 @@ public final class ExoPlayerTest { SimpleExoPlayer player = new TestExoPlayerBuilder(context).build(); player.setMediaSource(new FakeMediaSource(fakeTimeline, videoFormat, audioFormat)); - player.addListener(eventListener); + player.addListener(playerListener); player.prepare(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); @@ -8438,7 +8778,7 @@ public final class ExoPlayerTest { player.release(); assertThat(metadata).isEmpty(); - verify(eventListener, never()).onStaticMetadataChanged(any()); + verify(playerListener, never()).onStaticMetadataChanged(any()); } @Test @@ -8447,7 +8787,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline timeline = new FakeTimeline( @@ -8463,7 +8804,7 @@ public final class ExoPlayerTest { /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder().setUri(Uri.EMPTY).setLiveTargetOffsetMs(9_000).build())); - Player.EventListener mockListener = mock(Player.EventListener.class); + Player.Listener mockListener = mock(Player.Listener.class); player.addListener(mockListener); player.pause(); player.setMediaSource(new FakeMediaSource(timeline)); @@ -8491,7 +8832,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline timeline = new FakeTimeline( @@ -8531,7 +8873,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline timeline = new FakeTimeline( @@ -8573,7 +8916,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline initialTimeline = new FakeTimeline( @@ -8625,6 +8969,20 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtEnd).isIn(Range.closed(3_900L, 4_100L)); } + @Test + public void playerIdle_withSetPlaybackSpeed_usesPlaybackParameterSpeedWithPitchUnchanged() { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1, /* pitch= */ 2)); + Player.Listener mockListener = mock(Player.Listener.class); + player.addListener(mockListener); + player.prepare(); + + player.setPlaybackSpeed(2); + + verify(mockListener) + .onPlaybackParametersChanged(new PlaybackParameters(/* speed= */ 2, /* pitch= */ 2)); + } + @Test public void targetLiveOffsetInMedia_withSetPlaybackParameters_usesPlaybackParameterSpeed() throws Exception { @@ -8632,7 +8990,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline timeline = new FakeTimeline( @@ -8648,7 +9007,7 @@ public final class ExoPlayerTest { /* windowOffsetInFirstPeriodUs= */ C.msToUs(windowStartUnixTimeMs), AdPlaybackState.NONE, new MediaItem.Builder().setUri(Uri.EMPTY).setLiveTargetOffsetMs(9_000).build())); - Player.EventListener mockListener = mock(Player.EventListener.class); + Player.Listener mockListener = mock(Player.Listener.class); player.addListener(mockListener); player.pause(); player.setMediaSource(new FakeMediaSource(timeline)); @@ -8679,7 +9038,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 10_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline nonLiveTimeline = new FakeTimeline(); Timeline liveTimeline = @@ -8719,7 +9079,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline liveTimeline1 = new FakeTimeline( @@ -8776,7 +9137,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline liveTimeline1 = new FakeTimeline( @@ -8826,10 +9188,10 @@ public final class ExoPlayerTest { } @Test - @Ignore("This is flaky in the release branch. It's stable on dev-v2 due to FakeClock changes.") public void targetLiveOffsetInMedia_unknownWindowStartTime_doesNotAdjustLiveOffset() throws Exception { - FakeClock fakeClock = new AutoAdvancingFakeClock(/* initialTimeMs= */ 987_654_321L); + FakeClock fakeClock = + new FakeClock(/* initialTimeMs= */ 987_654_321L, /* isAutoAdvancing= */ true); ExoPlayer player = new TestExoPlayerBuilder(context).setClock(fakeClock).build(); MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).setLiveTargetOffsetMs(4_000).build(); @@ -8868,7 +9230,8 @@ public final class ExoPlayerTest { long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; ExoPlayer player = new TestExoPlayerBuilder(context) - .setClock(new AutoAdvancingFakeClock(/* initialTimeMs= */ nowUnixTimeMs)) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) .build(); Timeline liveTimelineWithoutTargetLiveOffset = new FakeTimeline( @@ -8904,7 +9267,7 @@ public final class ExoPlayerTest { @Test public void onEvents_correspondToListenerCalls() throws Exception { ExoPlayer player = new TestExoPlayerBuilder(context).build(); - EventListener listener = mock(EventListener.class); + Player.Listener listener = mock(Player.Listener.class); player.addListener(listener); Format formatWithStaticMetadata = new Format.Builder() @@ -8916,11 +9279,11 @@ public final class ExoPlayerTest { player.setMediaSource(new FakeMediaSource(new FakeTimeline(), formatWithStaticMetadata)); player.seekTo(2_000); player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f)); - ShadowLooper.runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); verify(listener).onTimelineChanged(any(), anyInt()); verify(listener).onMediaItemTransition(any(), anyInt()); - verify(listener).onPositionDiscontinuity(anyInt()); + verify(listener).onPositionDiscontinuity(any(), any(), anyInt()); verify(listener).onPlaybackParametersChanged(any()); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Player.Events.class); verify(listener).onEvents(eq(player), eventCaptor.capture()); @@ -8932,14 +9295,14 @@ public final class ExoPlayerTest { // Set values recursively. player.addListener( - new EventListener() { + new Player.Listener() { @Override public void onRepeatModeChanged(int repeatMode) { player.setShuffleModeEnabled(true); } }); player.setRepeatMode(Player.REPEAT_MODE_ONE); - ShadowLooper.runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); verify(listener).onRepeatModeChanged(anyInt()); verify(listener).onShuffleModeEnabledChanged(anyBoolean()); @@ -8955,13 +9318,13 @@ public final class ExoPlayerTest { player.play(); player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4")); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE); - ShadowLooper.runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); player.release(); // Verify that all callbacks have been called at least once. verify(listener, atLeastOnce()).onTimelineChanged(any(), anyInt()); verify(listener, atLeastOnce()).onMediaItemTransition(any(), anyInt()); - verify(listener, atLeastOnce()).onPositionDiscontinuity(anyInt()); + verify(listener, atLeastOnce()).onPositionDiscontinuity(any(), any(), anyInt()); verify(listener, atLeastOnce()).onPlaybackParametersChanged(any()); verify(listener, atLeastOnce()).onRepeatModeChanged(anyInt()); verify(listener, atLeastOnce()).onShuffleModeEnabledChanged(anyBoolean()); @@ -8991,6 +9354,1048 @@ public final class ExoPlayerTest { assertThat(containsEvent(allEvents, Player.EVENT_PLAYER_ERROR)).isTrue(); } + @Test + public void repeatMode_windowTransition_callsOnPositionDiscontinuityAndOnMediaItemTransition() + throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + FakeMediaSource secondMediaSource = + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 20 * C.MICROS_PER_SECOND))); + player.addListener(listener); + player.setMediaSource( + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND)))); + player.setRepeatMode(Player.REPEAT_MODE_ONE); + + player.prepare(); + player.play(); + TestPlayerRunHelper.runUntilPositionDiscontinuity( + player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); + player.setRepeatMode(Player.REPEAT_MODE_ALL); + player.play(); + TestPlayerRunHelper.runUntilPositionDiscontinuity( + player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); + player.addMediaSource(secondMediaSource); + player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET); + player.play(); + TestPlayerRunHelper.runUntilPositionDiscontinuity( + player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); + player.setRepeatMode(Player.REPEAT_MODE_OFF); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + InOrder inOrder = inOrder(listener); + // Expect media item transition for repeat mode ONE to be attributed to + // DISCONTINUITY_REASON_REPEAT. + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + Player.PositionInfo oldPositionInfo = oldPosition.getValue(); + Player.PositionInfo newPositionInfo = newPosition.getValue(); + assertThat(oldPositionInfo.periodUid).isEqualTo(newPositionInfo.periodUid); + assertThat(oldPositionInfo.periodIndex).isEqualTo(newPositionInfo.periodIndex); + assertThat(oldPositionInfo.windowIndex).isEqualTo(newPositionInfo.windowIndex); + assertThat(oldPositionInfo.windowUid).isEqualTo(newPositionInfo.windowUid); + assertThat(oldPositionInfo.positionMs).isEqualTo(10_000); + assertThat(oldPositionInfo.contentPositionMs).isEqualTo(10_000); + assertThat(newPositionInfo.positionMs).isEqualTo(0); + assertThat(newPositionInfo.contentPositionMs).isEqualTo(0); + inOrder + .verify(listener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT)); + // Expect media item transition for repeat mode ALL with a single item to be attributed to + // DISCONTINUITY_REASON_REPEAT. + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + oldPositionInfo = oldPosition.getValue(); + newPositionInfo = newPosition.getValue(); + assertThat(oldPositionInfo.periodUid).isEqualTo(newPositionInfo.periodUid); + assertThat(oldPositionInfo.periodIndex).isEqualTo(newPositionInfo.periodIndex); + assertThat(oldPositionInfo.windowIndex).isEqualTo(newPositionInfo.windowIndex); + assertThat(oldPositionInfo.windowUid).isEqualTo(newPositionInfo.windowUid); + assertThat(oldPositionInfo.positionMs).isEqualTo(10_000); + assertThat(oldPositionInfo.contentPositionMs).isEqualTo(10_000); + assertThat(newPositionInfo.positionMs).isEqualTo(0); + assertThat(newPositionInfo.contentPositionMs).isEqualTo(0); + inOrder + .verify(listener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT)); + // Expect media item transition for repeat mode ALL with more than one item which is attributed + // to DISCONTINUITY_REASON_AUTO_TRANSITION not DISCONTINUITY_REASON_REPEAT. + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + oldPositionInfo = oldPosition.getValue(); + newPositionInfo = newPosition.getValue(); + assertThat(oldPositionInfo.windowIndex).isEqualTo(1); + assertThat(oldPositionInfo.windowUid).isNotEqualTo(newPositionInfo.windowUid); + assertThat(oldPositionInfo.positionMs).isEqualTo(20_000); + assertThat(oldPositionInfo.contentPositionMs).isEqualTo(20_000); + assertThat(newPositionInfo.positionMs).isEqualTo(0); + assertThat(newPositionInfo.windowIndex).isEqualTo(0); + 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. + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(listener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)); + // No more callbacks called. + inOrder + .verify(listener, never()) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder.verify(listener, never()).onMediaItemTransition(any(), anyInt()); + player.release(); + } + + @Test + public void play_withPreMidAndPostRollAd_callsOnDiscontinuityCorrectly() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + AdPlaybackState adPlaybackState = + FakeTimeline.createAdPlaybackState( + /* adsPerAdGroup= */ 2, + /* adGroupTimesUs...= */ 0, + 7 * C.MICROS_PER_SECOND, + C.TIME_END_OF_SOURCE); + TimelineWindowDefinition adTimeline = + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* isLive= */ false, + /* isPlaceholder= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND, + /* defaultPositionUs= */ 0, + /* windowOffsetInFirstPeriodUs= */ 0, + adPlaybackState); + player.setMediaSource(new FakeMediaSource(new FakeTimeline(adTimeline))); + + player.prepare(); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + verify(listener, never()) + .onPositionDiscontinuity( + any(), any(), not(eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION))); + verify(listener, times(8)) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + + // first ad group (pre-roll) + // starts with ad to ad transition + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(0).positionMs).isEqualTo(5000); + assertThat(oldPositions.get(0).contentPositionMs).isEqualTo(0); + assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(0); + assertThat(oldPositions.get(0).adIndexInAdGroup).isEqualTo(0); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).positionMs).isEqualTo(0); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(0); + assertThat(newPositions.get(0).adGroupIndex).isEqualTo(0); + assertThat(newPositions.get(0).adIndexInAdGroup).isEqualTo(1); + // ad to content transition + assertThat(oldPositions.get(1).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(1).positionMs).isEqualTo(5000); + assertThat(oldPositions.get(1).contentPositionMs).isEqualTo(0); + assertThat(oldPositions.get(1).adGroupIndex).isEqualTo(0); + assertThat(oldPositions.get(1).adIndexInAdGroup).isEqualTo(1); + assertThat(newPositions.get(1).windowIndex).isEqualTo(0); + assertThat(newPositions.get(1).positionMs).isEqualTo(0); + assertThat(newPositions.get(1).contentPositionMs).isEqualTo(0); + assertThat(newPositions.get(1).adGroupIndex).isEqualTo(-1); + assertThat(newPositions.get(1).adIndexInAdGroup).isEqualTo(-1); + + // second add group (mid-roll) + assertThat(oldPositions.get(2).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(2).positionMs).isEqualTo(7000); + assertThat(oldPositions.get(2).contentPositionMs).isEqualTo(7000); + assertThat(oldPositions.get(2).adGroupIndex).isEqualTo(-1); + assertThat(oldPositions.get(2).adIndexInAdGroup).isEqualTo(-1); + assertThat(newPositions.get(2).windowIndex).isEqualTo(0); + assertThat(newPositions.get(2).positionMs).isEqualTo(0); + assertThat(newPositions.get(2).contentPositionMs).isEqualTo(7000); + assertThat(newPositions.get(2).adGroupIndex).isEqualTo(1); + assertThat(newPositions.get(2).adIndexInAdGroup).isEqualTo(0); + // ad to ad transition + assertThat(oldPositions.get(3).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(3).positionMs).isEqualTo(5000); + assertThat(oldPositions.get(3).contentPositionMs).isEqualTo(7000); + assertThat(oldPositions.get(3).adGroupIndex).isEqualTo(1); + assertThat(oldPositions.get(3).adIndexInAdGroup).isEqualTo(0); + assertThat(newPositions.get(3).windowIndex).isEqualTo(0); + assertThat(newPositions.get(3).positionMs).isEqualTo(0); + assertThat(newPositions.get(3).contentPositionMs).isEqualTo(7000); + assertThat(newPositions.get(3).adGroupIndex).isEqualTo(1); + assertThat(newPositions.get(3).adIndexInAdGroup).isEqualTo(1); + // ad to content transition + assertThat(oldPositions.get(4).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(4).positionMs).isEqualTo(5000); + assertThat(oldPositions.get(4).contentPositionMs).isEqualTo(7000); + assertThat(oldPositions.get(4).adGroupIndex).isEqualTo(1); + assertThat(oldPositions.get(4).adIndexInAdGroup).isEqualTo(1); + assertThat(newPositions.get(4).windowIndex).isEqualTo(0); + assertThat(newPositions.get(4).positionMs).isEqualTo(7000); + assertThat(newPositions.get(4).contentPositionMs).isEqualTo(7000); + assertThat(newPositions.get(4).adGroupIndex).isEqualTo(-1); + assertThat(newPositions.get(4).adIndexInAdGroup).isEqualTo(-1); + + // third add group (post-roll) + assertThat(oldPositions.get(5).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(5).positionMs).isEqualTo(10000); + assertThat(oldPositions.get(5).contentPositionMs).isEqualTo(10000); + assertThat(oldPositions.get(5).adGroupIndex).isEqualTo(-1); + assertThat(oldPositions.get(5).adIndexInAdGroup).isEqualTo(-1); + assertThat(newPositions.get(5).windowIndex).isEqualTo(0); + assertThat(newPositions.get(5).positionMs).isEqualTo(0); + assertThat(newPositions.get(5).contentPositionMs).isEqualTo(10000); + assertThat(newPositions.get(5).adGroupIndex).isEqualTo(2); + assertThat(newPositions.get(5).adIndexInAdGroup).isEqualTo(0); + // ad to ad transition + assertThat(oldPositions.get(6).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(6).positionMs).isEqualTo(5000); + assertThat(oldPositions.get(6).contentPositionMs).isEqualTo(10000); + assertThat(oldPositions.get(6).adGroupIndex).isEqualTo(2); + assertThat(oldPositions.get(6).adIndexInAdGroup).isEqualTo(0); + assertThat(newPositions.get(6).windowIndex).isEqualTo(0); + assertThat(newPositions.get(6).positionMs).isEqualTo(0); + assertThat(newPositions.get(6).contentPositionMs).isEqualTo(10000); + assertThat(newPositions.get(6).adGroupIndex).isEqualTo(2); + assertThat(newPositions.get(6).adIndexInAdGroup).isEqualTo(1); + // post roll ad to end of content transition + assertThat(oldPositions.get(7).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(7).positionMs).isEqualTo(5000); + assertThat(oldPositions.get(7).contentPositionMs).isEqualTo(10000); + assertThat(oldPositions.get(7).adGroupIndex).isEqualTo(2); + assertThat(oldPositions.get(7).adIndexInAdGroup).isEqualTo(1); + assertThat(newPositions.get(7).windowIndex).isEqualTo(0); + assertThat(newPositions.get(7).positionMs).isEqualTo(9999); + assertThat(newPositions.get(7).contentPositionMs).isEqualTo(9999); + assertThat(newPositions.get(7).adGroupIndex).isEqualTo(-1); + assertThat(newPositions.get(7).adIndexInAdGroup).isEqualTo(-1); + player.release(); + } + + @Test + public void seekTo_seekOverMidRoll_callsOnDiscontinuityCorrectly() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + AdPlaybackState adPlaybackState = + FakeTimeline.createAdPlaybackState( + /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */ 2 * C.MICROS_PER_SECOND); + TimelineWindowDefinition adTimeline = + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* isLive= */ false, + /* isPlaceholder= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND, + /* defaultPositionUs= */ 0, + /* windowOffsetInFirstPeriodUs= */ 0, + adPlaybackState); + player.setMediaSource(new FakeMediaSource(new FakeTimeline(adTimeline))); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 1000); + player.seekTo(/* positionMs= */ 8_000); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT)); + verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + verify(listener, never()) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verify(listener, never()) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SKIP)); + + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + // SEEK behind mid roll + assertThat(oldPositions.get(0).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(980L, 1_000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(980L, 1_000L)); + assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1); + assertThat(oldPositions.get(0).adIndexInAdGroup).isEqualTo(-1); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).positionMs).isEqualTo(8_000); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(8_000); + assertThat(newPositions.get(0).adGroupIndex).isEqualTo(-1); + assertThat(newPositions.get(0).adIndexInAdGroup).isEqualTo(-1); + // SEEK_ADJUSTMENT back to ad + assertThat(oldPositions.get(1).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(1).positionMs).isEqualTo(8_000); + assertThat(oldPositions.get(1).contentPositionMs).isEqualTo(8_000); + assertThat(oldPositions.get(1).adGroupIndex).isEqualTo(-1); + assertThat(oldPositions.get(1).adIndexInAdGroup).isEqualTo(-1); + assertThat(newPositions.get(1).windowIndex).isEqualTo(0); + assertThat(newPositions.get(1).positionMs).isEqualTo(0); + assertThat(newPositions.get(1).contentPositionMs).isEqualTo(8000); + assertThat(newPositions.get(1).adGroupIndex).isEqualTo(0); + assertThat(newPositions.get(1).adIndexInAdGroup).isEqualTo(0); + // AUTO_TRANSITION back to content + assertThat(oldPositions.get(2).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(2).positionMs).isEqualTo(5_000); + assertThat(oldPositions.get(2).contentPositionMs).isEqualTo(8_000); + assertThat(oldPositions.get(2).adGroupIndex).isEqualTo(0); + assertThat(oldPositions.get(2).adIndexInAdGroup).isEqualTo(0); + assertThat(newPositions.get(2).windowIndex).isEqualTo(0); + assertThat(newPositions.get(2).positionMs).isEqualTo(8_000); + assertThat(newPositions.get(2).contentPositionMs).isEqualTo(8_000); + assertThat(newPositions.get(2).adGroupIndex).isEqualTo(-1); + assertThat(newPositions.get(2).adIndexInAdGroup).isEqualTo(-1); + + player.release(); + } + + @Test + public void play_multiItemPlaylistWidthAds_callsOnDiscontinuityCorrectly() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + AdPlaybackState postRollAdPlaybackState = + FakeTimeline.createAdPlaybackState( + /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE); + TimelineWindowDefinition postRollWindow = + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* isLive= */ false, + /* isPlaceholder= */ false, + /* durationUs= */ 20 * C.MICROS_PER_SECOND, + /* defaultPositionUs= */ 0, + /* windowOffsetInFirstPeriodUs= */ 0, + postRollAdPlaybackState); + AdPlaybackState preRollAdPlaybackState = + FakeTimeline.createAdPlaybackState(/* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */ 0); + TimelineWindowDefinition preRollWindow = + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* isLive= */ false, + /* isPlaceholder= */ false, + /* durationUs= */ 25 * C.MICROS_PER_SECOND, + /* defaultPositionUs= */ 0, + /* windowOffsetInFirstPeriodUs= */ 0, + preRollAdPlaybackState); + player.setMediaSources( + Lists.newArrayList( + new FakeMediaSource(), + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 15 * C.MICROS_PER_SECOND))), + new FakeMediaSource(new FakeTimeline(postRollWindow)), + new FakeMediaSource(new FakeTimeline(preRollWindow)))); + + player.prepare(); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + Window window = new Window(); + InOrder inOrder = Mockito.inOrder(listener); + // from first to second window + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(listener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)); + assertThat(oldPosition.getValue().windowUid) + .isEqualTo(player.getCurrentTimeline().getWindow(0, window).uid); + assertThat(oldPosition.getValue().windowIndex).isEqualTo(0); + assertThat(oldPosition.getValue().positionMs).isEqualTo(10_000); + assertThat(oldPosition.getValue().contentPositionMs).isEqualTo(10_000); + assertThat(oldPosition.getValue().adGroupIndex).isEqualTo(-1); + assertThat(oldPosition.getValue().adIndexInAdGroup).isEqualTo(-1); + assertThat(newPosition.getValue().windowUid) + .isEqualTo(player.getCurrentTimeline().getWindow(1, window).uid); + assertThat(newPosition.getValue().windowIndex).isEqualTo(1); + assertThat(newPosition.getValue().positionMs).isEqualTo(0); + assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); + assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); + assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); + // from second window to third + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(listener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)); + assertThat(oldPosition.getValue().windowUid) + .isEqualTo(player.getCurrentTimeline().getWindow(1, window).uid); + assertThat(newPosition.getValue().windowUid) + .isEqualTo(player.getCurrentTimeline().getWindow(2, window).uid); + assertThat(oldPosition.getValue().windowIndex).isEqualTo(1); + assertThat(oldPosition.getValue().positionMs).isEqualTo(15_000); + assertThat(oldPosition.getValue().contentPositionMs).isEqualTo(15_000); + assertThat(oldPosition.getValue().adGroupIndex).isEqualTo(-1); + assertThat(oldPosition.getValue().adIndexInAdGroup).isEqualTo(-1); + assertThat(newPosition.getValue().windowIndex).isEqualTo(2); + assertThat(newPosition.getValue().positionMs).isEqualTo(0); + 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 + @Nullable Object lastNewWindowUid = newPosition.getValue().windowUid; + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + assertThat(oldPosition.getValue().windowIndex).isEqualTo(2); + assertThat(oldPosition.getValue().windowUid).isEqualTo(lastNewWindowUid); + assertThat(oldPosition.getValue().positionMs).isEqualTo(20_000); + assertThat(oldPosition.getValue().contentPositionMs).isEqualTo(20_000); + assertThat(oldPosition.getValue().adGroupIndex).isEqualTo(-1); + assertThat(oldPosition.getValue().adIndexInAdGroup).isEqualTo(-1); + assertThat(newPosition.getValue().windowIndex).isEqualTo(2); + assertThat(newPosition.getValue().positionMs).isEqualTo(0); + 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 + lastNewWindowUid = newPosition.getValue().windowUid; + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + assertThat(oldPosition.getValue().windowUid).isEqualTo(lastNewWindowUid); + assertThat(oldPosition.getValue().windowIndex).isEqualTo(2); + assertThat(oldPosition.getValue().positionMs).isEqualTo(5_000); + assertThat(oldPosition.getValue().contentPositionMs).isEqualTo(20_000); + assertThat(oldPosition.getValue().adGroupIndex).isEqualTo(0); + assertThat(oldPosition.getValue().adIndexInAdGroup).isEqualTo(0); + assertThat(newPosition.getValue().windowUid).isEqualTo(oldPosition.getValue().windowUid); + assertThat(newPosition.getValue().windowIndex).isEqualTo(2); + assertThat(newPosition.getValue().positionMs).isEqualTo(19_999); + 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 + lastNewWindowUid = newPosition.getValue().windowUid; + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(listener) + .onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)); + assertThat(oldPosition.getValue().windowUid).isEqualTo(lastNewWindowUid); + assertThat(oldPosition.getValue().windowIndex).isEqualTo(2); + assertThat(oldPosition.getValue().positionMs).isEqualTo(20_000); + assertThat(oldPosition.getValue().contentPositionMs).isEqualTo(20_000); + assertThat(oldPosition.getValue().adGroupIndex).isEqualTo(-1); + assertThat(oldPosition.getValue().adIndexInAdGroup).isEqualTo(-1); + assertThat(newPosition.getValue().windowUid).isNotEqualTo(oldPosition.getValue().windowUid); + assertThat(newPosition.getValue().windowIndex).isEqualTo(3); + assertThat(newPosition.getValue().positionMs).isEqualTo(0); + 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 + lastNewWindowUid = newPosition.getValue().windowUid; + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), + newPosition.capture(), + eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + assertThat(oldPosition.getValue().windowUid).isEqualTo(lastNewWindowUid); + assertThat(oldPosition.getValue().windowIndex).isEqualTo(3); + assertThat(oldPosition.getValue().positionMs).isEqualTo(5_000); + assertThat(oldPosition.getValue().contentPositionMs).isEqualTo(0); + assertThat(oldPosition.getValue().adGroupIndex).isEqualTo(0); + assertThat(oldPosition.getValue().adIndexInAdGroup).isEqualTo(0); + assertThat(newPosition.getValue().windowUid).isEqualTo(oldPosition.getValue().windowUid); + assertThat(newPosition.getValue().windowIndex).isEqualTo(3); + assertThat(newPosition.getValue().positionMs).isEqualTo(0); + assertThat(newPosition.getValue().contentPositionMs).isEqualTo(0); + assertThat(newPosition.getValue().adGroupIndex).isEqualTo(-1); + assertThat(newPosition.getValue().adIndexInAdGroup).isEqualTo(-1); + inOrder + .verify(listener, never()) + .onPositionDiscontinuity( + any(), any(), not(eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION))); + inOrder + .verify(listener, never()) + .onMediaItemTransition(any(), not(eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO))); + player.release(); + } + + @Test + public void setMediaSources_removesPlayingPeriod_callsOnPositionDiscontinuity() throws Exception { + FakeMediaSource secondMediaSource = + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 15 * C.MICROS_PER_SECOND))); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + player.setMediaSource( + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND)))); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player.setMediaSources(Lists.newArrayList(secondMediaSource, secondMediaSource)); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + InOrder inOrder = inOrder(listener); + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).positionMs).isEqualTo(0); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(0); + player.release(); + } + + @Test + public void removeMediaItems_removesPlayingPeriod_callsOnPositionDiscontinuity() + throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + player.setMediaSources( + Lists.newArrayList( + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND))), + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 8 * C.MICROS_PER_SECOND))))); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 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); + // Removing the last item resets the position to 0 with an empty timeline. + player.removeMediaItem(0); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + InOrder inOrder = inOrder(listener); + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(listener) + .onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + inOrder + .verify(listener) + .onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(1); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).positionMs).isEqualTo(0); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(0); + assertThat(oldPositions.get(1).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(1).positionMs).isIn(Range.closed(1980L, 2000L)); + assertThat(oldPositions.get(1).contentPositionMs).isIn(Range.closed(1980L, 2000L)); + assertThat(newPositions.get(1).windowUid).isNull(); + assertThat(newPositions.get(1).windowIndex).isEqualTo(0); + assertThat(newPositions.get(1).positionMs).isEqualTo(0); + assertThat(newPositions.get(1).contentPositionMs).isEqualTo(0); + player.release(); + } + + @Test + public void + concatenatingMediaSourceRemoveMediaSource_removesPlayingPeriod_callsOnPositionDiscontinuity() + throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + ConcatenatingMediaSource concatenatingMediaSource = + new ConcatenatingMediaSource( + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND))), + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 8 * C.MICROS_PER_SECOND))), + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 6 * C.MICROS_PER_SECOND)))); + player.addMediaSource(concatenatingMediaSource); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + concatenatingMediaSource.removeMediaSource(1); + TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); + concatenatingMediaSource.removeMediaSource(1); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + InOrder inOrder = inOrder(listener); + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(listener, times(2)) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(1); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(newPositions.get(0).windowIndex).isEqualTo(1); + assertThat(newPositions.get(0).positionMs).isEqualTo(0); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(0); + assertThat(oldPositions.get(1).windowIndex).isEqualTo(1); + assertThat(oldPositions.get(1).positionMs).isEqualTo(0); + assertThat(oldPositions.get(1).contentPositionMs).isEqualTo(0); + assertThat(newPositions.get(1).windowIndex).isEqualTo(0); + assertThat(newPositions.get(1).positionMs).isEqualTo(0); + assertThat(newPositions.get(1).contentPositionMs).isEqualTo(0); + player.release(); + } + + @Test + public void + concatenatingMediaSourceRemoveMediaSourceWithSeek_overridesRemoval_callsOnPositionDiscontinuity() + throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + ConcatenatingMediaSource concatenatingMediaSource = + new ConcatenatingMediaSource( + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND))), + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 8 * C.MICROS_PER_SECOND))), + new FakeMediaSource( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 6 * C.MICROS_PER_SECOND)))); + player.addMediaSource(concatenatingMediaSource); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 1, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + concatenatingMediaSource.removeMediaSource(1); + player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234); + TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); + concatenatingMediaSource.removeMediaSource(0); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + InOrder inOrder = inOrder(listener); + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + // SEEK overrides concatenating media source modification. + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_SEEK)); + inOrder + .verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + // This fails once out of a hundred test runs due to a race condition whether the seek or the + // removal arrives first in EPI. + // inOrder.verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(1); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).positionMs).isEqualTo(1234); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(1234); + assertThat(oldPositions.get(1).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(1).positionMs).isEqualTo(1234); + assertThat(oldPositions.get(1).contentPositionMs).isEqualTo(1234); + assertThat(newPositions.get(1).windowIndex).isEqualTo(0); + assertThat(newPositions.get(1).positionMs).isEqualTo(1234); + assertThat(newPositions.get(1).contentPositionMs).isEqualTo(1234); + player.release(); + } + + @Test + public void seekTo_callsOnPositionDiscontinuity() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + player.setMediaSources(Lists.newArrayList(new FakeMediaSource(), new FakeMediaSource())); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 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.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + verify(listener, never()) + .onPositionDiscontinuity(any(), any(), not(eq(Player.DISCONTINUITY_REASON_SEEK))); + verify(listener, times(2)) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_SEEK)); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + assertThat(oldPositions.get(0).windowUid).isEqualTo(newPositions.get(0).windowUid); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).positionMs).isEqualTo(7_000); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(7_000); + assertThat(oldPositions.get(1).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(1).windowUid).isNotEqualTo(newPositions.get(1).windowUid); + assertThat(oldPositions.get(1).positionMs).isEqualTo(7_000); + assertThat(oldPositions.get(1).contentPositionMs).isEqualTo(7_000); + assertThat(newPositions.get(1).windowIndex).isEqualTo(1); + assertThat(newPositions.get(1).positionMs).isEqualTo(1_000); + assertThat(newPositions.get(1).contentPositionMs).isEqualTo(1_000); + player.release(); + } + + @Test + public void seekTo_whenTimelineEmpty_callsOnPositionDiscontinuity() { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + + player.seekTo(/* positionMs= */ 7 * C.MILLIS_PER_SECOND); + player.seekTo(/* windowIndex= */ 1, /* positionMs= */ C.MILLIS_PER_SECOND); + player.seekTo(/* positionMs= */ 5 * C.MILLIS_PER_SECOND); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + verify(listener, never()) + .onPositionDiscontinuity(any(), any(), not(eq(Player.DISCONTINUITY_REASON_SEEK))); + verify(listener, times(3)) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_SEEK)); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + // a seek from initial state to masked seek position + assertThat(oldPositions.get(0).windowUid).isNull(); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(0).positionMs).isEqualTo(0); + assertThat(oldPositions.get(0).contentPositionMs).isEqualTo(0); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).windowUid).isNull(); + assertThat(newPositions.get(0).positionMs).isEqualTo(7_000); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(7_000); + // a seek from masked seek position to another masked position across windows + assertThat(oldPositions.get(1).windowUid).isNull(); + assertThat(oldPositions.get(1).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(1).positionMs).isEqualTo(7_000); + assertThat(oldPositions.get(1).contentPositionMs).isEqualTo(7_000); + assertThat(newPositions.get(1).windowUid).isNull(); + assertThat(newPositions.get(1).windowIndex).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 + assertThat(oldPositions.get(2).windowUid).isNull(); + assertThat(oldPositions.get(2).windowIndex).isEqualTo(1); + assertThat(oldPositions.get(2).positionMs).isEqualTo(1_000); + assertThat(oldPositions.get(2).contentPositionMs).isEqualTo(1_000); + assertThat(newPositions.get(2).windowUid).isNull(); + assertThat(newPositions.get(2).windowIndex).isEqualTo(1); + assertThat(newPositions.get(2).positionMs).isEqualTo(5_000); + assertThat(newPositions.get(2).contentPositionMs).isEqualTo(5_000); + player.release(); + } + + @Test + public void stop_doesNotCallOnPositionDiscontinuity() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + player.setMediaSource(new FakeMediaSource()); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player.stop(); + + verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + player.release(); + } + + // Tests deprecated stop(boolean reset) + @SuppressWarnings("deprecation") + @Test + public void stop_withResetRemovesPlayingPeriod_callsOnPositionDiscontinuity() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + player.setMediaSource(new FakeMediaSource()); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition( + player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND); + player.stop(/* reset= */ true); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + verify(listener, never()) + .onPositionDiscontinuity(any(), any(), not(eq(Player.DISCONTINUITY_REASON_REMOVE))); + verify(listener) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + assertThat(oldPositions.get(0).windowIndex).isEqualTo(0); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(4980L, 5000L)); + assertThat(newPositions.get(0).windowUid).isNull(); + assertThat(newPositions.get(0).windowIndex).isEqualTo(0); + assertThat(newPositions.get(0).positionMs).isEqualTo(0); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(0); + player.release(); + } + + @Test + public void seekTo_cancelsSourceDiscontinuity_callsOnPositionDiscontinuity() throws Exception { + Timeline timeline1 = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2)); + final Timeline timeline2 = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 3)); + final FakeMediaSource mediaSource = + new FakeMediaSource(timeline1, ExoPlayerTestRunner.VIDEO_FORMAT); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + player.setMediaSource(mediaSource); + + player.prepare(); + TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 1, /* positionMs= */ 2000); + player.seekTo(/* windowIndex= */ 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); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + + ArgumentCaptor oldPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + ArgumentCaptor newPosition = + ArgumentCaptor.forClass(Player.PositionInfo.class); + InOrder inOrder = inOrder(listener); + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder + .verify(listener, times(2)) + .onPositionDiscontinuity( + oldPosition.capture(), newPosition.capture(), eq(Player.DISCONTINUITY_REASON_SEEK)); + inOrder + .verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + inOrder.verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + List oldPositions = oldPosition.getAllValues(); + List newPositions = newPosition.getAllValues(); + // First seek + assertThat(oldPositions.get(0).windowIndex).isEqualTo(1); + assertThat(oldPositions.get(0).positionMs).isIn(Range.closed(1980L, 2000L)); + assertThat(oldPositions.get(0).contentPositionMs).isIn(Range.closed(1980L, 2000L)); + assertThat(newPositions.get(0).windowIndex).isEqualTo(1); + assertThat(newPositions.get(0).positionMs).isEqualTo(2122); + assertThat(newPositions.get(0).contentPositionMs).isEqualTo(2122); + // Second seek. + assertThat(oldPositions.get(1).windowIndex).isEqualTo(1); + assertThat(oldPositions.get(1).positionMs).isEqualTo(2122); + assertThat(oldPositions.get(1).contentPositionMs).isEqualTo(2122); + assertThat(newPositions.get(1).windowIndex).isEqualTo(0); + assertThat(newPositions.get(1).positionMs).isEqualTo(2222); + assertThat(newPositions.get(1).contentPositionMs).isEqualTo(2222); + player.release(); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { @@ -9028,6 +10433,33 @@ public final class ExoPlayerTest { return false; } + private static Player.Commands createWithDefaultCommands( + @Player.Command int... additionalCommands) { + Player.Commands.Builder builder = new Player.Commands.Builder(); + builder.addAll( + COMMAND_PLAY_PAUSE, + COMMAND_PREPARE_STOP, + COMMAND_SET_SPEED_AND_PITCH, + COMMAND_SET_SHUFFLE_MODE, + COMMAND_SET_REPEAT_MODE, + COMMAND_GET_CURRENT_MEDIA_ITEM, + COMMAND_GET_MEDIA_ITEMS, + COMMAND_GET_MEDIA_ITEMS_METADATA, + COMMAND_CHANGE_MEDIA_ITEMS, + COMMAND_GET_AUDIO_ATTRIBUTES, + COMMAND_GET_VOLUME, + COMMAND_GET_DEVICE_VOLUME, + COMMAND_SET_VOLUME, + COMMAND_SET_DEVICE_VOLUME, + COMMAND_ADJUST_DEVICE_VOLUME, + COMMAND_SET_VIDEO_SURFACE, + COMMAND_GET_TEXT, + COMMAND_SEEK_TO_DEFAULT_POSITION, + COMMAND_SEEK_TO_MEDIA_ITEM); + builder.addAll(additionalCommands); + return builder.build(); + } + // Internal classes. /** {@link FakeRenderer} that can sleep and be woken-up. */ 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 e3067d8e25..8eaf476328 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 @@ -229,6 +229,49 @@ public final class MediaPeriodQueueTest { /* nextAdGroupIndex= */ C.INDEX_UNSET); } + @Test + public void getNextMediaPeriodInfo_withPlayedAdGroups_returnsCorrectMediaPeriodInfos() { + setupAdTimeline(/* adGroupTimesUs...= */ 0, FIRST_AD_START_TIME_US, C.TIME_END_OF_SOURCE); + setAdGroupLoaded(/* adGroupIndex= */ 0); + setAdGroupLoaded(/* adGroupIndex= */ 1); + setAdGroupLoaded(/* adGroupIndex= */ 2); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ C.TIME_UNSET); + setAdGroupPlayed(/* adGroupIndex= */ 0); + clear(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, + /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, + /* endPositionUs= */ FIRST_AD_START_TIME_US, + /* durationUs= */ FIRST_AD_START_TIME_US, + /* isLastInPeriod= */ false, + /* isLastInWindow= */ false, + /* nextAdGroupIndex= */ 1); + setAdGroupPlayed(/* adGroupIndex= */ 1); + clear(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, + /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, + /* endPositionUs= */ C.TIME_END_OF_SOURCE, + /* durationUs= */ CONTENT_DURATION_US, + /* isLastInPeriod= */ false, + /* isLastInWindow= */ false, + /* nextAdGroupIndex= */ 2); + setAdGroupPlayed(/* adGroupIndex= */ 2); + clear(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, + /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ true, + /* nextAdGroupIndex= */ C.INDEX_UNSET); + } + @Test public void getNextMediaPeriodInfo_inMultiPeriodWindow_returnsCorrectMediaPeriodInfos() { setupTimeline( @@ -269,14 +312,13 @@ public final class MediaPeriodQueueTest { setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. - advancePlaying(); enqueueNext(); // First ad. enqueueNext(); // Content between ads. enqueueNext(); // Second ad. // Change position of second ad (= change duration of content between ads). updateAdPlaybackStateAndTimeline( - /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US + 1); + /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); boolean changeHandled = @@ -294,15 +336,16 @@ public final class MediaPeriodQueueTest { setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. - advancePlaying(); enqueueNext(); // First ad. enqueueNext(); // Content between ads. enqueueNext(); // Second ad. advanceReading(); // Reading first ad. + advanceReading(); // Reading content between ads. + advanceReading(); // Reading second ad. - // Change position of first ad (= change duration of content before first ad). + // Change position of second ad (= change duration of content between ads). updateAdPlaybackStateAndTimeline( - /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US + 1, SECOND_AD_START_TIME_US); + /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); boolean changeHandled = @@ -312,7 +355,7 @@ public final class MediaPeriodQueueTest { /* maxRendererReadPositionUs= */ FIRST_AD_START_TIME_US); assertThat(changeHandled).isFalse(); - assertThat(getQueueLength()).isEqualTo(1); + assertThat(getQueueLength()).isEqualTo(3); } @Test @@ -322,7 +365,6 @@ public final class MediaPeriodQueueTest { setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. - advancePlaying(); enqueueNext(); // First ad. enqueueNext(); // Content between ads. enqueueNext(); // Second ad. @@ -352,7 +394,6 @@ public final class MediaPeriodQueueTest { setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. - advancePlaying(); enqueueNext(); // First ad. enqueueNext(); // Content between ads. enqueueNext(); // Second ad. @@ -382,7 +423,6 @@ public final class MediaPeriodQueueTest { setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. - advancePlaying(); enqueueNext(); // First ad. enqueueNext(); // Content between ads. enqueueNext(); // Second ad. @@ -430,6 +470,7 @@ public final class MediaPeriodQueueTest { mediaPeriodQueue.resolveMediaPeriodIdForAds( playlistTimeline, firstPeriodUid, /* positionUs= */ 0), /* requestedContentPositionUs= */ C.TIME_UNSET, + /* discontinuityStartPositionUs= */ 0, Player.STATE_READY, /* playbackError= */ null, /* isLoading= */ false, @@ -473,6 +514,21 @@ public final class MediaPeriodQueueTest { new RendererConfiguration[0], new ExoTrackSelection[0], /* info= */ null)); } + private void clear() { + mediaPeriodQueue.clear(); + playbackInfo = + playbackInfo.copyWithNewPosition( + mediaPeriodQueue.resolveMediaPeriodIdForAds( + mediaSourceList.createTimeline(), firstPeriodUid, /* positionUs= */ 0), + /* positionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, + /* discontinuityStartPositionUs= */ 0, + /* totalBufferedDurationUs= */ 0, + /* trackGroups= */ null, + /* trackSelectorResult= */ null, + /* staticMetadata= */ ImmutableList.of()); + } + private MediaPeriodInfo getNextMediaPeriodInfo() { return mediaPeriodQueue.getNextMediaPeriodInfo(/* rendererPositionUs= */ 0, playbackInfo); } @@ -491,6 +547,13 @@ public final class MediaPeriodQueueTest { updateTimeline(); } + private void setAdGroupPlayed(int adGroupIndex) { + for (int i = 0; i < adPlaybackState.adGroups[adGroupIndex].count; i++) { + adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, /* adIndexInAdGroup= */ i); + } + updateTimeline(); + } + private void setAdGroupFailedToLoad(int adGroupIndex) { adPlaybackState = adPlaybackState diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MetadataRetrieverTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MetadataRetrieverTest.java index 53f6c24f10..be110e2dc5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MetadataRetrieverTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MetadataRetrieverTest.java @@ -30,7 +30,7 @@ import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData; import com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; @@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.shadows.ShadowLooper; /** Tests for {@link MetadataRetriever}. */ @RunWith(AndroidJUnit4.class) @@ -48,12 +49,12 @@ public class MetadataRetrieverTest { private static final long TEST_TIMEOUT_SEC = 10; private Context context; - private AutoAdvancingFakeClock clock; + private FakeClock clock; @Before public void setUp() throws Exception { context = ApplicationProvider.getApplicationContext(); - clock = new AutoAdvancingFakeClock(); + clock = new FakeClock(/* isAutoAdvancing= */ true); } @Test @@ -63,6 +64,7 @@ public class MetadataRetrieverTest { ListenableFuture trackGroupsFuture = retrieveMetadata(context, mediaItem, clock); + ShadowLooper.idleMainLooper(); TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS); assertThat(trackGroups.length).isEqualTo(2); @@ -85,6 +87,7 @@ public class MetadataRetrieverTest { retrieveMetadata(context, mediaItem1, clock); ListenableFuture trackGroupsFuture2 = retrieveMetadata(context, mediaItem2, clock); + ShadowLooper.idleMainLooper(); TrackGroupArray trackGroups1 = trackGroupsFuture1.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS); TrackGroupArray trackGroups2 = trackGroupsFuture2.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS); @@ -118,6 +121,7 @@ public class MetadataRetrieverTest { ListenableFuture trackGroupsFuture = retrieveMetadata(context, mediaItem, clock); + ShadowLooper.idleMainLooper(); TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS); assertThat(trackGroups.length).isEqualTo(1); @@ -134,6 +138,7 @@ public class MetadataRetrieverTest { ListenableFuture trackGroupsFuture = retrieveMetadata(context, mediaItem, clock); + ShadowLooper.idleMainLooper(); TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS); assertThat(trackGroups.length).isEqualTo(1); @@ -164,6 +169,7 @@ public class MetadataRetrieverTest { ListenableFuture trackGroupsFuture = retrieveMetadata(context, mediaItem, clock); + ShadowLooper.idleMainLooper(); TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS); assertThat(trackGroups.length).isEqualTo(2); // Video and audio @@ -185,6 +191,7 @@ public class MetadataRetrieverTest { ListenableFuture trackGroupsFuture = retrieveMetadata(context, mediaItem, clock); + ShadowLooper.idleMainLooper(); assertThrows( ExecutionException.class, () -> trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS)); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/SimpleExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/SimpleExoPlayerTest.java index a11961c301..fb4ad4c648 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/SimpleExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/SimpleExoPlayerTest.java @@ -18,14 +18,21 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import android.graphics.SurfaceTexture; +import android.view.Surface; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.analytics.AnalyticsListener; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeVideoRenderer; @@ -62,7 +69,7 @@ public class SimpleExoPlayerTest { ApplicationProvider.getApplicationContext(), (handler, videoListener, audioListener, textOutput, metadataOutput) -> new Renderer[] {new FakeVideoRenderer(handler, videoListener)}) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); AnalyticsListener listener = mock(AnalyticsListener.class); player.addAnalyticsListener(listener); @@ -79,4 +86,66 @@ public class SimpleExoPlayerTest { verify(listener).onVideoDisabled(any(), any()); verify(listener).onPlayerReleased(any()); } + + @Test + public void releaseAfterRendererEvents_triggersPendingVideoEventsInListener() throws Exception { + Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0)); + SimpleExoPlayer player = + new SimpleExoPlayer.Builder( + ApplicationProvider.getApplicationContext(), + (handler, videoListener, audioListener, textOutput, metadataOutput) -> + new Renderer[] {new FakeVideoRenderer(handler, videoListener)}) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) + .build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + player.setMediaSource( + new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT)); + player.setVideoSurface(surface); + player.prepare(); + player.play(); + runUntilPlaybackState(player, Player.STATE_READY); + + player.release(); + surface.release(); + ShadowLooper.runMainLooperToNextTask(); + + verify(listener, atLeastOnce()).onEvents(any(), any()); // EventListener + verify(listener).onRenderedFirstFrame(); // VideoListener + } + + @Test + public void releaseAfterVolumeChanges_triggerPendingVolumeEventInListener() throws Exception { + SimpleExoPlayer player = + new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + + player.setVolume(0F); + player.release(); + ShadowLooper.runMainLooperToNextTask(); + + verify(listener).onVolumeChanged(anyFloat()); + } + + @Test + public void releaseAfterVolumeChanges_triggerPendingDeviceVolumeEventsInListener() { + SimpleExoPlayer player = + new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build(); + Player.Listener listener = mock(Player.Listener.class); + player.addListener(listener); + + int deviceVolume = player.getDeviceVolume(); + try { + player.setDeviceVolume(deviceVolume + 1); // No-op if at max volume. + player.setDeviceVolume(deviceVolume - 1); // No-op if at min volume. + } finally { + player.setDeviceVolume(deviceVolume); // Restore original volume. + } + + player.release(); + ShadowLooper.runMainLooperToNextTask(); + + verify(listener, atLeast(2)).onDeviceVolumeChanged(anyInt(), anyBoolean()); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index ad807c4079..9545209805 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -60,6 +60,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.graphics.SurfaceTexture; import android.os.Looper; import android.util.SparseArray; import android.view.Surface; @@ -80,11 +81,13 @@ import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; import com.google.android.exoplayer2.drm.DrmInitData; +import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.ExoMediaDrm; import com.google.android.exoplayer2.drm.MediaDrmCallback; import com.google.android.exoplayer2.drm.MediaDrmCallbackException; import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.robolectric.RobolectricUtil; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.LoadEventInfo; @@ -107,8 +110,10 @@ import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.ArrayList; @@ -215,7 +220,7 @@ public final class AnalyticsCollectorTest { period0 /* ENDED */) .inOrder(); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */) + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */) .inOrder(); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) .containsExactly(period0 /* started */, period0 /* stopped */) @@ -656,9 +661,11 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGE */, - WINDOW_0 /* SOURCE_UPDATE */, + period0Seq0 /* SOURCE_UPDATE */, WINDOW_0 /* PLAYLIST_CHANGE */, - WINDOW_0 /* SOURCE_UPDATE */); + period0Seq1 /* SOURCE_UPDATE */); + assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) + .containsExactly(WINDOW_0 /* REMOVE */); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) .containsExactly(period0Seq0, period0Seq0, period0Seq1, period0Seq1) .inOrder(); @@ -748,7 +755,7 @@ public final class AnalyticsCollectorTest { period0Seq0 /* ENDED */) .inOrder(); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* prepared */, WINDOW_0 /* prepared */); + .containsExactly(WINDOW_0 /* prepared */, period0Seq0 /* prepared */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0Seq0); @@ -929,11 +936,14 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGED */, - WINDOW_0 /* SOURCE_UPDATE (first item) */, + period0Seq0 /* SOURCE_UPDATE (first item) */, period0Seq0 /* PLAYLIST_CHANGED (add) */, period0Seq0 /* SOURCE_UPDATE (second item) */, period0Seq1 /* PLAYLIST_CHANGED (remove) */) .inOrder(); + assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) + .containsExactly(period0Seq1 /* REMOVE */) + .inOrder(); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) .containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) @@ -949,7 +959,7 @@ public final class AnalyticsCollectorTest { .containsExactly(period0Seq0, period1Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(period0Seq0, period1Seq1, period0Seq1) + .containsExactly(period0Seq0, period0Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_DECODER_INIT)) .containsExactly(period0Seq0, period1Seq1) @@ -957,10 +967,9 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) .containsExactly(period0Seq0, period1Seq1) .inOrder(); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)) - .containsExactly(period0Seq0, period0Seq0); + assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)) - .containsExactly(period0Seq0, period1Seq1, period0Seq1) + .containsExactly(period0Seq0, period0Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)) .containsExactly(period0Seq0, period1Seq1) @@ -968,13 +977,13 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)) .containsExactly(period0Seq0, period1Seq1) .inOrder(); - assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0, period0Seq0); + assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(period0Seq0, period0Seq1) + .containsExactly(period0Seq0, period1Seq1, period0Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) - .containsExactly(period0Seq0, period0Seq1); + .containsExactly(period0Seq0, period1Seq1, period0Seq1); assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)) .containsExactly(period0Seq1); listener.assertNoMoreEvents(); @@ -1032,12 +1041,14 @@ public final class AnalyticsCollectorTest { @Override public void run(SimpleExoPlayer player) { player.addListener( - new Player.EventListener() { + new Player.Listener() { @Override public void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) { if (!player.isPlayingAd() - && reason == Player.DISCONTINUITY_REASON_AD_INSERTION) { + && reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { // Finished playing ad. Marked as played. adPlaybackState.set( adPlaybackState @@ -1132,7 +1143,7 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGED */, - WINDOW_0 /* SOURCE_UPDATE (initial) */, + prerollAd /* SOURCE_UPDATE (initial) */, contentAfterPreroll /* SOURCE_UPDATE (played preroll) */, contentAfterMidroll /* SOURCE_UPDATE (played midroll) */, contentAfterPostroll /* SOURCE_UPDATE (played postroll) */) @@ -1327,7 +1338,7 @@ public final class AnalyticsCollectorTest { contentAfterMidroll /* ENDED */) .inOrder(); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, contentBeforeMidroll /* SOURCE_UPDATE */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) .containsExactly( contentAfterMidroll /* seek */, @@ -1436,19 +1447,43 @@ public final class AnalyticsCollectorTest { } @Test - public void drmEvents_periodWithSameDrmData_keysReused() throws Exception { + public void drmEvents_periodsWithSameDrmData_keysReusedButLoadEventReportedTwice() + throws Exception { + BlockingDrmCallback mediaDrmCallback = BlockingDrmCallback.returnsEmpty(); + DrmSessionManager blockingDrmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm()) + .setMultiSession(true) + .build(mediaDrmCallback); MediaSource mediaSource = new ConcatenatingMediaSource( - new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1), - new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1)); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + new FakeMediaSource( + SINGLE_PERIOD_TIMELINE, blockingDrmSessionManager, VIDEO_FORMAT_DRM_1), + new FakeMediaSource( + SINGLE_PERIOD_TIMELINE, blockingDrmSessionManager, VIDEO_FORMAT_DRM_1)); + TestAnalyticsListener listener = + runAnalyticsTest( + mediaSource, + // Wait for the media to be fully buffered before unblocking the DRM key request. This + // ensures both periods report the same load event (because period1's DRM session is + // already preacquired by the time the key load completes). + new ActionSchedule.Builder(TAG) + .waitForIsLoading(false) + .waitForIsLoading(true) + .waitForIsLoading(false) + .executeRunnable(mediaDrmCallback.keyCondition::open) + .build()); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty(); assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED)) .containsExactly(period0, period1) .inOrder(); - assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0); + // This includes both period0 and period1 because period1's DrmSession was preacquired before + // the key load completed. There's only one key load (a second would block forever). We can't + // assume the order these events will arrive in because it depends on the iteration order of a + // HashSet of EventDispatchers inside DefaultDrmSession. + assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0, period1); // The period1 release event is lost because it's posted to "ExoPlayerTest thread" after that // thread has been quit during clean-up. assertThat(listener.getEvents(EVENT_DRM_SESSION_RELEASED)).containsExactly(period0); @@ -1470,9 +1505,9 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED)) .containsExactly(period0, period1) .inOrder(); - assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)) - .containsExactly(period0, period1) - .inOrder(); + // The pre-fetched key load for period1 might complete before the blocking key load for period0, + // so we can't assert the order: + assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0, period1); // The period1 release event is lost because it's posted to "ExoPlayerTest thread" after that // thread has been quit during clean-up. assertThat(listener.getEvents(EVENT_DRM_SESSION_RELEASED)).containsExactly(period0); @@ -1480,11 +1515,21 @@ public final class AnalyticsCollectorTest { @Test public void drmEvents_errorHandling() throws Exception { + BlockingDrmCallback mediaDrmCallback = BlockingDrmCallback.alwaysFailing(); DrmSessionManager failingDrmSessionManager = - new DefaultDrmSessionManager.Builder().build(new FailingDrmCallback()); + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm()) + .setMultiSession(true) + .build(mediaDrmCallback); MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + TestAnalyticsListener listener = + runAnalyticsTest( + mediaSource, + new ActionSchedule.Builder(TAG) + .waitForIsLoading(false) + .executeRunnable(mediaDrmCallback.keyCondition::open) + .build()); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0); @@ -1595,6 +1640,9 @@ public final class AnalyticsCollectorTest { public void onEvents_isReportedWithCorrectEventTimes() throws Exception { SimpleExoPlayer player = new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build(); + Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0)); + player.setVideoSurface(surface); + AnalyticsListener listener = mock(AnalyticsListener.class); Format[] formats = new Format[] { @@ -1615,11 +1663,12 @@ public final class AnalyticsCollectorTest { player.addMediaSource(new FakeMediaSource(new FakeTimeline(), formats)); player.play(); TestPlayerRunHelper.runUntilPositionDiscontinuity( - player, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4")); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE); ShadowLooper.runMainLooperToNextTask(); player.release(); + surface.release(); // Verify that expected individual callbacks have been called and capture EventTimes. ArgumentCaptor individualTimelineChangedEventTimes = @@ -1701,12 +1750,12 @@ public final class AnalyticsCollectorTest { ArgumentCaptor.forClass(AnalyticsListener.EventTime.class); verify(listener, atLeastOnce()) .onVideoDecoderInitialized( - individualVideoDecoderInitializedEventTimes.capture(), any(), anyLong()); + individualVideoDecoderInitializedEventTimes.capture(), any(), anyLong(), anyLong()); ArgumentCaptor individualAudioDecoderInitializedEventTimes = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class); verify(listener, atLeastOnce()) .onAudioDecoderInitialized( - individualAudioDecoderInitializedEventTimes.capture(), any(), anyLong()); + individualAudioDecoderInitializedEventTimes.capture(), any(), anyLong(), anyLong()); ArgumentCaptor individualVideoDisabledEventTimes = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class); verify(listener, atLeastOnce()) @@ -1718,9 +1767,11 @@ public final class AnalyticsCollectorTest { ArgumentCaptor individualRenderedFirstFrameEventTimes = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class); verify(listener, atLeastOnce()) - .onRenderedFirstFrame(individualRenderedFirstFrameEventTimes.capture(), any()); + .onRenderedFirstFrame(individualRenderedFirstFrameEventTimes.capture(), any(), anyLong()); ArgumentCaptor individualVideoSizeChangedEventTimes = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class); + verify(listener, atLeastOnce()) + .onVideoSizeChanged(individualVideoSizeChangedEventTimes.capture(), any()); verify(listener, atLeastOnce()) .onVideoSizeChanged( individualVideoSizeChangedEventTimes.capture(), @@ -1939,11 +1990,13 @@ public final class AnalyticsCollectorTest { @Nullable ActionSchedule actionSchedule, RenderersFactory renderersFactory) throws Exception { + Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0)); TestAnalyticsListener listener = new TestAnalyticsListener(); try { new ExoPlayerTestRunner.Builder(ApplicationProvider.getApplicationContext()) .setMediaSources(mediaSource) .setRenderersFactory(renderersFactory) + .setVideoSurface(surface) .setAnalyticsListener(listener) .setActionSchedule(actionSchedule) .build() @@ -1952,6 +2005,8 @@ public final class AnalyticsCollectorTest { .blockUntilEnded(TIMEOUT_MS); } catch (ExoPlaybackException e) { // Ignore ExoPlaybackException as these may be expected. + } finally { + surface.release(); } return listener; } @@ -2049,7 +2104,11 @@ public final class AnalyticsCollectorTest { } @Override - public void onPositionDiscontinuity(EventTime eventTime, int reason) { + public void onPositionDiscontinuity( + EventTime eventTime, + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + int reason) { reportedEvents.add(new ReportedEvent(EVENT_POSITION_DISCONTINUITY, eventTime)); } @@ -2184,7 +2243,10 @@ public final class AnalyticsCollectorTest { @Override public void onAudioDecoderInitialized( - EventTime eventTime, String decoderName, long initializationDurationMs) { + EventTime eventTime, + String decoderName, + long initializedTimestampMs, + long initializationDurationMs) { reportedEvents.add(new ReportedEvent(EVENT_AUDIO_DECODER_INITIALIZED, eventTime)); } @@ -2221,7 +2283,10 @@ public final class AnalyticsCollectorTest { @Override public void onVideoDecoderInitialized( - EventTime eventTime, String decoderName, long initializationDurationMs) { + EventTime eventTime, + String decoderName, + long initializedTimestampMs, + long initializationDurationMs) { reportedEvents.add(new ReportedEvent(EVENT_VIDEO_DECODER_INITIALIZED, eventTime)); } @@ -2247,22 +2312,17 @@ public final class AnalyticsCollectorTest { } @Override - public void onRenderedFirstFrame(EventTime eventTime, @Nullable Surface surface) { + public void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) { reportedEvents.add(new ReportedEvent(EVENT_RENDERED_FIRST_FRAME, eventTime)); } @Override - public void onVideoSizeChanged( - EventTime eventTime, - int width, - int height, - int unappliedRotationDegrees, - float pixelWidthHeightRatio) { + public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) { reportedEvents.add(new ReportedEvent(EVENT_VIDEO_SIZE_CHANGED, eventTime)); } @Override - public void onDrmSessionAcquired(EventTime eventTime) { + public void onDrmSessionAcquired(EventTime eventTime, @DrmSession.State int state) { reportedEvents.add(new ReportedEvent(EVENT_DRM_SESSION_ACQUIRED, eventTime)); } @@ -2304,12 +2364,7 @@ public final class AnalyticsCollectorTest { @Override public String toString() { - return "{" - + "type=" - + Long.numberOfTrailingZeros(eventType) - + ", windowAndPeriodId=" - + eventWindowAndPeriodId - + '}'; + return "{" + "type=" + eventType + ", windowAndPeriodId=" + eventWindowAndPeriodId + '}'; } } } @@ -2321,34 +2376,67 @@ public final class AnalyticsCollectorTest { */ private static final class EmptyDrmCallback implements MediaDrmCallback { @Override - public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) - throws MediaDrmCallbackException { + public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) { return new byte[0]; } @Override - public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) - throws MediaDrmCallbackException { + public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) { return new byte[0]; } } /** - * A {@link MediaDrmCallback} that throws exceptions for both {@link - * #executeProvisionRequest(UUID, ExoMediaDrm.ProvisionRequest)} and {@link - * #executeKeyRequest(UUID, ExoMediaDrm.KeyRequest)}. + * A {@link MediaDrmCallback} that blocks each provision and key request until the associated + * {@link ConditionVariable} field is opened, and then returns an empty byte array. The {@link + * ConditionVariable} must be explicitly opened for each request. */ - private static final class FailingDrmCallback implements MediaDrmCallback { + private static final class BlockingDrmCallback implements MediaDrmCallback { + + public final ConditionVariable provisionCondition; + public final ConditionVariable keyCondition; + + private final boolean alwaysFail; + + private BlockingDrmCallback(boolean alwaysFail) { + this.provisionCondition = RobolectricUtil.createRobolectricConditionVariable(); + this.keyCondition = RobolectricUtil.createRobolectricConditionVariable(); + + this.alwaysFail = alwaysFail; + } + + /** Returns a callback that always returns an empty byte array from its execute methods. */ + public static BlockingDrmCallback returnsEmpty() { + return new BlockingDrmCallback(/* alwaysFail= */ false); + } + + /** Returns a callback that always throws an exception from its execute methods. */ + public static BlockingDrmCallback alwaysFailing() { + return new BlockingDrmCallback(/* alwaysFail= */ true); + } + @Override public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) throws MediaDrmCallbackException { - throw new RuntimeException("executeProvision failed"); + provisionCondition.blockUninterruptible(); + provisionCondition.close(); + if (alwaysFail) { + throw new RuntimeException("executeProvisionRequest failed"); + } else { + return new byte[0]; + } } @Override public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) throws MediaDrmCallbackException { - throw new RuntimeException("executeKey failed"); + keyCondition.blockUninterruptible(); + keyCondition.close(); + if (alwaysFail) { + throw new RuntimeException("executeKeyRequest failed"); + } else { + return new byte[0]; + } } } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java index 034d9712ce..930a835cec 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java @@ -59,6 +59,16 @@ public final class DefaultPlaybackSessionManagerTest { sessionManager.setListener(mockListener); } + @Test + public void updatesSession_withEmptyTimeline_doesNotCreateNewSession() { + EventTime eventTime = + createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId */ null); + + sessionManager.updateSessions(eventTime); + + verifyNoMoreInteractions(mockListener); + } + @Test public void updateSessions_withoutMediaPeriodId_createsNewSession() { Timeline timeline = new FakeTimeline(); @@ -461,9 +471,9 @@ public final class DefaultPlaybackSessionManagerTest { sessionManager.updateSessionsWithTimelineChange(contentEventTime1); sessionManager.updateSessions(adEventTime1); sessionManager.updateSessionsWithDiscontinuity( - adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION); + adEventTime1, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); sessionManager.updateSessionsWithDiscontinuity( - contentEventTime2, Player.DISCONTINUITY_REASON_AD_INSERTION); + contentEventTime2, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); String adSessionId2 = sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime2.mediaPeriodId); @@ -486,8 +496,6 @@ public final class DefaultPlaybackSessionManagerTest { @Test public void belongsToSession_withSameWindowIndex_returnsTrue() { - EventTime eventTime = - createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null); Timeline timeline = new FakeTimeline(); EventTime eventTimeWithTimeline = createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); @@ -496,11 +504,10 @@ public final class DefaultPlaybackSessionManagerTest { timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0); EventTime eventTimeWithMediaPeriodId = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId); - sessionManager.updateSessions(eventTime); + sessionManager.updateSessions(eventTimeWithTimeline); ArgumentCaptor sessionId = ArgumentCaptor.forClass(String.class); - verify(mockListener).onSessionCreated(eq(eventTime), sessionId.capture()); - assertThat(sessionManager.belongsToSession(eventTime, sessionId.getValue())).isTrue(); + verify(mockListener).onSessionCreated(eq(eventTimeWithTimeline), sessionId.capture()); assertThat(sessionManager.belongsToSession(eventTimeWithTimeline, sessionId.getValue())) .isTrue(); assertThat(sessionManager.belongsToSession(eventTimeWithMediaPeriodId, sessionId.getValue())) @@ -509,11 +516,11 @@ public final class DefaultPlaybackSessionManagerTest { @Test public void belongsToSession_withOtherWindowIndex_returnsFalse() { - EventTime eventTime = - createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null); - EventTime eventTimeOtherWindow = - createEventTime(Timeline.EMPTY, /* windowIndex= */ 1, /* mediaPeriodId= */ null); Timeline timeline = new FakeTimeline(/* windowCount= */ 2); + EventTime eventTime = + createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); + EventTime eventTimeOtherWindow = + createEventTime(timeline, /* windowIndex= */ 1, /* mediaPeriodId= */ null); MediaPeriodId mediaPeriodId = new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1); @@ -585,18 +592,18 @@ public final class DefaultPlaybackSessionManagerTest { } @Test - public void initialTimelineUpdate_finishesAllSessionsOutsideTimeline() { + public void timelineUpdate_toEmpty_finishesAllSessionsAndDoesNotCreateNewSessions() { + Timeline timeline = new FakeTimeline(/* windowCount= */ 2); EventTime eventTime1 = - createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null); + createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); EventTime eventTime2 = - createEventTime(Timeline.EMPTY, /* windowIndex= */ 1, /* mediaPeriodId= */ null); + createEventTime(timeline, /* windowIndex= */ 1, /* mediaPeriodId= */ null); sessionManager.updateSessions(eventTime1); sessionManager.updateSessions(eventTime2); - Timeline timeline = new FakeTimeline(); - EventTime newTimelineEventTime = - createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); - sessionManager.updateSessionsWithTimelineChange(newTimelineEventTime); + EventTime eventTimeWithEmptyTimeline = + createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null); + sessionManager.updateSessionsWithTimelineChange(eventTimeWithEmptyTimeline); ArgumentCaptor sessionId1 = ArgumentCaptor.forClass(String.class); ArgumentCaptor sessionId2 = ArgumentCaptor.forClass(String.class); @@ -605,14 +612,19 @@ public final class DefaultPlaybackSessionManagerTest { verify(mockListener).onSessionActive(eventTime1, sessionId1.getValue()); verify(mockListener) .onSessionFinished( - newTimelineEventTime, + eventTimeWithEmptyTimeline, + sessionId1.getValue(), + /* automaticTransitionToNextPlayback= */ false); + verify(mockListener) + .onSessionFinished( + eventTimeWithEmptyTimeline, sessionId2.getValue(), /* automaticTransitionToNextPlayback= */ false); verifyNoMoreInteractions(mockListener); } @Test - public void dynamicTimelineUpdate_resolvesWindowIndices() { + public void timelineUpdate_resolvesWindowIndices() { Timeline initialTimeline = new FakeTimeline( new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 100), @@ -751,7 +763,7 @@ public final class DefaultPlaybackSessionManagerTest { sessionManager.updateSessions(eventTime2); sessionManager.updateSessionsWithDiscontinuity( - eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + eventTime2, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); verify(mockListener).onSessionCreated(eq(eventTime1), anyString()); verify(mockListener).onSessionActive(eq(eventTime1), anyString()); @@ -781,7 +793,7 @@ public final class DefaultPlaybackSessionManagerTest { sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId); sessionManager.updateSessionsWithDiscontinuity( - eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); + eventTime2, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); verify(mockListener).onSessionCreated(eventTime1, sessionId1); verify(mockListener).onSessionActive(eventTime1, sessionId1); @@ -960,7 +972,7 @@ public final class DefaultPlaybackSessionManagerTest { adTimeline, contentEventTimeDuringPreroll.mediaPeriodId); sessionManager.updateSessionsWithDiscontinuity( - contentEventTimeBetweenAds, Player.DISCONTINUITY_REASON_AD_INSERTION); + contentEventTimeBetweenAds, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); InOrder inOrder = inOrder(mockListener); inOrder.verify(mockListener).onSessionCreated(contentEventTimeDuringPreroll, contentSessionId); @@ -1025,7 +1037,7 @@ public final class DefaultPlaybackSessionManagerTest { sessionManager.updateSessions(adEventTime2); sessionManager.updateSessionsWithDiscontinuity( - adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION); + adEventTime1, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean()); } @@ -1083,7 +1095,7 @@ public final class DefaultPlaybackSessionManagerTest { sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime2.mediaPeriodId); sessionManager.updateSessionsWithDiscontinuity( - adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION); + adEventTime1, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); sessionManager.updateSessionsWithDiscontinuity(adEventTime2, Player.DISCONTINUITY_REASON_SEEK); verify(mockListener).onSessionCreated(eq(contentEventTime), anyString()); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java index e99f6a6d65..668ed5f556 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.robolectric.shadows.ShadowLooper.runMainLooperToNextTask; +import static org.mockito.Mockito.verifyNoMoreInteractions; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; @@ -62,41 +62,44 @@ public final class PlaybackStatsListenerTest { } @Test - public void events_duringInitialIdleState_dontCreateNewPlaybackStats() { + public void events_duringInitialIdleState_dontCreateNewPlaybackStats() throws Exception { PlaybackStatsListener playbackStatsListener = new PlaybackStatsListener(/* keepHistory= */ true, /* callback= */ null); player.addAnalyticsListener(playbackStatsListener); player.seekTo(/* positionMs= */ 1234); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f)); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); player.play(); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); assertThat(playbackStatsListener.getPlaybackStats()).isNull(); } @Test - public void stateChangeEvent_toNonIdle_createsInitialPlaybackStats() { + public void stateChangeEvent_toEndedWithEmptyTimeline_doesNotCreateInitialPlaybackStats() + throws Exception { + PlaybackStatsListener.Callback callback = mock(PlaybackStatsListener.Callback.class); PlaybackStatsListener playbackStatsListener = - new PlaybackStatsListener(/* keepHistory= */ true, /* callback= */ null); + new PlaybackStatsListener(/* keepHistory= */ true, callback); player.addAnalyticsListener(playbackStatsListener); player.prepare(); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); - assertThat(playbackStatsListener.getPlaybackStats()).isNotNull(); + assertThat(playbackStatsListener.getPlaybackStats()).isNull(); + verifyNoMoreInteractions(callback); } @Test - public void timelineChangeEvent_toNonEmpty_createsInitialPlaybackStats() { + public void timelineChangeEvent_toNonEmpty_createsInitialPlaybackStats() throws Exception { PlaybackStatsListener playbackStatsListener = new PlaybackStatsListener(/* keepHistory= */ true, /* callback= */ null); player.addAnalyticsListener(playbackStatsListener); player.setMediaItem(MediaItem.fromUri("http://test.org")); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); assertThat(playbackStatsListener.getPlaybackStats()).isNotNull(); } @@ -111,7 +114,7 @@ public final class PlaybackStatsListenerTest { player.prepare(); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); @Nullable PlaybackStats playbackStats = playbackStatsListener.getPlaybackStats(); assertThat(playbackStats).isNotNull(); @@ -128,7 +131,7 @@ public final class PlaybackStatsListenerTest { player.prepare(); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); @Nullable PlaybackStats playbackStats = playbackStatsListener.getPlaybackStats(); assertThat(playbackStats).isNotNull(); @@ -136,7 +139,7 @@ public final class PlaybackStatsListenerTest { } @Test - public void finishedSession_callsCallback() { + public void finishedSession_callsCallback() throws Exception { PlaybackStatsListener.Callback callback = mock(PlaybackStatsListener.Callback.class); PlaybackStatsListener playbackStatsListener = new PlaybackStatsListener(/* keepHistory= */ true, callback); @@ -145,10 +148,10 @@ public final class PlaybackStatsListenerTest { // Create session with some events and finish it by removing it from the playlist. player.setMediaSource(new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1))); player.prepare(); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); verify(callback, never()).onPlaybackStatsReady(any(), any()); player.clearMediaItems(); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); verify(callback).onPlaybackStatsReady(any(), any()); } @@ -197,9 +200,9 @@ public final class PlaybackStatsListenerTest { // the first one isn't finished yet. TestPlayerRunHelper.playUntilPosition( player, /* windowIndex= */ 0, /* positionMs= */ player.getDuration()); - runMainLooperToNextTask(); + runUntilPendingCommandsAreFullyHandled(player); player.release(); - runMainLooperToNextTask(); + ShadowLooper.idleMainLooper(); ArgumentCaptor eventTimeCaptor = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java index 13c62c96b9..f1a4fb91f6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java @@ -66,7 +66,7 @@ public final class DefaultAudioSinkTest { new DefaultAudioSink.DefaultAudioProcessorChain(teeAudioProcessor), /* enableFloatOutput= */ false, /* enableAudioTrackPlaybackParams= */ false, - /* enableOffload= */ false); + DefaultAudioSink.OFFLOAD_MODE_DISABLED); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java index c0b83e7a65..07398e0bf1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java @@ -20,14 +20,18 @@ import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.SECONDS; import android.os.Looper; +import androidx.annotation.Nullable; 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.MediaSource; import com.google.android.exoplayer2.testutil.FakeExoMediaDrm; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.shadows.ShadowLooper; @@ -38,6 +42,7 @@ import org.robolectric.shadows.ShadowLooper; // - Multiple acquisitions & releases for same keys -> multiple requests. // - Provisioning. // - Key denial. +// - Handling of ResourceBusyException (indicating session scarcity). @RunWith(AndroidJUnit4.class) public class DefaultDrmSessionManagerTest { @@ -252,6 +257,156 @@ public class DefaultDrmSessionManagerTest { assertThat(secondDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); } + @Test(timeout = 10_000) + public void preacquireSession_loadsKeysBeforeFullAcquisition() throws Exception { + AtomicInteger keyLoadCount = new AtomicInteger(0); + DrmSessionEventListener.EventDispatcher eventDispatcher = + new DrmSessionEventListener.EventDispatcher(); + eventDispatcher.addEventListener( + Util.createHandlerForCurrentLooper(), + new DrmSessionEventListener() { + @Override + public void onDrmKeysLoaded( + int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) { + keyLoadCount.incrementAndGet(); + } + }); + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + DrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm()) + // Disable keepalive + .setSessionKeepaliveMs(C.TIME_UNSET) + .build(/* mediaDrmCallback= */ licenseServer); + + drmSessionManager.prepare(); + + DrmSessionManager.DrmSessionReference sessionReference = + drmSessionManager.preacquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + eventDispatcher, + FORMAT_WITH_DRM_INIT_DATA); + + // Wait for the key load event to propagate, indicating the pre-acquired session is in + // STATE_OPENED_WITH_KEYS. + while (keyLoadCount.get() == 0) { + // Allow the key response to be handled. + ShadowLooper.idleMainLooper(); + } + + DrmSession drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + + // Without idling the main/playback looper, we assert the session is already in OPENED_WITH_KEYS + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); + assertThat(keyLoadCount.get()).isEqualTo(1); + + // After releasing our concrete session reference, the session is held open by the pre-acquired + // reference. + drmSession.release(/* eventDispatcher= */ null); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); + + // Releasing the pre-acquired reference allows the session to be fully released. + sessionReference.release(); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED); + } + + @Test(timeout = 10_000) + public void + preacquireSession_releaseBeforeUnderlyingAcquisitionCompletesReleasesSessionOnceAcquired() + throws Exception { + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + DrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm()) + // Disable keepalive + .setSessionKeepaliveMs(C.TIME_UNSET) + .build(/* mediaDrmCallback= */ licenseServer); + + drmSessionManager.prepare(); + + DrmSessionManager.DrmSessionReference sessionReference = + drmSessionManager.preacquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA); + + // Release the pre-acquired reference before the underlying session has had a chance to be + // constructed. + sessionReference.release(); + + // Acquiring the same session triggers a second key load (because the pre-acquired session was + // fully released). + DrmSession drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED); + + waitForOpenedWithKeys(drmSession); + + drmSession.release(/* eventDispatcher= */ null); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED); + } + + @Test(timeout = 10_000) + public void preacquireSession_releaseManagerBeforeAcquisition_acquisitionDoesntHappen() + throws Exception { + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + DrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm()) + // Disable keepalive + .setSessionKeepaliveMs(C.TIME_UNSET) + .build(/* mediaDrmCallback= */ licenseServer); + + drmSessionManager.prepare(); + + DrmSessionManager.DrmSessionReference sessionReference = + drmSessionManager.preacquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA); + + // Release the manager before the underlying session has had a chance to be constructed. This + // will release all pre-acquired sessions. + drmSessionManager.release(); + + // Allow the acquisition event to be handled on the main/playback thread. + ShadowLooper.idleMainLooper(); + + // Re-prepare the manager so we can fully acquire the same session, and check the previous + // pre-acquisition didn't do anything. + drmSessionManager.prepare(); + DrmSession drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED); + waitForOpenedWithKeys(drmSession); + + drmSession.release(/* eventDispatcher= */ null); + // If the (still unreleased) pre-acquired session above was linked to the same underlying + // session then the state would still be OPENED_WITH_KEYS. + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED); + + // Release the pre-acquired session from above (this is a no-op, but we do it anyway for + // correctness). + sessionReference.release(); + drmSessionManager.release(); + } + private static void waitForOpenedWithKeys(DrmSession drmSession) { // Check the error first, so we get a meaningful failure if there's been an error. assertThat(drmSession.getError()).isNull(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/EndToEndGaplessTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/EndToEndGaplessTest.java index 9cd334ac0f..d00d58c740 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/EndToEndGaplessTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/EndToEndGaplessTest.java @@ -29,7 +29,7 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.robolectric.RandomizedMp3Decoder; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.util.Assertions; import com.google.common.collect.ImmutableList; import com.google.common.primitives.Bytes; @@ -90,7 +90,7 @@ public class EndToEndGaplessTest { public void testPlayback_twoIdenticalMp3Files() throws Exception { SimpleExoPlayer player = new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext()) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setMediaItems( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlacPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlacPlaybackTest.java index cb2c945ec3..c98bd52da7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlacPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlacPlaybackTest.java @@ -23,9 +23,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -68,7 +68,7 @@ public class FlacPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlvPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlvPlaybackTest.java index 598aae8249..39055b573b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlvPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/FlvPlaybackTest.java @@ -25,9 +25,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -59,7 +59,7 @@ public final class FlvPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkaPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkaPlaybackTest.java index 78ff4299d1..fae8974c9d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkaPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkaPlaybackTest.java @@ -23,9 +23,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -61,7 +61,7 @@ public final class MkaPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkvPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkvPlaybackTest.java index 7aa9fff35d..5e96ad2497 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkvPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/MkvPlaybackTest.java @@ -25,9 +25,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -65,7 +65,7 @@ public final class MkvPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp3PlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp3PlaybackTest.java index 1b1b9a886b..6bd5f711fc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp3PlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp3PlaybackTest.java @@ -23,9 +23,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -64,7 +64,7 @@ public final class Mp3PlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp4PlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp4PlaybackTest.java index 492276b659..32f9e3e171 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp4PlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp4PlaybackTest.java @@ -25,9 +25,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -81,7 +81,7 @@ public class Mp4PlaybackTest { CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, renderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/OggPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/OggPlaybackTest.java index f95f5f0084..56d50da290 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/OggPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/OggPlaybackTest.java @@ -23,9 +23,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -62,7 +62,7 @@ public final class OggPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/PlaylistPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/PlaylistPlaybackTest.java index 3e6d1cfb12..e00857cc92 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/PlaylistPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/PlaylistPlaybackTest.java @@ -24,9 +24,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,7 +49,7 @@ public final class PlaylistPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); @@ -71,7 +71,7 @@ public final class PlaylistPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java index 886b45aed6..c6206b2fc8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java @@ -24,9 +24,9 @@ import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; import com.google.android.exoplayer2.source.SilenceMediaSource; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,7 +49,7 @@ public final class SilencePlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); @@ -70,7 +70,7 @@ public final class SilencePlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/TsPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/TsPlaybackTest.java index 0587a105bd..e0a6a32617 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/TsPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/TsPlaybackTest.java @@ -25,9 +25,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -85,7 +85,7 @@ public class TsPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Vp9PlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Vp9PlaybackTest.java index 1d0fc0cb4d..90c6d16cb4 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Vp9PlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Vp9PlaybackTest.java @@ -25,9 +25,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -63,7 +63,7 @@ public final class Vp9PlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/WavPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/WavPlaybackTest.java index 99418204b6..d1d9c38bf1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/WavPlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/WavPlaybackTest.java @@ -23,9 +23,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; @@ -56,7 +56,7 @@ public final class WavPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java index 48ecd2e582..74a7d1c987 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java @@ -24,26 +24,32 @@ import android.media.MediaCodec; import android.media.MediaFormat; import android.os.HandlerThread; import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.io.IOException; +import com.google.android.exoplayer2.Format; import java.lang.reflect.Constructor; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.shadows.ShadowLooper; /** Unit tests for {@link AsynchronousMediaCodecAdapter}. */ @RunWith(AndroidJUnit4.class) public class AsynchronousMediaCodecAdapterTest { private AsynchronousMediaCodecAdapter adapter; - private MediaCodec codec; private HandlerThread callbackThread; private HandlerThread queueingThread; private MediaCodec.BufferInfo bufferInfo; @Before - public void setUp() throws IOException { - codec = MediaCodec.createByCodecName("h264"); + public void setUp() throws Exception { + MediaCodecInfo codecInfo = createMediaCodecInfo("h264", "video/mp4"); + MediaCodecAdapter.Configuration configuration = + new MediaCodecAdapter.Configuration( + codecInfo, + createMediaFormat("format"), + /* format= */ new Format.Builder().build(), + /* surface= */ null, + /* crypto= */ null, + /* flags= */ 0); callbackThread = new HandlerThread("TestCallbackThread"); queueingThread = new HandlerThread("TestQueueingThread"); adapter = @@ -52,8 +58,11 @@ public class AsynchronousMediaCodecAdapterTest { /* queueingThreadSupplier= */ () -> queueingThread, /* forceQueueingSynchronizationWorkaround= */ false, /* synchronizeCodecInteractionsWithQueueing= */ false) - .createAdapter(codec); + .createAdapter(configuration); bufferInfo = new MediaCodec.BufferInfo(); + // After start(), the ShadowMediaCodec offers input buffer 0. We advance the looper to make sure + // and messages have been propagated to the adapter. + shadowOf(callbackThread.getLooper()).idle(); } @After @@ -61,40 +70,14 @@ public class AsynchronousMediaCodecAdapterTest { adapter.release(); } - @Test - public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - // After adapter.start(), the ShadowMediaCodec offers one input buffer. We pause the looper so - // that the buffer is not propagated to the adapter. - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - - assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); - } - @Test public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0. We advance the looper to make sure - // and messages have been propagated to the adapter. - shadowOf(callbackThread.getLooper()).idle(); - assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0); } - @Test public void dequeueInputBufferIndex_withMediaCodecError_throwsException() throws Exception { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - // Pause the looper so that we interact with the adapter from this thread only. - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - // Set an error directly on the adapter (not through the looper). adapter.onError(createCodecException()); @@ -103,14 +86,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we - // progress the adapter's looper. We progress the looper so that we call shutdown() on a - // non-empty adapter. - shadowOf(callbackThread.getLooper()).idle(); - adapter.release(); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); @@ -118,14 +93,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - - adapter.start(); - // After start(), the ShadowMediaCodec offers an output format change. We progress the looper - // so that the format change is propagated to the adapter. - shadowOf(callbackThread.getLooper()).idle(); - assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); // Assert that output buffer is available. @@ -135,14 +102,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we - // progress the adapter's looper. - ShadowLooper callbackShadowLooper = shadowOf(callbackThread.getLooper()); - callbackShadowLooper.idle(); - int index = adapter.dequeueInputBufferIndex(); adapter.queueInputBuffer(index, 0, 0, 0, 0); // Progress the queueuing looper first so the asynchronous enqueuer submits the input buffer, @@ -150,7 +109,7 @@ public class AsynchronousMediaCodecAdapterTest { // the callback looper so that the available output buffer callback is handled and the output // buffer reaches the adapter. shadowOf(queueingThread.getLooper()).idle(); - callbackShadowLooper.idle(); + shadowOf(callbackThread.getLooper()).idle(); // The ShadowMediaCodec will first offer an output format and then the output buffer. assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) @@ -162,12 +121,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withMediaCodecError_throwsException() throws Exception { - // Pause the looper so that we interact with the adapter from this thread only. - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - // Set an error directly on the adapter. adapter.onError(createCodecException()); @@ -176,18 +129,10 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we - // progress the adapter's looper. - ShadowLooper shadowLooper = shadowOf(callbackThread.getLooper()); - shadowLooper.idle(); - int index = adapter.dequeueInputBufferIndex(); adapter.queueInputBuffer(index, 0, 0, 0, 0); // Progress the looper so that the ShadowMediaCodec processes the input buffer. - shadowLooper.idle(); + shadowOf(callbackThread.getLooper()).idle(); adapter.release(); assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) @@ -196,25 +141,11 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_withoutFormatReceived_throwsException() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - // After start() the ShadowMediaCodec offers an output format change. Pause the looper so that - // the format change is not propagated to the adapter. - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat()); } @Test public void getOutputFormat_withMultipleFormats_returnsCorrectFormat() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers an output format, which is available only if we - // progress the adapter's looper. - shadowOf(callbackThread.getLooper()).idle(); - // Add another format on the adapter. adapter.onOutputFormatChanged(createMediaFormat("format2")); @@ -232,23 +163,28 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_afterFlush_returnsPreviousFormat() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers an output format, which is available only if we - // progress the adapter's looper. - ShadowLooper shadowLooper = shadowOf(callbackThread.getLooper()); - shadowLooper.idle(); - adapter.dequeueOutputBufferIndex(bufferInfo); MediaFormat outputFormat = adapter.getOutputFormat(); // Flush the adapter and progress the looper so that flush is completed. adapter.flush(); - shadowLooper.idle(); + shadowOf(callbackThread.getLooper()).idle(); assertThat(adapter.getOutputFormat()).isEqualTo(outputFormat); } + private static MediaCodecInfo createMediaCodecInfo(String name, String mimeType) { + return MediaCodecInfo.newInstance( + name, + mimeType, + /* codecMimeType= */ mimeType, + /* capabilities= */ null, + /* hardwareAccelerated= */ false, + /* softwareOnly= */ false, + /* vendor= */ false, + /* forceDisableAdaptive= */ false, + /* forceSecure= */ false); + } + private static MediaFormat createMediaFormat(String name) { MediaFormat format = new MediaFormat(); format.setString("name", name); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloaderTest.java index 52d83c133a..b80483a4e6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloaderTest.java @@ -23,6 +23,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.database.DatabaseProvider; +import com.google.android.exoplayer2.testutil.FailOnCloseDataSink; import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.TestUtil; @@ -34,6 +35,7 @@ import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -66,7 +68,7 @@ public class ProgressiveDownloaderTest { } @Test - public void download_afterSingleFailure_succeeds() throws Exception { + public void download_afterReadFailure_succeeds() throws Exception { Uri uri = Uri.parse("test:///test.mp4"); // Fake data has a built in failure after 10 bytes. @@ -92,6 +94,39 @@ public class ProgressiveDownloaderTest { assertThat(progressListener.bytesDownloaded).isEqualTo(30); } + @Test + public void download_afterWriteFailureOnClose_succeeds() throws Exception { + Uri uri = Uri.parse("test:///test.mp4"); + + FakeDataSet data = new FakeDataSet(); + data.newData(uri).appendReadData(1024); + DataSource.Factory upstreamDataSource = new FakeDataSource.Factory().setFakeDataSet(data); + + AtomicBoolean failOnClose = new AtomicBoolean(/* initialValue= */ true); + FailOnCloseDataSink.Factory dataSinkFactory = + new FailOnCloseDataSink.Factory(downloadCache, failOnClose); + + MediaItem mediaItem = MediaItem.fromUri(uri); + CacheDataSource.Factory cacheDataSourceFactory = + new CacheDataSource.Factory() + .setCache(downloadCache) + .setCacheWriteDataSinkFactory(dataSinkFactory) + .setUpstreamDataSourceFactory(upstreamDataSource); + ProgressiveDownloader downloader = new ProgressiveDownloader(mediaItem, cacheDataSourceFactory); + + TestProgressListener progressListener = new TestProgressListener(); + + // Failure expected after 1024 bytes. + assertThrows(IOException.class, () -> downloader.download(progressListener)); + assertThat(progressListener.bytesDownloaded).isEqualTo(1024); + + failOnClose.set(false); + + // Retry should succeed. + downloader.download(progressListener); + assertThat(progressListener.bytesDownloaded).isEqualTo(1024); + } + private static final class TestProgressListener implements Downloader.ProgressListener { public long bytesDownloaded; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index 5e39105359..94986175bc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import android.net.Uri; -import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; @@ -27,8 +26,6 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Window; -import com.google.android.exoplayer2.drm.DrmSessionEventListener; -import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException; import com.google.android.exoplayer2.source.MaskingMediaSource.PlaceholderTimeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; @@ -37,9 +34,6 @@ import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; import com.google.android.exoplayer2.testutil.TimelineAsserts; -import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.upstream.TransferListener; -import com.google.android.exoplayer2.util.Util; import java.io.IOException; import org.junit.Before; import org.junit.Test; @@ -56,7 +50,7 @@ public final class ClippingMediaSourceTest { private Period period; @Before - public void setUp() throws Exception { + public void setUp() { window = new Timeline.Window(); period = new Timeline.Period(); } @@ -485,140 +479,6 @@ public final class ClippingMediaSourceTest { TimelineAsserts.assertNextWindowIndices(clippedTimeline, Player.REPEAT_MODE_ALL, false, 0); } - @Test - public void eventTimeWithinClippedRange() throws IOException { - MediaLoadData mediaLoadData = - getClippingMediaSourceMediaLoadData( - /* clippingStartUs= */ TEST_CLIP_AMOUNT_US, - /* clippingEndUs= */ TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US, - /* eventStartUs= */ TEST_CLIP_AMOUNT_US + 1000, - /* eventEndUs= */ TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US - 1000); - assertThat(C.msToUs(mediaLoadData.mediaStartTimeMs)).isEqualTo(1000); - assertThat(C.msToUs(mediaLoadData.mediaEndTimeMs)) - .isEqualTo(TEST_PERIOD_DURATION_US - 2 * TEST_CLIP_AMOUNT_US - 1000); - } - - @Test - public void eventTimeOutsideClippedRange() throws IOException { - MediaLoadData mediaLoadData = - getClippingMediaSourceMediaLoadData( - /* clippingStartUs= */ TEST_CLIP_AMOUNT_US, - /* clippingEndUs= */ TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US, - /* eventStartUs= */ TEST_CLIP_AMOUNT_US - 1000, - /* eventEndUs= */ TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US + 1000); - assertThat(C.msToUs(mediaLoadData.mediaStartTimeMs)).isEqualTo(0); - assertThat(C.msToUs(mediaLoadData.mediaEndTimeMs)) - .isEqualTo(TEST_PERIOD_DURATION_US - 2 * TEST_CLIP_AMOUNT_US); - } - - @Test - public void unsetEventTime() throws IOException { - MediaLoadData mediaLoadData = - getClippingMediaSourceMediaLoadData( - /* clippingStartUs= */ TEST_CLIP_AMOUNT_US, - /* clippingEndUs= */ TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US, - /* eventStartUs= */ C.TIME_UNSET, - /* eventEndUs= */ C.TIME_UNSET); - assertThat(C.msToUs(mediaLoadData.mediaStartTimeMs)).isEqualTo(C.TIME_UNSET); - assertThat(C.msToUs(mediaLoadData.mediaEndTimeMs)).isEqualTo(C.TIME_UNSET); - } - - @Test - public void eventTimeWithUnsetDuration() throws IOException { - MediaLoadData mediaLoadData = - getClippingMediaSourceMediaLoadData( - /* clippingStartUs= */ TEST_CLIP_AMOUNT_US, - /* clippingEndUs= */ C.TIME_END_OF_SOURCE, - /* eventStartUs= */ TEST_CLIP_AMOUNT_US, - /* eventEndUs= */ TEST_CLIP_AMOUNT_US + 1_000_000); - assertThat(C.msToUs(mediaLoadData.mediaStartTimeMs)).isEqualTo(0); - assertThat(C.msToUs(mediaLoadData.mediaEndTimeMs)).isEqualTo(1_000_000); - } - - /** - * Wraps a timeline of duration {@link #TEST_PERIOD_DURATION_US} in a {@link ClippingMediaSource}, - * sends a media source event from the child source and returns the reported {@link MediaLoadData} - * for the clipping media source. - * - * @param clippingStartUs The start time of the media source clipping, in microseconds. - * @param clippingEndUs The end time of the media source clipping, in microseconds. - * @param eventStartUs The start time of the media source event (before clipping), in - * microseconds. - * @param eventEndUs The end time of the media source event (before clipping), in microseconds. - * @return The reported {@link MediaLoadData} for that event. - */ - private static MediaLoadData getClippingMediaSourceMediaLoadData( - long clippingStartUs, long clippingEndUs, final long eventStartUs, final long eventEndUs) - throws IOException { - Timeline timeline = - new SinglePeriodTimeline( - TEST_PERIOD_DURATION_US, - /* isSeekable= */ true, - /* isDynamic= */ false, - /* useLiveConfiguration= */ false, - /* manifest= */ null, - MediaItem.fromUri(Uri.EMPTY)); - FakeMediaSource fakeMediaSource = - new FakeMediaSource(timeline) { - @Override - protected MediaPeriod createMediaPeriod( - MediaPeriodId id, - TrackGroupArray trackGroupArray, - Allocator allocator, - MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, - DrmSessionManager drmSessionManager, - DrmSessionEventListener.EventDispatcher drmEventDispatcher, - @Nullable TransferListener transferListener) { - mediaSourceEventDispatcher.downstreamFormatChanged( - new MediaLoadData( - C.DATA_TYPE_MEDIA, - C.TRACK_TYPE_UNKNOWN, - /* trackFormat= */ null, - C.SELECTION_REASON_UNKNOWN, - /* trackSelectionData= */ null, - C.usToMs(eventStartUs), - C.usToMs(eventEndUs))); - return super.createMediaPeriod( - id, - trackGroupArray, - allocator, - mediaSourceEventDispatcher, - drmSessionManager, - drmEventDispatcher, - transferListener); - } - }; - final ClippingMediaSource clippingMediaSource = - new ClippingMediaSource(fakeMediaSource, clippingStartUs, clippingEndUs); - MediaSourceTestRunner testRunner = - new MediaSourceTestRunner(clippingMediaSource, /* allocator= */ null); - final MediaLoadData[] reportedMediaLoadData = new MediaLoadData[1]; - try { - testRunner.runOnPlaybackThread( - () -> - clippingMediaSource.addEventListener( - Util.createHandlerForCurrentLooper(), - new MediaSourceEventListener() { - @Override - public void onDownstreamFormatChanged( - int windowIndex, - @Nullable MediaPeriodId mediaPeriodId, - MediaLoadData mediaLoadData) { - reportedMediaLoadData[0] = mediaLoadData; - } - })); - testRunner.prepareSource(); - // Create period to send the test event configured above. - testRunner.createPeriod( - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); - assertThat(reportedMediaLoadData[0]).isNotNull(); - } finally { - testRunner.release(); - } - return reportedMediaLoadData[0]; - } - /** * Wraps the specified timeline in a {@link ClippingMediaSource} and returns the clipped timeline. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactoryTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactoryTest.java index 67f06e4f85..72a7c8c638 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactoryTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactoryTest.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.util.MimeTypes; import java.util.Arrays; import java.util.Collections; @@ -205,7 +206,7 @@ public final class DefaultMediaSourceFactoryTest { DefaultMediaSourceFactory defaultMediaSourceFactory = new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext()) .setAdsLoaderProvider(ignoredAdsConfiguration -> mock(AdsLoader.class)) - .setAdViewProvider(mock(AdsLoader.AdViewProvider.class)); + .setAdViewProvider(mock(AdViewProvider.class)); MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java index 9c883a149a..93531fa3d8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java @@ -30,6 +30,7 @@ import org.junit.Test; import org.junit.runner.RunWith; /** Unit tests for {@link LoopingMediaSource}. */ +@SuppressWarnings("deprecation") // Testing deprecated class. @RunWith(AndroidJUnit4.class) public class LoopingMediaSourceTest { @@ -49,18 +50,28 @@ public class LoopingMediaSourceTest { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); - for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1); - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); - TimelineAsserts.assertNextWindowIndices( - timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, C.INDEX_UNSET); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); - } + boolean shuffled = false; + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); + shuffled = true; // FakeTimeline has FakeShuffleOrder which returns a reverse order. + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); } @Test @@ -68,20 +79,32 @@ public class LoopingMediaSourceTest { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1); - for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8); - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_ALL, shuffled, 8, 0, 1, 2, 3, 4, 5, 6, 7); - TimelineAsserts.assertNextWindowIndices( - timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, 3, 4, 5, 6, 7, 8, C.INDEX_UNSET); - TimelineAsserts.assertNextWindowIndices( - timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); - TimelineAsserts.assertNextWindowIndices( - timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 3, 4, 5, 6, 7, 8, 0); - } + boolean shuffled = false; + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 8, 0, 1, 2, 3, 4, 5, 6, 7); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, 3, 4, 5, 6, 7, 8, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 3, 4, 5, 6, 7, 8, 0); + shuffled = true; // FakeTimeline has FakeShuffleOrder which returns a reverse order. + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, C.INDEX_UNSET, 4, 5, 0, 7, 8, 3); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 6, 4, 5, 0, 7, 8, 3); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 5, 0, 1, 8, 3, 4, C.INDEX_UNSET, 6, 7); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 5, 0, 1, 8, 3, 4, 2, 6, 7); } @Test @@ -89,17 +112,26 @@ public class LoopingMediaSourceTest { Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); - for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_OFF, shuffled, 2, 0, 1); - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); - TimelineAsserts.assertPreviousWindowIndices( - timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, 0); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); - } + boolean shuffled = false; + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, 0); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); + shuffled = true; // FakeTimeline has FakeShuffleOrder which returns a reverse order. + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, 0); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, 2, 0, 1); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java index 4a756ccf9f..6847d4a518 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.common.truth.Truth.assertThat; @@ -93,11 +94,11 @@ public final class MergingMediaPeriodTest { FormatHolder formatHolder = new FormatHolder(); DecoderInputBuffer inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); - assertThat(streams[1].readData(formatHolder, inputBuffer, /* formatRequired= */ true)) + assertThat(streams[1].readData(formatHolder, inputBuffer, FLAG_REQUIRE_FORMAT)) .isEqualTo(C.RESULT_FORMAT_READ); assertThat(formatHolder.format).isEqualTo(childFormat12); - assertThat(streams[2].readData(formatHolder, inputBuffer, /* formatRequired= */ true)) + assertThat(streams[2].readData(formatHolder, inputBuffer, FLAG_REQUIRE_FORMAT)) .isEqualTo(C.RESULT_FORMAT_READ); assertThat(formatHolder.format).isEqualTo(childFormat21); } @@ -134,20 +135,20 @@ public final class MergingMediaPeriodTest { FormatHolder formatHolder = new FormatHolder(); DecoderInputBuffer inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); - streams[0].readData(formatHolder, inputBuffer, /* formatRequired= */ true); - streams[1].readData(formatHolder, inputBuffer, /* formatRequired= */ true); + streams[0].readData(formatHolder, inputBuffer, FLAG_REQUIRE_FORMAT); + streams[1].readData(formatHolder, inputBuffer, FLAG_REQUIRE_FORMAT); FakeMediaPeriodWithSelectTracksPosition childMediaPeriod1 = (FakeMediaPeriodWithSelectTracksPosition) mergingMediaPeriod.getChildPeriod(0); assertThat(childMediaPeriod1.selectTracksPositionUs).isEqualTo(0); - assertThat(streams[0].readData(formatHolder, inputBuffer, /* formatRequired= */ false)) + assertThat(streams[0].readData(formatHolder, inputBuffer, /* readFlags= */ 0)) .isEqualTo(C.RESULT_BUFFER_READ); assertThat(inputBuffer.timeUs).isEqualTo(123_000L); FakeMediaPeriodWithSelectTracksPosition childMediaPeriod2 = (FakeMediaPeriodWithSelectTracksPosition) mergingMediaPeriod.getChildPeriod(1); assertThat(childMediaPeriod2.selectTracksPositionUs).isEqualTo(3000L); - assertThat(streams[1].readData(formatHolder, inputBuffer, /* formatRequired= */ false)) + assertThat(streams[1].readData(formatHolder, inputBuffer, /* readFlags= */ 0)) .isEqualTo(C.RESULT_BUFFER_READ); assertThat(inputBuffer.timeUs).isEqualTo(456_000 - 3000); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriodTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriodTest.java index aaf00388f6..2ca6ee6343 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriodTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriodTest.java @@ -24,22 +24,37 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.upstream.AssetDataSource; import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; /** Unit test for {@link ProgressiveMediaPeriod}. */ @RunWith(AndroidJUnit4.class) public final class ProgressiveMediaPeriodTest { @Test - public void prepare_updatesSourceInfoBeforeOnPreparedCallback() throws Exception { + public void prepareUsingBundledExtractors_updatesSourceInfoBeforeOnPreparedCallback() + throws TimeoutException { + testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( + new BundledExtractorsAdapter(Mp4Extractor.FACTORY)); + } + + @Test + @Config(sdk = 30) + public void prepareUsingMediaParser_updatesSourceInfoBeforeOnPreparedCallback() + throws TimeoutException { + testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback(new MediaParserExtractorAdapter()); + } + + private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( + ProgressiveMediaExtractor extractor) throws TimeoutException { AtomicBoolean sourceInfoRefreshCalled = new AtomicBoolean(false); ProgressiveMediaPeriod.Listener sourceInfoRefreshListener = (durationUs, isSeekable, isLive) -> sourceInfoRefreshCalled.set(true); @@ -48,7 +63,7 @@ public final class ProgressiveMediaPeriodTest { new ProgressiveMediaPeriod( Uri.parse("asset://android_asset/media/mp4/sample.mp4"), new AssetDataSource(ApplicationProvider.getApplicationContext()), - () -> new Extractor[] {new Mp4Extractor()}, + extractor, DrmSessionManager.DRM_UNSUPPORTED, new DrmSessionEventListener.EventDispatcher() .withParameters(/* windowIndex= */ 0, mediaPeriodId), diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java index db9eee2ba2..8ce5c999f7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java @@ -20,6 +20,8 @@ import static com.google.android.exoplayer2.C.BUFFER_FLAG_KEY_FRAME; import static com.google.android.exoplayer2.C.RESULT_BUFFER_READ; import static com.google.android.exoplayer2.C.RESULT_FORMAT_READ; import static com.google.android.exoplayer2.C.RESULT_NOTHING_READ; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_OMIT_SAMPLE_DATA; +import static com.google.android.exoplayer2.source.SampleStream.FLAG_PEEK; import static com.google.common.truth.Truth.assertThat; import static java.lang.Long.MAX_VALUE; import static java.lang.Long.MIN_VALUE; @@ -208,14 +210,11 @@ public final class SampleQueueTest { sampleQueue.format(FORMAT_1); clearFormatHolderAndInputBuffer(); int result = - sampleQueue.peek( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + sampleQueue.read(formatHolder, inputBuffer, FLAG_PEEK, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); // formatHolder should be populated. assertThat(formatHolder.format).isEqualTo(FORMAT_1); - result = - sampleQueue.peek( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + result = sampleQueue.read(formatHolder, inputBuffer, FLAG_PEEK, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_NOTHING_READ); } @@ -454,7 +453,7 @@ public final class SampleQueueTest { int result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession); assertReadEncryptedSample(/* sampleIndex= */ 0); @@ -463,13 +462,13 @@ public final class SampleQueueTest { assertThat(formatHolder.drmSession).isNull(); result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); assertThat(formatHolder.drmSession).isNull(); assertReadEncryptedSample(/* sampleIndex= */ 2); result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession); } @@ -484,7 +483,7 @@ public final class SampleQueueTest { int result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession); assertReadEncryptedSample(/* sampleIndex= */ 0); @@ -493,13 +492,13 @@ public final class SampleQueueTest { assertThat(formatHolder.drmSession).isNull(); result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); assertThat(formatHolder.drmSession).isSameInstanceAs(mockPlaceholderDrmSession); assertReadEncryptedSample(/* sampleIndex= */ 2); result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession); assertReadEncryptedSample(/* sampleIndex= */ 3); @@ -527,7 +526,7 @@ public final class SampleQueueTest { int result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); // Fill cryptoInfo.iv with non-zero data. When the 8 byte initialization vector is written into @@ -537,7 +536,7 @@ public final class SampleQueueTest { result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_BUFFER_READ); // Assert cryptoInfo.iv contains the 8-byte initialization vector and that the trailing 8 bytes @@ -1558,7 +1557,11 @@ public final class SampleQueueTest { private void assertReadNothing(boolean formatRequired) { clearFormatHolderAndInputBuffer(); int result = - sampleQueue.read(formatHolder, inputBuffer, formatRequired, /* loadingFinished= */ false); + sampleQueue.read( + formatHolder, + inputBuffer, + formatRequired ? SampleStream.FLAG_REQUIRE_FORMAT : 0, + /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_NOTHING_READ); // formatHolder should not be populated. assertThat(formatHolder.format).isNull(); @@ -1576,7 +1579,11 @@ public final class SampleQueueTest { private void assertReadEndOfStream(boolean formatRequired) { clearFormatHolderAndInputBuffer(); int result = - sampleQueue.read(formatHolder, inputBuffer, formatRequired, /* loadingFinished= */ true); + sampleQueue.read( + formatHolder, + inputBuffer, + formatRequired ? SampleStream.FLAG_REQUIRE_FORMAT : 0, + /* loadingFinished= */ true); assertThat(result).isEqualTo(RESULT_BUFFER_READ); // formatHolder should not be populated. assertThat(formatHolder.format).isNull(); @@ -1597,7 +1604,11 @@ public final class SampleQueueTest { private void assertReadFormat(boolean formatRequired, Format format) { clearFormatHolderAndInputBuffer(); int result = - sampleQueue.read(formatHolder, inputBuffer, formatRequired, /* loadingFinished= */ false); + sampleQueue.read( + formatHolder, + inputBuffer, + formatRequired ? SampleStream.FLAG_REQUIRE_FORMAT : 0, + /* loadingFinished= */ false); assertThat(result).isEqualTo(RESULT_FORMAT_READ); // formatHolder should be populated. assertThat(formatHolder.format).isEqualTo(format); @@ -1641,24 +1652,51 @@ public final class SampleQueueTest { byte[] sampleData, int offset, int length) { - // Check that peeks yields the expected values. - clearFormatHolderAndInputBuffer(); + // Check that peek whilst omitting data yields the expected values. + formatHolder.format = null; + DecoderInputBuffer flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance(); int result = - sampleQueue.peek( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); - assertBufferReadResult( + sampleQueue.read( + formatHolder, + flagsOnlyBuffer, + FLAG_OMIT_SAMPLE_DATA | FLAG_PEEK, + /* loadingFinished= */ false); + assertSampleBufferReadResult( + flagsOnlyBuffer, result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted); + + // Check that peek yields the expected values. + clearFormatHolderAndInputBuffer(); + result = sampleQueue.read(formatHolder, inputBuffer, FLAG_PEEK, /* loadingFinished= */ false); + assertSampleBufferReadResult( result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted, sampleData, offset, length); // Check that read yields the expected values. clearFormatHolderAndInputBuffer(); result = sampleQueue.read( - formatHolder, inputBuffer, /* formatRequired= */ false, /* loadingFinished= */ false); - assertBufferReadResult( + formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); + assertSampleBufferReadResult( result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted, sampleData, offset, length); } - private void assertBufferReadResult( + private void assertSampleBufferReadResult( + DecoderInputBuffer inputBuffer, + int result, + long timeUs, + boolean isKeyFrame, + boolean isDecodeOnly, + boolean isEncrypted) { + assertThat(result).isEqualTo(RESULT_BUFFER_READ); + // formatHolder should not be populated. + assertThat(formatHolder.format).isNull(); + // inputBuffer should be populated with metadata. + assertThat(inputBuffer.timeUs).isEqualTo(timeUs); + assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyFrame); + assertThat(inputBuffer.isDecodeOnly()).isEqualTo(isDecodeOnly); + assertThat(inputBuffer.isEncrypted()).isEqualTo(isEncrypted); + } + + private void assertSampleBufferReadResult( int result, long timeUs, boolean isKeyFrame, @@ -1667,14 +1705,9 @@ public final class SampleQueueTest { byte[] sampleData, int offset, int length) { - assertThat(result).isEqualTo(RESULT_BUFFER_READ); - // formatHolder should not be populated. - assertThat(formatHolder.format).isNull(); - // inputBuffer should be populated. - assertThat(inputBuffer.timeUs).isEqualTo(timeUs); - assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyFrame); - assertThat(inputBuffer.isDecodeOnly()).isEqualTo(isDecodeOnly); - assertThat(inputBuffer.isEncrypted()).isEqualTo(isEncrypted); + assertSampleBufferReadResult( + inputBuffer, result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted); + // inputBuffer should be populated with data. inputBuffer.flip(); assertThat(inputBuffer.data.limit()).isEqualTo(length); byte[] readData = new byte[length]; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SpannedDataTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SpannedDataTest.java new file mode 100644 index 0000000000..4dcccbaaad --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SpannedDataTest.java @@ -0,0 +1,222 @@ +/* + * 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.source; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.drm.DrmSessionManager.DrmSessionReference; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Tests for {@link SpannedData}. */ +@RunWith(AndroidJUnit4.class) +public final class SpannedDataTest { + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock private DrmSessionReference value1; + @Mock private DrmSessionReference value2; + @Mock private DrmSessionReference value3; + + @Test + public void appendMultipleSpansThenRead() { + SpannedData spannedData = + new SpannedData<>(/* removeCallback= */ DrmSessionReference::release); + + spannedData.appendSpan(/* startKey= */ 0, value1); + spannedData.appendSpan(/* startKey= */ 2, value2); + spannedData.appendSpan(/* startKey= */ 4, value3); + + assertThat(spannedData.get(0)).isEqualTo(value1); + assertThat(spannedData.get(1)).isEqualTo(value1); + assertThat(spannedData.get(2)).isEqualTo(value2); + assertThat(spannedData.get(3)).isEqualTo(value2); + assertThat(spannedData.get(4)).isEqualTo(value3); + assertThat(spannedData.get(5)).isEqualTo(value3); + + verify(value1, never()).release(); + verify(value2, never()).release(); + verify(value3, never()).release(); + } + + @Test + public void append_emptySpansDiscarded() { + SpannedData spannedData = new SpannedData<>(); + + spannedData.appendSpan(/* startKey= */ 0, value1); + spannedData.appendSpan(/* startKey= */ 2, value2); + spannedData.appendSpan(/* startKey= */ 2, value3); + + assertThat(spannedData.get(0)).isEqualTo(value1); + assertThat(spannedData.get(1)).isEqualTo(value1); + assertThat(spannedData.get(2)).isEqualTo(value3); + assertThat(spannedData.get(3)).isEqualTo(value3); + } + + @Test + public void getEndValue() { + SpannedData spannedData = new SpannedData<>(); + + assertThrows(Exception.class, spannedData::getEndValue); + + spannedData.appendSpan(/* startKey= */ 0, "test 1"); + spannedData.appendSpan(/* startKey= */ 2, "test 2"); + spannedData.appendSpan(/* startKey= */ 4, "test 3"); + + assertThat(spannedData.getEndValue()).isEqualTo("test 3"); + + spannedData.discardFrom(2); + assertThat(spannedData.getEndValue()).isEqualTo("test 2"); + + spannedData.clear(); + assertThrows(Exception.class, spannedData::getEndValue); + } + + @Test + public void discardTo() { + SpannedData spannedData = + new SpannedData<>(/* removeCallback= */ DrmSessionReference::release); + + spannedData.appendSpan(/* startKey= */ 0, value1); + spannedData.appendSpan(/* startKey= */ 2, value2); + spannedData.appendSpan(/* startKey= */ 4, value3); + + spannedData.discardTo(2); + + verify(value1).release(); + verify(value2, never()).release(); + assertThat(spannedData.get(0)).isEqualTo(value2); + assertThat(spannedData.get(2)).isEqualTo(value2); + + spannedData.discardTo(4); + + verify(value2).release(); + verify(value3, never()).release(); + assertThat(spannedData.get(3)).isEqualTo(value3); + assertThat(spannedData.get(4)).isEqualTo(value3); + } + + @Test + public void discardTo_prunesEmptySpans() { + SpannedData spannedData = new SpannedData<>(); + + spannedData.appendSpan(/* startKey= */ 0, value1); + spannedData.appendSpan(/* startKey= */ 2, value2); + spannedData.appendSpan(/* startKey= */ 2, value3); + + spannedData.discardTo(2); + + assertThat(spannedData.get(0)).isEqualTo(value3); + assertThat(spannedData.get(2)).isEqualTo(value3); + } + + @Test + public void discardFromThenAppend_keepsValueIfSpanEndsUpNonEmpty() { + SpannedData spannedData = + new SpannedData<>(/* removeCallback= */ DrmSessionReference::release); + + spannedData.appendSpan(/* startKey= */ 0, value1); + spannedData.appendSpan(/* startKey= */ 2, value2); + spannedData.appendSpan(/* startKey= */ 4, value3); + + spannedData.discardFrom(2); + + verify(value3).release(); + assertThat(spannedData.getEndValue()).isEqualTo(value2); + + spannedData.appendSpan(/* startKey= */ 3, value3); + + verify(value1, never()).release(); + verify(value2, never()).release(); + assertThat(spannedData.get(0)).isEqualTo(value1); + assertThat(spannedData.get(1)).isEqualTo(value1); + assertThat(spannedData.get(2)).isEqualTo(value2); + assertThat(spannedData.get(3)).isEqualTo(value3); + } + + @Test + public void discardFromThenAppend_prunesEmptySpan() { + SpannedData spannedData = + new SpannedData<>(/* removeCallback= */ DrmSessionReference::release); + + spannedData.appendSpan(/* startKey= */ 0, value1); + spannedData.appendSpan(/* startKey= */ 2, value2); + + spannedData.discardFrom(2); + + verify(value2, never()).release(); + + spannedData.appendSpan(/* startKey= */ 2, value3); + + verify(value2).release(); + assertThat(spannedData.get(0)).isEqualTo(value1); + assertThat(spannedData.get(1)).isEqualTo(value1); + assertThat(spannedData.get(2)).isEqualTo(value3); + } + + @Test + public void clear() { + SpannedData spannedData = + new SpannedData<>(/* removeCallback= */ DrmSessionReference::release); + + spannedData.appendSpan(/* startKey= */ 0, value1); + spannedData.appendSpan(/* startKey= */ 2, value2); + + spannedData.clear(); + + verify(value1).release(); + verify(value2).release(); + + spannedData.appendSpan(/* startKey= */ 1, value3); + + assertThat(spannedData.get(0)).isEqualTo(value3); + assertThat(spannedData.get(1)).isEqualTo(value3); + } + + @Test + public void isEmpty() { + SpannedData spannedData = new SpannedData<>(); + + assertThat(spannedData.isEmpty()).isTrue(); + + spannedData.appendSpan(/* startKey= */ 0, "test 1"); + spannedData.appendSpan(/* startKey= */ 2, "test 2"); + + assertThat(spannedData.isEmpty()).isFalse(); + + // Discarding from 0 still retains the 'first' span, so collection doesn't end up empty. + spannedData.discardFrom(0); + assertThat(spannedData.isEmpty()).isFalse(); + + spannedData.appendSpan(/* startKey= */ 2, "test 2"); + + // Discarding to 3 still retains the 'last' span, so collection doesn't end up empty. + spannedData.discardTo(3); + assertThat(spannedData.isEmpty()).isFalse(); + + spannedData.clear(); + + assertThat(spannedData.isEmpty()).isTrue(); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java index b5748aaa42..1580a39f17 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java @@ -34,9 +34,9 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaSourceCaller; import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.SinglePeriodTimeline; -import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider; import com.google.android.exoplayer2.source.ads.AdsLoader.EventListener; import com.google.android.exoplayer2.testutil.FakeMediaSource; +import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSpec; import org.junit.Before; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java index 74d87e08b8..1d33c2834e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java @@ -37,6 +37,7 @@ public class CueTest { new Cue.Builder() .setText(SpannedString.valueOf("text")) .setTextAlignment(Layout.Alignment.ALIGN_CENTER) + .setMultiRowAlignment(Layout.Alignment.ALIGN_NORMAL) .setLine(5, Cue.LINE_TYPE_NUMBER) .setLineAnchor(Cue.ANCHOR_TYPE_END) .setPosition(0.4f) @@ -52,6 +53,7 @@ public class CueTest { assertThat(cue.text.toString()).isEqualTo("text"); assertThat(cue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER); + assertThat(cue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL); assertThat(cue.line).isEqualTo(5); assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER); assertThat(cue.position).isEqualTo(0.4f); @@ -66,6 +68,7 @@ public class CueTest { assertThat(modifiedCue.text).isSameInstanceAs(cue.text); assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment); + assertThat(modifiedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment); assertThat(modifiedCue.line).isEqualTo(cue.line); assertThat(modifiedCue.lineType).isEqualTo(cue.lineType); assertThat(modifiedCue.position).isEqualTo(cue.position); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index 46192e4618..3a06360b86 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -50,6 +50,8 @@ public final class SsaDecoderTest { private static final String STYLE_COLORS = "media/ssa/style_colors"; private static final String STYLE_FONT_SIZE = "media/ssa/style_font_size"; private static final String STYLE_BOLD_ITALIC = "media/ssa/style_bold_italic"; + private static final String STYLE_UNDERLINE = "media/ssa/style_underline"; + private static final String STYLE_STRIKEOUT = "media/ssa/style_strikeout"; @Test public void decodeEmpty() throws IOException { @@ -356,6 +358,39 @@ public final class SsaDecoderTest { SpannedSubject.assertThat(thirdCueText).hasBoldItalicSpanBetween(0, thirdCueText.length()); } + @Test + public void decodeUnderline() throws IOException { + SsaDecoder decoder = new SsaDecoder(); + byte[] bytes = + TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_UNDERLINE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + assertThat(subtitle.getEventTimeCount()).isEqualTo(4); + + Spanned firstCueText = + (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))).text; + SpannedSubject.assertThat(firstCueText).hasUnderlineSpanBetween(0, firstCueText.length()); + Spanned secondCueText = + (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))).text; + SpannedSubject.assertThat(secondCueText).hasNoUnderlineSpanBetween(0, secondCueText.length()); + } + + @Test + public void decodeStrikeout() throws IOException { + SsaDecoder decoder = new SsaDecoder(); + byte[] bytes = + TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_STRIKEOUT); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + assertThat(subtitle.getEventTimeCount()).isEqualTo(4); + + Spanned firstCueText = + (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))).text; + SpannedSubject.assertThat(firstCueText).hasStrikethroughSpanBetween(0, firstCueText.length()); + Spanned secondCueText = + (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))).text; + SpannedSubject.assertThat(secondCueText) + .hasNoStrikethroughSpanBetween(0, secondCueText.length()); + } + private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) { assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java index 2e908f3325..d068ade74b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java @@ -65,6 +65,7 @@ public final class TtmlDecoderTest { private static final String BITMAP_UNSUPPORTED_REGION_FILE = "media/ttml/bitmap_unsupported_region.xml"; private static final String TEXT_ALIGN_FILE = "media/ttml/text_align.xml"; + private static final String MULTI_ROW_ALIGN_FILE = "media/ttml/multi_row_align.xml"; private static final String VERTICAL_TEXT_FILE = "media/ttml/vertical_text.xml"; private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml"; private static final String RUBIES_FILE = "media/ttml/rubies.xml"; @@ -607,6 +608,40 @@ public final class TtmlDecoderTest { Cue seventhCue = getOnlyCueAtTimeUs(subtitle, 70_000_000); assertThat(seventhCue.text.toString()).isEqualTo("No textAlign property"); assertThat(seventhCue.textAlignment).isNull(); + + Cue eighthCue = getOnlyCueAtTimeUs(subtitle, 80_000_000); + assertThat(eighthCue.text.toString()).isEqualTo("Ancestor start alignment"); + assertThat(eighthCue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL); + + Cue ninthCue = getOnlyCueAtTimeUs(subtitle, 90_000_000); + assertThat(ninthCue.text.toString()).isEqualTo("Not a P node"); + assertThat(ninthCue.textAlignment).isNull(); + } + + @Test + public void multiRowAlign() throws IOException, SubtitleDecoderException { + TtmlSubtitle subtitle = getSubtitle(MULTI_ROW_ALIGN_FILE); + + Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000); + assertThat(firstCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL); + + Cue secondCue = getOnlyCueAtTimeUs(subtitle, 20_000_000); + assertThat(secondCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER); + + Cue thirdCue = getOnlyCueAtTimeUs(subtitle, 30_000_000); + assertThat(thirdCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE); + + Cue fourthCue = getOnlyCueAtTimeUs(subtitle, 40_000_000); + assertThat(fourthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL); + + Cue fifthCue = getOnlyCueAtTimeUs(subtitle, 50_000_000); + assertThat(fifthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE); + + Cue sixthCue = getOnlyCueAtTimeUs(subtitle, 60_000_000); + assertThat(sixthCue.multiRowAlignment).isNull(); + + Cue seventhCue = getOnlyCueAtTimeUs(subtitle, 70_000_000); + assertThat(seventhCue.multiRowAlignment).isNull(); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java index 4583701cc3..972d7876e2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java @@ -66,6 +66,7 @@ public final class TtmlStyleTest { .setRubyType(RUBY_TYPE) .setRubyPosition(RUBY_POSITION) .setTextAlign(TEXT_ALIGN) + .setMultiRowAlign(Layout.Alignment.ALIGN_NORMAL) .setTextCombine(TEXT_COMBINE) .setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE)) .setShearPercentage(SHEAR_PERCENTAGE); @@ -85,6 +86,7 @@ public final class TtmlStyleTest { assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT); assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION); assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN); + assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL); assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE); assertWithMessage("rubyType should not be inherited") .that(style.getRubyType()) @@ -115,6 +117,7 @@ public final class TtmlStyleTest { assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT); assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION); assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN); + assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL); assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE); assertWithMessage("backgroundColor should be chained") .that(style.getBackgroundColor()) @@ -253,6 +256,18 @@ public final class TtmlStyleTest { assertThat(style.getTextAlign()).isNull(); } + @Test + public void multiRowAlign() { + TtmlStyle style = new TtmlStyle(); + assertThat(style.getMultiRowAlign()).isEqualTo(null); + style.setMultiRowAlign(Layout.Alignment.ALIGN_CENTER); + assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_CENTER); + style.setMultiRowAlign(Layout.Alignment.ALIGN_NORMAL); + assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL); + style.setMultiRowAlign(Layout.Alignment.ALIGN_OPPOSITE); + assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE); + } + @Test public void textCombine() { TtmlStyle style = new TtmlStyle(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java index aa6f420c3e..021694eb23 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeMediaChunk; @@ -32,6 +33,7 @@ import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection.AdaptationCheckpoint; import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition; import com.google.android.exoplayer2.upstream.BandwidthMeter; +import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.ArrayList; @@ -46,10 +48,7 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) public final class AdaptiveTrackSelectionTest { - private static final MediaChunkIterator[] THREE_EMPTY_MEDIA_CHUNK_ITERATORS = - new MediaChunkIterator[] { - MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY - }; + private static final long TEST_CHUNK_DURATION_US = 2_000_000; @Mock private BandwidthMeter mockBandwidthMeter; private FakeClock fakeClock; @@ -58,33 +57,53 @@ public final class AdaptiveTrackSelectionTest { public void setUp() { initMocks(this); fakeClock = new FakeClock(0); + when(mockBandwidthMeter.getTimeToFirstByteEstimateUs()).thenReturn(C.TIME_UNSET); } @Test - public void selectInitialIndexUseMaxInitialBitrateIfNoBandwidthEstimate() { + public void initial_updateSelectedTrack_selectsHighestBitrateWithinBandwidth() { Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240); Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480); Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720); TrackGroup trackGroup = new TrackGroup(format1, format2, format3); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L); - AdaptiveTrackSelection adaptiveTrackSelection = adaptiveTrackSelection(trackGroup); + AdaptiveTrackSelection adaptiveTrackSelection = + prepareAdaptiveTrackSelectionWithBandwidthFraction(trackGroup, /* bandwidthFraction= */ 1f); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL); } @Test - public void selectInitialIndexUseBandwidthEstimateIfAvailable() { + public void initial_updateSelectedTrack_selectsHighestBitrateWithinBandwidthFraction() { Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240); Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480); Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720); TrackGroup trackGroup = new TrackGroup(format1, format2, format3); - when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); - AdaptiveTrackSelection adaptiveTrackSelection = adaptiveTrackSelection(trackGroup); + when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(2000L); + AdaptiveTrackSelection adaptiveTrackSelection = + prepareAdaptiveTrackSelectionWithBandwidthFraction( + trackGroup, /* bandwidthFraction= */ 0.5f); - assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); + assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); + assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL); + } + + @Test + public void initial_updateSelectedTrack_selectsHighestBitrateWithinBandwidthAndTimeToFirstByte() { + Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240); + Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480); + Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720); + TrackGroup trackGroup = new TrackGroup(format1, format2, format3); + + when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(2000L); + when(mockBandwidthMeter.getTimeToFirstByteEstimateUs()).thenReturn(1_000_000L); + AdaptiveTrackSelection adaptiveTrackSelection = + prepareAdaptiveTrackSelectionWithBandwidthFraction(trackGroup, /* bandwidthFraction= */ 1f); + + assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL); } @@ -99,7 +118,7 @@ public final class AdaptiveTrackSelectionTest { // if possible. when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 2000L); AdaptiveTrackSelection adaptiveTrackSelection = - adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( + prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( trackGroup, /* minDurationForQualityIncreaseMs= */ 10_000); adaptiveTrackSelection.updateSelectedTrack( @@ -107,7 +126,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 9_999_000, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ Collections.emptyList(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); // When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate // format. However, since we only buffered 9_999_000 us, which is smaller than @@ -127,7 +146,7 @@ public final class AdaptiveTrackSelectionTest { // if possible. when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 2000L); AdaptiveTrackSelection adaptiveTrackSelection = - adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( + prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( trackGroup, /* minDurationForQualityIncreaseMs= */ 10_000); adaptiveTrackSelection.updateSelectedTrack( @@ -135,7 +154,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 10_000_000, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ Collections.emptyList(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); // When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate // format. When we have buffered enough (10_000_000 us, which is equal to @@ -155,7 +174,7 @@ public final class AdaptiveTrackSelectionTest { // if necessary. when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 500L); AdaptiveTrackSelection adaptiveTrackSelection = - adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( + prepareAdaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( trackGroup, /* maxDurationForQualityDecreaseMs= */ 25_000); adaptiveTrackSelection.updateSelectedTrack( @@ -163,7 +182,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 25_000_000, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ Collections.emptyList(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); // When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate // format. However, since we have enough buffer at higher quality (25_000_000 us, which is equal @@ -183,7 +202,7 @@ public final class AdaptiveTrackSelectionTest { // if necessary. when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 500L); AdaptiveTrackSelection adaptiveTrackSelection = - adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( + prepareAdaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( trackGroup, /* maxDurationForQualityDecreaseMs= */ 25_000); adaptiveTrackSelection.updateSelectedTrack( @@ -191,7 +210,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 24_999_000, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ Collections.emptyList(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); // When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate // format. When we don't have enough buffer at higher quality (24_999_000 us is smaller than @@ -219,7 +238,7 @@ public final class AdaptiveTrackSelectionTest { queue.add(chunk3); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); - AdaptiveTrackSelection adaptiveTrackSelection = adaptiveTrackSelection(trackGroup); + AdaptiveTrackSelection adaptiveTrackSelection = prepareAdaptiveTrackSelection(trackGroup); int size = adaptiveTrackSelection.evaluateQueueSize(0, queue); assertThat(size).isEqualTo(3); @@ -245,7 +264,7 @@ public final class AdaptiveTrackSelectionTest { when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); AdaptiveTrackSelection adaptiveTrackSelection = - adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( + prepareAdaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( trackGroup, /* durationToRetainAfterDiscardMs= */ 15_000); int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue); @@ -285,7 +304,7 @@ public final class AdaptiveTrackSelectionTest { when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); AdaptiveTrackSelection adaptiveTrackSelection = - adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( + prepareAdaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( trackGroup, /* durationToRetainAfterDiscardMs= */ 15_000); int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue); @@ -315,8 +334,9 @@ public final class AdaptiveTrackSelectionTest { /* bandwidthFraction= */ 1f) .createAdaptiveTrackSelection( trackGroup, - mockBandwidthMeter, /* tracks= */ new int[] {0, 1}, + /* type= */ TrackSelection.TYPE_UNSET, + mockBandwidthMeter, /* adaptationCheckpoints= */ ImmutableList.of()); // Make initial selection. @@ -343,7 +363,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 4_000_000, /* availableDurationUs= */ C.TIME_UNSET, queue, - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE); @@ -357,7 +377,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 4_000_000, /* availableDurationUs= */ C.TIME_UNSET, queue, - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL); @@ -369,7 +389,7 @@ public final class AdaptiveTrackSelectionTest { Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480); TrackGroup trackGroup = new TrackGroup(format1, format2); AdaptiveTrackSelection adaptiveTrackSelection = - prepareTrackSelection(adaptiveTrackSelection(trackGroup)); + prepareTrackSelection(prepareAdaptiveTrackSelection(trackGroup)); Format unknownFormat = videoFormat(/* bitrate= */ 42, /* width= */ 300, /* height= */ 123); FakeMediaChunk chunk = new FakeMediaChunk(unknownFormat, /* startTimeUs= */ 0, /* endTimeUs= */ 2_000_000); @@ -380,7 +400,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 2_000_000, /* availableDurationUs= */ C.TIME_UNSET, queue, - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isAnyOf(format1, format2); } @@ -403,7 +423,7 @@ public final class AdaptiveTrackSelectionTest { new AdaptationCheckpoint(/* totalBandwidth= */ 5000, /* allocatedBandwidth= */ 1300)); AdaptiveTrackSelection adaptiveTrackSelection = prepareTrackSelection( - adaptiveTrackSelectionWithAdaptationCheckpoints(trackGroup, checkpoints)); + prepareAdaptiveTrackSelectionWithAdaptationCheckpoints(trackGroup, checkpoints)); // Ensure format0 is selected initially so that we can assert the upswitches. when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1L); @@ -412,7 +432,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 999_999_999_999L, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ ImmutableList.of(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(999L); @@ -421,7 +441,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 999_999_999_999L, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ ImmutableList.of(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L); @@ -430,7 +450,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 999_999_999_999L, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ ImmutableList.of(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(2499L); @@ -439,7 +459,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 999_999_999_999L, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ ImmutableList.of(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(3500L); @@ -448,7 +468,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 999_999_999_999L, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ ImmutableList.of(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(8999L); @@ -457,7 +477,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 999_999_999_999L, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ ImmutableList.of(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(9000L); @@ -466,7 +486,7 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 999_999_999_999L, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ ImmutableList.of(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US)); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3); } @@ -491,12 +511,13 @@ public final class AdaptiveTrackSelectionTest { @Override protected AdaptiveTrackSelection createAdaptiveTrackSelection( TrackGroup group, - BandwidthMeter bandwidthMeter, int[] tracks, + int type, + BandwidthMeter bandwidthMeter, ImmutableList adaptationCheckpoints) { checkPoints.add(adaptationCheckpoints); return super.createAdaptiveTrackSelection( - group, bandwidthMeter, tracks, adaptationCheckpoints); + group, tracks, TrackSelection.TYPE_UNSET, bandwidthMeter, adaptationCheckpoints); } }; @@ -541,12 +562,13 @@ public final class AdaptiveTrackSelectionTest { @Override protected AdaptiveTrackSelection createAdaptiveTrackSelection( TrackGroup group, - BandwidthMeter bandwidthMeter, int[] tracks, + int type, + BandwidthMeter bandwidthMeter, ImmutableList adaptationCheckpoints) { checkPoints.add(adaptationCheckpoints); return super.createAdaptiveTrackSelection( - group, bandwidthMeter, tracks, adaptationCheckpoints); + group, tracks, TrackSelection.TYPE_UNSET, bandwidthMeter, adaptationCheckpoints); } }; @@ -578,17 +600,35 @@ public final class AdaptiveTrackSelectionTest { .inOrder(); } - private AdaptiveTrackSelection adaptiveTrackSelection(TrackGroup trackGroup) { - return adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( + private AdaptiveTrackSelection prepareAdaptiveTrackSelection(TrackGroup trackGroup) { + return prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( trackGroup, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS); } - private AdaptiveTrackSelection adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( + private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithBandwidthFraction( + TrackGroup trackGroup, float bandwidthFraction) { + return prepareTrackSelection( + new AdaptiveTrackSelection( + trackGroup, + selectedAllTracksInGroup(trackGroup), + TrackSelection.TYPE_UNSET, + mockBandwidthMeter, + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, + AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, + bandwidthFraction, + AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, + /* adaptationCheckpoints= */ ImmutableList.of(), + fakeClock)); + } + + private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( TrackGroup trackGroup, long minDurationForQualityIncreaseMs) { return prepareTrackSelection( new AdaptiveTrackSelection( trackGroup, selectedAllTracksInGroup(trackGroup), + TrackSelection.TYPE_UNSET, mockBandwidthMeter, minDurationForQualityIncreaseMs, AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, @@ -599,12 +639,13 @@ public final class AdaptiveTrackSelectionTest { fakeClock)); } - private AdaptiveTrackSelection adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( + private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( TrackGroup trackGroup, long maxDurationForQualityDecreaseMs) { return prepareTrackSelection( new AdaptiveTrackSelection( trackGroup, selectedAllTracksInGroup(trackGroup), + TrackSelection.TYPE_UNSET, mockBandwidthMeter, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, maxDurationForQualityDecreaseMs, @@ -615,12 +656,14 @@ public final class AdaptiveTrackSelectionTest { fakeClock)); } - private AdaptiveTrackSelection adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( - TrackGroup trackGroup, long durationToRetainAfterDiscardMs) { + private AdaptiveTrackSelection + prepareAdaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( + TrackGroup trackGroup, long durationToRetainAfterDiscardMs) { return prepareTrackSelection( new AdaptiveTrackSelection( trackGroup, selectedAllTracksInGroup(trackGroup), + TrackSelection.TYPE_UNSET, mockBandwidthMeter, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, @@ -631,12 +674,13 @@ public final class AdaptiveTrackSelectionTest { fakeClock)); } - private AdaptiveTrackSelection adaptiveTrackSelectionWithAdaptationCheckpoints( + private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithAdaptationCheckpoints( TrackGroup trackGroup, List adaptationCheckpoints) { return prepareTrackSelection( new AdaptiveTrackSelection( trackGroup, selectedAllTracksInGroup(trackGroup), + TrackSelection.TYPE_UNSET, mockBandwidthMeter, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, @@ -655,10 +699,35 @@ public final class AdaptiveTrackSelectionTest { /* bufferedDurationUs= */ 0, /* availableDurationUs= */ C.TIME_UNSET, /* queue= */ Collections.emptyList(), - /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); + createMediaChunkIterators(adaptiveTrackSelection.getTrackGroup(), TEST_CHUNK_DURATION_US)); return adaptiveTrackSelection; } + private MediaChunkIterator[] createMediaChunkIterators( + TrackGroup trackGroup, long chunkDurationUs) { + MediaChunkIterator[] iterators = new MediaChunkIterator[trackGroup.length]; + for (int i = 0; i < trackGroup.length; i++) { + iterators[i] = + new BaseMediaChunkIterator(/* fromIndex= */ 0, /* toIndex= */ 0) { + @Override + public DataSpec getDataSpec() { + return new DataSpec.Builder().setUri("https://test.example").build(); + } + + @Override + public long getChunkStartTimeUs() { + return 123_456_789; + } + + @Override + public long getChunkEndTimeUs() { + return 123_456_789 + chunkDurationUs; + } + }; + } + return iterators; + } + private int[] selectedAllTracksInGroup(TrackGroup trackGroup) { int[] listIndices = new int[trackGroup.length]; for (int i = 0; i < trackGroup.length; i++) { 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 9aebfb7718..29f3e69001 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 @@ -88,6 +88,7 @@ public final class DefaultTrackSelectorTest { .setSampleMimeType(MimeTypes.VIDEO_H264) .setWidth(1024) .setHeight(768) + .setAverageBitrate(450000) .build(); private static final Format AUDIO_FORMAT = new Format.Builder() @@ -1496,6 +1497,21 @@ public final class DefaultTrackSelectorTest { assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 1, 2); } + @Test + public void selectTracks_multipleVideoTracksWithoutBitrate_onlySelectsSingleTrack() + throws Exception { + TrackGroupArray trackGroups = + singleTrackGroup( + VIDEO_FORMAT.buildUpon().setId("0").setAverageBitrate(Format.NO_VALUE).build(), + VIDEO_FORMAT.buildUpon().setId("1").setAverageBitrate(Format.NO_VALUE).build()); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections[0], trackGroups.get(0), /* expectedTrack= */ 0); + } + @Test public void selectTracks_multipleVideoAndAudioTracks() throws Exception { Format videoFormat1 = VIDEO_FORMAT.buildUpon().setAverageBitrate(1000).build(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java index 1aa2198fc5..911c9d5c71 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceContractTest.java @@ -54,4 +54,19 @@ public class ByteArrayDataSourceContractTest extends DataSourceContractTest { @Test @Ignore public void resourceNotFound() {} + + @Override + @Test + @Ignore + public void resourceNotFound_transferListenerCallbacks() {} + + @Override + @Test + @Ignore + public void getUri_resourceNotFound_returnsNullIfNotOpened() throws Exception {} + + @Override + @Test + @Ignore + public void getResponseHeaders_resourceNotFound_isEmptyWhileNotOpen() throws Exception {} } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java index 564973f51c..21c942974a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java @@ -74,25 +74,17 @@ public final class ByteArrayDataSourceTest { // And with bound. readTestData(TEST_DATA, 1, 6, 1, 0, 1, false); // Read from the last possible offset without bound. - readTestData(TEST_DATA, TEST_DATA.length - 1, C.LENGTH_UNSET, 1, 0, 1, false); + readTestData(TEST_DATA, TEST_DATA.length, C.LENGTH_UNSET, 1, 0, 1, false); // And with bound. - readTestData(TEST_DATA, TEST_DATA.length - 1, 1, 1, 0, 1, false); + readTestData(TEST_DATA, TEST_DATA.length, 1, 1, 0, 1, false); } @Test public void readFromInvalidOffsets() { // Read from first invalid offset and check failure without bound. - readTestData(TEST_DATA, TEST_DATA.length, C.LENGTH_UNSET, 1, 0, 1, true); + readTestData(TEST_DATA, TEST_DATA.length + 1, C.LENGTH_UNSET, 1, 0, 1, true); // And with bound. - readTestData(TEST_DATA, TEST_DATA.length, 1, 1, 0, 1, true); - } - - @Test - public void readWithInvalidLength() { - // Read more data than is available. - readTestData(TEST_DATA, 0, TEST_DATA.length + 1, 1, 0, 1, true); - // And with bound. - readTestData(TEST_DATA, 1, TEST_DATA.length, 1, 0, 1, true); + readTestData(TEST_DATA, TEST_DATA.length + 1, 1, 1, 0, 1, true); } /** @@ -108,8 +100,10 @@ public final class ByteArrayDataSourceTest { */ private void readTestData(byte[] testData, int dataOffset, int dataLength, int outputBufferLength, int writeOffset, int maxReadLength, boolean expectFailOnOpen) { - int expectedFinalBytesRead = - dataLength == C.LENGTH_UNSET ? (testData.length - dataOffset) : dataLength; + int expectedFinalBytesRead = testData.length - dataOffset; + if (dataLength != C.LENGTH_UNSET) { + expectedFinalBytesRead = min(expectedFinalBytesRead, dataLength); + } ByteArrayDataSource dataSource = new ByteArrayDataSource(testData); boolean opened = false; try { @@ -119,7 +113,8 @@ public final class ByteArrayDataSourceTest { assertThat(expectFailOnOpen).isFalse(); // Verify the resolved length is as we expect. - assertThat(length).isEqualTo(expectedFinalBytesRead); + assertThat(length) + .isEqualTo(dataLength != C.LENGTH_UNSET ? dataLength : expectedFinalBytesRead); byte[] outputBuffer = new byte[outputBufferLength]; int accumulatedBytesRead = 0; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java index b75ff45f13..360bc37c59 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java @@ -16,9 +16,12 @@ package com.google.android.exoplayer2.upstream; import android.net.Uri; +import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.testutil.DataSourceContractTest; +import com.google.android.exoplayer2.testutil.FakeDataSet; +import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; @@ -27,27 +30,36 @@ import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import org.junit.Before; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; /** {@link DataSource} contract tests for {@link CacheDataSource}. */ @RunWith(AndroidJUnit4.class) public class CacheDataSourceContractTest extends DataSourceContractTest { - private static final byte[] DATA = TestUtil.buildTestData(20); - - @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); private Uri simpleUri; + private Uri unknownLengthUri; + private byte[] simpleData; + private byte[] unknownLengthData; + private FakeDataSet fakeDataSet; + + private FakeDataSource upstreamDataSource; @Before public void setUp() throws IOException { - File file = tempFolder.newFile(); - Files.write(Paths.get(file.getAbsolutePath()), DATA); - simpleUri = Uri.fromFile(file); + simpleUri = Uri.parse("test://simple.test"); + unknownLengthUri = Uri.parse("test://unknown-length.test"); + simpleData = TestUtil.buildTestData(/* length= */ 20); + unknownLengthData = TestUtil.buildTestData(/* length= */ 40); + fakeDataSet = + new FakeDataSet() + .newData(simpleUri) + .appendReadData(simpleData) + .endData() + .newData(unknownLengthUri) + .setSimulateUnknownLength(true) + .appendReadData(unknownLengthData) + .endData(); } @Override @@ -56,13 +68,18 @@ public class CacheDataSourceContractTest extends DataSourceContractTest { new TestResource.Builder() .setName("simple") .setUri(simpleUri) - .setExpectedBytes(DATA) + .setExpectedBytes(simpleData) + .build(), + new TestResource.Builder() + .setName("unknown length") + .setUri(unknownLengthUri) + .setExpectedBytes(unknownLengthData) .build()); } @Override protected Uri getNotFoundUri() { - return Uri.fromFile(tempFolder.getRoot().toPath().resolve("nonexistent").toFile()); + return Uri.parse("test://not-found.test"); } @Override @@ -71,6 +88,13 @@ public class CacheDataSourceContractTest extends DataSourceContractTest { Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest"); SimpleCache cache = new SimpleCache(tempFolder, new NoOpCacheEvictor(), TestUtil.getInMemoryDatabaseProvider()); - return new CacheDataSource(cache, new FileDataSource()); + upstreamDataSource = new FakeDataSource(fakeDataSet); + return new CacheDataSource(cache, upstreamDataSource); + } + + @Override + @Nullable + protected DataSource getTransferListenerDataSource() { + return upstreamDataSource; } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java index 97bd701865..861fe358f6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceContractTest.java @@ -39,12 +39,12 @@ public class DataSchemeDataSourceContractTest extends DataSourceContractTest { return ImmutableList.of( new TestResource.Builder() .setName("plain text") - .setUri(Uri.parse("data:text/plain," + DATA)) + .setUri("data:text/plain," + DATA) .setExpectedBytes(DATA.getBytes(UTF_8)) .build(), new TestResource.Builder() .setName("base64 encoded text") - .setUri(Uri.parse("data:text/plain;base64," + BASE64_ENCODED_DATA)) + .setUri("data:text/plain;base64," + BASE64_ENCODED_DATA) .setExpectedBytes(Base64.decode(BASE64_ENCODED_DATA, Base64.DEFAULT)) .build()); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java index 7a99b97bd5..119d473e7d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java @@ -107,18 +107,6 @@ public final class DataSchemeDataSourceTest { } } - @Test - public void rangeExceedingResourceLengthRequest() throws IOException { - try { - // Try to open a range exceeding the resource's length. - schemeDataDataSource.open( - buildDataSpec(DATA_SCHEME_URI, /* position= */ 97, /* length= */ 11)); - fail(); - } catch (DataSourceException e) { - assertThat(e.reason).isEqualTo(DataSourceException.POSITION_OUT_OF_RANGE); - } - } - @Test public void incorrectScheme() { try { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java index 0b807c487a..e4c5c315f1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java @@ -20,6 +20,7 @@ import static android.net.NetworkInfo.State.DISCONNECTED; import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; @@ -30,11 +31,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeDataSource; +import com.google.android.exoplayer2.util.NetworkTypeObserver; import java.util.Random; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowNetworkInfo; /** Unit test for {@link DefaultBandwidthMeter}. */ @@ -42,7 +46,7 @@ import org.robolectric.shadows.ShadowNetworkInfo; public final class DefaultBandwidthMeterTest { private static final int SIMULATED_TRANSFER_COUNT = 100; - private static final String FAST_COUNTRY_ISO = "EE"; + private static final String FAST_COUNTRY_ISO = "TW"; private static final String SLOW_COUNTRY_ISO = "PG"; private TelephonyManager telephonyManager; @@ -52,10 +56,14 @@ public final class DefaultBandwidthMeterTest { private NetworkInfo networkInfo2g; private NetworkInfo networkInfo3g; private NetworkInfo networkInfo4g; + // TODO: Add tests covering 5G-NSA networks. Not testable right now because we need to set the + // TelephonyDisplayInfo on API 31, which isn't available for Robolectric yet. + private NetworkInfo networkInfo5gSa; private NetworkInfo networkInfoEthernet; @Before public void setUp() { + NetworkTypeObserver.resetForTests(); connectivityManager = (ConnectivityManager) ApplicationProvider.getApplicationContext() @@ -99,6 +107,13 @@ public final class DefaultBandwidthMeterTest { TelephonyManager.NETWORK_TYPE_LTE, /* isAvailable= */ true, CONNECTED); + networkInfo5gSa = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_NR, + /* isAvailable= */ true, + CONNECTED); networkInfoEthernet = ShadowNetworkInfo.newInstance( DetailedState.CONNECTED, @@ -213,6 +228,22 @@ public final class DefaultBandwidthMeterTest { assertThat(initialEstimate3g).isGreaterThan(initialEstimate2g); } + @Config(sdk = Config.NEWEST_SDK) // TODO: Remove once targetSDK >= 29 + @Test + public void defaultInitialBitrateEstimate_for5gSa_isGreaterThanEstimateFor4g() { + setActiveNetworkInfo(networkInfo4g); + DefaultBandwidthMeter bandwidthMeter4g = + new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo5gSa); + DefaultBandwidthMeter bandwidthMeter5gSa = + new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate5gSa = bandwidthMeter5gSa.getBitrateEstimate(); + + assertThat(initialEstimate5gSa).isGreaterThan(initialEstimate4g); + } + @Test public void defaultInitialBitrateEstimate_forOffline_isReasonable() { setActiveNetworkInfo(networkInfoOffline); @@ -309,6 +340,24 @@ public final class DefaultBandwidthMeterTest { assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); } + @Config(sdk = Config.NEWEST_SDK) // TODO: Remove once targetSDK >= 29 + @Test + public void + defaultInitialBitrateEstimate_for5gSa_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo5gSa); + setNetworkCountryIso(FAST_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + @Test public void initialBitrateEstimateOverwrite_whileConnectedToNetwork_setsInitialEstimate() { setActiveNetworkInfo(networkInfoWifi); @@ -459,6 +508,33 @@ public final class DefaultBandwidthMeterTest { assertThat(initialEstimate).isNotEqualTo(123456789); } + @Config(sdk = Config.NEWEST_SDK) // TODO: Remove once targetSDK >= 29 + @Test + public void initialBitrateEstimateOverwrite_for5gSa_whileConnectedTo5gSa_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo5gSa); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_5G_SA, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Config(sdk = Config.NEWEST_SDK) // TODO: Remove once targetSDK >= 29 + @Test + public void + initialBitrateEstimateOverwrite_for5gSa_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_5G_SA, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + @Test public void initialBitrateEstimateOverwrite_forOffline_whileOffline_setsInitialEstimate() { setActiveNetworkInfo(networkInfoOffline); @@ -559,8 +635,12 @@ public final class DefaultBandwidthMeterTest { assertThat(initialEstimateWithoutBuilder).isLessThan(50_000_000L); } + @SuppressWarnings("StickyBroadcast") private void setActiveNetworkInfo(NetworkInfo networkInfo) { Shadows.shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo); + ApplicationProvider.getApplicationContext() + .sendStickyBroadcast(new Intent(ConnectivityManager.CONNECTIVITY_ACTION)); + ShadowLooper.idleMainLooper(); } private void setNetworkCountryIso(String countryIso) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java new file mode 100644 index 0000000000..1c550497c4 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ResolvingDataSourceContractTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import android.net.Uri; +import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.testutil.DataSourceContractTest; +import com.google.android.exoplayer2.testutil.FakeDataSet; +import com.google.android.exoplayer2.testutil.FakeDataSource; +import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.upstream.ResolvingDataSource.Resolver; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import org.junit.Before; +import org.junit.runner.RunWith; + +/** {@link DataSource} contract tests for {@link ResolvingDataSourceContractTest}. */ +@RunWith(AndroidJUnit4.class) +public class ResolvingDataSourceContractTest extends DataSourceContractTest { + + private static final String URI = "test://simple.test"; + private static final String RESOLVED_URI = "resolved://simple.resolved"; + + private byte[] simpleData; + private FakeDataSet fakeDataSet; + private FakeDataSource fakeDataSource; + + @Before + public void setUp() { + simpleData = TestUtil.buildTestData(/* length= */ 20); + fakeDataSet = new FakeDataSet().newData(RESOLVED_URI).appendReadData(simpleData).endData(); + } + + @Override + protected ImmutableList getTestResources() { + return ImmutableList.of( + new TestResource.Builder() + .setName("simple") + .setUri(URI) + .setExpectedBytes(simpleData) + .build()); + } + + @Override + protected Uri getNotFoundUri() { + return Uri.parse("test://not-found.test"); + } + + @Override + protected DataSource createDataSource() { + fakeDataSource = new FakeDataSource(fakeDataSet); + return new ResolvingDataSource( + fakeDataSource, + new Resolver() { + @Override + public DataSpec resolveDataSpec(DataSpec dataSpec) throws IOException { + return URI.equals(dataSpec.uri.toString()) + ? dataSpec.buildUpon().setUri(RESOLVED_URI).build() + : dataSpec; + } + }); + } + + @Override + @Nullable + protected DataSource getTransferListenerDataSource() { + return fakeDataSource; + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java index c8dd082e67..4b2115da50 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/UdpDataSourceContractTest.java @@ -53,14 +53,18 @@ public class UdpDataSourceContractTest extends DataSourceContractTest { return udpDataSource; } + @Override + protected boolean unboundedReadsAreIndefinite() { + return true; + } + @Override protected ImmutableList getTestResources() { return ImmutableList.of( new TestResource.Builder() .setName("local-udp-unicast-socket") - .setUri(Uri.parse("udp://localhost:" + findFreeUdpPort())) + .setUri("udp://localhost:" + findFreeUdpPort()) .setExpectedBytes(data) - .setEndOfInputExpected(false) .build()); } @@ -84,6 +88,26 @@ public class UdpDataSourceContractTest extends DataSourceContractTest { @Override public void dataSpecWithPositionAndLength_readExpectedRange() {} + @Test + @Ignore("UdpDataSource doesn't support DataSpec's position or length [internal: b/175856954]") + @Override + public void dataSpecWithPositionAtEnd_throwsPositionOutOfRangeException() {} + + @Test + @Ignore("UdpDataSource doesn't support DataSpec's position or length [internal: b/175856954]") + @Override + public void dataSpecWithPositionAtEndAndLength_throwsPositionOutOfRangeException() {} + + @Test + @Ignore("UdpDataSource doesn't support DataSpec's position or length [internal: b/175856954]") + @Override + public void dataSpecWithPositionOutOfRange_throwsPositionOutOfRangeException() {} + + @Test + @Ignore("UdpDataSource doesn't support DataSpec's position or length [internal: b/175856954]") + @Override + public void dataSpecWithEndPositionOutOfRange_readsToEnd() {} + /** * Finds a free UDP port in the range of unreserved ports 50000-60000 that can be used from the * test or throws an {@link IllegalStateException} if no port is available. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 5ee8e423b7..705188f88e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -154,7 +154,7 @@ public final class CacheDataSourceTest { try { cacheDataSource = createCacheDataSource(/* setReadException= */ false, /* unknownLength= */ false); - cacheDataSource.open(buildDataSpec(TEST_DATA.length, /* length= */ 1, defaultCacheKey)); + cacheDataSource.open(buildDataSpec(TEST_DATA.length + 1, /* length= */ 1, defaultCacheKey)); fail(); } catch (IOException e) { // Expected. @@ -204,7 +204,7 @@ public final class CacheDataSourceTest { cacheDataSource = createCacheDataSource( /* setReadException= */ false, /* unknownLength= */ false, cacheKeyFactory); - cacheDataSource.open(buildDataSpec(TEST_DATA.length, /* length= */ 1, customCacheKey)); + cacheDataSource.open(buildDataSpec(TEST_DATA.length + 1, /* length= */ 1, customCacheKey)); fail(); } catch (IOException e) { // Expected. @@ -257,7 +257,7 @@ public final class CacheDataSourceTest { cacheDataSource = createCacheDataSource( /* setReadException= */ false, /* unknownLength= */ false, cacheKeyFactory); - cacheDataSource.open(buildDataSpec(TEST_DATA.length, /* length= */ 1, customCacheKey)); + cacheDataSource.open(buildDataSpec(TEST_DATA.length + 1, /* length= */ 1, customCacheKey)); fail(); } catch (IOException e) { // Expected. @@ -265,30 +265,31 @@ public final class CacheDataSourceTest { } @Test - public void contentLengthEdgeCases() throws Exception { - DataSpec dataSpec = buildDataSpec(TEST_DATA.length - 2, 2); + public void boundedRead_doesNotSetContentLength() throws Exception { + DataSpec dataSpec = buildDataSpec(0, TEST_DATA.length); - // Read partial at EOS but don't cross it so length is unknown. + // Read up to the end of the data, but since the DataSpec is bounded, the read doesn't see the + // EOS, and so the content length remains unknown. CacheDataSource cacheDataSource = createCacheDataSource(false, true); + assertReadData(cacheDataSource, dataSpec, true); assertThat(ContentMetadata.getContentLength(cache.getContentMetadata(defaultCacheKey))) .isEqualTo(C.LENGTH_UNSET); + } - // Now do an unbounded request for whole data. This will cause a bounded request from upstream. - // End of data from upstream shouldn't be mixed up with EOS and cause length set wrong. - cacheDataSource = createCacheDataSource(false, true); + @Test + public void unboundedRead_setsContentLength() throws IOException { + // Perform an unbounded request for the whole data. This should cause the content length to + // become known. + CacheDataSource cacheDataSource = createCacheDataSource(false, true); assertReadDataContentLength(cacheDataSource, unboundedDataSpec, true, false); - // Now the length set correctly do an unbounded request with offset. + // Check the correct length is returned for an unbounded request. assertThat( cacheDataSource.open( buildDataSpec(TEST_DATA.length - 2, C.LENGTH_UNSET, defaultCacheKey))) .isEqualTo(2); - - // An unbounded request with offset for not cached content. - dataSpec = - new DataSpec(Uri.parse("https://www.test.com/other"), TEST_DATA.length - 2, C.LENGTH_UNSET); - assertThat(cacheDataSource.open(dataSpec)).isEqualTo(C.LENGTH_UNSET); + cacheDataSource.close(); } @Test @@ -363,7 +364,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream2), unboundedDataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); @@ -413,7 +413,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream2), unboundedDataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); @@ -438,7 +437,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); @@ -475,7 +473,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java index 4c2fd3f765..b094f332de 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java @@ -23,14 +23,16 @@ import android.net.Uri; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.testutil.FailOnCloseDataSink; import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.TestUtil; -import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -69,7 +71,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(Uri.parse("test_data")), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -91,7 +92,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -103,7 +103,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(testUri), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -126,7 +125,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -150,7 +148,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -162,7 +159,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(testUri), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -184,7 +180,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ true, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -194,25 +189,46 @@ public final class CacheWriterTest { } @Test - public void cacheThrowEOFException() throws Exception { + public void cache_afterFailureOnClose_succeeds() throws Exception { FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100); - FakeDataSource dataSource = new FakeDataSource(fakeDataSet); + FakeDataSource upstreamDataSource = new FakeDataSource(fakeDataSet); - Uri testUri = Uri.parse("test_data"); - DataSpec dataSpec = new DataSpec(testUri, /* position= */ 0, /* length= */ 1000); + AtomicBoolean failOnClose = new AtomicBoolean(/* initialValue= */ true); + FailOnCloseDataSink dataSink = new FailOnCloseDataSink(cache, failOnClose); - IOException exception = - assertThrows( - IOException.class, - () -> - new CacheWriter( - new CacheDataSource(cache, dataSource), - dataSpec, - /* allowShortContent= */ false, - /* temporaryBuffer= */ null, - /* progressListener= */ null) - .cache()); - assertThat(DataSourceException.isCausedByPositionOutOfRange(exception)).isTrue(); + CacheDataSource cacheDataSource = + new CacheDataSource( + cache, + upstreamDataSource, + new FileDataSource(), + dataSink, + /* flags= */ 0, + /* eventListener= */ null); + + CachingCounters counters = new CachingCounters(); + + CacheWriter cacheWriter = + new CacheWriter( + cacheDataSource, + new DataSpec(Uri.parse("test_data")), + /* temporaryBuffer= */ null, + counters); + + // DataSink.close failing must cause the operation to fail rather than succeed. + assertThrows(IOException.class, cacheWriter::cache); + // Since all of the bytes were read through the DataSource chain successfully before the sink + // was closed, the progress listener will have seen all of the bytes being cached, even though + // this may not really be the case. + counters.assertValues( + /* bytesAlreadyCached= */ 0, /* bytesNewlyCached= */ 100, /* contentLength= */ 100); + + failOnClose.set(false); + + // The bytes will be downloaded again, but cached successfully this time. + cacheWriter.cache(); + counters.assertValues( + /* bytesAlreadyCached= */ 0, /* bytesNewlyCached= */ 100, /* contentLength= */ 100); + assertCachedData(cache, fakeDataSet); } @Test @@ -233,7 +249,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(Uri.parse("test_data")), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/MutableFlagsTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/MutableFlagsTest.java deleted file mode 100644 index 1e68f80476..0000000000 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/MutableFlagsTest.java +++ /dev/null @@ -1,140 +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.util; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.util.ArrayList; -import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Unit test for {@link MutableFlags}. */ -@RunWith(AndroidJUnit4.class) -public final class MutableFlagsTest { - - @Test - public void contains_withoutAdd_returnsFalseForAllValues() { - MutableFlags flags = new MutableFlags(); - - assertThat(flags.contains(/* flag= */ -1234)).isFalse(); - assertThat(flags.contains(/* flag= */ 0)).isFalse(); - assertThat(flags.contains(/* flag= */ 2)).isFalse(); - assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isFalse(); - } - - @Test - public void contains_afterAdd_returnsTrueForAddedValues() { - MutableFlags flags = new MutableFlags(); - - flags.add(/* flag= */ -1234); - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 2); - flags.add(/* flag= */ Integer.MAX_VALUE); - - assertThat(flags.contains(/* flag= */ -1235)).isFalse(); - assertThat(flags.contains(/* flag= */ -1234)).isTrue(); - assertThat(flags.contains(/* flag= */ 0)).isTrue(); - assertThat(flags.contains(/* flag= */ 1)).isFalse(); - assertThat(flags.contains(/* flag= */ 2)).isTrue(); - assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE - 1)).isFalse(); - assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isTrue(); - } - - @Test - public void contains_afterClear_returnsFalseForAllValues() { - MutableFlags flags = new MutableFlags(); - flags.add(/* flag= */ -1234); - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 2); - flags.add(/* flag= */ Integer.MAX_VALUE); - - flags.clear(); - - assertThat(flags.contains(/* flag= */ -1234)).isFalse(); - assertThat(flags.contains(/* flag= */ 0)).isFalse(); - assertThat(flags.contains(/* flag= */ 2)).isFalse(); - assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isFalse(); - } - - @Test - public void size_withoutAdd_returnsZero() { - MutableFlags flags = new MutableFlags(); - - assertThat(flags.size()).isEqualTo(0); - } - - @Test - public void size_afterAdd_returnsNumberUniqueOfElements() { - MutableFlags flags = new MutableFlags(); - - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 123); - flags.add(/* flag= */ 123); - - assertThat(flags.size()).isEqualTo(2); - } - - @Test - public void size_afterClear_returnsZero() { - MutableFlags flags = new MutableFlags(); - - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 123); - flags.clear(); - - assertThat(flags.size()).isEqualTo(0); - } - - @Test - public void get_withNegativeIndex_throwsIllegalArgumentException() { - MutableFlags flags = new MutableFlags(); - - assertThrows(IllegalArgumentException.class, () -> flags.get(/* index= */ -1)); - } - - @Test - public void get_withIndexExceedingSize_throwsIllegalArgumentException() { - MutableFlags flags = new MutableFlags(); - - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 123); - - assertThrows(IllegalArgumentException.class, () -> flags.get(/* index= */ 2)); - } - - @Test - public void get_afterAdd_returnsAllUniqueValues() { - MutableFlags flags = new MutableFlags(); - - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 0); - flags.add(/* flag= */ 123); - flags.add(/* flag= */ 123); - flags.add(/* flag= */ 456); - - List values = new ArrayList<>(); - for (int i = 0; i < flags.size(); i++) { - values.add(flags.get(i)); - } - assertThat(values).containsExactly(0, 123, 456); - } -} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java index 848b0ce410..cf37eed42f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java @@ -17,7 +17,8 @@ package com.google.android.exoplayer2.video; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -67,11 +68,13 @@ public final class DecoderVideoRendererTest { .setHeight(1080) .build(); + private Surface surface; private DecoderVideoRenderer renderer; @Mock private VideoRendererEventListener eventListener; @Before public void setUp() { + surface = new Surface(new SurfaceTexture(/* texName= */ 0)); renderer = new DecoderVideoRenderer( /* allowedJoiningTimeMs= */ 0, @@ -168,17 +171,18 @@ public final class DecoderVideoRendererTest { }; } }; - renderer.setOutputSurface(new Surface(new SurfaceTexture(/* texName= */ 0))); + renderer.setOutput(surface); } @After - public void shutDown() throws Exception { + public void shutDown() { if (renderer.getState() == Renderer.STATE_STARTED) { renderer.stop(); } if (renderer.getState() == Renderer.STATE_ENABLED) { renderer.disable(); } + surface.release(); } @Test @@ -208,7 +212,7 @@ public final class DecoderVideoRendererTest { ShadowLooper.idleMainLooper(); } - verify(eventListener).onRenderedFirstFrame(any()); + verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } @Test @@ -239,7 +243,7 @@ public final class DecoderVideoRendererTest { ShadowLooper.idleMainLooper(); } - verify(eventListener, never()).onRenderedFirstFrame(any()); + verify(eventListener, never()).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } @Test @@ -270,7 +274,7 @@ public final class DecoderVideoRendererTest { ShadowLooper.idleMainLooper(); } - verify(eventListener).onRenderedFirstFrame(any()); + verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } // TODO: Fix rendering of first frame at stream transition. @@ -322,7 +326,8 @@ public final class DecoderVideoRendererTest { ShadowLooper.idleMainLooper(); } - verify(eventListener, times(2)).onRenderedFirstFrame(any()); + verify(eventListener, times(2)) + .onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } // TODO: Fix rendering of first frame at stream transition. @@ -373,11 +378,12 @@ public final class DecoderVideoRendererTest { ShadowLooper.idleMainLooper(); } - verify(eventListener).onRenderedFirstFrame(any()); + verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); // Render to streamOffsetUs and verify the new first frame gets rendered. renderer.render(/* positionUs= */ 100, SystemClock.elapsedRealtime() * 1000); - verify(eventListener, times(2)).onRenderedFirstFrame(any()); + verify(eventListener, times(2)) + .onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java index ccc4e89d58..58b9424bb6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java @@ -19,11 +19,8 @@ import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSample import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.format; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -52,11 +49,13 @@ import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.Collections; +import java.util.stream.Collectors; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InOrder; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -75,6 +74,7 @@ public class MediaCodecVideoRendererTest { .build(); private Looper testMainLooper; + private Surface surface; private MediaCodecVideoRenderer mediaCodecVideoRenderer; @Nullable private Format currentOutputFormat; @@ -118,8 +118,13 @@ public class MediaCodecVideoRendererTest { } }; - mediaCodecVideoRenderer.handleMessage( - Renderer.MSG_SET_SURFACE, new Surface(new SurfaceTexture(/* texName= */ 0))); + surface = new Surface(new SurfaceTexture(/* texName= */ 0)); + mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface); + } + + @After + public void cleanUp() { + surface.release(); } @Test @@ -194,10 +199,11 @@ public class MediaCodecVideoRendererTest { verify(eventListener) .onVideoSizeChanged( - VIDEO_H264.width, - VIDEO_H264.height, - VIDEO_H264.rotationDegrees, - VIDEO_H264.pixelWidthHeightRatio); + new VideoSize( + VIDEO_H264.width, + VIDEO_H264.height, + VIDEO_H264.rotationDegrees, + VIDEO_H264.pixelWidthHeightRatio)); } @Test @@ -250,11 +256,13 @@ public class MediaCodecVideoRendererTest { } while (!mediaCodecVideoRenderer.isEnded()); shadowOf(testMainLooper).idle(); - InOrder orderVerifier = inOrder(eventListener); - orderVerifier.verify(eventListener).onVideoSizeChanged(anyInt(), anyInt(), anyInt(), eq(1f)); - orderVerifier.verify(eventListener).onVideoSizeChanged(anyInt(), anyInt(), anyInt(), eq(2f)); - orderVerifier.verify(eventListener).onVideoSizeChanged(anyInt(), anyInt(), anyInt(), eq(3f)); - orderVerifier.verifyNoMoreInteractions(); + ArgumentCaptor videoSizesCaptor = ArgumentCaptor.forClass(VideoSize.class); + verify(eventListener, times(3)).onVideoSizeChanged(videoSizesCaptor.capture()); + assertThat( + videoSizesCaptor.getAllValues().stream() + .map(videoSize -> videoSize.pixelWidthHeightRatio) + .collect(Collectors.toList())) + .containsExactly(1f, 2f, 3f); } @Test @@ -323,7 +331,7 @@ public class MediaCodecVideoRendererTest { } shadowOf(testMainLooper).idle(); - verify(eventListener).onRenderedFirstFrame(any()); + verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } @Test @@ -353,7 +361,7 @@ public class MediaCodecVideoRendererTest { } shadowOf(testMainLooper).idle(); - verify(eventListener, never()).onRenderedFirstFrame(any()); + verify(eventListener, never()).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } @Test @@ -383,7 +391,7 @@ public class MediaCodecVideoRendererTest { } shadowOf(testMainLooper).idle(); - verify(eventListener).onRenderedFirstFrame(any()); + verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } @Test @@ -437,7 +445,8 @@ public class MediaCodecVideoRendererTest { // Expect only the first frame of the first stream to have been rendered. shadowLooper.idle(); - verify(eventListener, times(2)).onRenderedFirstFrame(any()); + verify(eventListener, times(2)) + .onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } @Test @@ -488,12 +497,13 @@ public class MediaCodecVideoRendererTest { } shadowLooper.idle(); - verify(eventListener).onRenderedFirstFrame(any()); + verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); // Render to streamOffsetUs and verify the new first frame gets rendered. mediaCodecVideoRenderer.render(/* positionUs= */ 100, SystemClock.elapsedRealtime() * 1000); shadowLooper.idle(); - verify(eventListener, times(2)).onRenderedFirstFrame(any()); + verify(eventListener, times(2)) + .onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong()); } } diff --git a/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/TouchTrackerTest.java similarity index 98% rename from library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/video/spherical/TouchTrackerTest.java index 9537c36303..a4965bf92e 100644 --- a/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/TouchTrackerTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.video.spherical; import static com.google.common.truth.Truth.assertThat; 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 d63295adde..4136fab1bf 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 @@ -562,7 +562,7 @@ public final class DashMediaSource extends BaseMediaSource { processManifest(false); } else { dataSource = manifestDataSourceFactory.createDataSource(); - loader = new Loader("Loader:DashMediaSource"); + loader = new Loader("DashMediaSource"); handler = Util.createHandlerForCurrentLooper(); startLoadingManifest(); } @@ -1139,7 +1139,7 @@ public final class DashMediaSource extends BaseMediaSource { if (index == null) { return periodStartTimeInManifestUs; } - int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); + long availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); if (availableSegmentCount == 0) { return periodStartTimeInManifestUs; } @@ -1171,7 +1171,7 @@ public final class DashMediaSource extends BaseMediaSource { if (index == null) { return periodStartTimeInManifestUs + periodDurationUs; } - int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); + long availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); if (availableSegmentCount == 0) { return periodStartTimeInManifestUs; } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java index 527ed6ce82..b62b5d844d 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source.dash; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.dash.manifest.RangedUri; -/** - * Indexes the segments within a media stream. - */ +/** Indexes the segments within a media stream. */ public interface DashSegmentIndex { int INDEX_UNBOUNDED = -1; @@ -89,7 +87,7 @@ public interface DashSegmentIndex { * C#TIME_UNSET} if the period's duration is not yet known. * @return The number of segments in the index, or {@link #INDEX_UNBOUNDED}. */ - int getSegmentCount(long periodDurationUs); + long getSegmentCount(long periodDurationUs); /** * Returns the number of available segments in the index. @@ -99,7 +97,7 @@ public interface DashSegmentIndex { * @param nowUnixTimeUs The current time in milliseconds since the Unix epoch. * @return The number of available segments in the index. */ - int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs); + long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs); /** * Returns the time, in microseconds, at which a new segment becomes available, or {@link diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 1aee832a37..17ee580036 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -40,9 +40,7 @@ import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; import java.util.List; -/** - * Utility methods for DASH streams. - */ +/** Utility methods for DASH streams. */ public final class DashUtil { /** diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java index 4c771cdcbf..1d46d08422 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java @@ -20,8 +20,8 @@ import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.source.dash.manifest.RangedUri; /** - * An implementation of {@link DashSegmentIndex} that wraps a {@link ChunkIndex} parsed from a - * media stream. + * An implementation of {@link DashSegmentIndex} that wraps a {@link ChunkIndex} parsed from a media + * stream. */ public final class DashWrappingSegmentIndex implements DashSegmentIndex { @@ -48,12 +48,12 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex { } @Override - public int getSegmentCount(long periodDurationUs) { + public long getSegmentCount(long periodDurationUs) { return chunkIndex.length; } @Override - public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { + public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { return chunkIndex.length; } 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 2225589950..45806d390a 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 @@ -26,11 +26,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.extractor.ChunkIndex; -import com.google.android.exoplayer2.extractor.Extractor; -import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; -import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; -import com.google.android.exoplayer2.extractor.rawcc.RawCcExtractor; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.BundledChunkExtractor; @@ -53,27 +48,49 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; -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.List; -/** - * A default {@link DashChunkSource} implementation. - */ +/** A default {@link DashChunkSource} implementation. */ public class DefaultDashChunkSource implements DashChunkSource { public static final class Factory implements DashChunkSource.Factory { private final DataSource.Factory dataSourceFactory; private final int maxSegmentsPerLoad; + private final ChunkExtractor.Factory chunkExtractorFactory; + /** + * Equivalent to {@link #Factory(ChunkExtractor.Factory, DataSource.Factory, int) new + * Factory(BundledChunkExtractor.FACTORY, dataSourceFactory, maxSegmentsPerLoad = 1)}. + */ public Factory(DataSource.Factory dataSourceFactory) { this(dataSourceFactory, /* maxSegmentsPerLoad= */ 1); } + /** + * Equivalent to {@link #Factory(ChunkExtractor.Factory, DataSource.Factory, int) new + * Factory(BundledChunkExtractor.FACTORY, dataSourceFactory, maxSegmentsPerLoad)}. + */ public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) { + this(BundledChunkExtractor.FACTORY, dataSourceFactory, maxSegmentsPerLoad); + } + + /** + * Creates a new instance. + * + * @param chunkExtractorFactory Creates {@link ChunkExtractor} instances to use for extracting + * chunks. + * @param dataSourceFactory Creates the {@link DataSource} to use for downloading chunks. + * @param maxSegmentsPerLoad See {@link DefaultDashChunkSource#DefaultDashChunkSource}. + */ + public Factory( + ChunkExtractor.Factory chunkExtractorFactory, + DataSource.Factory dataSourceFactory, + int maxSegmentsPerLoad) { + this.chunkExtractorFactory = chunkExtractorFactory; this.dataSourceFactory = dataSourceFactory; this.maxSegmentsPerLoad = maxSegmentsPerLoad; } @@ -96,6 +113,7 @@ public class DefaultDashChunkSource implements DashChunkSource { dataSource.addTransferListener(transferListener); } return new DefaultDashChunkSource( + chunkExtractorFactory, manifestLoaderErrorThrower, manifest, periodIndex, @@ -129,6 +147,8 @@ public class DefaultDashChunkSource implements DashChunkSource { private boolean missingLastSegment; /** + * @param chunkExtractorFactory Creates {@link ChunkExtractor} instances to use for extracting + * chunks. * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. * @param manifest The initial manifest. * @param periodIndex The index of the period in the manifest. @@ -148,6 +168,7 @@ public class DefaultDashChunkSource implements DashChunkSource { * messages targeting the player. Maybe null if this is not necessary. */ public DefaultDashChunkSource( + ChunkExtractor.Factory chunkExtractorFactory, LoaderErrorThrower manifestLoaderErrorThrower, DashManifest manifest, int periodIndex, @@ -180,11 +201,15 @@ public class DefaultDashChunkSource implements DashChunkSource { representationHolders[i] = new RepresentationHolder( periodDurationUs, - trackType, representation, - enableEventMessageTrack, - closedCaptionFormats, - playerTrackEmsgHandler); + BundledChunkExtractor.FACTORY.createProgressiveMediaExtractor( + trackType, + representation.format, + enableEventMessageTrack, + closedCaptionFormats, + playerTrackEmsgHandler), + /* segmentNumShift= */ 0, + representation.getIndex()); } } @@ -195,7 +220,7 @@ public class DefaultDashChunkSource implements DashChunkSource { if (representationHolder.segmentIndex != null) { long segmentNum = representationHolder.getSegmentNum(positionUs); long firstSyncUs = representationHolder.getSegmentStartTimeUs(segmentNum); - int segmentCount = representationHolder.getSegmentCount(); + long segmentCount = representationHolder.getSegmentCount(); long secondSyncUs = firstSyncUs < positionUs && (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED @@ -438,7 +463,7 @@ public class DefaultDashChunkSource implements DashChunkSource { && ((InvalidResponseCodeException) e).responseCode == 404) { RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(chunk.trackFormat)]; - int segmentCount = representationHolder.getSegmentCount(); + long segmentCount = representationHolder.getSegmentCount(); if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED && segmentCount != 0) { long lastAvailableSegmentNum = representationHolder.getFirstSegmentNum() + segmentCount - 1; if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) { @@ -665,26 +690,6 @@ public class DefaultDashChunkSource implements DashChunkSource { private final long segmentNumShift; /* package */ RepresentationHolder( - long periodDurationUs, - int trackType, - Representation representation, - boolean enableEventMessageTrack, - List closedCaptionFormats, - @Nullable TrackOutput playerEmsgTrackOutput) { - this( - periodDurationUs, - representation, - createChunkExtractor( - trackType, - representation, - enableEventMessageTrack, - closedCaptionFormats, - playerEmsgTrackOutput), - /* segmentNumShift= */ 0, - representation.getIndex()); - } - - private RepresentationHolder( long periodDurationUs, Representation representation, @Nullable ChunkExtractor chunkExtractor, @@ -716,7 +721,7 @@ public class DefaultDashChunkSource implements DashChunkSource { newPeriodDurationUs, newRepresentation, chunkExtractor, segmentNumShift, newIndex); } - int oldIndexSegmentCount = oldIndex.getSegmentCount(newPeriodDurationUs); + long oldIndexSegmentCount = oldIndex.getSegmentCount(newPeriodDurationUs); if (oldIndexSegmentCount == 0) { // Segment numbers cannot shift if the old index was empty. return new RepresentationHolder( @@ -770,7 +775,7 @@ public class DefaultDashChunkSource implements DashChunkSource { + segmentNumShift; } - public int getSegmentCount() { + public long getSegmentCount() { return segmentIndex.getSegmentCount(periodDurationUs); } @@ -798,42 +803,13 @@ public class DefaultDashChunkSource implements DashChunkSource { } public boolean isSegmentAvailableAtFullNetworkSpeed(long segmentNum, long nowPeriodTimeUs) { - return nowPeriodTimeUs == C.TIME_UNSET || getSegmentEndTimeUs(segmentNum) <= nowPeriodTimeUs; - } - - @Nullable - private static ChunkExtractor createChunkExtractor( - int trackType, - Representation representation, - boolean enableEventMessageTrack, - List closedCaptionFormats, - @Nullable TrackOutput playerEmsgTrackOutput) { - String containerMimeType = representation.format.containerMimeType; - Extractor extractor; - if (MimeTypes.isText(containerMimeType)) { - if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { - // RawCC is special because it's a text specific container format. - extractor = new RawCcExtractor(representation.format); - } else { - // All other text types are raw formats that do not need an extractor. - return null; - } - } else if (MimeTypes.isMatroska(containerMimeType)) { - extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES); - } else { - int flags = 0; - if (enableEventMessageTrack) { - flags |= FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK; - } - extractor = - new FragmentedMp4Extractor( - flags, - /* timestampAdjuster= */ null, - /* sideloadedTrack= */ null, - closedCaptionFormats, - playerEmsgTrackOutput); + if (segmentIndex.isExplicit()) { + // We don't support segment availability for explicit indices (internal ref: b/172894901). + // Hence, also assume all segments in explicit indices are always available at full network + // speed even if they end in the future. + return true; } - return new BundledChunkExtractor(extractor, trackType, representation.format); + return nowPeriodTimeUs == C.TIME_UNSET || getSegmentEndTimeUs(segmentNum) <= nowPeriodTimeUs; } } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java index 66fcd280c6..1abe48a047 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java @@ -98,9 +98,9 @@ import java.io.IOException; } @Override - public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean formatRequired) { - if (formatRequired || !isFormatSentDownstream) { + public int readData( + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { + if ((readFlags & FLAG_REQUIRE_FORMAT) != 0 || !isFormatSentDownstream) { formatHolder.format = upstreamFormat; isFormatSentDownstream = true; return C.RESULT_FORMAT_READ; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java index 65a83a4829..b9c1ae995c 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java @@ -205,7 +205,8 @@ public final class PlayerEmsgHandler implements Handler.Callback { } } - private @Nullable Map.Entry ceilingExpiryEntryForPublishTime(long publishTimeMs) { + @Nullable + private Map.Entry ceilingExpiryEntryForPublishTime(long publishTimeMs) { return manifestPublishTimeToExpiryTimeUs.ceilingEntry(publishTimeMs); } @@ -360,8 +361,7 @@ public final class PlayerEmsgHandler implements Handler.Callback { private MetadataInputBuffer dequeueSample() { buffer.clear(); int result = - sampleQueue.read( - formatHolder, buffer, /* formatRequired= */ false, /* loadingFinished= */ false); + sampleQueue.read(formatHolder, buffer, /* readFlags= */ 0, /* loadingFinished= */ false); if (result == C.RESULT_BUFFER_READ) { buffer.flip(); return buffer; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java index b0689eeb11..316b98ebcd 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source.dash.manifest; import java.util.Collections; import java.util.List; -/** - * Represents a set of interchangeable encoded versions of a media content component. - */ +/** Represents a set of interchangeable encoded versions of a media content component. */ public class AdaptationSet { /** 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 36135e0454..b1b61fde5f 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 @@ -93,39 +93,6 @@ public class DashManifest implements FilterableManifest { private final List periods; - /** - * @deprecated Use {@link #DashManifest(long, long, long, boolean, long, long, long, long, - * ProgramInformation, UtcTimingElement, ServiceDescriptionElement, Uri, List)}. - */ - @Deprecated - public DashManifest( - long availabilityStartTimeMs, - long durationMs, - long minBufferTimeMs, - boolean dynamic, - long minUpdatePeriodMs, - long timeShiftBufferDepthMs, - long suggestedPresentationDelayMs, - long publishTimeMs, - @Nullable UtcTimingElement utcTiming, - @Nullable Uri location, - List periods) { - this( - availabilityStartTimeMs, - durationMs, - minBufferTimeMs, - dynamic, - minUpdatePeriodMs, - timeShiftBufferDepthMs, - suggestedPresentationDelayMs, - publishTimeMs, - /* programInformation= */ null, - utcTiming, - /* serviceDescription= */ null, - location, - periods); - } - public DashManifest( long availabilityStartTimeMs, long durationMs, 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 8c932f800d..6fc6038f50 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 @@ -57,9 +57,7 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlSerializer; -/** - * A parser of media presentation description files. - */ +/** A parser of media presentation description files. */ public class DashManifestParser extends DefaultHandler implements ParsingLoadable.Parser { @@ -549,7 +547,7 @@ public class DashManifestParser extends DefaultHandler String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); if (schemeIdUri != null) { - switch (Util.toLowerInvariant(schemeIdUri)) { + switch (Ascii.toLowerCase(schemeIdUri)) { case "urn:mpeg:dash:mp4protection:2011": schemeType = xpp.getAttributeValue(null, "value"); String defaultKid = XmlPullParserUtil.getAttributeValueIgnorePrefix(xpp, "default_KID"); @@ -1798,11 +1796,11 @@ public class DashManifestParser extends DefaultHandler * not be parsed. */ protected static int parseDolbyChannelConfiguration(XmlPullParser xpp) { - String value = Util.toLowerInvariant(xpp.getAttributeValue(null, "value")); + @Nullable String value = xpp.getAttributeValue(null, "value"); if (value == null) { return Format.NO_VALUE; } - switch (value) { + switch (Ascii.toLowerCase(value)) { case "4000": return 1; case "a000": diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Descriptor.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Descriptor.java index d68690d363..55dba54db1 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Descriptor.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Descriptor.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source.dash.manifest; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; -/** - * A descriptor, as defined by ISO 23009-1, 2nd edition, 5.8.2. - */ +/** A descriptor, as defined by ISO 23009-1, 2nd edition, 5.8.2. */ public final class Descriptor { /** The scheme URI. */ diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/EventStream.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/EventStream.java index 8a4e1ad058..178693fe04 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/EventStream.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/EventStream.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.source.dash.manifest; import com.google.android.exoplayer2.metadata.emsg.EventMessage; -/** - * A DASH in-MPD EventStream element, as defined by ISO/IEC 23009-1, 2nd edition, section 5.10. - */ +/** A DASH in-MPD EventStream element, as defined by ISO/IEC 23009-1, 2nd edition, section 5.10. */ public final class EventStream { /** diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java index b5b852ed7e..a2396e2533 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java @@ -20,9 +20,7 @@ import com.google.android.exoplayer2.C; import java.util.Collections; import java.util.List; -/** - * Encapsulates media content components over a contiguous period of time. - */ +/** Encapsulates media content components over a contiguous period of time. */ public class Period { /** diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index ac264bd2b1..5c434adb4a 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -19,7 +19,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; /** A parsed program information element. */ -public class ProgramInformation { +public final class ProgramInformation { /** The title for the media presentation. */ @Nullable public final String title; @@ -53,7 +53,7 @@ public class ProgramInformation { if (this == obj) { return true; } - if (obj == null || getClass() != obj.getClass()) { + if (!(obj instanceof ProgramInformation)) { return false; } ProgramInformation other = (ProgramInformation) obj; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java index bcd783f0cb..60975dd54b 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java @@ -20,9 +20,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.UriUtil; -/** - * Defines a range of data located at a reference uri. - */ +/** Defines a range of data located at a reference uri. */ public final class RangedUri { /** diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java index c0b1dceec5..226ae84c51 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java @@ -26,9 +26,7 @@ import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegm import java.util.Collections; import java.util.List; -/** - * A DASH representation. - */ +/** A DASH representation. */ public abstract class Representation { /** @@ -352,12 +350,12 @@ public abstract class Representation { } @Override - public int getSegmentCount(long periodDurationUs) { + public long getSegmentCount(long periodDurationUs) { return segmentBase.getSegmentCount(periodDurationUs); } @Override - public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { + public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { return segmentBase.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java index 495f288805..20f45b8036 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java @@ -24,11 +24,12 @@ import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.dash.DashSegmentIndex; import com.google.android.exoplayer2.util.Util; +import com.google.common.math.BigIntegerMath; +import java.math.BigInteger; +import java.math.RoundingMode; import java.util.List; -/** - * An approximate representation of a SegmentBase manifest element. - */ +/** An approximate representation of a SegmentBase manifest element. */ public abstract class SegmentBase { @Nullable /* package */ final RangedUri initialization; @@ -214,7 +215,7 @@ public abstract class SegmentBase { long duration = segmentTimeline.get((int) (sequenceNumber - startNumber)).duration; return (duration * C.MICROS_PER_SECOND) / timescale; } else { - int segmentCount = getSegmentCount(periodDurationUs); + long segmentCount = getSegmentCount(periodDurationUs); return segmentCount != INDEX_UNBOUNDED && sequenceNumber == (getFirstSegmentNum() + segmentCount - 1) ? (periodDurationUs - getSegmentTimeUs(sequenceNumber)) @@ -264,8 +265,8 @@ public abstract class SegmentBase { } /** See {@link DashSegmentIndex#getAvailableSegmentCount(long, long)}. */ - public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { - int segmentCount = getSegmentCount(periodDurationUs); + public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { + long segmentCount = getSegmentCount(periodDurationUs); if (segmentCount != INDEX_UNBOUNDED) { return segmentCount; } @@ -298,7 +299,7 @@ public abstract class SegmentBase { } /** See {@link DashSegmentIndex#getSegmentCount(long)}. */ - public abstract int getSegmentCount(long periodDurationUs); + public abstract long getSegmentCount(long periodDurationUs); } /** A {@link MultiSegmentBase} that uses a SegmentList to define its segments. */ @@ -356,7 +357,7 @@ public abstract class SegmentBase { } @Override - public int getSegmentCount(long periodDurationUs) { + public long getSegmentCount(long periodDurationUs) { return mediaSegments.size(); } @@ -455,14 +456,17 @@ public abstract class SegmentBase { } @Override - public int getSegmentCount(long periodDurationUs) { + public long getSegmentCount(long periodDurationUs) { if (segmentTimeline != null) { return segmentTimeline.size(); } else if (endNumber != C.INDEX_UNSET) { - return (int) (endNumber - startNumber + 1); + return endNumber - startNumber + 1; } else if (periodDurationUs != C.TIME_UNSET) { - long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; - return (int) Util.ceilDivide(periodDurationUs, durationUs); + BigInteger numerator = + BigInteger.valueOf(periodDurationUs).multiply(BigInteger.valueOf(timescale)); + BigInteger denominator = + BigInteger.valueOf(duration).multiply(BigInteger.valueOf(C.MICROS_PER_SECOND)); + return BigIntegerMath.divide(numerator, denominator, RoundingMode.CEILING).longValue(); } else { return INDEX_UNBOUNDED; } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java index 523bc2d071..a5806450a3 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java @@ -63,12 +63,12 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex; } @Override - public int getSegmentCount(long periodDurationUs) { + public long getSegmentCount(long periodDurationUs) { return 1; } @Override - public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { + public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) { return 1; } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java index 7d13993655..fbf06c5885 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java @@ -19,8 +19,8 @@ import java.util.Locale; /** * A template from which URLs can be built. - *

      - * URLs are built according to the substitution rules defined in ISO/IEC 23009-1:2014 5.3.9.4.4. + * + *

      URLs are built according to the substitution rules defined in ISO/IEC 23009-1:2014 5.3.9.4.4. */ public final class UrlTemplate { diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java index 79e7452459..9508623c9d 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.source.dash.manifest; -/** - * Represents a UTCTiming element. - */ +/** Represents a UTCTiming element. */ public final class UtcTimingElement { public final String schemeIdUri; 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 7b99d55fd9..ed195ec417 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 @@ -183,7 +183,7 @@ public final class DashDownloader extends SegmentDownloader { continue; } - int segmentCount = index.getSegmentCount(periodDurationUs); + long segmentCount = index.getSegmentCount(periodDurationUs); if (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED) { throw new DownloadException("Unbounded segment index"); } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/e2etest/DashPlaybackTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/e2etest/DashPlaybackTest.java index d14c702a25..372fa07646 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/e2etest/DashPlaybackTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/e2etest/DashPlaybackTest.java @@ -28,9 +28,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; -import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; +import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import org.junit.Rule; import org.junit.Test; @@ -55,7 +55,7 @@ public final class DashPlaybackTest { new CapturingRenderersFactory(applicationContext); SimpleExoPlayer player = new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setClock(new AutoAdvancingFakeClock()) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); @@ -73,4 +73,27 @@ public final class DashPlaybackTest { DumpFileAsserts.assertOutput( applicationContext, playbackOutput, "playbackdumps/dash/webvtt-in-mp4.dump"); } + + // https://github.com/google/ExoPlayer/issues/8710 + @Test + public void emsgNearToPeriodBoundary() throws Exception { + Context applicationContext = ApplicationProvider.getApplicationContext(); + CapturingRenderersFactory capturingRenderersFactory = + new CapturingRenderersFactory(applicationContext); + SimpleExoPlayer player = + new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) + .build(); + player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); + PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); + + player.setMediaItem(MediaItem.fromUri("asset:///media/dash/emsg/sample.mpd")); + player.prepare(); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + player.release(); + + DumpFileAsserts.assertOutput( + applicationContext, playbackOutput, "playbackdumps/dash/emsg.dump"); + } } 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 93700d5ec3..e403482b6c 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 @@ -24,6 +24,7 @@ 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.source.chunk.BundledChunkExtractor; import com.google.android.exoplayer2.source.chunk.ChunkHolder; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; @@ -58,6 +59,7 @@ public class DefaultDashChunkSourceTest { SAMPLE_MPD_LIVE_WITH_OFFSET_INSIDE_WINDOW)); DefaultDashChunkSource chunkSource = new DefaultDashChunkSource( + BundledChunkExtractor.FACTORY, new LoaderErrorThrower.Dummy(), manifest, /* periodIndex= */ 0, @@ -104,6 +106,7 @@ public class DefaultDashChunkSourceTest { ApplicationProvider.getApplicationContext(), SAMPLE_MPD_VOD)); DefaultDashChunkSource chunkSource = new DefaultDashChunkSource( + BundledChunkExtractor.FACTORY, new LoaderErrorThrower.Dummy(), manifest, /* periodIndex= */ 0, diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java index 11f4b37c67..348290eb69 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java @@ -58,7 +58,7 @@ public final class EventSampleStreamTest { } /** - * Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} will + * Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} will * return format for the first call. */ @Test @@ -106,7 +106,7 @@ public final class EventSampleStreamTest { } /** - * Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} will + * Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} will * return sample data after the first call. */ @Test @@ -127,8 +127,8 @@ public final class EventSampleStreamTest { /** * Tests that {@link EventSampleStream#skipData(long)} will skip until the given position, and the - * next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will - * return sample data from that position. + * next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} call will return + * sample data from that position. */ @Test public void skipDataThenReadDataReturnDataFromSkippedPosition() { @@ -153,7 +153,7 @@ public final class EventSampleStreamTest { /** * Tests that {@link EventSampleStream#seekToUs(long)} (long)} will seek to the given position, - * and the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * and the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} call * will return sample data from that position. */ @Test @@ -179,8 +179,8 @@ public final class EventSampleStreamTest { /** * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the * underlying event stream, but keep the read timestamp, so the next {@link - * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample - * data from after the last read sample timestamp. + * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} call will return sample data + * from after the last read sample timestamp. */ @Test public void updateEventStreamContinueToReadAfterLastReadSamplePresentationTime() { @@ -213,8 +213,8 @@ public final class EventSampleStreamTest { /** * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the * underlying event stream, but keep the timestamp the stream has skipped to, so the next {@link - * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample - * data from the skipped position. + * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} call will return sample data + * from the skipped position. */ @Test public void skipDataThenUpdateStreamContinueToReadFromSkippedPosition() { @@ -246,8 +246,8 @@ public final class EventSampleStreamTest { * Tests that {@link EventSampleStream#skipData(long)} will only skip to the point right after it * last event. A following {@link EventSampleStream#updateEventStream(EventStream, boolean)} will * update the underlying event stream and keep the timestamp the stream has skipped to, so the - * next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will - * return sample data from the skipped position. + * next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} call will return + * sample data from the skipped position. */ @Test public void skipDataThenUpdateStreamContinueToReadDoNotSkippedMoreThanAvailable() { @@ -280,8 +280,8 @@ public final class EventSampleStreamTest { /** * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the * underlying event stream, but keep the timestamp the stream has seek to, so the next {@link - * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample - * data from the seek position. + * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} call will return sample data + * from the seek position. */ @Test public void seekToUsThenUpdateStreamContinueToReadFromSeekPosition() { @@ -312,8 +312,8 @@ public final class EventSampleStreamTest { /** * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the * underlying event stream, but keep the timestamp the stream has seek to, so the next {@link - * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample - * data from the seek position. + * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, int)} call will return sample data + * from the seek position. */ @Test public void seekToThenUpdateStreamContinueToReadFromSeekPositionEvenSeekMoreThanAvailable() { @@ -343,7 +343,7 @@ public final class EventSampleStreamTest { private int readData(EventSampleStream sampleStream) { inputBuffer.clear(); - return sampleStream.readData(formatHolder, inputBuffer, false); + return sampleStream.readData(formatHolder, inputBuffer, /* readFlags= */ 0); } private EventMessage newEventMessageWithId(int id) { diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBaseTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBaseTest.java index dd442a91f4..91ddfbbde9 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBaseTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBaseTest.java @@ -182,4 +182,43 @@ public final class SegmentBaseTest { /* nowUnixTimeUs= */ periodStartUnixTimeUs + 17_500_000)) .isEqualTo(19_500_000); } + + /** Regression test for https://github.com/google/ExoPlayer/issues/8804. */ + @Test + public void getSegmentCount_withSegmentTemplate_avoidsIncorrectRounding() { + SegmentBase.SegmentTemplate segmentTemplate = + new SegmentBase.SegmentTemplate( + /* initialization= */ null, + /* timescale= */ 90000, + /* presentationTimeOffset= */ 0, + /* startNumber= */ 0, + /* endNumber= */ C.INDEX_UNSET, + /* duration= */ 179989, + /* segmentTimeline= */ null, + /* availabilityTimeOffsetUs= */ C.TIME_UNSET, + /* initializationTemplate= */ null, + /* mediaTemplate= */ null, + /* timeShiftBufferDepthUs= */ C.TIME_UNSET, + /* periodStartUnixTimeUs= */ C.TIME_UNSET); + assertThat(segmentTemplate.getSegmentCount(2931820000L)).isEqualTo(1466); + } + + @Test + public void getSegmentCount_withSegmentTemplate_avoidsOverflow() { + SegmentBase.SegmentTemplate segmentTemplate = + new SegmentBase.SegmentTemplate( + /* initialization= */ null, + /* timescale= */ 1000000, + /* presentationTimeOffset= */ 0, + /* startNumber= */ 0, + /* endNumber= */ C.INDEX_UNSET, + /* duration= */ 179989, + /* segmentTimeline= */ null, + /* availabilityTimeOffsetUs= */ C.TIME_UNSET, + /* initializationTemplate= */ null, + /* mediaTemplate= */ null, + /* timeShiftBufferDepthUs= */ C.TIME_UNSET, + /* periodStartUnixTimeUs= */ C.TIME_UNSET); + assertThat(segmentTemplate.getSegmentCount(1618875028000000L)).isEqualTo(8994299808L); + } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java index 45c567235a..3a783e4df9 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.extractor; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; -/** - * Defines chunks of samples within a media stream. - */ +/** Defines chunks of samples within a media stream. */ public final class ChunkIndex implements SeekMap { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java index c3920ca7da..63870282b5 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java @@ -22,9 +22,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * Extracts media data from a container format. - */ +/** Extracts media data from a container format. */ public interface Extractor { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java index 95b1daeb6e..09f5fa493d 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor; -/** - * Receives stream level data extracted by an {@link Extractor}. - */ +/** Receives stream level data extracted by an {@link Extractor}. */ public interface ExtractorOutput { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java index 1ba316c64b..09d2fd1f88 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java @@ -24,9 +24,7 @@ import com.google.android.exoplayer2.metadata.id3.InternalFrame; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * Holder for gapless playback information. - */ +/** Holder for gapless playback information. */ public final class GaplessInfoHolder { private static final String GAPLESS_DOMAIN = "com.apple.iTunes"; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java index d1f5d76468..e4ac523350 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor; -/** - * Holds a position in the stream. - */ +/** Holds a position in the stream. */ public final class PositionHolder { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java index b071237cf5..a461f4b700 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java @@ -28,9 +28,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; -/** - * Receives track level data extracted by an {@link Extractor}. - */ +/** Receives track level data extracted by an {@link Extractor}. */ public interface TrackOutput { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java index 2b6e341500..d6eb4772c4 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java @@ -75,7 +75,7 @@ public final class FlacExtractor implements Extractor { * required. */ public static final int FLAG_DISABLE_ID3_METADATA = 1; - // LINT.ThenChange(../../../../../../../../../../../extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java) + // LINT.ThenChange(../../../../../../../../../../decoder_flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java) /** Parser state. */ @Documented diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java index 6f9c5b9c40..2fdaf7dc46 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java @@ -35,9 +35,7 @@ import java.lang.annotation.RetentionPolicy; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Extracts data from the FLV container format. - */ +/** Extracts data from the FLV container format. */ public final class FlvExtractor implements Extractor { /** Factory for {@link FlvExtractor} instances. */ 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 c2aba6d7bd..21b7c8544f 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 @@ -48,9 +48,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Extracts data from the MP3 container format. - */ +/** Extracts data from the MP3 container format. */ public final class Mp3Extractor implements Extractor { /** Factory for {@link Mp3Extractor} instances. */ diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java index 53ed281152..a5470dace6 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java @@ -279,8 +279,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; "Psybient" }; - private static final String LANGUAGE_UNDEFINED = "und"; - private static final int TYPE_TOP_BYTE_COPYRIGHT = 0xA9; private static final int TYPE_TOP_BYTE_REPLACEMENT = 0xFD; // Truncated value of \uFFFD. @@ -467,7 +465,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (atomType == Atom.TYPE_data) { data.skipBytes(8); // version (1), flags (3), empty (4) String value = data.readNullTerminatedString(atomSize - 16); - return new CommentFrame(LANGUAGE_UNDEFINED, value, value); + return new CommentFrame(C.LANGUAGE_UNDETERMINED, value, value); } Log.w(TAG, "Failed to parse comment attribute: " + Atom.getAtomTypeString(type)); return null; @@ -487,7 +485,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (value >= 0) { return isTextInformationFrame ? new TextInformationFrame(id, /* description= */ null, Integer.toString(value)) - : new CommentFrame(LANGUAGE_UNDEFINED, id, Integer.toString(value)); + : new CommentFrame(C.LANGUAGE_UNDETERMINED, id, Integer.toString(value)); } Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type)); return null; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 08c399baff..092dbae496 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -58,9 +58,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Extracts data from the MP4 container format. - */ +/** Extracts data from the MP4 container format. */ public final class Mp4Extractor implements Extractor, SeekMap { /** Factory for {@link Mp4Extractor} instances. */ diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java index fb94fb9ed2..c47ca8d6ba 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import java.nio.ByteBuffer; import java.util.UUID; -/** - * Utility methods for handling PSSH atoms. - */ +/** Utility methods for handling PSSH atoms. */ public final class PsshAtomUtil { private static final String TAG = "PsshAtomUtil"; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java index 7676926c4d..1e917f825d 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java @@ -23,9 +23,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * Encapsulates information describing an MP4 track. - */ +/** Encapsulates information describing an MP4 track. */ public final class Track { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java index a35d211aa4..986c5bb255 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java @@ -22,7 +22,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; /** - * Encapsulates information parsed from a track encryption (tenc) box or sample group description + * Encapsulates information parsed from a track encryption (tenc) box or sample group description * (sgpd) box in an MP4 stream. */ public final class TrackEncryptionBox { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java index 6d7f16116c..49c8e2fb3b 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java @@ -31,9 +31,7 @@ import java.io.IOException; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * Extracts data from the Ogg container format. - */ +/** Extracts data from the Ogg container format. */ public class OggExtractor implements Extractor { /** Factory for {@link OggExtractor} instances. */ diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java index 44e67c955c..d9339fa689 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java @@ -30,9 +30,7 @@ import java.io.IOException; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Extracts data from the RawCC container format. - */ +/** Extracts data from the RawCC container format. */ public final class RawCcExtractor implements Extractor { private static final int SCRATCH_SIZE = 9; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java index 75839e0917..94a277582d 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java @@ -31,9 +31,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerat import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; -/** - * Extracts data from (E-)AC-3 bitstreams. - */ +/** Extracts data from (E-)AC-3 bitstreams. */ public final class Ac3Extractor implements Extractor { /** Factory for {@link Ac3Extractor} instances. */ diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index bfb828415c..2de59a5a07 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -36,9 +36,7 @@ import java.lang.annotation.RetentionPolicy; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses a continuous (E-)AC-3 byte stream and extracts individual samples. - */ +/** Parses a continuous (E-)AC-3 byte stream and extracts individual samples. */ public final class Ac3Reader implements ElementaryStreamReader { @Documented diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index 54a6a20b36..7c93c210f3 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -41,9 +41,7 @@ import java.lang.annotation.RetentionPolicy; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Extracts data from AAC bit streams with ADTS framing. - */ +/** Extracts data from AAC bit streams with ADTS framing. */ public final class AdtsExtractor implements Extractor { /** Factory for {@link AdtsExtractor} instances. */ diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index 5a024b0a15..13d76134da 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -38,9 +38,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses a continuous ADTS byte stream and extracts individual frames. - */ +/** Parses a continuous ADTS byte stream and extracts individual frames. */ public final class AdtsReader implements ElementaryStreamReader { private static final String TAG = "AdtsReader"; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index c74b70fdec..6b5579e3a9 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -30,9 +30,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; -/** - * Default {@link TsPayloadReader.Factory} implementation. - */ +/** Default {@link TsPayloadReader.Factory} implementation. */ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index f4f9e62975..3ffc44b730 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -29,9 +29,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses a continuous DTS byte stream and extracts individual samples. - */ +/** Parses a continuous DTS byte stream and extracts individual samples. */ public final class DtsReader implements ElementaryStreamReader { private static final int STATE_FINDING_SYNC = 0; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java index 9baaf85662..592e28ba57 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java @@ -28,9 +28,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import java.util.Collections; import java.util.List; -/** - * Parses DVB subtitle data and extracts individual frames. - */ +/** Parses DVB subtitle data and extracts individual frames. */ public final class DvbSubtitleReader implements ElementaryStreamReader { private final List subtitleInfos; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java index e022fc237b..a876959723 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java @@ -20,9 +20,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.util.ParsableByteArray; -/** - * Extracts individual samples from an elementary media stream, preserving original order. - */ +/** Extracts individual samples from an elementary media stream, preserving original order. */ public interface ElementaryStreamReader { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java index 898084013f..de9756e155 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java @@ -33,9 +33,7 @@ import java.util.Arrays; import java.util.Collections; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * Parses a continuous H262 byte stream and extracts individual frames. - */ +/** Parses a continuous H262 byte stream and extracts individual frames. */ public final class H262Reader implements ElementaryStreamReader { private static final int START_PICTURE = 0x00; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java index d0bf2067c9..116bcfef29 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java @@ -39,9 +39,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses a continuous H264 byte stream and extracts individual frames. - */ +/** Parses a continuous H264 byte stream and extracts individual frames. */ public final class H264Reader implements ElementaryStreamReader { private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index 7b8cabeb00..dd5dcc1782 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -36,9 +36,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses a continuous H.265 byte stream and extracts individual frames. - */ +/** Parses a continuous H.265 byte stream and extracts individual frames. */ public final class H265Reader implements ElementaryStreamReader { private static final String TAG = "H265Reader"; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java index a50e36b51c..66eb67f16c 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java @@ -30,9 +30,7 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * Parses ID3 data and extracts individual text information frames. - */ +/** Parses ID3 data and extracts individual text information frames. */ public final class Id3Reader implements ElementaryStreamReader { private static final String TAG = "Id3Reader"; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java index da477e88e5..64f56f6455 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java @@ -33,9 +33,7 @@ import java.util.Collections; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses and extracts samples from an AAC/LATM elementary stream. - */ +/** Parses and extracts samples from an AAC/LATM elementary stream. */ public final class LatmReader implements ElementaryStreamReader { private static final int STATE_FINDING_SYNC_1 = 0; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java index c89d61df2c..f65f515a9a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java @@ -29,9 +29,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses a continuous MPEG Audio byte stream and extracts individual frames. - */ +/** Parses a continuous MPEG Audio byte stream and extracts individual frames. */ public final class MpegAudioReader implements ElementaryStreamReader { private static final int STATE_FINDING_HEADER = 0; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java index 97fe7a7336..eb3606a8b0 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java @@ -29,9 +29,7 @@ import com.google.android.exoplayer2.util.TimestampAdjuster; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Parses PES packet data and extracts samples. - */ +/** Parses PES packet data and extracts samples. */ public final class PesReader implements TsPayloadReader { private static final String TAG = "PesReader"; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java index ec6a8cca65..f8aed61321 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java @@ -34,9 +34,7 @@ import java.io.IOException; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** - * Extracts data from the MPEG-2 PS container format. - */ +/** Extracts data from the MPEG-2 PS container format. */ public final class PsExtractor implements Extractor { /** Factory for {@link PsExtractor} instances. */ diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java index d6e6eadf3f..4bd5867565 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerat import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; -/** - * Reads section data. - */ +/** Reads section data. */ public interface SectionPayloadReader { /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 8266400971..325f05ff2b 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -49,9 +49,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -/** - * Extracts data from the MPEG-2 TS container format. - */ +/** Extracts data from the MPEG-2 TS container format. */ public final class TsExtractor implements Extractor { /** Factory for {@link TsExtractor} instances. */ diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java index 03ed10ff0d..581f4ec4ea 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; -/** - * Parses TS packet payload data. - */ +/** Parses TS packet payload data. */ public interface TsPayloadReader { /** 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 c1693abec9..bb4734f183 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 @@ -37,9 +37,7 @@ import java.io.IOException; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * Extracts data from WAV byte streams. - */ +/** Extracts data from WAV byte streams. */ public final class WavExtractor implements Extractor { /** diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java index f404f24651..646c378eb4 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java @@ -227,12 +227,16 @@ public class DefaultExtractorInputTest { @Test public void largeSkip() throws Exception { - DefaultExtractorInput input = createDefaultExtractorInput(); + FakeDataSource testDataSource = buildLargeDataSource(); + DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); // Check that skipping the entire data source succeeds. int bytesToSkip = LARGE_TEST_DATA_LENGTH; while (bytesToSkip > 0) { - bytesToSkip -= input.skip(bytesToSkip); + int skipped = input.skip(bytesToSkip); + assertThat(skipped).isGreaterThan(0); + bytesToSkip -= skipped; } + assertThat(bytesToSkip).isEqualTo(0); } @Test @@ -612,6 +616,13 @@ public class DefaultExtractorInputTest { return testDataSource; } + private static FakeDataSource buildLargeDataSource() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData().appendReadData(new byte[LARGE_TEST_DATA_LENGTH]); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + return testDataSource; + } + private static FakeDataSource buildFailingDataSource() throws Exception { FakeDataSource testDataSource = new FakeDataSource(); testDataSource.getDataSet().newDefaultData() diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java index b90dcb2139..5382e5f6e3 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.source.hls; import com.google.android.exoplayer2.upstream.DataSource; -/** - * Default implementation of {@link HlsDataSourceFactory}. - */ +/** Default implementation of {@link HlsDataSourceFactory}. */ public final class DefaultHlsDataSourceFactory implements HlsDataSourceFactory { private final DataSource.Factory dataSourceFactory; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java index 77333fd4cb..81798aa676 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java @@ -43,9 +43,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; -/** - * Default {@link HlsExtractorFactory} implementation. - */ +/** Default {@link HlsExtractorFactory} implementation. */ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { // Extractors order is optimized according to diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java index 30e7af5a0b..7ec2f6c1f5 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source.hls; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSource; -/** - * Creates {@link DataSource}s for HLS playlists, encryption and media chunks. - */ +/** Creates {@link DataSource}s for HLS playlists, encryption and media chunks. */ public interface HlsDataSourceFactory { /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java index 4fe78514cf..4682ffa963 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java @@ -26,9 +26,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; -/** - * Factory for HLS media chunk extractors. - */ +/** Factory for HLS media chunk extractors. */ public interface HlsExtractorFactory { HlsExtractorFactory DEFAULT = new DefaultHlsExtractorFactory(); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java index 81d63fd4ad..cf908145a0 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source.hls; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; -/** - * Holds a master playlist along with a snapshot of one of its media playlists. - */ +/** Holds a master playlist along with a snapshot of one of its media playlists. */ public final class HlsManifest { /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 81cfcd2ef7..3527eb1fc3 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; import java.io.EOFException; import java.io.IOException; @@ -540,7 +541,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private static byte[] getEncryptionIvArray(String ivString) { String trimmedIv; - if (Util.toLowerInvariant(ivString).startsWith("0x")) { + if (Ascii.toLowerCase(ivString).startsWith("0x")) { trimmedIv = ivString.substring(2); } else { trimmedIv = ivString; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index a4db3d9c52..66dd308e52 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -59,11 +59,11 @@ import java.util.Map; import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * A {@link MediaPeriod} that loads an HLS stream. - */ -public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback, - HlsPlaylistTracker.PlaylistEventListener { +/** A {@link MediaPeriod} that loads an HLS stream. */ +public final class HlsMediaPeriod + implements MediaPeriod, + HlsSampleStreamWrapper.Callback, + HlsPlaylistTracker.PlaylistEventListener { private final HlsExtractorFactory extractorFactory; private final HlsPlaylistTracker playlistTracker; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java index c820038b80..53d9b0cd9e 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java @@ -70,13 +70,14 @@ import java.io.IOException; } @Override - public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) { + public int readData( + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL) { buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); return C.RESULT_BUFFER_READ; } return hasValidSampleQueueIndex() - ? sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, requireFormat) + ? sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, readFlags) : C.RESULT_NOTHING_READ; } 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 14852ae00a..da28de4094 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 @@ -48,6 +48,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.SampleStream.ReadFlags; import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -569,8 +570,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; chunkSource.maybeThrowError(); } - public int readData(int sampleQueueIndex, FormatHolder formatHolder, DecoderInputBuffer buffer, - boolean requireFormat) { + public int readData( + int sampleQueueIndex, + FormatHolder formatHolder, + DecoderInputBuffer buffer, + @ReadFlags int readFlags) { if (isPendingReset()) { return C.RESULT_NOTHING_READ; } @@ -602,7 +606,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } int result = - sampleQueues[sampleQueueIndex].read(formatHolder, buffer, requireFormat, loadingFinished); + sampleQueues[sampleQueueIndex].read(formatHolder, buffer, readFlags, loadingFinished); if (result == C.RESULT_FORMAT_READ) { Format format = Assertions.checkNotNull(formatHolder.format); if (sampleQueueIndex == primarySampleQueueIndex) { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java index 85a4276ea2..83d4c31924 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java @@ -18,9 +18,7 @@ package com.google.android.exoplayer2.source.hls; import android.util.SparseArray; import com.google.android.exoplayer2.util.TimestampAdjuster; -/** - * Provides {@link TimestampAdjuster} instances for use during HLS playbacks. - */ +/** Provides {@link TimestampAdjuster} instances for use during HLS playbacks. */ public final class TimestampAdjusterProvider { // TODO: Prevent this array from growing indefinitely large by removing adjusters that are no diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index c4ab3fc662..ea7303656c 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.source.hls.playlist; - import android.net.Uri; import androidx.annotation.IntDef; import androidx.annotation.Nullable; 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 314f8e7d87..7b5aa3ef3f 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 @@ -64,9 +64,7 @@ import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.PolyNull; -/** - * HLS playlists parsing logic. - */ +/** HLS playlists parsing logic. */ public final class HlsPlaylistParser implements ParsingLoadable.Parser { /** Exception thrown when merging a delta update fails. */ @@ -1190,7 +1188,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions) { return parseOptionalStringAttr(line, pattern, null, variableDefinitions); } diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index 48f6a06b14..17ce2251b4 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -135,7 +135,7 @@ public class HlsMediaPlaylistParserTest { .isEqualTo("https://priv.example.com/key.php?r=2682"); // 0xA7A == 2682. assertThat(segment.encryptionIV).isNotNull(); - assertThat(Util.toUpperInvariant(segment.encryptionIV)).isEqualTo("A7A"); + assertThat(segment.encryptionIV).ignoringCase().isEqualTo("A7A"); assertThat(segment.byteRangeLength).isEqualTo(51740); assertThat(segment.byteRangeOffset).isEqualTo(2147586650L); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts"); @@ -148,7 +148,7 @@ public class HlsMediaPlaylistParserTest { .isEqualTo("https://priv.example.com/key.php?r=2682"); // 0xA7B == 2683. assertThat(segment.encryptionIV).isNotNull(); - assertThat(Util.toUpperInvariant(segment.encryptionIV)).isEqualTo("A7B"); + assertThat(segment.encryptionIV).ignoringCase().isEqualTo("A7B"); assertThat(segment.byteRangeLength).isEqualTo(C.LENGTH_UNSET); assertThat(segment.byteRangeOffset).isEqualTo(0); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts"); @@ -1220,6 +1220,33 @@ public class HlsMediaPlaylistParserTest { } } + @Test + public void iframeOnly_withExplicitInitSegment_hasCorrectByteRange() throws IOException { + Uri playlistUri = Uri.parse("https://example.com/test3.m3u8"); + String playlistString = + "#EXTM3U\n" + + "#EXT-X-VERSION:6\n" + + "#EXT-X-MEDIA-SEQUENCE:1616630672\n" + + "#EXT-X-TARGETDURATION:7\n" + + "#EXT-X-DISCONTINUITY-SEQUENCE:491 \n" + + "#EXT-X-MAP:URI=\"iframe0.tsv\",BYTERANGE=\"564@0\"\n" + + "\n" + + "#EXT-X-I-FRAMES-ONLY\n" + + "#EXT-X-PROGRAM-DATE-TIME:2021-04-12T17:08:22.000Z\n" + + "#EXTINF:1.001000,\n" + + "#EXT-X-BYTERANGE:121260@1128\n" + + "iframe0.tsv"; + + InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); + HlsMediaPlaylist standalonePlaylist = + (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); + @Nullable Segment initSegment = standalonePlaylist.segments.get(0).initializationSegment; + + assertThat(standalonePlaylist.segments).hasSize(1); + assertThat(initSegment.byteRangeLength).isEqualTo(564); + assertThat(initSegment.byteRangeOffset).isEqualTo(0); + } + @Test public void masterPlaylistAttributeInheritance() throws IOException { Uri playlistUri = Uri.parse("https://example.com/test3.m3u8"); diff --git a/library/rtsp/build.gradle b/library/rtsp/build.gradle new file mode 100644 index 0000000000..c97391a858 --- /dev/null +++ b/library/rtsp/build.gradle @@ -0,0 +1,49 @@ +// Copyright 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 { + buildTypes { + debug { + testCoverageEnabled = true + } + } + + sourceSets.test.assets.srcDir '../../testdata/src/test/assets/' +} + +dependencies { + implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion + compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion + compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion + implementation project(modulePrefix + 'library-core') + testImplementation project(modulePrefix + 'robolectricutils') + testImplementation project(modulePrefix + 'testutils') + testImplementation project(modulePrefix + 'testdata') + testImplementation 'org.checkerframework:checker-qual:' + checkerframeworkVersion + testImplementation 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion + testImplementation 'org.robolectric:robolectric:' + robolectricVersion +} + +ext { + javadocTitle = 'RTSP module' +} +apply from: '../../javadoc_library.gradle' + +ext { + releaseArtifact = 'exoplayer-rtsp' + releaseDescription = 'The ExoPlayer library RTSP module.' +} +apply from: '../../publish.gradle' diff --git a/extensions/gvr/src/main/res/values/styles.xml b/library/rtsp/src/main/AndroidManifest.xml similarity index 80% rename from extensions/gvr/src/main/res/values/styles.xml rename to library/rtsp/src/main/AndroidManifest.xml index 2affbb2f05..4becfcc68e 100644 --- a/extensions/gvr/src/main/res/values/styles.xml +++ b/library/rtsp/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - - -