Merge pull request #10443 from google/dev-v2-r2.18.1

r2.18.1
This commit is contained in:
Rohit Kumar Singh 2022-07-22 09:31:03 +00:00 committed by GitHub
commit ab4d37f499
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
92 changed files with 4425 additions and 1282 deletions

View file

@ -18,6 +18,7 @@ body:
label: ExoPlayer Version
description: What version of ExoPlayer are you using?
options:
- 2.18.1
- 2.18.0
- 2.17.1
- 2.17.0

View file

@ -1,5 +1,48 @@
# Release notes
### 2.18.1 (2022-07-21)
This release corresponds to the
[AndroidX media3 1.0.0-beta02 release](https://github.com/androidx/media/releases/tag/1.0.0-beta02).
* Core library:
* Ensure that changing the `ShuffleOrder` with `ExoPlayer.setShuffleOrder`
results in a call to `Player.Listener#onTimelineChanged` with
`reason=Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED`
([#9889](https://github.com/google/ExoPlayer/issues/9889)).
* For progressive media, only include selected tracks in buffered position
([#10361](https://github.com/google/ExoPlayer/issues/10361)).
* Allow custom logger for all ExoPlayer log output
([#9752](https://github.com/google/ExoPlayer/issues/9752)).
* Fix implementation of `setDataSourceFactory` in
`DefaultMediaSourceFactory`, which was non-functional in some cases
([#116](https://github.com/androidx/media/issues/116)).
* Extractors:
* Fix parsing of H265 short term reference picture sets
([#10316](https://github.com/google/ExoPlayer/issues/10316)).
* Fix parsing of bitrates from `esds` boxes
([#10381](https://github.com/google/ExoPlayer/issues/10381)).
* DASH:
* Parse ClearKey license URL from manifests
([#10246](https://github.com/google/ExoPlayer/issues/10246)).
* UI:
* Ensure TalkBack announces the currently active speed option in the
playback controls menu
([#10298](https://github.com/google/ExoPlayer/issues/10298)).
* RTSP:
* Add VP8 fragmented packet handling
([#110](https://github.com/androidx/media/pull/110)).
* Leanback extension:
* Listen to `playWhenReady` changes in `LeanbackAdapter`
([10420](https://github.com/google/ExoPlayer/issues/10420)).
* Cast:
* Use the `MediaItem` that has been passed to the playlist methods as
`Window.mediaItem` in `CastTimeline`
([#25](https://github.com/androidx/media/issues/25),
[#8212](https://github.com/google/ExoPlayer/issues/8212)).
* Support `Player.getMetadata()` and `Listener.onMediaMetadataChanged()`
with `CastPlayer` ([#25](https://github.com/androidx/media/issues/25)).
### 2.18.0 (2022-06-16)
This release corresponds to the
@ -37,7 +80,9 @@ This release corresponds to the
* Rename `TracksInfo` to `Tracks` and `TracksInfo.TrackGroupInfo` to
`Tracks.Group`. `Player.getCurrentTracksInfo` and
`Player.Listener.onTracksInfoChanged` have also been renamed to
`Player.getCurrentTracks` and `Player.Listener.onTracksChanged`.
`Player.getCurrentTracks` and `Player.Listener.onTracksChanged`. This
includes 'un-deprecating' the `Player.Listener.onTracksChanged` method
name, but with different parameter types.
* Change `DefaultTrackSelector.buildUponParameters` and
`DefaultTrackSelector.Parameters.buildUpon` to return
`DefaultTrackSelector.Parameters.Builder` instead of the deprecated
@ -91,6 +136,8 @@ This release corresponds to the
* Remove `RawCcExtractor`, which was only used to handle a Google-internal
subtitle format.
* Extractors:
* Add support for AVI
([#2092](https://github.com/google/ExoPlayer/issues/2092)).
* Matroska: Parse `DiscardPadding` for Opus tracks.
* MP4: Parse bitrates from `esds` boxes.
* Ogg: Allow duplicate Opus ID and comment headers
@ -140,6 +187,8 @@ This release corresponds to the
of `DefaultCompositeSequenceableLoaderFactory` can be passed explicitly
if required.
* RTSP:
* Add RTP reader for H263
([#63](https://github.com/androidx/media/pull/63)).
* Add RTP reader for MPEG4
([#35](https://github.com/androidx/media/pull/35)).
* Add RTP reader for HEVC
@ -172,10 +221,11 @@ This release corresponds to the
AndroidStudio's gradle sync to fail
([#9933](https://github.com/google/ExoPlayer/issues/9933)).
* Remove deprecated symbols:
* Remove `Player.Listener.onTracksChanged`. Use
`Player.Listener.onTracksInfoChanged` instead.
* Remove `Player.Listener.onTracksChanged(TrackGroupArray,
TrackSelectionArray)`. Use `Player.Listener.onTracksChanged(Tracks)`
instead.
* Remove `Player.getCurrentTrackGroups` and
`Player.getCurrentTrackSelections`. Use `Player.getCurrentTracksInfo`
`Player.getCurrentTrackSelections`. Use `Player.getCurrentTracks`
instead. You can also continue to use `ExoPlayer.getCurrentTrackGroups`
and `ExoPlayer.getCurrentTrackSelections`, although these methods remain
deprecated.
@ -371,7 +421,7 @@ This release corresponds to the
when creating `PendingIntent`s
([#9528](https://github.com/google/ExoPlayer/issues/9528)).
* Remove deprecated symbols:
* Remove `Player.EventLister`. Use `Player.Listener` instead.
* Remove `Player.EventListener`. Use `Player.Listener` instead.
* Remove `MediaSourceFactory.setDrmSessionManager`,
`MediaSourceFactory.setDrmHttpDataSourceFactory`, and
`MediaSourceFactory.setDrmUserAgent`. Use

View file

@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
releaseVersion = '2.18.0'
releaseVersionCode = 2_018_000
releaseVersion = '2.18.1'
releaseVersionCode = 2_018_001
minSdkVersion = 16
appTargetSdkVersion = 29
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some

View file

@ -78,6 +78,12 @@
<data android:scheme="file"/>
<data android:scheme="ssai"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.android.exoplayer.demo.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.android.exoplayer.demo.action.VIEW_LIST"/>
<category android:name="android.intent.category.DEFAULT"/>

View file

@ -43,6 +43,12 @@
<data android:scheme="asset"/>
<data android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.android.exoplayer.surfacedemo.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
</intent-filter>
</activity>
</application>

View file

@ -49,6 +49,12 @@
<data android:scheme="asset"/>
<data android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.android.exoplayer2.transformerdemo.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
</intent-filter>
</activity>
<activity android:name=".TransformerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"

File diff suppressed because one or more lines are too long

View file

@ -709,6 +709,7 @@
<li><a href="com/google/android/exoplayer2/source/LoadEventInfo.html" title="class in com.google.android.exoplayer2.source">LoadEventInfo</a></li>
<li><a href="com/google/android/exoplayer2/drm/LocalMediaDrmCallback.html" title="class in com.google.android.exoplayer2.drm">LocalMediaDrmCallback</a></li>
<li><a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></li>
<li><a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><span class="interfaceName">Log.Logger</span></a></li>
<li><a href="com/google/android/exoplayer2/util/Log.LogLevel.html" title="annotation in com.google.android.exoplayer2.util">Log.LogLevel</a></li>
<li><a href="com/google/android/exoplayer2/util/LongArray.html" title="class in com.google.android.exoplayer2.util">LongArray</a></li>
<li><a href="com/google/android/exoplayer2/source/LoopingMediaSource.html" title="class in com.google.android.exoplayer2.source">LoopingMediaSource</a></li>
@ -833,7 +834,6 @@
<li><a href="com/google/android/exoplayer2/NoSampleRenderer.html" title="class in com.google.android.exoplayer2">NoSampleRenderer</a></li>
<li><a href="com/google/android/exoplayer2/util/NotificationUtil.html" title="class in com.google.android.exoplayer2.util">NotificationUtil</a></li>
<li><a href="com/google/android/exoplayer2/util/NotificationUtil.Importance.html" title="annotation in com.google.android.exoplayer2.util">NotificationUtil.Importance</a></li>
<li><a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil">NoUidTimeline</a></li>
<li><a href="com/google/android/exoplayer2/drm/OfflineLicenseHelper.html" title="class in com.google.android.exoplayer2.drm">OfflineLicenseHelper</a></li>
<li><a href="com/google/android/exoplayer2/extractor/ogg/OggExtractor.html" title="class in com.google.android.exoplayer2.extractor.ogg">OggExtractor</a></li>
<li><a href="com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.html" title="class in com.google.android.exoplayer2.ext.okhttp">OkHttpDataSource</a></li>

File diff suppressed because one or more lines are too long

View file

@ -3049,6 +3049,20 @@ static final&nbsp;int COMMAND_SEEK_TO_WINDOW</pre>
</dl>
</li>
</ul>
<a id="COMMAND_SET_MEDIA_ITEM">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>COMMAND_SET_MEDIA_ITEM</h4>
<pre>static final&nbsp;int COMMAND_SET_MEDIA_ITEM</pre>
<div class="block">Command to set a <a href="MediaItem.html" title="class in com.google.android.exoplayer2"><code>MediaItem</code></a>.</div>
<dl>
<dt><span class="seeLabel">See Also:</span></dt>
<dd><a href="../../../../constant-values.html#com.google.android.exoplayer2.Player.COMMAND_SET_MEDIA_ITEM">Constant Field Values</a></dd>
</dl>
</li>
</ul>
<a id="COMMAND_CHANGE_MEDIA_ITEMS">
<!-- -->
</a>
@ -3203,20 +3217,6 @@ static final&nbsp;int COMMAND_SEEK_TO_WINDOW</pre>
</dl>
</li>
</ul>
<a id="COMMAND_SET_MEDIA_ITEM">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>COMMAND_SET_MEDIA_ITEM</h4>
<pre>static final&nbsp;int COMMAND_SET_MEDIA_ITEM</pre>
<div class="block">Command to set a <a href="MediaItem.html" title="class in com.google.android.exoplayer2"><code>MediaItem</code></a>.</div>
<dl>
<dt><span class="seeLabel">See Also:</span></dt>
<dd><a href="../../../../constant-values.html#com.google.android.exoplayer2.Player.COMMAND_SET_MEDIA_ITEM">Constant Field Values</a></dd>
</dl>
</li>
</ul>
<a id="COMMAND_INVALID">
<!-- -->
</a>

View file

@ -500,7 +500,8 @@ public final&nbsp;byte[] data</pre>
<ul class="blockList">
<li class="blockList">
<h4>copyWithData</h4>
<pre class="methodSignature">public&nbsp;<a href="DrmInitData.SchemeData.html" title="class in com.google.android.exoplayer2.drm">DrmInitData.SchemeData</a>&nbsp;copyWithData&#8203;(@Nullable
<pre class="methodSignature">@CheckResult
public&nbsp;<a href="DrmInitData.SchemeData.html" title="class in com.google.android.exoplayer2.drm">DrmInitData.SchemeData</a>&nbsp;copyWithData&#8203;(@Nullable
byte[]&nbsp;data)</pre>
<div class="block">Returns a copy of this instance with the specified data.</div>
<dl>

View file

@ -517,7 +517,8 @@ public static&nbsp;<a href="DrmInitData.html" title="class in com.google.android
<ul class="blockList">
<li class="blockList">
<h4>copyWithSchemeType</h4>
<pre class="methodSignature">public&nbsp;<a href="DrmInitData.html" title="class in com.google.android.exoplayer2.drm">DrmInitData</a>&nbsp;copyWithSchemeType&#8203;(@Nullable
<pre class="methodSignature">@CheckResult
public&nbsp;<a href="DrmInitData.html" title="class in com.google.android.exoplayer2.drm">DrmInitData</a>&nbsp;copyWithSchemeType&#8203;(@Nullable
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;schemeType)</pre>
<div class="block">Returns a copy with the specified protection scheme type.</div>
<dl>

View file

@ -25,7 +25,7 @@
catch(err) {
}
//-->
var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":10,"i50":10,"i51":10,"i52":10,"i53":10,"i54":10,"i55":10,"i56":10,"i57":10,"i58":10,"i59":10,"i60":10,"i61":10,"i62":10,"i63":10,"i64":10,"i65":10,"i66":10,"i67":10,"i68":10,"i69":10,"i70":42};
var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":10,"i50":10,"i51":10,"i52":10,"i53":10,"i54":10,"i55":10,"i56":10,"i57":10,"i58":10,"i59":10,"i60":10,"i61":10,"i62":10,"i63":10,"i64":10,"i65":10,"i66":10,"i67":10,"i68":10,"i69":10,"i70":10,"i71":42};
var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]};
var altColor = "altColor";
var rowColor = "rowColor";
@ -477,20 +477,25 @@ extends <a href="../../BasePlayer.html" title="class in com.google.android.exopl
</td>
</tr>
<tr id="i28" class="altColor">
<td class="colFirst"><code><a href="../../MediaMetadata.html" title="class in com.google.android.exoplayer2">MediaMetadata</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getMediaMetadataInternal()">getMediaMetadataInternal</a></span>()</code></th>
<td class="colLast">&nbsp;</td>
</tr>
<tr id="i29" class="rowColor">
<td class="colFirst"><code><a href="../../PlaybackParameters.html" title="class in com.google.android.exoplayer2">PlaybackParameters</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getPlaybackParameters()">getPlaybackParameters</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the currently active playback parameters.</div>
</td>
</tr>
<tr id="i29" class="rowColor">
<tr id="i30" class="altColor">
<td class="colFirst"><code>@com.google.android.exoplayer2.Player.State int</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getPlaybackState()">getPlaybackState</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the current <a href="../../Player.State.html" title="annotation in com.google.android.exoplayer2"><code>playback state</code></a> of the player.</div>
</td>
</tr>
<tr id="i30" class="altColor">
<tr id="i31" class="rowColor">
<td class="colFirst"><code>@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getPlaybackSuppressionReason()">getPlaybackSuppressionReason</a></span>()</code></th>
<td class="colLast">
@ -498,119 +503,119 @@ extends <a href="../../BasePlayer.html" title="class in com.google.android.exopl
true</code>, or <a href="../../Player.html#PLAYBACK_SUPPRESSION_REASON_NONE"><code>Player.PLAYBACK_SUPPRESSION_REASON_NONE</code></a> if playback is not suppressed.</div>
</td>
</tr>
<tr id="i31" class="rowColor">
<tr id="i32" class="altColor">
<td class="colFirst"><code><a href="../../PlaybackException.html" title="class in com.google.android.exoplayer2">PlaybackException</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getPlayerError()">getPlayerError</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the error that caused playback to fail.</div>
</td>
</tr>
<tr id="i32" class="altColor">
<tr id="i33" class="rowColor">
<td class="colFirst"><code><a href="../../MediaMetadata.html" title="class in com.google.android.exoplayer2">MediaMetadata</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getPlaylistMetadata()">getPlaylistMetadata</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the playlist <a href="../../MediaMetadata.html" title="class in com.google.android.exoplayer2"><code>MediaMetadata</code></a>, as set by <a href="../../Player.html#setPlaylistMetadata(com.google.android.exoplayer2.MediaMetadata)"><code>Player.setPlaylistMetadata(MediaMetadata)</code></a>, or <a href="../../MediaMetadata.html#EMPTY"><code>MediaMetadata.EMPTY</code></a> if not supported.</div>
</td>
</tr>
<tr id="i33" class="rowColor">
<tr id="i34" class="altColor">
<td class="colFirst"><code>boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getPlayWhenReady()">getPlayWhenReady</a></span>()</code></th>
<td class="colLast">
<div class="block">Whether playback will proceed when <a href="../../Player.html#getPlaybackState()"><code>Player.getPlaybackState()</code></a> == <a href="../../Player.html#STATE_READY"><code>Player.STATE_READY</code></a>.</div>
</td>
</tr>
<tr id="i34" class="altColor">
<tr id="i35" class="rowColor">
<td class="colFirst"><code>@com.google.android.exoplayer2.Player.RepeatMode int</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getRepeatMode()">getRepeatMode</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the current <a href="../../Player.RepeatMode.html" title="annotation in com.google.android.exoplayer2"><code>Player.RepeatMode</code></a> used for playback.</div>
</td>
</tr>
<tr id="i35" class="rowColor">
<tr id="i36" class="altColor">
<td class="colFirst"><code>long</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getSeekBackIncrement()">getSeekBackIncrement</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the <a href="../../Player.html#seekBack()"><code>Player.seekBack()</code></a> increment.</div>
</td>
</tr>
<tr id="i36" class="altColor">
<tr id="i37" class="rowColor">
<td class="colFirst"><code>long</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getSeekForwardIncrement()">getSeekForwardIncrement</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the <a href="../../Player.html#seekForward()"><code>Player.seekForward()</code></a> increment.</div>
</td>
</tr>
<tr id="i37" class="rowColor">
<tr id="i38" class="altColor">
<td class="colFirst"><code>boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getShuffleModeEnabled()">getShuffleModeEnabled</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns whether shuffling of media items is enabled.</div>
</td>
</tr>
<tr id="i38" class="altColor">
<tr id="i39" class="rowColor">
<td class="colFirst"><code>long</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getTotalBufferedDuration()">getTotalBufferedDuration</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns an estimate of the total buffered duration from the current position, in milliseconds.</div>
</td>
</tr>
<tr id="i39" class="rowColor">
<tr id="i40" class="altColor">
<td class="colFirst"><code><a href="../../trackselection/TrackSelectionParameters.html" title="class in com.google.android.exoplayer2.trackselection">TrackSelectionParameters</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getTrackSelectionParameters()">getTrackSelectionParameters</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns the parameters constraining the track selection.</div>
</td>
</tr>
<tr id="i40" class="altColor">
<tr id="i41" class="rowColor">
<td class="colFirst"><code><a href="../../video/VideoSize.html" title="class in com.google.android.exoplayer2.video">VideoSize</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getVideoSize()">getVideoSize</a></span>()</code></th>
<td class="colLast">
<div class="block">This method is not supported and returns <a href="../../video/VideoSize.html#UNKNOWN"><code>VideoSize.UNKNOWN</code></a>.</div>
</td>
</tr>
<tr id="i41" class="rowColor">
<tr id="i42" class="altColor">
<td class="colFirst"><code>float</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getVolume()">getVolume</a></span>()</code></th>
<td class="colLast">
<div class="block">This method is not supported and returns 1.</div>
</td>
</tr>
<tr id="i42" class="altColor">
<tr id="i43" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#increaseDeviceVolume()">increaseDeviceVolume</a></span>()</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i43" class="rowColor">
<tr id="i44" class="altColor">
<td class="colFirst"><code>boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#isCastSessionAvailable()">isCastSessionAvailable</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns whether a cast session is available.</div>
</td>
</tr>
<tr id="i44" class="altColor">
<tr id="i45" class="rowColor">
<td class="colFirst"><code>boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#isDeviceMuted()">isDeviceMuted</a></span>()</code></th>
<td class="colLast">
<div class="block">This method is not supported and always returns <code>false</code>.</div>
</td>
</tr>
<tr id="i45" class="rowColor">
<tr id="i46" class="altColor">
<td class="colFirst"><code>boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#isLoading()">isLoading</a></span>()</code></th>
<td class="colLast">
<div class="block">Whether the player is currently loading the source.</div>
</td>
</tr>
<tr id="i46" class="altColor">
<tr id="i47" class="rowColor">
<td class="colFirst"><code>boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#isPlayingAd()">isPlayingAd</a></span>()</code></th>
<td class="colLast">
<div class="block">Returns whether the player is currently playing an ad.</div>
</td>
</tr>
<tr id="i47" class="rowColor">
<tr id="i48" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#moveMediaItems(int,int,int)">moveMediaItems</a></span>&#8203;(int&nbsp;fromIndex,
int&nbsp;toIndex,
@ -619,28 +624,28 @@ extends <a href="../../BasePlayer.html" title="class in com.google.android.exopl
<div class="block">Moves the media item range to the new index.</div>
</td>
</tr>
<tr id="i48" class="altColor">
<tr id="i49" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#prepare()">prepare</a></span>()</code></th>
<td class="colLast">
<div class="block">Prepares the player.</div>
</td>
</tr>
<tr id="i49" class="rowColor">
<tr id="i50" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#release()">release</a></span>()</code></th>
<td class="colLast">
<div class="block">Releases the player.</div>
</td>
</tr>
<tr id="i50" class="altColor">
<tr id="i51" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#removeListener(com.google.android.exoplayer2.Player.Listener)">removeListener</a></span>&#8203;(<a href="../../Player.Listener.html" title="interface in com.google.android.exoplayer2">Player.Listener</a>&nbsp;listener)</code></th>
<td class="colLast">
<div class="block">Unregister a listener registered through <a href="../../Player.html#addListener(com.google.android.exoplayer2.Player.Listener)"><code>Player.addListener(Listener)</code></a>.</div>
</td>
</tr>
<tr id="i51" class="rowColor">
<tr id="i52" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#removeMediaItems(int,int)">removeMediaItems</a></span>&#8203;(int&nbsp;fromIndex,
int&nbsp;toIndex)</code></th>
@ -648,7 +653,7 @@ extends <a href="../../BasePlayer.html" title="class in com.google.android.exopl
<div class="block">Removes a range of media items from the playlist.</div>
</td>
</tr>
<tr id="i52" class="altColor">
<tr id="i53" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#seekTo(int,long)">seekTo</a></span>&#8203;(int&nbsp;mediaItemIndex,
long&nbsp;positionMs)</code></th>
@ -656,21 +661,21 @@ extends <a href="../../BasePlayer.html" title="class in com.google.android.exopl
<div class="block">Seeks to a position specified in milliseconds in the specified <a href="../../MediaItem.html" title="class in com.google.android.exoplayer2"><code>MediaItem</code></a>.</div>
</td>
</tr>
<tr id="i53" class="rowColor">
<tr id="i54" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setDeviceMuted(boolean)">setDeviceMuted</a></span>&#8203;(boolean&nbsp;muted)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i54" class="altColor">
<tr id="i55" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setDeviceVolume(int)">setDeviceVolume</a></span>&#8203;(int&nbsp;volume)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i55" class="rowColor">
<tr id="i56" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setMediaItems(java.util.List,boolean)">setMediaItems</a></span>&#8203;(<a href="https://developer.android.com/reference/java/util/List.html" title="class or interface in java.util" class="externalLink">List</a>&lt;<a href="../../MediaItem.html" title="class in com.google.android.exoplayer2" target="_top">MediaItem</a>&gt;&nbsp;mediaItems,
boolean&nbsp;resetPosition)</code></th>
@ -678,7 +683,7 @@ extends <a href="../../BasePlayer.html" title="class in com.google.android.exopl
<div class="block">Clears the playlist and adds the specified <a href="../../MediaItem.html" title="class in com.google.android.exoplayer2"><code>MediaItems</code></a>.</div>
</td>
</tr>
<tr id="i56" class="altColor">
<tr id="i57" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setMediaItems(java.util.List,int,long)">setMediaItems</a></span>&#8203;(<a href="https://developer.android.com/reference/java/util/List.html" title="class or interface in java.util" class="externalLink">List</a>&lt;<a href="../../MediaItem.html" title="class in com.google.android.exoplayer2" target="_top">MediaItem</a>&gt;&nbsp;mediaItems,
int&nbsp;startIndex,
@ -687,98 +692,98 @@ extends <a href="../../BasePlayer.html" title="class in com.google.android.exopl
<div class="block">Clears the playlist and adds the specified <a href="../../MediaItem.html" title="class in com.google.android.exoplayer2"><code>MediaItems</code></a>.</div>
</td>
</tr>
<tr id="i57" class="rowColor">
<tr id="i58" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setPlaybackParameters(com.google.android.exoplayer2.PlaybackParameters)">setPlaybackParameters</a></span>&#8203;(<a href="../../PlaybackParameters.html" title="class in com.google.android.exoplayer2">PlaybackParameters</a>&nbsp;playbackParameters)</code></th>
<td class="colLast">
<div class="block">Attempts to set the playback parameters.</div>
</td>
</tr>
<tr id="i58" class="altColor">
<tr id="i59" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setPlaylistMetadata(com.google.android.exoplayer2.MediaMetadata)">setPlaylistMetadata</a></span>&#8203;(<a href="../../MediaMetadata.html" title="class in com.google.android.exoplayer2">MediaMetadata</a>&nbsp;mediaMetadata)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i59" class="rowColor">
<tr id="i60" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setPlayWhenReady(boolean)">setPlayWhenReady</a></span>&#8203;(boolean&nbsp;playWhenReady)</code></th>
<td class="colLast">
<div class="block">Sets whether playback should proceed when <a href="../../Player.html#getPlaybackState()"><code>Player.getPlaybackState()</code></a> == <a href="../../Player.html#STATE_READY"><code>Player.STATE_READY</code></a>.</div>
</td>
</tr>
<tr id="i60" class="altColor">
<tr id="i61" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setRepeatMode(@com.google.android.exoplayer2.Player.RepeatModeint)">setRepeatMode</a></span>&#8203;(@com.google.android.exoplayer2.Player.RepeatMode int&nbsp;repeatMode)</code></th>
<td class="colLast">
<div class="block">Sets the <a href="../../Player.RepeatMode.html" title="annotation in com.google.android.exoplayer2"><code>Player.RepeatMode</code></a> to be used for playback.</div>
</td>
</tr>
<tr id="i61" class="rowColor">
<tr id="i62" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setSessionAvailabilityListener(com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener)">setSessionAvailabilityListener</a></span>&#8203;(<a href="SessionAvailabilityListener.html" title="interface in com.google.android.exoplayer2.ext.cast">SessionAvailabilityListener</a>&nbsp;listener)</code></th>
<td class="colLast">
<div class="block">Sets a listener for updates on the cast session availability.</div>
</td>
</tr>
<tr id="i62" class="altColor">
<tr id="i63" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setShuffleModeEnabled(boolean)">setShuffleModeEnabled</a></span>&#8203;(boolean&nbsp;shuffleModeEnabled)</code></th>
<td class="colLast">
<div class="block">Sets whether shuffling of media items is enabled.</div>
</td>
</tr>
<tr id="i63" class="rowColor">
<tr id="i64" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setTrackSelectionParameters(com.google.android.exoplayer2.trackselection.TrackSelectionParameters)">setTrackSelectionParameters</a></span>&#8203;(<a href="../../trackselection/TrackSelectionParameters.html" title="class in com.google.android.exoplayer2.trackselection">TrackSelectionParameters</a>&nbsp;parameters)</code></th>
<td class="colLast">
<div class="block">Sets the parameters constraining the track selection.</div>
</td>
</tr>
<tr id="i64" class="altColor">
<tr id="i65" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setVideoSurface(android.view.Surface)">setVideoSurface</a></span>&#8203;(<a href="https://developer.android.com/reference/android/view/Surface.html" title="class or interface in android.view" class="externalLink" target="_top">Surface</a>&nbsp;surface)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i65" class="rowColor">
<tr id="i66" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setVideoSurfaceHolder(android.view.SurfaceHolder)">setVideoSurfaceHolder</a></span>&#8203;(<a href="https://developer.android.com/reference/android/view/SurfaceHolder.html" title="class or interface in android.view" class="externalLink" target="_top">SurfaceHolder</a>&nbsp;surfaceHolder)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i66" class="altColor">
<tr id="i67" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setVideoSurfaceView(android.view.SurfaceView)">setVideoSurfaceView</a></span>&#8203;(<a href="https://developer.android.com/reference/android/view/SurfaceView.html" title="class or interface in android.view" class="externalLink" target="_top">SurfaceView</a>&nbsp;surfaceView)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i67" class="rowColor">
<tr id="i68" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setVideoTextureView(android.view.TextureView)">setVideoTextureView</a></span>&#8203;(<a href="https://developer.android.com/reference/android/view/TextureView.html" title="class or interface in android.view" class="externalLink" target="_top">TextureView</a>&nbsp;textureView)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i68" class="altColor">
<tr id="i69" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setVolume(float)">setVolume</a></span>&#8203;(float&nbsp;volume)</code></th>
<td class="colLast">
<div class="block">This method is not supported and does nothing.</div>
</td>
</tr>
<tr id="i69" class="rowColor">
<tr id="i70" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#stop()">stop</a></span>()</code></th>
<td class="colLast">
<div class="block">Stops playback without resetting the playlist.</div>
</td>
</tr>
<tr id="i70" class="altColor">
<tr id="i71" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#stop(boolean)">stop</a></span>&#8203;(boolean&nbsp;reset)</code></th>
<td class="colLast">
@ -1537,6 +1542,15 @@ public&nbsp;@com.google.android.exoplayer2.Player.RepeatMode int&nbsp;getRepeatM
it will be prioritised above the same field coming from static or timed metadata.</div>
</li>
</ul>
<a id="getMediaMetadataInternal()">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>getMediaMetadataInternal</h4>
<pre class="methodSignature">public&nbsp;<a href="../../MediaMetadata.html" title="class in com.google.android.exoplayer2">MediaMetadata</a>&nbsp;getMediaMetadataInternal()</pre>
</li>
</ul>
<a id="getPlaylistMetadata()">
<!-- -->
</a>

View file

@ -25,7 +25,7 @@
catch(err) {
}
//-->
var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9};
var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9};
var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"]};
var altColor = "altColor";
var rowColor = "rowColor";
@ -216,14 +216,6 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
</td>
</tr>
<tr id="i7" class="rowColor">
<td class="colFirst"><code>static boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#runUntilReceiveOffloadSchedulingEnabledNewState(com.google.android.exoplayer2.ExoPlayer)">runUntilReceiveOffloadSchedulingEnabledNewState</a></span>&#8203;(<a href="../ExoPlayer.html" title="interface in com.google.android.exoplayer2">ExoPlayer</a>&nbsp;player)</code></th>
<td class="colLast">
<div class="block">Runs tasks of the main <a href="https://developer.android.com/reference/android/os/Looper.html" title="class or interface in android.os" class="externalLink"><code>Looper</code></a> until <a href="../ExoPlayer.AudioOffloadListener.html#onExperimentalOffloadSchedulingEnabledChanged(boolean)" target="_top"><code>ExoPlayer.AudioOffloadListener.onExperimentalOffloadSchedulingEnabledChanged(boolean)</code></a> is called or a
playback error occurs.</div>
</td>
</tr>
<tr id="i8" class="altColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#runUntilRenderedFirstFrame(com.google.android.exoplayer2.ExoPlayer)">runUntilRenderedFirstFrame</a></span>&#8203;(<a href="../ExoPlayer.html" title="interface in com.google.android.exoplayer2">ExoPlayer</a>&nbsp;player)</code></th>
<td class="colLast">
@ -231,7 +223,7 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
callback is called or a playback error occurs.</div>
</td>
</tr>
<tr id="i9" class="rowColor">
<tr id="i8" class="altColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#runUntilSleepingForOffload(com.google.android.exoplayer2.ExoPlayer,boolean)">runUntilSleepingForOffload</a></span>&#8203;(<a href="../ExoPlayer.html" title="interface in com.google.android.exoplayer2">ExoPlayer</a>&nbsp;player,
boolean&nbsp;expectedSleepForOffload)</code></th>
@ -240,14 +232,14 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
playback error occurs.</div>
</td>
</tr>
<tr id="i10" class="altColor">
<tr id="i9" class="rowColor">
<td class="colFirst"><code>static <a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#runUntilTimelineChanged(com.google.android.exoplayer2.Player)">runUntilTimelineChanged</a></span>&#8203;(<a href="../Player.html" title="interface in com.google.android.exoplayer2">Player</a>&nbsp;player)</code></th>
<td class="colLast">
<div class="block">Runs tasks of the main <a href="https://developer.android.com/reference/android/os/Looper.html" title="class or interface in android.os" class="externalLink" target="_top"><code>Looper</code></a> until a timeline change or a playback error occurs.</div>
</td>
</tr>
<tr id="i11" class="rowColor">
<tr id="i10" class="altColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#runUntilTimelineChanged(com.google.android.exoplayer2.Player,com.google.android.exoplayer2.Timeline)">runUntilTimelineChanged</a></span>&#8203;(<a href="../Player.html" title="interface in com.google.android.exoplayer2">Player</a>&nbsp;player,
<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a>&nbsp;expectedTimeline)</code></th>
@ -416,29 +408,6 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
</dl>
</li>
</ul>
<a id="runUntilReceiveOffloadSchedulingEnabledNewState(com.google.android.exoplayer2.ExoPlayer)">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>runUntilReceiveOffloadSchedulingEnabledNewState</h4>
<pre class="methodSignature">public static&nbsp;boolean&nbsp;runUntilReceiveOffloadSchedulingEnabledNewState&#8203;(<a href="../ExoPlayer.html" title="interface in com.google.android.exoplayer2">ExoPlayer</a>&nbsp;player)
throws <a href="https://developer.android.com/reference/java/util/concurrent/TimeoutException.html" title="class or interface in java.util.concurrent" class="externalLink" target="_top">TimeoutException</a></pre>
<div class="block">Runs tasks of the main <a href="https://developer.android.com/reference/android/os/Looper.html" title="class or interface in android.os" class="externalLink"><code>Looper</code></a> until <a href="../ExoPlayer.AudioOffloadListener.html#onExperimentalOffloadSchedulingEnabledChanged(boolean)" target="_top"><code>ExoPlayer.AudioOffloadListener.onExperimentalOffloadSchedulingEnabledChanged(boolean)</code></a> is called or a
playback error occurs.
<p>If a playback error occurs it will be thrown wrapped in an <a href="https://developer.android.com/reference/java/lang/IllegalStateException.html" title="class or interface in java.lang" class="externalLink" target="_top"><code>IllegalStateException</code></a>.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>player</code> - The <a href="../Player.html" title="interface in com.google.android.exoplayer2"><code>Player</code></a>.</dd>
<dt><span class="returnLabel">Returns:</span></dt>
<dd>The new offloadSchedulingEnabled state.</dd>
<dt><span class="throwsLabel">Throws:</span></dt>
<dd><code><a href="https://developer.android.com/reference/java/util/concurrent/TimeoutException.html" title="class or interface in java.util.concurrent" class="externalLink">TimeoutException</a></code> - If the <a href="RobolectricUtil.html#DEFAULT_TIMEOUT_MS" target="_top"><code>default timeout</code></a> is
exceeded.</dd>
</dl>
</li>
</ul>
<a id="runUntilSleepingForOffload(com.google.android.exoplayer2.ExoPlayer,boolean)">
<!-- -->
</a>

View file

@ -139,7 +139,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
</dl>
<dl>
<dt>Direct Known Subclasses:</dt>
<dd><code><a href="../testutil/FakeMediaSource.InitialTimeline.html" title="class in com.google.android.exoplayer2.testutil">FakeMediaSource.InitialTimeline</a></code>, <code><a href="../testutil/NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil">NoUidTimeline</a></code>, <code><a href="ads/SinglePeriodAdTimeline.html" title="class in com.google.android.exoplayer2.source.ads">SinglePeriodAdTimeline</a></code></dd>
<dd><code><a href="../testutil/FakeMediaSource.InitialTimeline.html" title="class in com.google.android.exoplayer2.testutil">FakeMediaSource.InitialTimeline</a></code>, <code><a href="ads/SinglePeriodAdTimeline.html" title="class in com.google.android.exoplayer2.source.ads">SinglePeriodAdTimeline</a></code></dd>
</dl>
<hr>
<pre>public abstract class <span class="typeNameLabel">ForwardingTimeline</span>

View file

@ -253,6 +253,14 @@ extends <a href="../Timeline.html" title="class in com.google.android.exoplayer2
</td>
</tr>
<tr class="rowColor">
<th class="colConstructorName" scope="row"><code><span class="memberNameLink"><a href="#%3Cinit%3E(java.lang.Object%5B%5D,com.google.android.exoplayer2.source.ShuffleOrder,com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition...)">FakeTimeline</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/Object.html" title="class or interface in java.lang" class="externalLink" target="_top">Object</a>[]&nbsp;manifests,
<a href="../source/ShuffleOrder.html" title="interface in com.google.android.exoplayer2.source">ShuffleOrder</a>&nbsp;shuffleOrder,
<a href="FakeTimeline.TimelineWindowDefinition.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline.TimelineWindowDefinition</a>...&nbsp;windowDefinitions)</code></th>
<td class="colLast">
<div class="block">Creates a fake timeline with the given window definitions and <a href="../source/ShuffleOrder.html" title="interface in com.google.android.exoplayer2.source"><code>ShuffleOrder</code></a>.</div>
</td>
</tr>
<tr class="altColor">
<th class="colConstructorName" scope="row"><code><span class="memberNameLink"><a href="#%3Cinit%3E(java.lang.Object%5B%5D,com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition...)">FakeTimeline</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/Object.html" title="class or interface in java.lang" class="externalLink" target="_top">Object</a>[]&nbsp;manifests,
<a href="FakeTimeline.TimelineWindowDefinition.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline.TimelineWindowDefinition</a>...&nbsp;windowDefinitions)</code></th>
<td class="colLast">
@ -481,7 +489,7 @@ extends <a href="../Timeline.html" title="class in com.google.android.exoplayer2
<a id="&lt;init&gt;(java.lang.Object[],com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition...)">
<!-- -->
</a>
<ul class="blockListLast">
<ul class="blockList">
<li class="blockList">
<h4>FakeTimeline</h4>
<pre>public&nbsp;FakeTimeline&#8203;(<a href="https://developer.android.com/reference/java/lang/Object.html" title="class or interface in java.lang" class="externalLink" target="_top">Object</a>[]&nbsp;manifests,
@ -493,6 +501,22 @@ extends <a href="../Timeline.html" title="class in com.google.android.exoplayer2
</dl>
</li>
</ul>
<a id="&lt;init&gt;(java.lang.Object[],com.google.android.exoplayer2.source.ShuffleOrder,com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition...)">
<!-- -->
</a>
<ul class="blockListLast">
<li class="blockList">
<h4>FakeTimeline</h4>
<pre>public&nbsp;FakeTimeline&#8203;(<a href="https://developer.android.com/reference/java/lang/Object.html" title="class or interface in java.lang" class="externalLink" target="_top">Object</a>[]&nbsp;manifests,
<a href="../source/ShuffleOrder.html" title="interface in com.google.android.exoplayer2.source">ShuffleOrder</a>&nbsp;shuffleOrder,
<a href="FakeTimeline.TimelineWindowDefinition.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline.TimelineWindowDefinition</a>...&nbsp;windowDefinitions)</pre>
<div class="block">Creates a fake timeline with the given window definitions and <a href="../source/ShuffleOrder.html" title="interface in com.google.android.exoplayer2.source"><code>ShuffleOrder</code></a>.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>windowDefinitions</code> - A list of <a href="FakeTimeline.TimelineWindowDefinition.html" title="class in com.google.android.exoplayer2.testutil"><code>FakeTimeline.TimelineWindowDefinition</code></a>s.</dd>
</dl>
</li>
</ul>
</li>
</ul>
</section>

View file

@ -1,440 +0,0 @@
<!DOCTYPE HTML>
<!-- NewPage -->
<html lang="en">
<head><!-- start favicons snippet, use https://realfavicongenerator.net/ --><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="manifest" href="/assets/site.webmanifest"><link rel="mask-icon" href="/assets/safari-pinned-tab.svg" color="#fc4d50"><link rel="shortcut icon" href="/assets/favicon.ico"><meta name="msapplication-TileColor" content="#ffc40d"><meta name="msapplication-config" content="/assets/browserconfig.xml"><meta name="theme-color" content="#ffffff"><!-- end favicons snippet -->
<title>NoUidTimeline (ExoPlayer library)</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="../../../../../stylesheet.css" title="Style">
<link rel="stylesheet" type="text/css" href="../../../../../jquery/jquery-ui.css" title="Style">
<script type="text/javascript" src="../../../../../script.js"></script>
<script type="text/javascript" src="../../../../../jquery/jszip/dist/jszip.min.js"></script>
<script type="text/javascript" src="../../../../../jquery/jszip-utils/dist/jszip-utils.min.js"></script>
<!--[if IE]>
<script type="text/javascript" src="../../../../../jquery/jszip-utils/dist/jszip-utils-ie.min.js"></script>
<![endif]-->
<script type="text/javascript" src="../../../../../jquery/jquery-3.5.1.js"></script>
<script type="text/javascript" src="../../../../../jquery/jquery-ui.js"></script>
</head>
<body>
<script type="text/javascript"><!--
try {
if (location.href.indexOf('is-external=true') == -1) {
parent.document.title="NoUidTimeline (ExoPlayer library)";
}
}
catch(err) {
}
//-->
var data = {"i0":10,"i1":10};
var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]};
var altColor = "altColor";
var rowColor = "rowColor";
var tableTab = "tableTab";
var activeTableTab = "activeTableTab";
var pathtoroot = "../../../../../";
var useModuleDirectories = false;
loadScripts(document, 'script');</script>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
<header role="banner">
<nav role="navigation">
<div class="fixedNav">
<!-- ========= START OF TOP NAVBAR ======= -->
<div class="topNav"><a id="navbar.top">
<!-- -->
</a>
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
<a id="navbar.top.firstrow">
<!-- -->
</a>
<ul class="navList" title="Navigation">
<li><a href="../../../../../index.html">Overview</a></li>
<li><a href="package-summary.html">Package</a></li>
<li class="navBarCell1Rev">Class</li>
<li><a href="package-tree.html">Tree</a></li>
<li><a href="../../../../../deprecated-list.html">Deprecated</a></li>
<li><a href="../../../../../index-all.html">Index</a></li>
<li><a href="../../../../../help-doc.html">Help</a></li>
</ul>
</div>
<div class="subNav">
<ul class="navList" id="allclasses_navbar_top">
<li><a href="../../../../../allclasses.html">All&nbsp;Classes</a></li>
</ul>
<ul class="navListSearch">
<li><label for="search">SEARCH:</label>
<input type="text" id="search" value="search" disabled="disabled">
<input type="reset" id="reset" value="reset" disabled="disabled">
</li>
</ul>
<div>
<script type="text/javascript"><!--
allClassesLink = document.getElementById("allclasses_navbar_top");
if(window==top) {
allClassesLink.style.display = "block";
}
else {
allClassesLink.style.display = "none";
}
//-->
</script>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
</div>
<div>
<ul class="subNavList">
<li>Summary:&nbsp;</li>
<li><a href="#nested.class.summary">Nested</a>&nbsp;|&nbsp;</li>
<li><a href="#field.summary">Field</a>&nbsp;|&nbsp;</li>
<li><a href="#constructor.summary">Constr</a>&nbsp;|&nbsp;</li>
<li><a href="#method.summary">Method</a></li>
</ul>
<ul class="subNavList">
<li>Detail:&nbsp;</li>
<li>Field&nbsp;|&nbsp;</li>
<li><a href="#constructor.detail">Constr</a>&nbsp;|&nbsp;</li>
<li><a href="#method.detail">Method</a></li>
</ul>
</div>
<a id="skip.navbar.top">
<!-- -->
</a></div>
<!-- ========= END OF TOP NAVBAR ========= -->
</div>
<div class="navPadding">&nbsp;</div>
<script type="text/javascript"><!--
$('.navPadding').css('padding-top', $('.fixedNav').css("height"));
//-->
</script>
</nav>
</header>
<!-- ======== START OF CLASS DATA ======== -->
<main role="main">
<div class="header">
<div class="subTitle"><span class="packageLabelInType">Package</span>&nbsp;<a href="package-summary.html">com.google.android.exoplayer2.testutil</a></div>
<h2 title="Class NoUidTimeline" class="title">Class NoUidTimeline</h2>
</div>
<div class="contentContainer">
<ul class="inheritance">
<li><a href="https://developer.android.com/reference/java/lang/Object.html" title="class or interface in java.lang" class="externalLink" target="_top">java.lang.Object</a></li>
<li>
<ul class="inheritance">
<li><a href="../Timeline.html" title="class in com.google.android.exoplayer2">com.google.android.exoplayer2.Timeline</a></li>
<li>
<ul class="inheritance">
<li><a href="../source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source">com.google.android.exoplayer2.source.ForwardingTimeline</a></li>
<li>
<ul class="inheritance">
<li>com.google.android.exoplayer2.testutil.NoUidTimeline</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="description">
<ul class="blockList">
<li class="blockList">
<dl>
<dt>All Implemented Interfaces:</dt>
<dd><code><a href="../Bundleable.html" title="interface in com.google.android.exoplayer2">Bundleable</a></code></dd>
</dl>
<hr>
<pre>public class <span class="typeNameLabel">NoUidTimeline</span>
extends <a href="../source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source">ForwardingTimeline</a></pre>
<div class="block">A timeline which wraps another timeline and overrides all window and period uids to 0. This is
useful for testing timeline equality without taking uids into account.</div>
</li>
</ul>
</div>
<div class="summary">
<ul class="blockList">
<li class="blockList">
<!-- ======== NESTED CLASS SUMMARY ======== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="nested.class.summary">
<!-- -->
</a>
<h3>Nested Class Summary</h3>
<ul class="blockList">
<li class="blockList"><a id="nested.classes.inherited.from.class.com.google.android.exoplayer2.Timeline">
<!-- -->
</a>
<h3>Nested classes/interfaces inherited from class&nbsp;com.google.android.exoplayer2.<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a></h3>
<code><a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2">Timeline.Period</a>, <a href="../Timeline.RemotableTimeline.html" title="class in com.google.android.exoplayer2">Timeline.RemotableTimeline</a>, <a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2">Timeline.Window</a></code></li>
</ul>
<ul class="blockList">
<li class="blockList"><a id="nested.classes.inherited.from.class.com.google.android.exoplayer2.Bundleable">
<!-- -->
</a>
<h3>Nested classes/interfaces inherited from interface&nbsp;com.google.android.exoplayer2.<a href="../Bundleable.html" title="interface in com.google.android.exoplayer2">Bundleable</a></h3>
<code><a href="../Bundleable.Creator.html" title="interface in com.google.android.exoplayer2">Bundleable.Creator</a>&lt;<a href="../Bundleable.Creator.html" title="type parameter in Bundleable.Creator">T</a> extends <a href="../Bundleable.html" title="interface in com.google.android.exoplayer2">Bundleable</a>&gt;</code></li>
</ul>
</li>
</ul>
</section>
<!-- =========== FIELD SUMMARY =========== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="field.summary">
<!-- -->
</a>
<h3>Field Summary</h3>
<ul class="blockList">
<li class="blockList"><a id="fields.inherited.from.class.com.google.android.exoplayer2.source.ForwardingTimeline">
<!-- -->
</a>
<h3>Fields inherited from class&nbsp;com.google.android.exoplayer2.source.<a href="../source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source">ForwardingTimeline</a></h3>
<code><a href="../source/ForwardingTimeline.html#timeline">timeline</a></code></li>
</ul>
<ul class="blockList">
<li class="blockList"><a id="fields.inherited.from.class.com.google.android.exoplayer2.Timeline">
<!-- -->
</a>
<h3>Fields inherited from class&nbsp;com.google.android.exoplayer2.<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a></h3>
<code><a href="../Timeline.html#CREATOR">CREATOR</a>, <a href="../Timeline.html#EMPTY">EMPTY</a></code></li>
</ul>
</li>
</ul>
</section>
<!-- ======== CONSTRUCTOR SUMMARY ======== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="constructor.summary">
<!-- -->
</a>
<h3>Constructor Summary</h3>
<table class="memberSummary">
<caption><span>Constructors</span><span class="tabEnd">&nbsp;</span></caption>
<tr>
<th class="colFirst" scope="col">Constructor</th>
<th class="colLast" scope="col">Description</th>
</tr>
<tr class="altColor">
<th class="colConstructorName" scope="row"><code><span class="memberNameLink"><a href="#%3Cinit%3E(com.google.android.exoplayer2.Timeline)">NoUidTimeline</a></span>&#8203;(<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a>&nbsp;timeline)</code></th>
<td class="colLast">
<div class="block">Creates an instance.</div>
</td>
</tr>
</table>
</li>
</ul>
</section>
<!-- ========== METHOD SUMMARY =========== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="method.summary">
<!-- -->
</a>
<h3>Method Summary</h3>
<table class="memberSummary">
<caption><span id="t0" class="activeTableTab"><span>All Methods</span><span class="tabEnd">&nbsp;</span></span><span id="t2" class="tableTab"><span><a href="javascript:show(2);">Instance Methods</a></span><span class="tabEnd">&nbsp;</span></span><span id="t4" class="tableTab"><span><a href="javascript:show(8);">Concrete Methods</a></span><span class="tabEnd">&nbsp;</span></span></caption>
<tr>
<th class="colFirst" scope="col">Modifier and Type</th>
<th class="colSecond" scope="col">Method</th>
<th class="colLast" scope="col">Description</th>
</tr>
<tr id="i0" class="altColor">
<td class="colFirst"><code><a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2">Timeline.Period</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getPeriod(int,com.google.android.exoplayer2.Timeline.Period,boolean)">getPeriod</a></span>&#8203;(int&nbsp;periodIndex,
<a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2">Timeline.Period</a>&nbsp;period,
boolean&nbsp;setIds)</code></th>
<td class="colLast">
<div class="block">Populates a <a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2"><code>Timeline.Period</code></a> with data for the period at the specified index.</div>
</td>
</tr>
<tr id="i1" class="rowColor">
<td class="colFirst"><code><a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2">Timeline.Window</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#getWindow(int,com.google.android.exoplayer2.Timeline.Window,long)">getWindow</a></span>&#8203;(int&nbsp;windowIndex,
<a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2">Timeline.Window</a>&nbsp;window,
long&nbsp;defaultPositionProjectionUs)</code></th>
<td class="colLast">
<div class="block">Populates a <a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2"><code>Timeline.Window</code></a> with data for the window at the specified index.</div>
</td>
</tr>
</table>
<ul class="blockList">
<li class="blockList"><a id="methods.inherited.from.class.com.google.android.exoplayer2.source.ForwardingTimeline">
<!-- -->
</a>
<h3>Methods inherited from class&nbsp;com.google.android.exoplayer2.source.<a href="../source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source">ForwardingTimeline</a></h3>
<code><a href="../source/ForwardingTimeline.html#getFirstWindowIndex(boolean)">getFirstWindowIndex</a>, <a href="../source/ForwardingTimeline.html#getIndexOfPeriod(java.lang.Object)">getIndexOfPeriod</a>, <a href="../source/ForwardingTimeline.html#getLastWindowIndex(boolean)">getLastWindowIndex</a>, <a href="../source/ForwardingTimeline.html#getNextWindowIndex(int,@com.google.android.exoplayer2.Player.RepeatModeint,boolean)">getNextWindowIndex</a>, <a href="../source/ForwardingTimeline.html#getPeriodCount()">getPeriodCount</a>, <a href="../source/ForwardingTimeline.html#getPreviousWindowIndex(int,@com.google.android.exoplayer2.Player.RepeatModeint,boolean)">getPreviousWindowIndex</a>, <a href="../source/ForwardingTimeline.html#getUidOfPeriod(int)">getUidOfPeriod</a>, <a href="../source/ForwardingTimeline.html#getWindowCount()">getWindowCount</a></code></li>
</ul>
<ul class="blockList">
<li class="blockList"><a id="methods.inherited.from.class.com.google.android.exoplayer2.Timeline">
<!-- -->
</a>
<h3>Methods inherited from class&nbsp;com.google.android.exoplayer2.<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a></h3>
<code><a href="../Timeline.html#equals(java.lang.Object)">equals</a>, <a href="../Timeline.html#getNextPeriodIndex(int,com.google.android.exoplayer2.Timeline.Period,com.google.android.exoplayer2.Timeline.Window,@com.google.android.exoplayer2.Player.RepeatModeint,boolean)">getNextPeriodIndex</a>, <a href="../Timeline.html#getPeriod(int,com.google.android.exoplayer2.Timeline.Period)">getPeriod</a>, <a href="../Timeline.html#getPeriodByUid(java.lang.Object,com.google.android.exoplayer2.Timeline.Period)">getPeriodByUid</a>, <a href="../Timeline.html#getPeriodPosition(com.google.android.exoplayer2.Timeline.Window,com.google.android.exoplayer2.Timeline.Period,int,long)">getPeriodPosition</a>, <a href="../Timeline.html#getPeriodPosition(com.google.android.exoplayer2.Timeline.Window,com.google.android.exoplayer2.Timeline.Period,int,long,long)">getPeriodPosition</a>, <a href="../Timeline.html#getPeriodPositionUs(com.google.android.exoplayer2.Timeline.Window,com.google.android.exoplayer2.Timeline.Period,int,long)">getPeriodPositionUs</a>, <a href="../Timeline.html#getPeriodPositionUs(com.google.android.exoplayer2.Timeline.Window,com.google.android.exoplayer2.Timeline.Period,int,long,long)">getPeriodPositionUs</a>, <a href="../Timeline.html#getWindow(int,com.google.android.exoplayer2.Timeline.Window)">getWindow</a>, <a href="../Timeline.html#hashCode()">hashCode</a>, <a href="../Timeline.html#isEmpty()">isEmpty</a>, <a href="../Timeline.html#isLastPeriod(int,com.google.android.exoplayer2.Timeline.Period,com.google.android.exoplayer2.Timeline.Window,@com.google.android.exoplayer2.Player.RepeatModeint,boolean)">isLastPeriod</a>, <a href="../Timeline.html#toBundle()">toBundle</a>, <a href="../Timeline.html#toBundle(boolean)">toBundle</a></code></li>
</ul>
<ul class="blockList">
<li class="blockList"><a id="methods.inherited.from.class.java.lang.Object">
<!-- -->
</a>
<h3>Methods inherited from class&nbsp;java.lang.<a href="https://developer.android.com/reference/java/lang/Object.html" title="class or interface in java.lang" class="externalLink" target="_top">Object</a></h3>
<code><a href="https://developer.android.com/reference/java/lang/Object.html#clone()" title="class or interface in java.lang" class="externalLink">clone</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#finalize()" title="class or interface in java.lang" class="externalLink">finalize</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#getClass()" title="class or interface in java.lang" class="externalLink">getClass</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#notify()" title="class or interface in java.lang" class="externalLink">notify</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#notifyAll()" title="class or interface in java.lang" class="externalLink">notifyAll</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#toString()" title="class or interface in java.lang" class="externalLink">toString</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#wait()" title="class or interface in java.lang" class="externalLink">wait</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#wait(long)" title="class or interface in java.lang" class="externalLink">wait</a>, <a href="https://developer.android.com/reference/java/lang/Object.html?is-external=true#wait(long,int)" title="class or interface in java.lang" class="externalLink" target="_top">wait</a></code></li>
</ul>
</li>
</ul>
</section>
</li>
</ul>
</div>
<div class="details">
<ul class="blockList">
<li class="blockList">
<!-- ========= CONSTRUCTOR DETAIL ======== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="constructor.detail">
<!-- -->
</a>
<h3>Constructor Detail</h3>
<a id="&lt;init&gt;(com.google.android.exoplayer2.Timeline)">
<!-- -->
</a>
<ul class="blockListLast">
<li class="blockList">
<h4>NoUidTimeline</h4>
<pre>public&nbsp;NoUidTimeline&#8203;(<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a>&nbsp;timeline)</pre>
<div class="block">Creates an instance.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>timeline</code> - The underlying timeline.</dd>
</dl>
</li>
</ul>
</li>
</ul>
</section>
<!-- ============ METHOD DETAIL ========== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="method.detail">
<!-- -->
</a>
<h3>Method Detail</h3>
<a id="getWindow(int,com.google.android.exoplayer2.Timeline.Window,long)">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>getWindow</h4>
<pre class="methodSignature">public&nbsp;<a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2">Timeline.Window</a>&nbsp;getWindow&#8203;(int&nbsp;windowIndex,
<a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2">Timeline.Window</a>&nbsp;window,
long&nbsp;defaultPositionProjectionUs)</pre>
<div class="block"><span class="descfrmTypeLabel">Description copied from class:&nbsp;<code><a href="../Timeline.html#getWindow(int,com.google.android.exoplayer2.Timeline.Window,long)">Timeline</a></code></span></div>
<div class="block">Populates a <a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2"><code>Timeline.Window</code></a> with data for the window at the specified index.</div>
<dl>
<dt><span class="overrideSpecifyLabel">Overrides:</span></dt>
<dd><code><a href="../source/ForwardingTimeline.html#getWindow(int,com.google.android.exoplayer2.Timeline.Window,long)">getWindow</a></code>&nbsp;in class&nbsp;<code><a href="../source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source">ForwardingTimeline</a></code></dd>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>windowIndex</code> - The index of the window.</dd>
<dd><code>window</code> - The <a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2"><code>Timeline.Window</code></a> to populate. Must not be null.</dd>
<dd><code>defaultPositionProjectionUs</code> - A duration into the future that the populated window's
default start position should be projected.</dd>
<dt><span class="returnLabel">Returns:</span></dt>
<dd>The populated <a href="../Timeline.Window.html" title="class in com.google.android.exoplayer2"><code>Timeline.Window</code></a>, for convenience.</dd>
</dl>
</li>
</ul>
<a id="getPeriod(int,com.google.android.exoplayer2.Timeline.Period,boolean)">
<!-- -->
</a>
<ul class="blockListLast">
<li class="blockList">
<h4>getPeriod</h4>
<pre class="methodSignature">public&nbsp;<a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2">Timeline.Period</a>&nbsp;getPeriod&#8203;(int&nbsp;periodIndex,
<a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2">Timeline.Period</a>&nbsp;period,
boolean&nbsp;setIds)</pre>
<div class="block"><span class="descfrmTypeLabel">Description copied from class:&nbsp;<code><a href="../Timeline.html#getPeriod(int,com.google.android.exoplayer2.Timeline.Period,boolean)">Timeline</a></code></span></div>
<div class="block">Populates a <a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2"><code>Timeline.Period</code></a> with data for the period at the specified index.</div>
<dl>
<dt><span class="overrideSpecifyLabel">Overrides:</span></dt>
<dd><code><a href="../source/ForwardingTimeline.html#getPeriod(int,com.google.android.exoplayer2.Timeline.Period,boolean)">getPeriod</a></code>&nbsp;in class&nbsp;<code><a href="../source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source">ForwardingTimeline</a></code></dd>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>periodIndex</code> - The index of the period.</dd>
<dd><code>period</code> - The <a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2"><code>Timeline.Period</code></a> to populate. Must not be null.</dd>
<dd><code>setIds</code> - Whether <a href="../Timeline.Period.html#id"><code>Timeline.Period.id</code></a> and <a href="../Timeline.Period.html#uid"><code>Timeline.Period.uid</code></a> should be populated. If false,
the fields will be set to null. The caller should pass false for efficiency reasons unless
the fields are required.</dd>
<dt><span class="returnLabel">Returns:</span></dt>
<dd>The populated <a href="../Timeline.Period.html" title="class in com.google.android.exoplayer2"><code>Timeline.Period</code></a>, for convenience.</dd>
</dl>
</li>
</ul>
</li>
</ul>
</section>
</li>
</ul>
</div>
</div>
</main>
<!-- ========= END OF CLASS DATA ========= -->
<footer role="contentinfo">
<nav role="navigation">
<!-- ======= START OF BOTTOM NAVBAR ====== -->
<div class="bottomNav"><a id="navbar.bottom">
<!-- -->
</a>
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
<a id="navbar.bottom.firstrow">
<!-- -->
</a>
<ul class="navList" title="Navigation">
<li><a href="../../../../../index.html">Overview</a></li>
<li><a href="package-summary.html">Package</a></li>
<li class="navBarCell1Rev">Class</li>
<li><a href="package-tree.html">Tree</a></li>
<li><a href="../../../../../deprecated-list.html">Deprecated</a></li>
<li><a href="../../../../../index-all.html">Index</a></li>
<li><a href="../../../../../help-doc.html">Help</a></li>
</ul>
</div>
<div class="subNav">
<ul class="navList" id="allclasses_navbar_bottom">
<li><a href="../../../../../allclasses.html">All&nbsp;Classes</a></li>
</ul>
<div>
<script type="text/javascript"><!--
allClassesLink = document.getElementById("allclasses_navbar_bottom");
if(window==top) {
allClassesLink.style.display = "block";
}
else {
allClassesLink.style.display = "none";
}
//-->
</script>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
</div>
<div>
<ul class="subNavList">
<li>Summary:&nbsp;</li>
<li><a href="#nested.class.summary">Nested</a>&nbsp;|&nbsp;</li>
<li><a href="#field.summary">Field</a>&nbsp;|&nbsp;</li>
<li><a href="#constructor.summary">Constr</a>&nbsp;|&nbsp;</li>
<li><a href="#method.summary">Method</a></li>
</ul>
<ul class="subNavList">
<li>Detail:&nbsp;</li>
<li>Field&nbsp;|&nbsp;</li>
<li><a href="#constructor.detail">Constr</a>&nbsp;|&nbsp;</li>
<li><a href="#method.detail">Method</a></li>
</ul>
</div>
<a id="skip.navbar.bottom">
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
</nav>
</footer>
</body>
</html>

View file

@ -867,7 +867,8 @@ public&nbsp;@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int&
<ul class="blockList">
<li class="blockList">
<h4>getPlayerError</h4>
<pre class="methodSignature">public&nbsp;<a href="../PlaybackException.html" title="class in com.google.android.exoplayer2">PlaybackException</a>&nbsp;getPlayerError()</pre>
<pre class="methodSignature">@Nullable
public&nbsp;<a href="../PlaybackException.html" title="class in com.google.android.exoplayer2">PlaybackException</a>&nbsp;getPlayerError()</pre>
<div class="block"><span class="descfrmTypeLabel">Description copied from interface:&nbsp;<code><a href="../Player.html#getPlayerError()">Player</a></code></span></div>
<div class="block">Returns the error that caused playback to fail. This is the same error that will have been
reported via <a href="../Player.Listener.html#onPlayerError(com.google.android.exoplayer2.PlaybackException)"><code>Player.Listener.onPlayerError(PlaybackException)</code></a> at the time of failure. It can

View file

@ -25,7 +25,7 @@
catch(err) {
}
//-->
var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9,"i12":9,"i13":9,"i14":9,"i15":9,"i16":9,"i17":9,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9,"i23":9,"i24":9};
var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9,"i12":9,"i13":9,"i14":9,"i15":9,"i16":9,"i17":9,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9,"i23":9,"i24":9,"i25":9};
var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"]};
var altColor = "altColor";
var rowColor = "rowColor";
@ -360,6 +360,14 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
end-of-input is reached.</div>
</td>
</tr>
<tr id="i25" class="rowColor">
<td class="colFirst"><code>static boolean</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#timelinesAreSame(com.google.android.exoplayer2.Timeline,com.google.android.exoplayer2.Timeline)">timelinesAreSame</a></span>&#8203;(<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a>&nbsp;thisTimeline,
<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a>&nbsp;thatTimeline)</code></th>
<td class="colLast">
<div class="block">Returns true if <code>thisTimeline</code> is equal to <code>thatTimeline</code>, ignoring <a href="../Timeline.Window.html#uid"><code>Timeline.Window.uid</code></a> and <a href="../Timeline.Period.html#uid"><code>Timeline.Period.uid</code></a> values, and shuffle order.</div>
</td>
</tr>
</table>
<ul class="blockList">
<li class="blockList"><a id="methods.inherited.from.class.java.lang.Object">
@ -618,8 +626,12 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
<pre class="methodSignature">public static&nbsp;void&nbsp;assertTimelinesSame&#8203;(<a href="https://developer.android.com/reference/java/util/List.html" title="class or interface in java.util" class="externalLink">List</a>&lt;<a href="../Timeline.html" title="class in com.google.android.exoplayer2" target="_top">Timeline</a>&gt;&nbsp;actualTimelines,
<a href="https://developer.android.com/reference/java/util/List.html" title="class or interface in java.util" class="externalLink">List</a>&lt;<a href="../Timeline.html" title="class in com.google.android.exoplayer2" target="_top">Timeline</a>&gt;&nbsp;expectedTimelines)</pre>
<div class="block">Asserts that the actual timelines are the same to the expected timelines. This assert differs
from testing equality by not comparing period ids which may be different due to id mapping of
child source period ids.</div>
from testing equality by not comparing:
<ul>
<li>Period IDs, which may be different due to ID mapping of child source period IDs.
<li>Shuffle order, which by default is random and non-deterministic.
</ul></div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>actualTimelines</code> - A list of actual <a href="../Timeline.html" title="class in com.google.android.exoplayer2"><code>timelines</code></a>.</dd>
@ -627,6 +639,17 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
</dl>
</li>
</ul>
<a id="timelinesAreSame(com.google.android.exoplayer2.Timeline,com.google.android.exoplayer2.Timeline)">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>timelinesAreSame</h4>
<pre class="methodSignature">public static&nbsp;boolean&nbsp;timelinesAreSame&#8203;(<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a>&nbsp;thisTimeline,
<a href="../Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a>&nbsp;thatTimeline)</pre>
<div class="block">Returns true if <code>thisTimeline</code> is equal to <code>thatTimeline</code>, ignoring <a href="../Timeline.Window.html#uid"><code>Timeline.Window.uid</code></a> and <a href="../Timeline.Period.html#uid"><code>Timeline.Period.uid</code></a> values, and shuffle order.</div>
</li>
</ul>
<a id="assertDataSourceContent(com.google.android.exoplayer2.upstream.DataSource,com.google.android.exoplayer2.upstream.DataSpec,byte[],boolean)">
<!-- -->
</a>

View file

@ -800,57 +800,51 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
</td>
</tr>
<tr class="altColor">
<th class="colFirst" scope="row"><a href="NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil">NoUidTimeline</a></th>
<td class="colLast">
<div class="block">A timeline which wraps another timeline and overrides all window and period uids to 0.</div>
</td>
</tr>
<tr class="rowColor">
<th class="colFirst" scope="row"><a href="StubExoPlayer.html" title="class in com.google.android.exoplayer2.testutil">StubExoPlayer</a></th>
<td class="colLast">
<div class="block">An abstract <a href="../ExoPlayer.html" title="interface in com.google.android.exoplayer2"><code>ExoPlayer</code></a> implementation that throws <a href="https://developer.android.com/reference/java/lang/UnsupportedOperationException.html" title="class or interface in java.lang" class="externalLink" target="_top"><code>UnsupportedOperationException</code></a>
from every method.</div>
</td>
</tr>
<tr class="altColor">
<tr class="rowColor">
<th class="colFirst" scope="row"><a href="StubPlayer.html" title="class in com.google.android.exoplayer2.testutil">StubPlayer</a></th>
<td class="colLast">
<div class="block">An abstract <a href="../Player.html" title="interface in com.google.android.exoplayer2"><code>Player</code></a> implementation that throws <a href="https://developer.android.com/reference/java/lang/UnsupportedOperationException.html" title="class or interface in java.lang" class="externalLink" target="_top"><code>UnsupportedOperationException</code></a> from
every method.</div>
</td>
</tr>
<tr class="rowColor">
<tr class="altColor">
<th class="colFirst" scope="row"><a href="TestExoPlayerBuilder.html" title="class in com.google.android.exoplayer2.testutil">TestExoPlayerBuilder</a></th>
<td class="colLast">
<div class="block">A builder of <a href="../ExoPlayer.html" title="interface in com.google.android.exoplayer2"><code>ExoPlayer</code></a> instances for testing.</div>
</td>
</tr>
<tr class="altColor">
<tr class="rowColor">
<th class="colFirst" scope="row"><a href="TestUtil.html" title="class in com.google.android.exoplayer2.testutil">TestUtil</a></th>
<td class="colLast">
<div class="block">Utility methods for tests.</div>
</td>
</tr>
<tr class="rowColor">
<tr class="altColor">
<th class="colFirst" scope="row"><a href="TimelineAsserts.html" title="class in com.google.android.exoplayer2.testutil">TimelineAsserts</a></th>
<td class="colLast">
<div class="block">Assertion methods for <a href="../Timeline.html" title="class in com.google.android.exoplayer2"><code>Timeline</code></a>.</div>
</td>
</tr>
<tr class="altColor">
<tr class="rowColor">
<th class="colFirst" scope="row"><a href="WebServerDispatcher.html" title="class in com.google.android.exoplayer2.testutil">WebServerDispatcher</a></th>
<td class="colLast">
<div class="block">A <code>Dispatcher</code> for <code>MockWebServer</code> that allows per-path
customisation of the static data served.</div>
</td>
</tr>
<tr class="rowColor">
<tr class="altColor">
<th class="colFirst" scope="row"><a href="WebServerDispatcher.Resource.html" title="class in com.google.android.exoplayer2.testutil">WebServerDispatcher.Resource</a></th>
<td class="colLast">
<div class="block">A resource served by <a href="WebServerDispatcher.html" title="class in com.google.android.exoplayer2.testutil"><code>WebServerDispatcher</code></a>.</div>
</td>
</tr>
<tr class="altColor">
<tr class="rowColor">
<th class="colFirst" scope="row"><a href="WebServerDispatcher.Resource.Builder.html" title="class in com.google.android.exoplayer2.testutil">WebServerDispatcher.Resource.Builder</a></th>
<td class="colLast">
<div class="block">Builder for <a href="WebServerDispatcher.Resource.html" title="class in com.google.android.exoplayer2.testutil"><code>WebServerDispatcher.Resource</code></a>.</div>

View file

@ -304,7 +304,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<li class="circle">com.google.android.exoplayer2.source.<a href="../source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source"><span class="typeNameLink">ForwardingTimeline</span></a>
<ul>
<li class="circle">com.google.android.exoplayer2.testutil.<a href="FakeMediaSource.InitialTimeline.html" title="class in com.google.android.exoplayer2.testutil"><span class="typeNameLink">FakeMediaSource.InitialTimeline</span></a></li>
<li class="circle">com.google.android.exoplayer2.testutil.<a href="NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil"><span class="typeNameLink">NoUidTimeline</span></a></li>
</ul>
</li>
</ul>

View file

@ -203,7 +203,7 @@ default&nbsp;void&nbsp;onCues&#8203;(<a href="https://developer.android.com/refe
<div class="block">Called when there is a change in the <a href="CueGroup.html" title="class in com.google.android.exoplayer2.text"><code>CueGroup</code></a>.
<p>Both <a href="#onCues(java.util.List)"><code>onCues(List)</code></a> and <a href="#onCues(com.google.android.exoplayer2.text.CueGroup)"><code>onCues(CueGroup)</code></a> are called when there is a change
in the cues You should only implement one or the other.</div>
in the cues. You should only implement one or the other.</div>
</li>
</ul>
</li>

View file

@ -0,0 +1,380 @@
<!DOCTYPE HTML>
<!-- NewPage -->
<html lang="en">
<head><!-- start favicons snippet, use https://realfavicongenerator.net/ --><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="manifest" href="/assets/site.webmanifest"><link rel="mask-icon" href="/assets/safari-pinned-tab.svg" color="#fc4d50"><link rel="shortcut icon" href="/assets/favicon.ico"><meta name="msapplication-TileColor" content="#ffc40d"><meta name="msapplication-config" content="/assets/browserconfig.xml"><meta name="theme-color" content="#ffffff"><!-- end favicons snippet -->
<title>Log.Logger (ExoPlayer library)</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="../../../../../stylesheet.css" title="Style">
<link rel="stylesheet" type="text/css" href="../../../../../jquery/jquery-ui.css" title="Style">
<script type="text/javascript" src="../../../../../script.js"></script>
<script type="text/javascript" src="../../../../../jquery/jszip/dist/jszip.min.js"></script>
<script type="text/javascript" src="../../../../../jquery/jszip-utils/dist/jszip-utils.min.js"></script>
<!--[if IE]>
<script type="text/javascript" src="../../../../../jquery/jszip-utils/dist/jszip-utils-ie.min.js"></script>
<![endif]-->
<script type="text/javascript" src="../../../../../jquery/jquery-3.5.1.js"></script>
<script type="text/javascript" src="../../../../../jquery/jquery-ui.js"></script>
</head>
<body>
<script type="text/javascript"><!--
try {
if (location.href.indexOf('is-external=true') == -1) {
parent.document.title="Log.Logger (ExoPlayer library)";
}
}
catch(err) {
}
//-->
var data = {"i0":6,"i1":6,"i2":6,"i3":6};
var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"]};
var altColor = "altColor";
var rowColor = "rowColor";
var tableTab = "tableTab";
var activeTableTab = "activeTableTab";
var pathtoroot = "../../../../../";
var useModuleDirectories = false;
loadScripts(document, 'script');</script>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
<header role="banner">
<nav role="navigation">
<div class="fixedNav">
<!-- ========= START OF TOP NAVBAR ======= -->
<div class="topNav"><a id="navbar.top">
<!-- -->
</a>
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
<a id="navbar.top.firstrow">
<!-- -->
</a>
<ul class="navList" title="Navigation">
<li><a href="../../../../../index.html">Overview</a></li>
<li><a href="package-summary.html">Package</a></li>
<li class="navBarCell1Rev">Class</li>
<li><a href="package-tree.html">Tree</a></li>
<li><a href="../../../../../deprecated-list.html">Deprecated</a></li>
<li><a href="../../../../../index-all.html">Index</a></li>
<li><a href="../../../../../help-doc.html">Help</a></li>
</ul>
</div>
<div class="subNav">
<ul class="navList" id="allclasses_navbar_top">
<li><a href="../../../../../allclasses.html">All&nbsp;Classes</a></li>
</ul>
<ul class="navListSearch">
<li><label for="search">SEARCH:</label>
<input type="text" id="search" value="search" disabled="disabled">
<input type="reset" id="reset" value="reset" disabled="disabled">
</li>
</ul>
<div>
<script type="text/javascript"><!--
allClassesLink = document.getElementById("allclasses_navbar_top");
if(window==top) {
allClassesLink.style.display = "block";
}
else {
allClassesLink.style.display = "none";
}
//-->
</script>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
</div>
<div>
<ul class="subNavList">
<li>Summary:&nbsp;</li>
<li>Nested&nbsp;|&nbsp;</li>
<li><a href="#field.summary">Field</a>&nbsp;|&nbsp;</li>
<li>Constr&nbsp;|&nbsp;</li>
<li><a href="#method.summary">Method</a></li>
</ul>
<ul class="subNavList">
<li>Detail:&nbsp;</li>
<li><a href="#field.detail">Field</a>&nbsp;|&nbsp;</li>
<li>Constr&nbsp;|&nbsp;</li>
<li><a href="#method.detail">Method</a></li>
</ul>
</div>
<a id="skip.navbar.top">
<!-- -->
</a></div>
<!-- ========= END OF TOP NAVBAR ========= -->
</div>
<div class="navPadding">&nbsp;</div>
<script type="text/javascript"><!--
$('.navPadding').css('padding-top', $('.fixedNav').css("height"));
//-->
</script>
</nav>
</header>
<!-- ======== START OF CLASS DATA ======== -->
<main role="main">
<div class="header">
<div class="subTitle"><span class="packageLabelInType">Package</span>&nbsp;<a href="package-summary.html">com.google.android.exoplayer2.util</a></div>
<h2 title="Interface Log.Logger" class="title">Interface Log.Logger</h2>
</div>
<div class="contentContainer">
<div class="description">
<ul class="blockList">
<li class="blockList">
<dl>
<dt>Enclosing class:</dt>
<dd><a href="Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dd>
</dl>
<hr>
<pre>public static interface <span class="typeNameLabel">Log.Logger</span></pre>
<div class="block">Interface for a logger that can output messages with a tag.
<p>Use <a href="#DEFAULT"><code>DEFAULT</code></a> to output to <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a>.</div>
</li>
</ul>
</div>
<div class="summary">
<ul class="blockList">
<li class="blockList">
<!-- =========== FIELD SUMMARY =========== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="field.summary">
<!-- -->
</a>
<h3>Field Summary</h3>
<table class="memberSummary">
<caption><span>Fields</span><span class="tabEnd">&nbsp;</span></caption>
<tr>
<th class="colFirst" scope="col">Modifier and Type</th>
<th class="colSecond" scope="col">Field</th>
<th class="colLast" scope="col">Description</th>
</tr>
<tr class="altColor">
<td class="colFirst"><code>static <a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#DEFAULT">DEFAULT</a></span></code></th>
<td class="colLast">
<div class="block">The default instance logging to <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a>.</div>
</td>
</tr>
</table>
</li>
</ul>
</section>
<!-- ========== METHOD SUMMARY =========== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="method.summary">
<!-- -->
</a>
<h3>Method Summary</h3>
<table class="memberSummary">
<caption><span id="t0" class="activeTableTab"><span>All Methods</span><span class="tabEnd">&nbsp;</span></span><span id="t2" class="tableTab"><span><a href="javascript:show(2);">Instance Methods</a></span><span class="tabEnd">&nbsp;</span></span><span id="t3" class="tableTab"><span><a href="javascript:show(4);">Abstract Methods</a></span><span class="tabEnd">&nbsp;</span></span></caption>
<tr>
<th class="colFirst" scope="col">Modifier and Type</th>
<th class="colSecond" scope="col">Method</th>
<th class="colLast" scope="col">Description</th>
</tr>
<tr id="i0" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#d(java.lang.String,java.lang.String)">d</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</code></th>
<td class="colLast">
<div class="block">Logs a debug-level message.</div>
</td>
</tr>
<tr id="i1" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#e(java.lang.String,java.lang.String)">e</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</code></th>
<td class="colLast">
<div class="block">Logs an error-level message.</div>
</td>
</tr>
<tr id="i2" class="altColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#i(java.lang.String,java.lang.String)">i</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</code></th>
<td class="colLast">
<div class="block">Logs an information-level message.</div>
</td>
</tr>
<tr id="i3" class="rowColor">
<td class="colFirst"><code>void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#w(java.lang.String,java.lang.String)">w</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</code></th>
<td class="colLast">
<div class="block">Logs a warning-level message.</div>
</td>
</tr>
</table>
</li>
</ul>
</section>
</li>
</ul>
</div>
<div class="details">
<ul class="blockList">
<li class="blockList">
<!-- ============ FIELD DETAIL =========== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="field.detail">
<!-- -->
</a>
<h3>Field Detail</h3>
<a id="DEFAULT">
<!-- -->
</a>
<ul class="blockListLast">
<li class="blockList">
<h4>DEFAULT</h4>
<pre>static final&nbsp;<a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a> DEFAULT</pre>
<div class="block">The default instance logging to <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a>.</div>
</li>
</ul>
</li>
</ul>
</section>
<!-- ============ METHOD DETAIL ========== -->
<section role="region">
<ul class="blockList">
<li class="blockList"><a id="method.detail">
<!-- -->
</a>
<h3>Method Detail</h3>
<a id="d(java.lang.String,java.lang.String)">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>d</h4>
<pre class="methodSignature">void&nbsp;d&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</pre>
<div class="block">Logs a debug-level message.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>tag</code> - The tag of the message.</dd>
<dd><code>message</code> - The message.</dd>
</dl>
</li>
</ul>
<a id="i(java.lang.String,java.lang.String)">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>i</h4>
<pre class="methodSignature">void&nbsp;i&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</pre>
<div class="block">Logs an information-level message.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>tag</code> - The tag of the message.</dd>
<dd><code>message</code> - The message.</dd>
</dl>
</li>
</ul>
<a id="w(java.lang.String,java.lang.String)">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>w</h4>
<pre class="methodSignature">void&nbsp;w&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</pre>
<div class="block">Logs a warning-level message.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>tag</code> - The tag of the message.</dd>
<dd><code>message</code> - The message.</dd>
</dl>
</li>
</ul>
<a id="e(java.lang.String,java.lang.String)">
<!-- -->
</a>
<ul class="blockListLast">
<li class="blockList">
<h4>e</h4>
<pre class="methodSignature">void&nbsp;e&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</pre>
<div class="block">Logs an error-level message.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>tag</code> - The tag of the message.</dd>
<dd><code>message</code> - The message.</dd>
</dl>
</li>
</ul>
</li>
</ul>
</section>
</li>
</ul>
</div>
</div>
</main>
<!-- ========= END OF CLASS DATA ========= -->
<footer role="contentinfo">
<nav role="navigation">
<!-- ======= START OF BOTTOM NAVBAR ====== -->
<div class="bottomNav"><a id="navbar.bottom">
<!-- -->
</a>
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
<a id="navbar.bottom.firstrow">
<!-- -->
</a>
<ul class="navList" title="Navigation">
<li><a href="../../../../../index.html">Overview</a></li>
<li><a href="package-summary.html">Package</a></li>
<li class="navBarCell1Rev">Class</li>
<li><a href="package-tree.html">Tree</a></li>
<li><a href="../../../../../deprecated-list.html">Deprecated</a></li>
<li><a href="../../../../../index-all.html">Index</a></li>
<li><a href="../../../../../help-doc.html">Help</a></li>
</ul>
</div>
<div class="subNav">
<ul class="navList" id="allclasses_navbar_bottom">
<li><a href="../../../../../allclasses.html">All&nbsp;Classes</a></li>
</ul>
<div>
<script type="text/javascript"><!--
allClassesLink = document.getElementById("allclasses_navbar_bottom");
if(window==top) {
allClassesLink.style.display = "block";
}
else {
allClassesLink.style.display = "none";
}
//-->
</script>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
</div>
<div>
<ul class="subNavList">
<li>Summary:&nbsp;</li>
<li>Nested&nbsp;|&nbsp;</li>
<li><a href="#field.summary">Field</a>&nbsp;|&nbsp;</li>
<li>Constr&nbsp;|&nbsp;</li>
<li><a href="#method.summary">Method</a></li>
</ul>
<ul class="subNavList">
<li>Detail:&nbsp;</li>
<li><a href="#field.detail">Field</a>&nbsp;|&nbsp;</li>
<li>Constr&nbsp;|&nbsp;</li>
<li><a href="#method.detail">Method</a></li>
</ul>
</div>
<a id="skip.navbar.bottom">
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
</nav>
</footer>
</body>
</html>

View file

@ -25,7 +25,7 @@
catch(err) {
}
//-->
var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9};
var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9,"i12":9};
var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"]};
var altColor = "altColor";
var rowColor = "rowColor";
@ -131,7 +131,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<hr>
<pre>public final class <span class="typeNameLabel">Log</span>
extends <a href="https://developer.android.com/reference/java/lang/Object.html" title="class or interface in java.lang" class="externalLink" target="_top">Object</a></pre>
<div class="block">Wrapper around <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a> which allows to set the log level.</div>
<div class="block">Wrapper around <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a> which allows to set the log level and to specify a custom
log output.</div>
</li>
</ul>
</div>
@ -154,6 +155,13 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
</tr>
<tr class="altColor">
<td class="colFirst"><code>static interface&nbsp;</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></span></code></th>
<td class="colLast">
<div class="block">Interface for a logger that can output messages with a tag.</div>
</td>
</tr>
<tr class="rowColor">
<td class="colFirst"><code>static interface&nbsp;</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="Log.LogLevel.html" title="annotation in com.google.android.exoplayer2.util">Log.LogLevel</a></span></code></th>
<td class="colLast">
<div class="block">Log level for ExoPlayer logcat logging.</div>
@ -286,25 +294,32 @@ extends <a href="https://developer.android.com/reference/java/lang/Object.html"
</tr>
<tr id="i8" class="altColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setLogger(com.google.android.exoplayer2.util.Log.Logger)">setLogger</a></span>&#8203;(<a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a>&nbsp;logger)</code></th>
<td class="colLast">
<div class="block">Sets a custom <a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><code>Log.Logger</code></a> as the output.</div>
</td>
</tr>
<tr id="i9" class="rowColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setLogLevel(@com.google.android.exoplayer2.util.Log.LogLevelint)">setLogLevel</a></span>&#8203;(@com.google.android.exoplayer2.util.Log.LogLevel int&nbsp;logLevel)</code></th>
<td class="colLast">
<div class="block">Sets the <a href="Log.LogLevel.html" title="annotation in com.google.android.exoplayer2.util"><code>Log.LogLevel</code></a> for ExoPlayer logcat logging.</div>
</td>
</tr>
<tr id="i9" class="rowColor">
<tr id="i10" class="altColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#setLogStackTraces(boolean)">setLogStackTraces</a></span>&#8203;(boolean&nbsp;logStackTraces)</code></th>
<td class="colLast">
<div class="block">Sets whether stack traces of <a href="https://developer.android.com/reference/java/lang/Throwable.html" title="class or interface in java.lang" class="externalLink" target="_top"><code>Throwable</code></a>s will be logged to logcat.</div>
</td>
</tr>
<tr id="i10" class="altColor">
<tr id="i11" class="rowColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#w(java.lang.String,java.lang.String)">w</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message)</code></th>
<td class="colLast">&nbsp;</td>
</tr>
<tr id="i11" class="rowColor">
<tr id="i12" class="altColor">
<td class="colFirst"><code>static void</code></td>
<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="#w(java.lang.String,java.lang.String,java.lang.Throwable)">w</a></span>&#8203;(<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;tag,
<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a>&nbsp;message,
@ -455,6 +470,20 @@ public static&nbsp;@com.google.android.exoplayer2.util.Log.LogLevel int&nbsp;get
</dl>
</li>
</ul>
<a id="setLogger(com.google.android.exoplayer2.util.Log.Logger)">
<!-- -->
</a>
<ul class="blockList">
<li class="blockList">
<h4>setLogger</h4>
<pre class="methodSignature">public static&nbsp;void&nbsp;setLogger&#8203;(<a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a>&nbsp;logger)</pre>
<div class="block">Sets a custom <a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><code>Log.Logger</code></a> as the output.</div>
<dl>
<dt><span class="paramLabel">Parameters:</span></dt>
<dd><code>logger</code> - The <a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><code>Log.Logger</code></a>.</dd>
</dl>
</li>
</ul>
<a id="d(java.lang.String,java.lang.String)">
<!-- -->
</a>

View file

@ -153,18 +153,24 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
</td>
</tr>
<tr class="altColor">
<th class="colFirst" scope="row"><a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></th>
<td class="colLast">
<div class="block">Interface for a logger that can output messages with a tag.</div>
</td>
</tr>
<tr class="rowColor">
<th class="colFirst" scope="row"><a href="MediaClock.html" title="interface in com.google.android.exoplayer2.util">MediaClock</a></th>
<td class="colLast">
<div class="block">Tracks the progression of media time.</div>
</td>
</tr>
<tr class="rowColor">
<tr class="altColor">
<th class="colFirst" scope="row"><a href="NetworkTypeObserver.Listener.html" title="interface in com.google.android.exoplayer2.util">NetworkTypeObserver.Listener</a></th>
<td class="colLast">
<div class="block">A listener for network type changes.</div>
</td>
</tr>
<tr class="altColor">
<tr class="rowColor">
<th class="colFirst" scope="row"><a href="SntpClient.InitializationCallback.html" title="interface in com.google.android.exoplayer2.util">SntpClient.InitializationCallback</a></th>
<td class="colLast">
<div class="block">Callback for calls to <a href="SntpClient.html#initialize(com.google.android.exoplayer2.upstream.Loader,com.google.android.exoplayer2.util.SntpClient.InitializationCallback)"><code>SntpClient.initialize(Loader, InitializationCallback)</code></a>.</div>
@ -295,7 +301,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<tr class="altColor">
<th class="colFirst" scope="row"><a href="Log.html" title="class in com.google.android.exoplayer2.util">Log</a></th>
<td class="colLast">
<div class="block">Wrapper around <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a> which allows to set the log level.</div>
<div class="block">Wrapper around <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a> which allows to set the log level and to specify a custom
log output.</div>
</td>
</tr>
<tr class="rowColor">

View file

@ -180,6 +180,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<li class="circle">com.google.android.exoplayer2.util.<a href="HandlerWrapper.Message.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">HandlerWrapper.Message</span></a></li>
<li class="circle">com.google.android.exoplayer2.util.<a href="ListenerSet.Event.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">ListenerSet.Event</span></a>&lt;T&gt;</li>
<li class="circle">com.google.android.exoplayer2.util.<a href="ListenerSet.IterationFinishedEvent.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">ListenerSet.IterationFinishedEvent</span></a>&lt;T&gt;</li>
<li class="circle">com.google.android.exoplayer2.util.<a href="Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">Log.Logger</span></a></li>
<li class="circle">com.google.android.exoplayer2.util.<a href="MediaClock.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">MediaClock</span></a></li>
<li class="circle">com.google.android.exoplayer2.util.<a href="NetworkTypeObserver.Listener.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">NetworkTypeObserver.Listener</span></a></li>
<li class="circle">com.google.android.exoplayer2.util.<a href="SntpClient.InitializationCallback.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">SntpClient.InitializationCallback</span></a></li>

View file

@ -1939,21 +1939,21 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<!-- -->
</a><code>public&nbsp;static&nbsp;final&nbsp;<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a></code></td>
<th class="colSecond" scope="row"><code><a href="com/google/android/exoplayer2/ExoPlayerLibraryInfo.html#VERSION">VERSION</a></code></th>
<td class="colLast"><code>"2.18.0"</code></td>
<td class="colLast"><code>"2.18.1"</code></td>
</tr>
<tr class="altColor">
<td class="colFirst"><a id="com.google.android.exoplayer2.ExoPlayerLibraryInfo.VERSION_INT">
<!-- -->
</a><code>public&nbsp;static&nbsp;final&nbsp;int</code></td>
<th class="colSecond" scope="row"><code><a href="com/google/android/exoplayer2/ExoPlayerLibraryInfo.html#VERSION_INT">VERSION_INT</a></code></th>
<td class="colLast"><code>2018000</code></td>
<td class="colLast"><code>2018001</code></td>
</tr>
<tr class="rowColor">
<td class="colFirst"><a id="com.google.android.exoplayer2.ExoPlayerLibraryInfo.VERSION_SLASHY">
<!-- -->
</a><code>public&nbsp;static&nbsp;final&nbsp;<a href="https://developer.android.com/reference/java/lang/String.html" title="class or interface in java.lang" class="externalLink" target="_top">String</a></code></td>
<th class="colSecond" scope="row"><code><a href="com/google/android/exoplayer2/ExoPlayerLibraryInfo.html#VERSION_SLASHY">VERSION_SLASHY</a></code></th>
<td class="colLast"><code>"ExoPlayerLib/2.18.0"</code></td>
<td class="colLast"><code>"ExoPlayerLib/2.18.1"</code></td>
</tr>
</tbody>
</table>

View file

@ -7322,6 +7322,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dl>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#d(java.lang.String,java.lang.String)">d(String, String)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.Logger.html#d(java.lang.String,java.lang.String)">d(String, String)</a></span> - Method in interface com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></dt>
<dd>
<div class="block">Logs a debug-level message.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#d(java.lang.String,java.lang.String,java.lang.Throwable)">d(String, String, Throwable)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>&nbsp;</dd>
<dt><a href="com/google/android/exoplayer2/source/dash/DashChunkSource.html" title="interface in com.google.android.exoplayer2.source.dash"><span class="typeNameLink">DashChunkSource</span></a> - Interface in <a href="com/google/android/exoplayer2/source/dash/package-summary.html">com.google.android.exoplayer2.source.dash</a></dt>
@ -8022,6 +8026,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dd>
<div class="block">Default <a href="com/google/android/exoplayer2/util/Clock.html" title="interface in com.google.android.exoplayer2.util"><code>Clock</code></a> to use for all non-test cases.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.Logger.html#DEFAULT">DEFAULT</a></span> - Static variable in interface com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></dt>
<dd>
<div class="block">The default instance logging to <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a>.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/ui/DefaultTimeBar.html#DEFAULT_AD_MARKER_COLOR">DEFAULT_AD_MARKER_COLOR</a></span> - Static variable in class com.google.android.exoplayer2.ui.<a href="com/google/android/exoplayer2/ui/DefaultTimeBar.html" title="class in com.google.android.exoplayer2.ui">DefaultTimeBar</a></dt>
<dd>
<div class="block">Default color for ad markers.</div>
@ -10214,6 +10222,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dl>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#e(java.lang.String,java.lang.String)">e(String, String)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.Logger.html#e(java.lang.String,java.lang.String)">e(String, String)</a></span> - Method in interface com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></dt>
<dd>
<div class="block">Logs an error-level message.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#e(java.lang.String,java.lang.String,java.lang.Throwable)">e(String, String, Throwable)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/audio/Ac3Util.html#E_AC3_MAX_RATE_BYTES_PER_SECOND">E_AC3_MAX_RATE_BYTES_PER_SECOND</a></span> - Static variable in class com.google.android.exoplayer2.audio.<a href="com/google/android/exoplayer2/audio/Ac3Util.html" title="class in com.google.android.exoplayer2.audio">Ac3Util</a></dt>
@ -12643,6 +12655,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dd>
<div class="block">Creates a fake timeline with the given window definitions.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/FakeTimeline.html#%3Cinit%3E(java.lang.Object%5B%5D,com.google.android.exoplayer2.source.ShuffleOrder,com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition...)">FakeTimeline(Object[], ShuffleOrder, FakeTimeline.TimelineWindowDefinition...)</a></span> - Constructor for class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/FakeTimeline.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline</a></dt>
<dd>
<div class="block">Creates a fake timeline with the given window definitions and <a href="com/google/android/exoplayer2/source/ShuffleOrder.html" title="interface in com.google.android.exoplayer2.source"><code>ShuffleOrder</code></a>.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/FakeTimeline.html#%3Cinit%3E(java.lang.Object%5B%5D,com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition...)">FakeTimeline(Object[], FakeTimeline.TimelineWindowDefinition...)</a></span> - Constructor for class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/FakeTimeline.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline</a></dt>
<dd>
<div class="block">Creates a fake timeline with the given window definitions.</div>
@ -16650,6 +16666,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/StubPlayer.html#getMediaMetadata()">getMediaMetadata()</a></span> - Method in class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/StubPlayer.html" title="class in com.google.android.exoplayer2.testutil">StubPlayer</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/ext/cast/CastPlayer.html#getMediaMetadataInternal()">getMediaMetadataInternal()</a></span> - Method in class com.google.android.exoplayer2.ext.cast.<a href="com/google/android/exoplayer2/ext/cast/CastPlayer.html" title="class in com.google.android.exoplayer2.ext.cast">CastPlayer</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/MimeTypes.html#getMediaMimeType(java.lang.String)">getMediaMimeType(String)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/MimeTypes.html" title="class in com.google.android.exoplayer2.util">MimeTypes</a></dt>
<dd>
<div class="block">Returns the MIME type corresponding to an RFC 6381 codec string, or <code>null</code> if it could
@ -17196,8 +17214,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/FakeTimeline.html#getPeriod(int,com.google.android.exoplayer2.Timeline.Period,boolean)">getPeriod(int, Timeline.Period, boolean)</a></span> - Method in class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/FakeTimeline.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html#getPeriod(int,com.google.android.exoplayer2.Timeline.Period,boolean)">getPeriod(int, Timeline.Period, boolean)</a></span> - Method in class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil">NoUidTimeline</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/Timeline.html#getPeriod(int,com.google.android.exoplayer2.Timeline.Period,boolean)">getPeriod(int, Timeline.Period, boolean)</a></span> - Method in class com.google.android.exoplayer2.<a href="com/google/android/exoplayer2/Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a></dt>
<dd>
<div class="block">Populates a <a href="com/google/android/exoplayer2/Timeline.Period.html" title="class in com.google.android.exoplayer2"><code>Timeline.Period</code></a> with data for the period at the specified index.</div>
@ -19189,8 +19205,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/FakeTimeline.html#getWindow(int,com.google.android.exoplayer2.Timeline.Window,long)">getWindow(int, Timeline.Window, long)</a></span> - Method in class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/FakeTimeline.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html#getWindow(int,com.google.android.exoplayer2.Timeline.Window,long)">getWindow(int, Timeline.Window, long)</a></span> - Method in class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil">NoUidTimeline</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/Timeline.html#getWindow(int,com.google.android.exoplayer2.Timeline.Window,long)">getWindow(int, Timeline.Window, long)</a></span> - Method in class com.google.android.exoplayer2.<a href="com/google/android/exoplayer2/Timeline.html" title="class in com.google.android.exoplayer2">Timeline</a></dt>
<dd>
<div class="block">Populates a <a href="com/google/android/exoplayer2/Timeline.Window.html" title="class in com.google.android.exoplayer2"><code>Timeline.Window</code></a> with data for the window at the specified index.</div>
@ -20405,6 +20419,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dl>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#i(java.lang.String,java.lang.String)">i(String, String)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.Logger.html#i(java.lang.String,java.lang.String)">i(String, String)</a></span> - Method in interface com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></dt>
<dd>
<div class="block">Logs an information-level message.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#i(java.lang.String,java.lang.String,java.lang.Throwable)">i(String, String, Throwable)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>&nbsp;</dd>
<dt><a href="com/google/android/exoplayer2/metadata/icy/IcyDecoder.html" title="class in com.google.android.exoplayer2.metadata.icy"><span class="typeNameLink">IcyDecoder</span></a> - Class in <a href="com/google/android/exoplayer2/metadata/icy/package-summary.html">com.google.android.exoplayer2.metadata.icy</a></dt>
@ -22798,7 +22816,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
</dd>
<dt><a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util"><span class="typeNameLink">Log</span></a> - Class in <a href="com/google/android/exoplayer2/util/package-summary.html">com.google.android.exoplayer2.util</a></dt>
<dd>
<div class="block">Wrapper around <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a> which allows to set the log level.</div>
<div class="block">Wrapper around <a href="https://developer.android.com/reference/android/util/Log.html" title="class or interface in android.util" class="externalLink" target="_top"><code>Log</code></a> which allows to set the log level and to specify a custom
log output.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#LOG_LEVEL_ALL">LOG_LEVEL_ALL</a></span> - Static variable in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>
@ -22820,6 +22839,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dd>
<div class="block">Log level to only log warning and error messages.</div>
</dd>
<dt><a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">Log.Logger</span></a> - Interface in <a href="com/google/android/exoplayer2/util/package-summary.html">com.google.android.exoplayer2.util</a></dt>
<dd>
<div class="block">Interface for a logger that can output messages with a tag.</div>
</dd>
<dt><a href="com/google/android/exoplayer2/util/Log.LogLevel.html" title="annotation in com.google.android.exoplayer2.util"><span class="typeNameLink">Log.LogLevel</span></a> - Annotation Type in <a href="com/google/android/exoplayer2/util/package-summary.html">com.google.android.exoplayer2.util</a></dt>
<dd>
<div class="block">Log level for ExoPlayer logcat logging.</div>
@ -24808,14 +24831,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/analytics/DefaultAnalyticsCollector.html#notifySeekStarted()">notifySeekStarted()</a></span> - Method in class com.google.android.exoplayer2.analytics.<a href="com/google/android/exoplayer2/analytics/DefaultAnalyticsCollector.html" title="class in com.google.android.exoplayer2.analytics">DefaultAnalyticsCollector</a></dt>
<dd>&nbsp;</dd>
<dt><a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil"><span class="typeNameLink">NoUidTimeline</span></a> - Class in <a href="com/google/android/exoplayer2/testutil/package-summary.html">com.google.android.exoplayer2.testutil</a></dt>
<dd>
<div class="block">A timeline which wraps another timeline and overrides all window and period uids to 0.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html#%3Cinit%3E(com.google.android.exoplayer2.Timeline)">NoUidTimeline(Timeline)</a></span> - Constructor for class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil">NoUidTimeline</a></dt>
<dd>
<div class="block">Creates an instance.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Util.html#nullSafeArrayAppend(T%5B%5D,T)">nullSafeArrayAppend(T[], T)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Util.html" title="class in com.google.android.exoplayer2.util">Util</a></dt>
<dd>
<div class="block">Creates a new array containing <code>original</code> with <code>newElement</code> appended.</div>
@ -32146,11 +32161,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<div class="block">Runs tasks of the main <a href="https://developer.android.com/reference/android/os/Looper.html" title="class or interface in android.os" class="externalLink"><code>Looper</code></a> until <a href="com/google/android/exoplayer2/Player.Listener.html#onPositionDiscontinuity(com.google.android.exoplayer2.Player.PositionInfo,com.google.android.exoplayer2.Player.PositionInfo,@com.google.android.exoplayer2.Player.DiscontinuityReasonint)" target="_top"><code>Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int)</code></a> is
called with the specified <a href="com/google/android/exoplayer2/Player.DiscontinuityReason.html" title="annotation in com.google.android.exoplayer2"><code>Player.DiscontinuityReason</code></a> or a playback error occurs.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html#runUntilReceiveOffloadSchedulingEnabledNewState(com.google.android.exoplayer2.ExoPlayer)">runUntilReceiveOffloadSchedulingEnabledNewState(ExoPlayer)</a></span> - Static method in class com.google.android.exoplayer2.robolectric.<a href="com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html" title="class in com.google.android.exoplayer2.robolectric">TestPlayerRunHelper</a></dt>
<dd>
<div class="block">Runs tasks of the main <a href="https://developer.android.com/reference/android/os/Looper.html" title="class or interface in android.os" class="externalLink"><code>Looper</code></a> until <a href="com/google/android/exoplayer2/ExoPlayer.AudioOffloadListener.html#onExperimentalOffloadSchedulingEnabledChanged(boolean)" target="_top"><code>ExoPlayer.AudioOffloadListener.onExperimentalOffloadSchedulingEnabledChanged(boolean)</code></a> is called or a
playback error occurs.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html#runUntilRenderedFirstFrame(com.google.android.exoplayer2.ExoPlayer)">runUntilRenderedFirstFrame(ExoPlayer)</a></span> - Static method in class com.google.android.exoplayer2.robolectric.<a href="com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html" title="class in com.google.android.exoplayer2.robolectric">TestPlayerRunHelper</a></dt>
<dd>
<div class="block">Runs tasks of the main <a href="https://developer.android.com/reference/android/os/Looper.html" title="class or interface in android.os" class="externalLink"><code>Looper</code></a> until the <a href="com/google/android/exoplayer2/Player.Listener.html#onRenderedFirstFrame()" target="_top"><code>Player.Listener.onRenderedFirstFrame()</code></a>
@ -35457,6 +35467,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dd>
<div class="block">Sets the components required for local ad insertion for media items that have <a href="com/google/android/exoplayer2/MediaItem.LocalConfiguration.html#adsConfiguration"><code>ads configurations</code></a></div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#setLogger(com.google.android.exoplayer2.util.Log.Logger)">setLogger(Log.Logger)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>
<div class="block">Sets a custom <a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><code>Log.Logger</code></a> as the output.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#setLogLevel(@com.google.android.exoplayer2.util.Log.LogLevelint)">setLogLevel(@com.google.android.exoplayer2.util.Log.LogLevel int)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>
<div class="block">Sets the <a href="com/google/android/exoplayer2/util/Log.LogLevel.html" title="annotation in com.google.android.exoplayer2.util"><code>Log.LogLevel</code></a> for ExoPlayer logcat logging.</div>
@ -40321,6 +40335,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<dd>
<div class="block">Creates an instance for a given <code>MediaSessionCompat</code> and maximum queue size.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/TestUtil.html#timelinesAreSame(com.google.android.exoplayer2.Timeline,com.google.android.exoplayer2.Timeline)">timelinesAreSame(Timeline, Timeline)</a></span> - Static method in class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/TestUtil.html" title="class in com.google.android.exoplayer2.testutil">TestUtil</a></dt>
<dd>
<div class="block">Returns true if <code>thisTimeline</code> is equal to <code>thatTimeline</code>, ignoring <a href="com/google/android/exoplayer2/Timeline.Window.html#uid"><code>Timeline.Window.uid</code></a> and <a href="com/google/android/exoplayer2/Timeline.Period.html#uid"><code>Timeline.Period.uid</code></a> values, and shuffle order.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/testutil/FakeTimeline.TimelineWindowDefinition.html#%3Cinit%3E(boolean,boolean,long)">TimelineWindowDefinition(boolean, boolean, long)</a></span> - Constructor for class com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/FakeTimeline.TimelineWindowDefinition.html" title="class in com.google.android.exoplayer2.testutil">FakeTimeline.TimelineWindowDefinition</a></dt>
<dd>
<div class="block">Creates a window definition with one period.</div>
@ -42501,6 +42519,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
</a>
<h2 class="title">W</h2>
<dl>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.Logger.html#w(java.lang.String,java.lang.String)">w(String, String)</a></span> - Method in interface com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util">Log.Logger</a></dt>
<dd>
<div class="block">Logs a warning-level message.</div>
</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#w(java.lang.String,java.lang.String)">w(String, String)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>
<dd>&nbsp;</dd>
<dt><span class="memberNameLink"><a href="com/google/android/exoplayer2/util/Log.html#w(java.lang.String,java.lang.String,java.lang.Throwable)">w(String, String, Throwable)</a></span> - Static method in class com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.html" title="class in com.google.android.exoplayer2.util">Log</a></dt>

File diff suppressed because one or more lines are too long

View file

@ -1198,7 +1198,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<li class="circle">com.google.android.exoplayer2.source.<a href="com/google/android/exoplayer2/source/ForwardingTimeline.html" title="class in com.google.android.exoplayer2.source"><span class="typeNameLink">ForwardingTimeline</span></a>
<ul>
<li class="circle">com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/FakeMediaSource.InitialTimeline.html" title="class in com.google.android.exoplayer2.testutil"><span class="typeNameLink">FakeMediaSource.InitialTimeline</span></a></li>
<li class="circle">com.google.android.exoplayer2.testutil.<a href="com/google/android/exoplayer2/testutil/NoUidTimeline.html" title="class in com.google.android.exoplayer2.testutil"><span class="typeNameLink">NoUidTimeline</span></a></li>
<li class="circle">com.google.android.exoplayer2.source.ads.<a href="com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.html" title="class in com.google.android.exoplayer2.source.ads"><span class="typeNameLink">SinglePeriodAdTimeline</span></a></li>
</ul>
</li>
@ -1506,6 +1505,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
<li class="circle">com.google.android.exoplayer2.upstream.<a href="com/google/android/exoplayer2/upstream/Loader.ReleaseCallback.html" title="interface in com.google.android.exoplayer2.upstream"><span class="typeNameLink">Loader.ReleaseCallback</span></a></li>
<li class="circle">com.google.android.exoplayer2.upstream.<a href="com/google/android/exoplayer2/upstream/LoaderErrorThrower.html" title="interface in com.google.android.exoplayer2.upstream"><span class="typeNameLink">LoaderErrorThrower</span></a></li>
<li class="circle">com.google.android.exoplayer2.upstream.<a href="com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.html" title="interface in com.google.android.exoplayer2.upstream"><span class="typeNameLink">LoadErrorHandlingPolicy</span></a></li>
<li class="circle">com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/Log.Logger.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">Log.Logger</span></a></li>
<li class="circle">com.google.android.exoplayer2.source.<a href="com/google/android/exoplayer2/source/MaskingMediaPeriod.PrepareListener.html" title="interface in com.google.android.exoplayer2.source"><span class="typeNameLink">MaskingMediaPeriod.PrepareListener</span></a></li>
<li class="circle">com.google.android.exoplayer2.source.chunk.<a href="com/google/android/exoplayer2/source/chunk/MediaChunkIterator.html" title="interface in com.google.android.exoplayer2.source.chunk"><span class="typeNameLink">MediaChunkIterator</span></a></li>
<li class="circle">com.google.android.exoplayer2.util.<a href="com/google/android/exoplayer2/util/MediaClock.html" title="interface in com.google.android.exoplayer2.util"><span class="typeNameLink">MediaClock</span></a></li>

View file

@ -1,2 +1 @@
packageSearchIndex = [{"l":"All Packages","url":"allpackages-index.html"},{"l":"com.google.android.exoplayer2"},{"l":"com.google.android.exoplayer2.analytics"},{"l":"com.google.android.exoplayer2.audio"},{"l":"com.google.android.exoplayer2.database"},{"l":"com.google.android.exoplayer2.decoder"},{"l":"com.google.android.exoplayer2.drm"},{"l":"com.google.android.exoplayer2.ext.av1"},{"l":"com.google.android.exoplayer2.ext.cast"},{"l":"com.google.android.exoplayer2.ext.cronet"},{"l":"com.google.android.exoplayer2.ext.ffmpeg"},{"l":"com.google.android.exoplayer2.ext.flac"},{"l":"com.google.android.exoplayer2.ext.ima"},{"l":"com.google.android.exoplayer2.ext.leanback"},{"l":"com.google.android.exoplayer2.ext.media2"},{"l":"com.google.android.exoplayer2.ext.mediasession"},{"l":"com.google.android.exoplayer2.ext.okhttp"},{"l":"com.google.android.exoplayer2.ext.opus"},{"l":"com.google.android.exoplayer2.ext.rtmp"},{"l":"com.google.android.exoplayer2.ext.vp9"},{"l":"com.google.android.exoplayer2.ext.workmanager"},{"l":"com.google.android.exoplayer2.extractor"},{"l":"com.google.android.exoplayer2.extractor.amr"},{"l":"com.google.android.exoplayer2.extractor.avi"},{"l":"com.google.android.exoplayer2.extractor.flac"},{"l":"com.google.android.exoplayer2.extractor.flv"},{"l":"com.google.android.exoplayer2.extractor.jpeg"},{"l":"com.google.android.exoplayer2.extractor.mkv"},{"l":"com.google.android.exoplayer2.extractor.mp3"},{"l":"com.google.android.exoplayer2.extractor.mp4"},{"l":"com.google.android.exoplayer2.extractor.ogg"},{"l":"com.google.android.exoplayer2.extractor.ts"},{"l":"com.google.android.exoplayer2.extractor.wav"},{"l":"com.google.android.exoplayer2.mediacodec"},{"l":"com.google.android.exoplayer2.metadata"},{"l":"com.google.android.exoplayer2.metadata.dvbsi"},{"l":"com.google.android.exoplayer2.metadata.emsg"},{"l":"com.google.android.exoplayer2.metadata.flac"},{"l":"com.google.android.exoplayer2.metadata.icy"},{"l":"com.google.android.exoplayer2.metadata.id3"},{"l":"com.google.android.exoplayer2.metadata.mp4"},{"l":"com.google.android.exoplayer2.metadata.scte35"},{"l":"com.google.android.exoplayer2.metadata.vorbis"},{"l":"com.google.android.exoplayer2.offline"},{"l":"com.google.android.exoplayer2.robolectric"},{"l":"com.google.android.exoplayer2.scheduler"},{"l":"com.google.android.exoplayer2.source"},{"l":"com.google.android.exoplayer2.source.ads"},{"l":"com.google.android.exoplayer2.source.chunk"},{"l":"com.google.android.exoplayer2.source.dash"},{"l":"com.google.android.exoplayer2.source.dash.manifest"},{"l":"com.google.android.exoplayer2.source.dash.offline"},{"l":"com.google.android.exoplayer2.source.hls"},{"l":"com.google.android.exoplayer2.source.hls.offline"},{"l":"com.google.android.exoplayer2.source.hls.playlist"},{"l":"com.google.android.exoplayer2.source.mediaparser"},{"l":"com.google.android.exoplayer2.source.rtsp"},{"l":"com.google.android.exoplayer2.source.rtsp.reader"},{"l":"com.google.android.exoplayer2.source.smoothstreaming"},{"l":"com.google.android.exoplayer2.source.smoothstreaming.manifest"},{"l":"com.google.android.exoplayer2.source.smoothstreaming.offline"},{"l":"com.google.android.exoplayer2.testutil"},{"l":"com.google.android.exoplayer2.testutil.truth"},{"l":"com.google.android.exoplayer2.text"},{"l":"com.google.android.exoplayer2.text.cea"},{"l":"com.google.android.exoplayer2.text.dvb"},{"l":"com.google.android.exoplayer2.text.pgs"},{"l":"com.google.android.exoplayer2.text.span"},{"l":"com.google.android.exoplayer2.text.ssa"},{"l":"com.google.android.exoplayer2.text.subrip"},{"l":"com.google.android.exoplayer2.text.ttml"},{"l":"com.google.android.exoplayer2.text.tx3g"},{"l":"com.google.android.exoplayer2.text.webvtt"},{"l":"com.google.android.exoplayer2.trackselection"},{"l":"com.google.android.exoplayer2.transformer"},{"l":"com.google.android.exoplayer2.ui"},{"l":"com.google.android.exoplayer2.upstream"},{"l":"com.google.android.exoplayer2.upstream.cache"},{"l":"com.google.android.exoplayer2.upstream.crypto"},{"l":"com.google.android.exoplayer2.util"},{"l":"com.google.android.exoplayer2.video"},{"l":"com.google.android.exoplayer2.video.spherical"}]

File diff suppressed because one or more lines are too long

View file

@ -99,9 +99,9 @@ public final class CastPlayer extends BasePlayer {
COMMAND_GET_TIMELINE,
COMMAND_GET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEM,
COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_GET_TRACKS,
COMMAND_SET_MEDIA_ITEM)
COMMAND_GET_TRACKS)
.build();
public static final float MIN_SPEED_SUPPORTED = 0.5f;
@ -143,6 +143,7 @@ public final class CastPlayer extends BasePlayer {
private int pendingSeekWindowIndex;
private long pendingSeekPositionMs;
@Nullable private PositionInfo pendingMediaItemRemovalPosition;
private MediaMetadata mediaMetadata;
/**
* Creates a new cast player.
@ -196,7 +197,7 @@ public final class CastPlayer extends BasePlayer {
this.mediaItemConverter = mediaItemConverter;
this.seekBackIncrementMs = seekBackIncrementMs;
this.seekForwardIncrementMs = seekForwardIncrementMs;
timelineTracker = new CastTimelineTracker();
timelineTracker = new CastTimelineTracker(mediaItemConverter);
period = new Timeline.Period();
statusListener = new StatusListener();
seekResultCallback = new SeekResultCallback();
@ -210,6 +211,7 @@ public final class CastPlayer extends BasePlayer {
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
playbackState = STATE_IDLE;
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
mediaMetadata = MediaMetadata.EMPTY;
currentTracks = Tracks.EMPTY;
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
pendingSeekWindowIndex = C.INDEX_UNSET;
@ -281,8 +283,7 @@ public final class CastPlayer extends BasePlayer {
@Override
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
setMediaItemsInternal(
toMediaQueueItems(mediaItems), startIndex, startPositionMs, repeatMode.value);
setMediaItemsInternal(mediaItems, startIndex, startPositionMs, repeatMode.value);
}
@Override
@ -292,7 +293,7 @@ public final class CastPlayer extends BasePlayer {
if (index < currentTimeline.getWindowCount()) {
uid = (int) currentTimeline.getWindow(/* windowIndex= */ index, window).uid;
}
addMediaItemsInternal(toMediaQueueItems(mediaItems), uid);
addMediaItemsInternal(mediaItems, uid);
}
@Override
@ -424,6 +425,13 @@ public final class CastPlayer extends BasePlayer {
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener ->
listener.onMediaItemTransition(mediaItem, MEDIA_ITEM_TRANSITION_REASON_SEEK));
MediaMetadata oldMediaMetadata = mediaMetadata;
mediaMetadata = getMediaMetadataInternal();
if (!oldMediaMetadata.equals(mediaMetadata)) {
listeners.queueEvent(
Player.EVENT_MEDIA_METADATA_CHANGED,
listener -> listener.onMediaMetadataChanged(mediaMetadata));
}
}
updateAvailableCommandsAndNotifyIfChanged();
} else if (pendingSeekCount == 0) {
@ -561,8 +569,12 @@ public final class CastPlayer extends BasePlayer {
@Override
public MediaMetadata getMediaMetadata() {
// CastPlayer does not currently support metadata.
return MediaMetadata.EMPTY;
return mediaMetadata;
}
public MediaMetadata getMediaMetadataInternal() {
MediaItem currentMediaItem = getCurrentMediaItem();
return currentMediaItem != null ? currentMediaItem.mediaMetadata : MediaMetadata.EMPTY;
}
@Override
@ -759,6 +771,7 @@ public final class CastPlayer extends BasePlayer {
return;
}
int oldWindowIndex = this.currentWindowIndex;
MediaMetadata oldMediaMetadata = mediaMetadata;
@Nullable
Object oldPeriodUid =
!getCurrentTimeline().isEmpty()
@ -770,6 +783,7 @@ public final class CastPlayer extends BasePlayer {
boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
Timeline currentTimeline = getCurrentTimeline();
currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
mediaMetadata = getMediaMetadataInternal();
@Nullable
Object currentPeriodUid =
!currentTimeline.isEmpty()
@ -823,6 +837,11 @@ public final class CastPlayer extends BasePlayer {
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTracks));
}
if (!oldMediaMetadata.equals(mediaMetadata)) {
listeners.queueEvent(
Player.EVENT_MEDIA_METADATA_CHANGED,
listener -> listener.onMediaMetadataChanged(mediaMetadata));
}
updateAvailableCommandsAndNotifyIfChanged();
listeners.flushEvents();
}
@ -1018,14 +1037,13 @@ public final class CastPlayer extends BasePlayer {
}
}
@Nullable
private PendingResult<MediaChannelResult> setMediaItemsInternal(
MediaQueueItem[] mediaQueueItems,
private void setMediaItemsInternal(
List<MediaItem> mediaItems,
int startIndex,
long startPositionMs,
@RepeatMode int repeatMode) {
if (remoteMediaClient == null || mediaQueueItems.length == 0) {
return null;
if (remoteMediaClient == null || mediaItems.isEmpty()) {
return;
}
startPositionMs = startPositionMs == C.TIME_UNSET ? 0 : startPositionMs;
if (startIndex == C.INDEX_UNSET) {
@ -1036,34 +1054,35 @@ public final class CastPlayer extends BasePlayer {
if (!currentTimeline.isEmpty()) {
pendingMediaItemRemovalPosition = getCurrentPositionInfo();
}
return remoteMediaClient.queueLoad(
MediaQueueItem[] mediaQueueItems = toMediaQueueItems(mediaItems);
timelineTracker.onMediaItemsSet(mediaItems, mediaQueueItems);
remoteMediaClient.queueLoad(
mediaQueueItems,
min(startIndex, mediaQueueItems.length - 1),
min(startIndex, mediaItems.size() - 1),
getCastRepeatMode(repeatMode),
startPositionMs,
/* customData= */ null);
}
@Nullable
private PendingResult<MediaChannelResult> addMediaItemsInternal(MediaQueueItem[] items, int uid) {
private void addMediaItemsInternal(List<MediaItem> mediaItems, int uid) {
if (remoteMediaClient == null || getMediaStatus() == null) {
return null;
return;
}
return remoteMediaClient.queueInsertItems(items, uid, /* customData= */ null);
MediaQueueItem[] itemsToInsert = toMediaQueueItems(mediaItems);
timelineTracker.onMediaItemsAdded(mediaItems, itemsToInsert);
remoteMediaClient.queueInsertItems(itemsToInsert, uid, /* customData= */ null);
}
@Nullable
private PendingResult<MediaChannelResult> moveMediaItemsInternal(
int[] uids, int fromIndex, int newIndex) {
private void moveMediaItemsInternal(int[] uids, int fromIndex, int newIndex) {
if (remoteMediaClient == null || getMediaStatus() == null) {
return null;
return;
}
int insertBeforeIndex = fromIndex < newIndex ? newIndex + uids.length : newIndex;
int insertBeforeItemId = MediaQueueItem.INVALID_ITEM_ID;
if (insertBeforeIndex < currentTimeline.getWindowCount()) {
insertBeforeItemId = (int) currentTimeline.getWindow(insertBeforeIndex, window).uid;
}
return remoteMediaClient.queueReorderItems(uids, insertBeforeItemId, /* customData= */ null);
remoteMediaClient.queueReorderItems(uids, insertBeforeItemId, /* customData= */ null);
}
@Nullable

View file

@ -15,13 +15,13 @@
*/
package com.google.android.exoplayer2.ext.cast;
import android.net.Uri;
import android.util.SparseArray;
import android.util.SparseIntArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
import com.google.android.gms.cast.MediaInfo;
import java.util.Arrays;
/** A {@link Timeline} for Cast media queues. */
@ -30,12 +30,16 @@ import java.util.Arrays;
/** Holds {@link Timeline} related data for a Cast media item. */
public static final class ItemData {
/* package */ static final String UNKNOWN_CONTENT_ID = "UNKNOWN_CONTENT_ID";
/** Holds no media information. */
public static final ItemData EMPTY =
new ItemData(
/* durationUs= */ C.TIME_UNSET,
/* defaultPositionUs= */ C.TIME_UNSET,
/* isLive= */ false);
/* isLive= */ false,
MediaItem.EMPTY,
UNKNOWN_CONTENT_ID);
/** The duration of the item in microseconds, or {@link C#TIME_UNSET} if unknown. */
public final long durationUs;
@ -45,6 +49,10 @@ import java.util.Arrays;
public final long defaultPositionUs;
/** Whether the item is live content, or {@code false} if unknown. */
public final boolean isLive;
/** The original media item that has been set or added to the playlist. */
public final MediaItem mediaItem;
/** The {@linkplain MediaInfo#getContentId() content ID} of the cast media queue item. */
public final String contentId;
/**
* Creates an instance.
@ -52,11 +60,20 @@ import java.util.Arrays;
* @param durationUs See {@link #durationsUs}.
* @param defaultPositionUs See {@link #defaultPositionUs}.
* @param isLive See {@link #isLive}.
* @param mediaItem See {@link #mediaItem}.
* @param contentId See {@link #contentId}.
*/
public ItemData(long durationUs, long defaultPositionUs, boolean isLive) {
public ItemData(
long durationUs,
long defaultPositionUs,
boolean isLive,
MediaItem mediaItem,
String contentId) {
this.durationUs = durationUs;
this.defaultPositionUs = defaultPositionUs;
this.isLive = isLive;
this.mediaItem = mediaItem;
this.contentId = contentId;
}
/**
@ -66,14 +83,23 @@ import java.util.Arrays;
* @param defaultPositionUs The default start position in microseconds, or {@link C#TIME_UNSET}
* if unknown.
* @param isLive Whether the item is live, or {@code false} if unknown.
* @param mediaItem The media item.
* @param contentId The content ID.
*/
public ItemData copyWithNewValues(long durationUs, long defaultPositionUs, boolean isLive) {
public ItemData copyWithNewValues(
long durationUs,
long defaultPositionUs,
boolean isLive,
MediaItem mediaItem,
String contentId) {
if (durationUs == this.durationUs
&& defaultPositionUs == this.defaultPositionUs
&& isLive == this.isLive) {
&& isLive == this.isLive
&& contentId.equals(this.contentId)
&& mediaItem.equals(this.mediaItem)) {
return this;
}
return new ItemData(durationUs, defaultPositionUs, isLive);
return new ItemData(durationUs, defaultPositionUs, isLive, mediaItem, contentId);
}
}
@ -82,6 +108,7 @@ import java.util.Arrays;
new CastTimeline(new int[0], new SparseArray<>());
private final SparseIntArray idsToIndex;
private final MediaItem[] mediaItems;
private final int[] ids;
private final long[] durationsUs;
private final long[] defaultPositionsUs;
@ -100,10 +127,12 @@ import java.util.Arrays;
durationsUs = new long[itemCount];
defaultPositionsUs = new long[itemCount];
isLive = new boolean[itemCount];
mediaItems = new MediaItem[itemCount];
for (int i = 0; i < ids.length; i++) {
int id = ids[i];
idsToIndex.put(id, i);
ItemData data = itemIdToData.get(id, ItemData.EMPTY);
mediaItems[i] = data.mediaItem;
durationsUs[i] = data.durationUs;
defaultPositionsUs[i] = data.defaultPositionUs == C.TIME_UNSET ? 0 : data.defaultPositionUs;
isLive[i] = data.isLive;
@ -121,18 +150,16 @@ import java.util.Arrays;
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
long durationUs = durationsUs[windowIndex];
boolean isDynamic = durationUs == C.TIME_UNSET;
MediaItem mediaItem =
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(ids[windowIndex]).build();
return window.set(
/* uid= */ ids[windowIndex],
/* mediaItem= */ mediaItem,
/* mediaItem= */ mediaItems[windowIndex],
/* manifest= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
/* isSeekable= */ !isDynamic,
isDynamic,
isLive[windowIndex] ? mediaItem.liveConfiguration : null,
isLive[windowIndex] ? mediaItems[windowIndex].liveConfiguration : null,
defaultPositionsUs[windowIndex],
durationUs,
/* firstPeriodIndex= */ windowIndex,

View file

@ -15,14 +15,23 @@
*/
package com.google.android.exoplayer2.ext.cast;
import static com.google.android.exoplayer2.ext.cast.CastTimeline.ItemData.UNKNOWN_CONTENT_ID;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Creates {@link CastTimeline CastTimelines} from cast receiver app status updates.
@ -33,9 +42,47 @@ import java.util.HashSet;
/* package */ final class CastTimelineTracker {
private final SparseArray<CastTimeline.ItemData> itemIdToData;
private final MediaItemConverter mediaItemConverter;
@VisibleForTesting /* package */ final HashMap<String, MediaItem> mediaItemsByContentId;
public CastTimelineTracker() {
/**
* Creates an instance.
*
* @param mediaItemConverter The converter used to convert from a {@link MediaQueueItem} to a
* {@link MediaItem}.
*/
public CastTimelineTracker(MediaItemConverter mediaItemConverter) {
this.mediaItemConverter = mediaItemConverter;
itemIdToData = new SparseArray<>();
mediaItemsByContentId = new HashMap<>();
}
/**
* Called when media items {@linkplain Player#setMediaItems have been set to the playlist} and are
* sent to the cast playback queue. A future queue update of the {@link RemoteMediaClient} will
* reflect this addition.
*
* @param mediaItems The media items that have been set.
* @param mediaQueueItems The corresponding media queue items.
*/
public void onMediaItemsSet(List<MediaItem> mediaItems, MediaQueueItem[] mediaQueueItems) {
mediaItemsByContentId.clear();
onMediaItemsAdded(mediaItems, mediaQueueItems);
}
/**
* Called when media items {@linkplain Player#addMediaItems(List) have been added} and are sent to
* the cast playback queue. A future queue update of the {@link RemoteMediaClient} will reflect
* this addition.
*
* @param mediaItems The media items that have been added.
* @param mediaQueueItems The corresponding media queue items.
*/
public void onMediaItemsAdded(List<MediaItem> mediaItems, MediaQueueItem[] mediaQueueItems) {
for (int i = 0; i < mediaItems.size(); i++) {
mediaItemsByContentId.put(
checkNotNull(mediaQueueItems[i].getMedia()).getContentId(), mediaItems.get(i));
}
}
/**
@ -63,18 +110,36 @@ import java.util.HashSet;
}
int currentItemId = mediaStatus.getCurrentItemId();
String currentContentId = checkStateNotNull(mediaStatus.getMediaInfo()).getContentId();
MediaItem mediaItem = mediaItemsByContentId.get(currentContentId);
updateItemData(
currentItemId, mediaStatus.getMediaInfo(), /* defaultPositionUs= */ C.TIME_UNSET);
currentItemId,
mediaItem != null ? mediaItem : MediaItem.EMPTY,
mediaStatus.getMediaInfo(),
currentContentId,
/* defaultPositionUs= */ C.TIME_UNSET);
for (MediaQueueItem item : mediaStatus.getQueueItems()) {
long defaultPositionUs = (long) (item.getStartTime() * C.MICROS_PER_SECOND);
updateItemData(item.getItemId(), item.getMedia(), defaultPositionUs);
for (MediaQueueItem queueItem : mediaStatus.getQueueItems()) {
long defaultPositionUs = (long) (queueItem.getStartTime() * C.MICROS_PER_SECOND);
@Nullable MediaInfo mediaInfo = queueItem.getMedia();
String contentId = mediaInfo != null ? mediaInfo.getContentId() : UNKNOWN_CONTENT_ID;
mediaItem = mediaItemsByContentId.get(contentId);
updateItemData(
queueItem.getItemId(),
mediaItem != null ? mediaItem : mediaItemConverter.toMediaItem(queueItem),
mediaInfo,
contentId,
defaultPositionUs);
}
return new CastTimeline(itemIds, itemIdToData);
}
private void updateItemData(int itemId, @Nullable MediaInfo mediaInfo, long defaultPositionUs) {
private void updateItemData(
int itemId,
MediaItem mediaItem,
@Nullable MediaInfo mediaInfo,
String contentId,
long defaultPositionUs) {
CastTimeline.ItemData previousData = itemIdToData.get(itemId, CastTimeline.ItemData.EMPTY);
long durationUs = CastUtils.getStreamDurationUs(mediaInfo);
if (durationUs == C.TIME_UNSET) {
@ -87,7 +152,10 @@ import java.util.HashSet;
if (defaultPositionUs == C.TIME_UNSET) {
defaultPositionUs = previousData.defaultPositionUs;
}
itemIdToData.put(itemId, previousData.copyWithNewValues(durationUs, defaultPositionUs, isLive));
itemIdToData.put(
itemId,
previousData.copyWithNewValues(
durationUs, defaultPositionUs, isLive, mediaItem, contentId));
}
private void removeUnusedItemDataEntries(int[] itemIds) {
@ -99,6 +167,8 @@ import java.util.HashSet;
int index = 0;
while (index < itemIdToData.size()) {
if (!scratchItemIds.contains(itemIdToData.keyAt(index))) {
CastTimeline.ItemData itemData = itemIdToData.valueAt(index);
mediaItemsByContentId.remove(itemData.contentId);
itemIdToData.removeAt(index);
} else {
index++;

View file

@ -126,11 +126,14 @@ public final class DefaultMediaItemConverter implements MediaItemConverter {
if (mediaItem.mediaMetadata.trackNumber != null) {
metadata.putInt(MediaMetadata.KEY_TRACK_NUMBER, mediaItem.mediaMetadata.trackNumber);
}
String contentUrl = mediaItem.localConfiguration.uri.toString();
String contentId =
mediaItem.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? contentUrl : mediaItem.mediaId;
MediaInfo mediaInfo =
new MediaInfo.Builder(mediaItem.localConfiguration.uri.toString())
new MediaInfo.Builder(contentId)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(mediaItem.localConfiguration.mimeType)
.setContentUrl(contentUrl)
.setMetadata(metadata)
.setCustomData(getCustomData(mediaItem))
.build();

View file

@ -64,8 +64,10 @@ import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
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.Listener;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
@ -98,6 +100,7 @@ import org.mockito.Mockito;
public class CastPlayerTest {
private CastPlayer castPlayer;
private DefaultMediaItemConverter mediaItemConverter;
private RemoteMediaClient.Callback remoteMediaClientCallback;
@Mock private RemoteMediaClient mockRemoteMediaClient;
@ -106,7 +109,7 @@ public class CastPlayerTest {
@Mock private CastContext mockCastContext;
@Mock private SessionManager mockSessionManager;
@Mock private CastSession mockCastSession;
@Mock private Player.Listener mockListener;
@Mock private Listener mockListener;
@Mock private PendingResult<RemoteMediaClient.MediaChannelResult> mockPendingResult;
@Captor
@ -126,12 +129,14 @@ public class CastPlayerTest {
when(mockCastSession.getRemoteMediaClient()).thenReturn(mockRemoteMediaClient);
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
when(mockMediaStatus.getMediaInfo()).thenReturn(new MediaInfo.Builder("contentId").build());
when(mockMediaQueue.getItemIds()).thenReturn(new int[0]);
// Make the remote media client present the same default values as ExoPlayer:
when(mockRemoteMediaClient.isPaused()).thenReturn(true);
when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_OFF);
when(mockMediaStatus.getPlaybackRate()).thenReturn(1.0d);
castPlayer = new CastPlayer(mockCastContext);
mediaItemConverter = new DefaultMediaItemConverter();
castPlayer = new CastPlayer(mockCastContext, mediaItemConverter);
castPlayer.addListener(mockListener);
verify(mockRemoteMediaClient).registerCallback(callbackArgumentCaptor.capture());
remoteMediaClientCallback = callbackArgumentCaptor.getValue();
@ -388,7 +393,7 @@ public class CastPlayerTest {
mediaItems.add(
new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
castPlayer.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
castPlayer.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 2000L);
verify(mockRemoteMediaClient)
.queueLoad(queueItemsArgumentCaptor.capture(), eq(1), anyInt(), eq(2000L), any());
@ -434,22 +439,23 @@ public class CastPlayerTest {
.setMimeType(MimeTypes.APPLICATION_MPD)
.build());
castPlayer.setMediaItems(
firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 2000L);
updateTimeLine(
firstPlaylist, /* mediaQueueItemIds= */ new int[] {1, 2}, /* currentItemId= */ 2);
// Replacing existing playlist.
castPlayer.setMediaItems(
secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L);
castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 1000L);
updateTimeLine(secondPlaylist, /* mediaQueueItemIds= */ new int[] {3}, /* currentItemId= */ 3);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener, times(2))
.verify(mockListener)
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
eq(firstPlaylist.get(1)), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onMediaItemTransition(
eq(secondPlaylist.get(0)), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getAllValues().get(1).localConfiguration.tag).isEqualTo(3);
}
@SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly.
@ -469,8 +475,7 @@ public class CastPlayerTest {
.setMimeType(MimeTypes.APPLICATION_MPD)
.build());
castPlayer.setMediaItems(
firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 2000L);
updateTimeLine(
firstPlaylist,
/* mediaQueueItemIds= */ new int[] {1, 2},
@ -481,8 +486,7 @@ public class CastPlayerTest {
/* durationsMs= */ new long[] {20_000, 20_000},
/* positionMs= */ 2000L);
// Replacing existing playlist.
castPlayer.setMediaItems(
secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L);
castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 1000L);
updateTimeLine(
secondPlaylist,
/* mediaQueueItemIds= */ new int[] {3},
@ -494,8 +498,8 @@ public class CastPlayerTest {
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 1,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
/* mediaItemIndex= */ 1,
firstPlaylist.get(1),
/* periodUid= */ 2,
/* periodIndex= */ 1,
/* positionMs= */ 2000,
@ -505,8 +509,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 3,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(3).build(),
/* mediaItemIndex= */ 0,
secondPlaylist.get(0),
/* periodUid= */ 3,
/* periodIndex= */ 0,
/* positionMs= */ 1000,
@ -536,34 +540,37 @@ public class CastPlayerTest {
verify(mockRemoteMediaClient)
.queueInsertItems(
queueItemsArgumentCaptor.capture(), eq(MediaQueueItem.INVALID_ITEM_ID), any());
MediaQueueItem[] mediaQueueItems = queueItemsArgumentCaptor.getValue();
assertThat(mediaQueueItems[0].getMedia().getContentId()).isEqualTo(uri1);
assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(uri2);
}
@SuppressWarnings("ConstantConditions")
@Test
public void addMediaItems_insertAtIndex_callsRemoteMediaClient() {
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 2);
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
// Add two items.
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
String uri = "http://www.google.com/video3";
MediaItem anotherMediaItem =
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build();
int index = 1;
List<MediaItem> newPlaylist = Collections.singletonList(anotherMediaItem);
// Add another on position 1
int index = 1;
castPlayer.addMediaItems(index, Collections.singletonList(anotherMediaItem));
castPlayer.addMediaItems(index, newPlaylist);
updateTimeLine(newPlaylist, /* mediaQueueItemIds= */ new int[] {123}, /* currentItemId= */ 1);
verify(mockRemoteMediaClient)
.queueInsertItems(
queueItemsArgumentCaptor.capture(),
eq((int) mediaItems.get(index).localConfiguration.tag),
any());
MediaQueueItem[] mediaQueueItems = queueItemsArgumentCaptor.getValue();
assertThat(mediaQueueItems[0].getMedia().getContentId()).isEqualTo(uri);
verify(mockRemoteMediaClient, times(2))
.queueInsertItems(queueItemsArgumentCaptor.capture(), anyInt(), any());
assertThat(queueItemsArgumentCaptor.getAllValues().get(1)[0])
.isEqualTo(mediaItemConverter.toMediaQueueItem(anotherMediaItem));
Timeline.Window currentWindow =
castPlayer
.getCurrentTimeline()
.getWindow(castPlayer.getCurrentMediaItemIndex(), new Timeline.Window());
assertThat(currentWindow.uid).isEqualTo(123);
assertThat(currentWindow.mediaItem).isEqualTo(anotherMediaItem);
}
@Test
@ -702,8 +709,8 @@ public class CastPlayerTest {
Timeline currentTimeline = castPlayer.getCurrentTimeline();
for (int i = 0; i < mediaItems.size(); i++) {
assertThat(currentTimeline.getWindow(/* windowIndex= */ i, window).uid)
.isEqualTo(mediaItems.get(i).localConfiguration.tag);
assertThat(currentTimeline.getWindow(/* windowIndex= */ i, window).mediaItem)
.isEqualTo(mediaItems.get(i));
}
}
@ -720,10 +727,8 @@ public class CastPlayerTest {
inOrder
.verify(mockListener)
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
eq(mediaItem), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getValue().localConfiguration.tag)
.isEqualTo(mediaItem.localConfiguration.tag);
}
@Test
@ -742,7 +747,8 @@ public class CastPlayerTest {
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
.onMediaItemTransition(
eq(mediaItems.get(0)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onMediaItemTransition(
@ -776,8 +782,8 @@ public class CastPlayerTest {
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 1234,
@ -787,7 +793,7 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ null,
/* windowIndex= */ 0,
/* mediaItemIndex= */ 0,
/* mediaItem= */ null,
/* periodUid= */ null,
/* periodIndex= */ 0,
@ -827,10 +833,8 @@ public class CastPlayerTest {
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getAllValues().get(0).localConfiguration.tag)
.isEqualTo(mediaItem1.localConfiguration.tag);
assertThat(mediaItemCaptor.getAllValues().get(1).localConfiguration.tag)
.isEqualTo(mediaItem2.localConfiguration.tag);
assertThat(mediaItemCaptor.getAllValues().get(0)).isEqualTo(mediaItem1);
assertThat(mediaItemCaptor.getAllValues().get(1)).isEqualTo(mediaItem2);
}
@Test
@ -862,8 +866,8 @@ public class CastPlayerTest {
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
/* mediaItemIndex= */ 0,
mediaItem1,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 1234,
@ -873,8 +877,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
/* mediaItemIndex= */ 0,
mediaItem2,
/* periodUid= */ 2,
/* periodIndex= */ 0,
/* positionMs= */ 0,
@ -912,10 +916,8 @@ public class CastPlayerTest {
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
List<MediaItem> capturedMediaItems = mediaItemCaptor.getAllValues();
assertThat(capturedMediaItems.get(0).localConfiguration.tag)
.isEqualTo(mediaItem1.localConfiguration.tag);
assertThat(capturedMediaItems.get(1).localConfiguration.tag)
.isEqualTo(mediaItem2.localConfiguration.tag);
assertThat(capturedMediaItems.get(0)).isEqualTo(mediaItem1);
assertThat(capturedMediaItems.get(1)).isEqualTo(mediaItem2);
}
@Test
@ -945,8 +947,8 @@ public class CastPlayerTest {
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
/* mediaItemIndex= */ 0,
mediaItem1,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 0, // position at which we receive the timeline change
@ -956,8 +958,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
/* mediaItemIndex= */ 0,
mediaItem2,
/* periodUid= */ 2,
/* periodIndex= */ 0,
/* positionMs= */ 0,
@ -992,7 +994,8 @@ public class CastPlayerTest {
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
.onMediaItemTransition(
eq(mediaItem1), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
}
@ -1027,19 +1030,21 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234);
MediaMetadata firstMediaMetadata = castPlayer.getMediaMetadata();
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1234);
MediaMetadata secondMediaMetadata = castPlayer.getMediaMetadata();
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
.onMediaItemTransition(
eq(mediaItem1), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK));
.onMediaItemTransition(eq(mediaItem2), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
assertThat(mediaItemCaptor.getValue().localConfiguration.tag)
.isEqualTo(mediaItem2.localConfiguration.tag);
assertThat(firstMediaMetadata).isEqualTo(mediaItem1.mediaMetadata);
assertThat(secondMediaMetadata).isEqualTo(mediaItem2.mediaMetadata);
}
@Test
@ -1054,13 +1059,13 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234);
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1234);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
/* mediaItemIndex= */ 0,
mediaItem1,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 0,
@ -1070,8 +1075,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 1,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
/* mediaItemIndex= */ 1,
mediaItem2,
/* periodUid= */ 2,
/* periodIndex= */ 1,
/* positionMs= */ 1234,
@ -1097,12 +1102,13 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234);
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1234);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
.onMediaItemTransition(
eq(mediaItems.get(0)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
}
@ -1115,14 +1121,13 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234);
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1234);
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build();
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
mediaItem,
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 0,
@ -1132,8 +1137,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
mediaItem,
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 1234,
@ -1164,13 +1169,12 @@ public class CastPlayerTest {
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
.onMediaItemTransition(
eq(mediaItems.get(0)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO));
.onMediaItemTransition(eq(mediaItems.get(1)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getValue().localConfiguration.tag).isEqualTo(2);
}
@Test
@ -1203,8 +1207,8 @@ public class CastPlayerTest {
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 12500,
@ -1214,8 +1218,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 1,
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
/* mediaItemIndex= */ 1,
mediaItems.get(1),
/* periodUid= */ 2,
/* periodIndex= */ 1,
/* positionMs= */ 0,
@ -1250,12 +1254,11 @@ public class CastPlayerTest {
mediaItems, mediaQueueItemIds, currentItemId, streamTypes, durationsMs, positionMs);
castPlayer.seekBack();
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build();
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
mediaItem,
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 2 * C.DEFAULT_SEEK_BACK_INCREMENT_MS,
@ -1265,8 +1268,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
mediaItem,
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ C.DEFAULT_SEEK_BACK_INCREMENT_MS,
@ -1299,12 +1302,11 @@ public class CastPlayerTest {
mediaItems, mediaQueueItemIds, currentItemId, streamTypes, durationsMs, positionMs);
castPlayer.seekForward();
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build();
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
mediaItem,
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 0,
@ -1314,8 +1316,8 @@ public class CastPlayerTest {
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
mediaItem,
/* mediaItemIndex= */ 0,
mediaItems.get(0),
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ C.DEFAULT_SEEK_FORWARD_INCREMENT_MS,
@ -1475,14 +1477,14 @@ public class CastPlayerTest {
// Check that there were no other calls to onAvailableCommandsChanged.
verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
castPlayer.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0);
castPlayer.seekTo(/* mediaItemIndex= */ 3, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow);
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
}
@ -1509,14 +1511,14 @@ public class CastPlayerTest {
// Check that there were no other calls to onAvailableCommandsChanged.
verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
castPlayer.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow);
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
}
@ -1533,8 +1535,8 @@ public class CastPlayerTest {
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(defaultCommands);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100);
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 200);
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 100);
// Check that there were no other calls to onAvailableCommandsChanged.
verify(mockListener).onAvailableCommandsChanged(any());
}
@ -1763,6 +1765,105 @@ public class CastPlayerTest {
verify(mockListener).onAvailableCommandsChanged(any());
}
@Test
public void setMediaItems_doesNotifyOnMetadataChanged() {
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
.thenReturn(mockPendingResult);
ArgumentCaptor<MediaMetadata> metadataCaptor = ArgumentCaptor.forClass(MediaMetadata.class);
String uri1 = "http://www.google.com/video1";
String uri2 = "http://www.google.com/video2";
ImmutableList<MediaItem> firstPlaylist =
ImmutableList.of(
new MediaItem.Builder()
.setUri(uri1)
.setMimeType(MimeTypes.APPLICATION_MPD)
.setMediaMetadata(new MediaMetadata.Builder().setArtist("foo").build())
.build());
ImmutableList<MediaItem> secondPlaylist =
ImmutableList.of(
new MediaItem.Builder()
.setUri(Uri.EMPTY)
.setMediaMetadata(new MediaMetadata.Builder().setArtist("bar").build())
.setMimeType(MimeTypes.APPLICATION_MPD)
.build(),
new MediaItem.Builder()
.setUri(uri2)
.setMimeType(MimeTypes.APPLICATION_MP4)
.setMediaMetadata(new MediaMetadata.Builder().setArtist("foobar").build())
.build());
castPlayer.addListener(mockListener);
MediaMetadata intitalMetadata = castPlayer.getMediaMetadata();
castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 2000L);
updateTimeLine(firstPlaylist, /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1);
MediaMetadata firstMetadata = castPlayer.getMediaMetadata();
// Replacing existing playlist.
castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 0L);
updateTimeLine(
secondPlaylist, /* mediaQueueItemIds= */ new int[] {2, 3}, /* currentItemId= */ 3);
MediaMetadata secondMetadata = castPlayer.getMediaMetadata();
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0);
MediaMetadata thirdMetadata = castPlayer.getMediaMetadata();
verify(mockListener, times(3)).onMediaItemTransition(mediaItemCaptor.capture(), anyInt());
assertThat(mediaItemCaptor.getAllValues())
.containsExactly(firstPlaylist.get(0), secondPlaylist.get(1), secondPlaylist.get(0))
.inOrder();
verify(mockListener, times(3)).onMediaMetadataChanged(metadataCaptor.capture());
assertThat(metadataCaptor.getAllValues())
.containsExactly(
firstPlaylist.get(0).mediaMetadata,
secondPlaylist.get(1).mediaMetadata,
secondPlaylist.get(0).mediaMetadata)
.inOrder();
assertThat(intitalMetadata).isEqualTo(MediaMetadata.EMPTY);
assertThat(ImmutableList.of(firstMetadata, secondMetadata, thirdMetadata))
.containsExactly(
firstPlaylist.get(0).mediaMetadata,
secondPlaylist.get(1).mediaMetadata,
secondPlaylist.get(0).mediaMetadata)
.inOrder();
}
@Test
public void setMediaItems_equalMetadata_doesNotNotifyOnMediaMetadataChanged() {
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
.thenReturn(mockPendingResult);
String uri1 = "http://www.google.com/video1";
String uri2 = "http://www.google.com/video2";
ImmutableList<MediaItem> firstPlaylist =
ImmutableList.of(
new MediaItem.Builder()
.setUri(uri1)
.setMimeType(MimeTypes.APPLICATION_MPD)
.setTag(1)
.build());
ImmutableList<MediaItem> secondPlaylist =
ImmutableList.of(
new MediaItem.Builder()
.setMediaMetadata(MediaMetadata.EMPTY)
.setUri(Uri.EMPTY)
.setTag(2)
.setMimeType(MimeTypes.APPLICATION_MPD)
.build(),
new MediaItem.Builder()
.setUri(uri2)
.setMimeType(MimeTypes.APPLICATION_MP4)
.setTag(3)
.build());
castPlayer.addListener(mockListener);
castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 2000L);
updateTimeLine(firstPlaylist, /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1);
castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 0L);
updateTimeLine(
secondPlaylist, /* mediaQueueItemIds= */ new int[] {2, 3}, /* currentItemId= */ 3);
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0);
verify(mockListener, times(3)).onMediaItemTransition(any(), anyInt());
verify(mockListener, never()).onMediaMetadataChanged(any());
}
private int[] createMediaQueueItemIds(int numberOfIds) {
int[] mediaQueueItemIds = new int[numberOfIds];
for (int i = 0; i < numberOfIds; i++) {
@ -1782,8 +1883,9 @@ public class CastPlayerTest {
private MediaItem createMediaItem(int mediaQueueItemId) {
return new MediaItem.Builder()
.setUri("http://www.google.com/video" + mediaQueueItemId)
.setMediaMetadata(
new MediaMetadata.Builder().setArtist("Foo Bar - " + mediaQueueItemId).build())
.setMimeType(MimeTypes.APPLICATION_MPD)
.setTag(mediaQueueItemId)
.build();
}
@ -1821,8 +1923,12 @@ public class CastPlayerTest {
int mediaQueueItemId = mediaQueueItemIds[i];
int streamType = streamTypes[i];
long durationMs = durationsMs[i];
String contentId =
mediaItem.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID)
? mediaItem.localConfiguration.uri.toString()
: mediaItem.mediaId;
MediaInfo.Builder mediaInfoBuilder =
new MediaInfo.Builder(mediaItem.localConfiguration.uri.toString())
new MediaInfo.Builder(contentId)
.setStreamType(streamType)
.setContentType(mediaItem.localConfiguration.mimeType);
if (durationMs != C.TIME_UNSET) {

View file

@ -15,21 +15,30 @@
*/
package com.google.android.exoplayer2.ext.cast;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.framework.media.MediaQueue;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
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;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
/** Tests for {@link CastTimelineTracker}. */
@RunWith(AndroidJUnit4.class)
@ -40,10 +49,19 @@ public class CastTimelineTrackerTest {
private static final long DURATION_4_MS = 4000;
private static final long DURATION_5_MS = 5000;
private MediaItemConverter mediaItemConverter;
private CastTimelineTracker castTimelineTracker;
@Before
public void init() {
mediaItemConverter = new DefaultMediaItemConverter();
castTimelineTracker = new CastTimelineTracker(mediaItemConverter);
}
/** Tests that duration of the current media info is correctly propagated to the timeline. */
@Test
public void getCastTimelinePersistsDuration() {
CastTimelineTracker tracker = new CastTimelineTracker();
CastTimelineTracker tracker = new CastTimelineTracker(new DefaultMediaItemConverter());
RemoteMediaClient remoteMediaClient =
mockRemoteMediaClient(
@ -104,10 +122,179 @@ public class CastTimelineTrackerTest {
Util.msToUs(DURATION_5_MS));
}
@Test
public void getCastTimeline_onMediaItemsSet_correctMediaItemsInTimeline() {
RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class);
MediaQueue mockMediaQueue = mock(MediaQueue.class);
MediaStatus mockMediaStatus = mock(MediaStatus.class);
ImmutableList<MediaItem> playlistMediaItems =
ImmutableList.of(createMediaItem(0), createMediaItem(1));
MediaQueueItem[] playlistMediaQueueItems =
new MediaQueueItem[] {
createMediaQueueItem(playlistMediaItems.get(0), 0),
createMediaQueueItem(playlistMediaItems.get(1), 1)
};
castTimelineTracker.onMediaItemsSet(playlistMediaItems, playlistMediaQueueItems);
// Mock remote media client state after adding two items.
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1});
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
when(mockMediaStatus.getCurrentItemId()).thenReturn(0);
when(mockMediaStatus.getMediaInfo()).thenReturn(playlistMediaQueueItems[0].getMedia());
when(mockMediaStatus.getQueueItems()).thenReturn(Arrays.asList(playlistMediaQueueItems));
CastTimeline castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
assertThat(castTimeline.getWindowCount()).isEqualTo(2);
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
.isEqualTo(playlistMediaItems.get(0));
assertThat(castTimeline.getWindow(/* windowIndex= */ 1, new Window()).mediaItem)
.isEqualTo(playlistMediaItems.get(1));
MediaItem thirdMediaItem = createMediaItem(2);
MediaQueueItem thirdMediaQueueItem = createMediaQueueItem(thirdMediaItem, 2);
castTimelineTracker.onMediaItemsSet(
ImmutableList.of(thirdMediaItem), new MediaQueueItem[] {thirdMediaQueueItem});
// Mock remote media client state after a single item overrides the previous playlist.
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {2});
when(mockMediaStatus.getCurrentItemId()).thenReturn(2);
when(mockMediaStatus.getMediaInfo()).thenReturn(thirdMediaQueueItem.getMedia());
when(mockMediaStatus.getQueueItems()).thenReturn(ImmutableList.of(thirdMediaQueueItem));
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
assertThat(castTimeline.getWindowCount()).isEqualTo(1);
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
.isEqualTo(thirdMediaItem);
}
@Test
public void getCastTimeline_onMediaItemsAdded_correctMediaItemsInTimeline() {
RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class);
MediaQueue mockMediaQueue = mock(MediaQueue.class);
MediaStatus mockMediaStatus = mock(MediaStatus.class);
ImmutableList<MediaItem> playlistMediaItems =
ImmutableList.of(createMediaItem(0), createMediaItem(1));
MediaQueueItem[] playlistQueueItems =
new MediaQueueItem[] {
createMediaQueueItem(playlistMediaItems.get(0), /* uid= */ 0),
createMediaQueueItem(playlistMediaItems.get(1), /* uid= */ 1)
};
ImmutableList<MediaItem> secondPlaylistMediaItems =
new ImmutableList.Builder<MediaItem>()
.addAll(playlistMediaItems)
.add(createMediaItem(2))
.build();
castTimelineTracker.onMediaItemsAdded(playlistMediaItems, playlistQueueItems);
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
// Mock remote media client state after two items have been added.
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1});
when(mockMediaStatus.getCurrentItemId()).thenReturn(0);
when(mockMediaStatus.getMediaInfo()).thenReturn(playlistQueueItems[0].getMedia());
when(mockMediaStatus.getQueueItems()).thenReturn(Arrays.asList(playlistQueueItems));
CastTimeline castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
assertThat(castTimeline.getWindowCount()).isEqualTo(2);
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
.isEqualTo(playlistMediaItems.get(0));
assertThat(castTimeline.getWindow(/* windowIndex= */ 1, new Window()).mediaItem)
.isEqualTo(playlistMediaItems.get(1));
// Mock remote media client state after adding a third item.
List<MediaQueueItem> playlistThreeQueueItems =
new ArrayList<>(Arrays.asList(playlistQueueItems));
playlistThreeQueueItems.add(createMediaQueueItem(secondPlaylistMediaItems.get(2), 2));
castTimelineTracker.onMediaItemsAdded(
secondPlaylistMediaItems, playlistThreeQueueItems.toArray(new MediaQueueItem[0]));
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1, 2});
when(mockMediaStatus.getQueueItems()).thenReturn(playlistThreeQueueItems);
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
assertThat(castTimeline.getWindowCount()).isEqualTo(3);
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
.isEqualTo(secondPlaylistMediaItems.get(0));
assertThat(castTimeline.getWindow(/* windowIndex= */ 1, new Window()).mediaItem)
.isEqualTo(secondPlaylistMediaItems.get(1));
assertThat(castTimeline.getWindow(/* windowIndex= */ 2, new Window()).mediaItem)
.isEqualTo(secondPlaylistMediaItems.get(2));
}
@Test
public void getCastTimeline_itemsRemoved_correctMediaItemsInTimelineAndMapCleanedUp() {
RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class);
MediaQueue mockMediaQueue = mock(MediaQueue.class);
MediaStatus mockMediaStatus = mock(MediaStatus.class);
ImmutableList<MediaItem> playlistMediaItems =
ImmutableList.of(createMediaItem(0), createMediaItem(1));
MediaQueueItem[] initialPlaylistTwoQueueItems =
new MediaQueueItem[] {
createMediaQueueItem(playlistMediaItems.get(0), 0),
createMediaQueueItem(playlistMediaItems.get(1), 1)
};
castTimelineTracker.onMediaItemsSet(playlistMediaItems, initialPlaylistTwoQueueItems);
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
// Mock remote media client state with two items in the queue.
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1});
when(mockMediaStatus.getCurrentItemId()).thenReturn(0);
when(mockMediaStatus.getMediaInfo()).thenReturn(initialPlaylistTwoQueueItems[0].getMedia());
when(mockMediaStatus.getQueueItems()).thenReturn(Arrays.asList(initialPlaylistTwoQueueItems));
CastTimeline castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
assertThat(castTimeline.getWindowCount()).isEqualTo(2);
assertThat(castTimelineTracker.mediaItemsByContentId).hasSize(2);
// Mock remote media client state after the first item has been removed.
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {1});
when(mockMediaStatus.getCurrentItemId()).thenReturn(1);
when(mockMediaStatus.getMediaInfo()).thenReturn(initialPlaylistTwoQueueItems[1].getMedia());
when(mockMediaStatus.getQueueItems())
.thenReturn(ImmutableList.of(initialPlaylistTwoQueueItems[1]));
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
assertThat(castTimeline.getWindowCount()).isEqualTo(1);
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
.isEqualTo(playlistMediaItems.get(1));
// Assert that the removed item has been removed from the content ID map.
assertThat(castTimelineTracker.mediaItemsByContentId).hasSize(1);
// Mock remote media client state for empty queue.
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(null);
when(mockMediaQueue.getItemIds()).thenReturn(new int[0]);
when(mockMediaStatus.getCurrentItemId()).thenReturn(MediaQueueItem.INVALID_ITEM_ID);
when(mockMediaStatus.getMediaInfo()).thenReturn(null);
when(mockMediaStatus.getQueueItems()).thenReturn(ImmutableList.of());
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
assertThat(castTimeline.getWindowCount()).isEqualTo(0);
// Queue is not emptied when remote media client is empty. See [Internal ref: b/128825216].
assertThat(castTimelineTracker.mediaItemsByContentId).hasSize(1);
}
private MediaItem createMediaItem(int uid) {
return new MediaItem.Builder()
.setUri("http://www.google.com/" + uid)
.setMimeType(MimeTypes.AUDIO_MPEG)
.setTag(uid)
.build();
}
private MediaQueueItem createMediaQueueItem(MediaItem mediaItem, int uid) {
return new MediaQueueItem.Builder(mediaItemConverter.toMediaQueueItem(mediaItem))
.setItemId(uid)
.build();
}
private static RemoteMediaClient mockRemoteMediaClient(
int[] itemIds, int currentItemId, long currentDurationMs) {
RemoteMediaClient remoteMediaClient = Mockito.mock(RemoteMediaClient.class);
MediaStatus status = Mockito.mock(MediaStatus.class);
RemoteMediaClient remoteMediaClient = mock(RemoteMediaClient.class);
MediaStatus status = mock(MediaStatus.class);
when(status.getQueueItems()).thenReturn(Collections.emptyList());
when(remoteMediaClient.getMediaStatus()).thenReturn(status);
when(status.getMediaInfo()).thenReturn(getMediaInfo(currentDurationMs));
@ -118,7 +305,7 @@ public class CastTimelineTrackerTest {
}
private static MediaQueue mockMediaQueue(int[] itemIds) {
MediaQueue mediaQueue = Mockito.mock(MediaQueue.class);
MediaQueue mediaQueue = mock(MediaQueue.class);
when(mediaQueue.getItemIds()).thenReturn(itemIds);
return mediaQueue;
}

View file

@ -50,6 +50,7 @@ public class DefaultMediaItemConverterTest {
MediaItem.Builder builder = new MediaItem.Builder();
MediaItem item =
builder
.setMediaId("fooBar")
.setUri(Uri.parse("http://example.com"))
.setMediaMetadata(MediaMetadata.EMPTY)
.setMimeType(MimeTypes.APPLICATION_MPD)
@ -66,4 +67,45 @@ public class DefaultMediaItemConverterTest {
assertThat(reconstructedItem).isEqualTo(item);
}
@Test
public void toMediaQueueItem_nonDefaultMediaId_usedAsContentId() {
MediaItem.Builder builder = new MediaItem.Builder();
MediaItem item =
builder
.setMediaId("fooBar")
.setUri("http://example.com")
.setMimeType(MimeTypes.APPLICATION_MPD)
.build();
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
MediaQueueItem queueItem = converter.toMediaQueueItem(item);
assertThat(queueItem.getMedia().getContentId()).isEqualTo("fooBar");
}
@Test
public void toMediaQueueItem_defaultMediaId_uriAsContentId() {
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
MediaItem mediaItem =
new MediaItem.Builder()
.setUri("http://example.com")
.setMimeType(MimeTypes.APPLICATION_MPD)
.build();
MediaQueueItem queueItem = converter.toMediaQueueItem(mediaItem);
assertThat(queueItem.getMedia().getContentId()).isEqualTo("http://example.com");
MediaItem secondMediaItem =
new MediaItem.Builder()
.setMediaId(MediaItem.DEFAULT_MEDIA_ID)
.setUri("http://example.com")
.setMimeType(MimeTypes.APPLICATION_MPD)
.build();
MediaQueueItem secondQueueItem = converter.toMediaQueueItem(secondMediaItem);
assertThat(secondQueueItem.getMedia().getContentId()).isEqualTo("http://example.com");
}
}

View file

@ -234,11 +234,6 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
// Player.Listener implementation.
@Override
public void onPlaybackStateChanged(@Player.State int playbackState) {
notifyStateChanged();
}
@Override
public void onPlayerError(PlaybackException error) {
Callback callback = getCallback();
@ -283,5 +278,13 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
int scaledWidth = Math.round(videoSize.width * videoSize.pixelWidthHeightRatio);
getCallback().onVideoSizeChanged(LeanbackPlayerAdapter.this, scaledWidth, videoSize.height);
}
@Override
public void onEvents(Player player, Player.Events events) {
if (events.containsAny(
Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED)) {
notifyStateChanged();
}
}
}
}

View file

@ -53,6 +53,7 @@ dependencies {
testImplementation 'junit:junit:' + junitVersion
testImplementation 'com.google.truth:truth:' + truthVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'library-core')
testImplementation project(modulePrefix + 'testutils')
}

View file

@ -27,11 +27,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.18.0";
public static final String VERSION = "2.18.1";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.18.0";
public static final String VERSION_SLASHY = "ExoPlayerLib/2.18.1";
/**
* The version of the library expressed as an integer, for example 1002003.
@ -41,7 +41,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2_018_000;
public static final int VERSION_INT = 2_018_001;
/** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true;

View file

@ -372,6 +372,7 @@ public interface Player {
COMMAND_GET_TIMELINE,
COMMAND_GET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEM,
COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_GET_AUDIO_ATTRIBUTES,
COMMAND_GET_VOLUME,
@ -383,7 +384,6 @@ public interface Player {
COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
COMMAND_GET_TRACKS,
COMMAND_SET_MEDIA_ITEM,
};
private final FlagSet.Builder flagsBuilder;
@ -1422,6 +1422,7 @@ public interface Player {
COMMAND_GET_TIMELINE,
COMMAND_GET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEM,
COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_GET_AUDIO_ATTRIBUTES,
COMMAND_GET_VOLUME,
@ -1433,7 +1434,6 @@ public interface Player {
COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
COMMAND_GET_TRACKS,
COMMAND_SET_MEDIA_ITEM,
})
@interface Command {}
/** Command to start, pause or resume playback. */
@ -1490,6 +1490,8 @@ public interface Player {
int COMMAND_GET_MEDIA_ITEMS_METADATA = 18;
/** Command to set the {@link MediaItem MediaItems} metadata. */
int COMMAND_SET_MEDIA_ITEMS_METADATA = 19;
/** Command to set a {@link MediaItem MediaItem}. */
int COMMAND_SET_MEDIA_ITEM = 31;
/** Command to change the {@link MediaItem MediaItems} in the playlist. */
int COMMAND_CHANGE_MEDIA_ITEMS = 20;
/** Command to get the player current {@link AudioAttributes}. */
@ -1512,8 +1514,6 @@ public interface Player {
int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29;
/** Command to get details of the current track selection. */
int COMMAND_GET_TRACKS = 30;
/** Command to set a {@link MediaItem MediaItem}. */
int COMMAND_SET_MEDIA_ITEM = 31;
/** Represents an invalid {@link Command}. */
int COMMAND_INVALID = -1;

View file

@ -1340,6 +1340,27 @@ public abstract class Timeline implements Bundleable {
return false;
}
}
// Check shuffled order
int windowIndex = getFirstWindowIndex(/* shuffleModeEnabled= */ true);
if (windowIndex != other.getFirstWindowIndex(/* shuffleModeEnabled= */ true)) {
return false;
}
int lastWindowIndex = getLastWindowIndex(/* shuffleModeEnabled= */ true);
if (lastWindowIndex != other.getLastWindowIndex(/* shuffleModeEnabled= */ true)) {
return false;
}
while (windowIndex != lastWindowIndex) {
int nextWindowIndex =
getNextWindowIndex(windowIndex, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true);
if (nextWindowIndex
!= other.getNextWindowIndex(
windowIndex, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true)) {
return false;
}
windowIndex = nextWindowIndex;
}
return true;
}
@ -1356,6 +1377,13 @@ public abstract class Timeline implements Bundleable {
for (int i = 0; i < getPeriodCount(); i++) {
result = 31 * result + getPeriod(i, period, /* setIds= */ true).hashCode();
}
for (int windowIndex = getFirstWindowIndex(true);
windowIndex != C.INDEX_UNSET;
windowIndex = getNextWindowIndex(windowIndex, Player.REPEAT_MODE_OFF, true)) {
result = 31 * result + windowIndex;
}
return result;
}

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.drm;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
@ -156,6 +157,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
* @param schemeType A protection scheme type. May be null.
* @return A copy with the specified protection scheme type.
*/
@CheckResult
public DrmInitData copyWithSchemeType(@Nullable String schemeType) {
if (Util.areEqual(this.schemeType, schemeType)) {
return this;
@ -332,6 +334,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
* @param data The data to include in the copy.
* @return The new instance.
*/
@CheckResult
public SchemeData copyWithData(@Nullable byte[] data) {
return new SchemeData(uuid, licenseServerUrl, mimeType, data);
}

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.text.TextUtils;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
@ -28,7 +29,10 @@ import java.lang.annotation.Target;
import java.net.UnknownHostException;
import org.checkerframework.dataflow.qual.Pure;
/** Wrapper around {@link android.util.Log} which allows to set the log level. */
/**
* Wrapper around {@link android.util.Log} which allows to set the log level and to specify a custom
* log output.
*/
public final class Log {
/**
@ -51,15 +55,89 @@ public final class Log {
/** Log level to disable all logging. */
public static final int LOG_LEVEL_OFF = Integer.MAX_VALUE;
/**
* Interface for a logger that can output messages with a tag.
*
* <p>Use {@link #DEFAULT} to output to {@link android.util.Log}.
*/
public interface Logger {
/** The default instance logging to {@link android.util.Log}. */
Logger DEFAULT =
new Logger() {
@Override
public void d(String tag, String message) {
android.util.Log.d(tag, message);
}
@Override
public void i(String tag, String message) {
android.util.Log.i(tag, message);
}
@Override
public void w(String tag, String message) {
android.util.Log.w(tag, message);
}
@Override
public void e(String tag, String message) {
android.util.Log.e(tag, message);
}
};
/**
* Logs a debug-level message.
*
* @param tag The tag of the message.
* @param message The message.
*/
void d(String tag, String message);
/**
* Logs an information-level message.
*
* @param tag The tag of the message.
* @param message The message.
*/
void i(String tag, String message);
/**
* Logs a warning-level message.
*
* @param tag The tag of the message.
* @param message The message.
*/
void w(String tag, String message);
/**
* Logs an error-level message.
*
* @param tag The tag of the message.
* @param message The message.
*/
void e(String tag, String message);
}
private static final Object lock = new Object();
@GuardedBy("lock")
private static int logLevel = LOG_LEVEL_ALL;
@GuardedBy("lock")
private static boolean logStackTraces = true;
@GuardedBy("lock")
private static Logger logger = Logger.DEFAULT;
private Log() {}
/** Returns current {@link LogLevel} for ExoPlayer logcat logging. */
@Pure
public static @LogLevel int getLogLevel() {
return logLevel;
synchronized (lock) {
return logLevel;
}
}
/**
@ -68,7 +146,9 @@ public final class Log {
* @param logLevel The new {@link LogLevel}.
*/
public static void setLogLevel(@LogLevel int logLevel) {
Log.logLevel = logLevel;
synchronized (lock) {
Log.logLevel = logLevel;
}
}
/**
@ -78,7 +158,20 @@ public final class Log {
* @param logStackTraces Whether stack traces will be logged.
*/
public static void setLogStackTraces(boolean logStackTraces) {
Log.logStackTraces = logStackTraces;
synchronized (lock) {
Log.logStackTraces = logStackTraces;
}
}
/**
* Sets a custom {@link Logger} as the output.
*
* @param logger The {@link Logger}.
*/
public static void setLogger(Logger logger) {
synchronized (lock) {
Log.logger = logger;
}
}
/**
@ -86,8 +179,10 @@ public final class Log {
*/
@Pure
public static void d(@Size(max = 23) String tag, String message) {
if (logLevel == LOG_LEVEL_ALL) {
android.util.Log.d(tag, message);
synchronized (lock) {
if (logLevel == LOG_LEVEL_ALL) {
logger.d(tag, message);
}
}
}
@ -104,8 +199,10 @@ public final class Log {
*/
@Pure
public static void i(@Size(max = 23) String tag, String message) {
if (logLevel <= LOG_LEVEL_INFO) {
android.util.Log.i(tag, message);
synchronized (lock) {
if (logLevel <= LOG_LEVEL_INFO) {
logger.i(tag, message);
}
}
}
@ -122,8 +219,10 @@ public final class Log {
*/
@Pure
public static void w(@Size(max = 23) String tag, String message) {
if (logLevel <= LOG_LEVEL_WARNING) {
android.util.Log.w(tag, message);
synchronized (lock) {
if (logLevel <= LOG_LEVEL_WARNING) {
logger.w(tag, message);
}
}
}
@ -140,8 +239,10 @@ public final class Log {
*/
@Pure
public static void e(@Size(max = 23) String tag, String message) {
if (logLevel <= LOG_LEVEL_ERROR) {
android.util.Log.e(tag, message);
synchronized (lock) {
if (logLevel <= LOG_LEVEL_ERROR) {
logger.e(tag, message);
}
}
}
@ -167,20 +268,23 @@ public final class Log {
@Nullable
@Pure
public static String getThrowableString(@Nullable Throwable throwable) {
if (throwable == null) {
return null;
} else if (isCausedByUnknownHostException(throwable)) {
// UnknownHostException implies the device doesn't have network connectivity.
// UnknownHostException.getMessage() may return a string that's more verbose than desired for
// logging an expected failure mode. Conversely, android.util.Log.getStackTraceString has
// special handling to return the empty string, which can result in logging that doesn't
// indicate the failure mode at all. Hence we special case this exception to always return a
// concise but useful message.
return "UnknownHostException (no network)";
} else if (!logStackTraces) {
return throwable.getMessage();
} else {
return android.util.Log.getStackTraceString(throwable).trim().replace("\t", " ");
synchronized (lock) {
if (throwable == null) {
return null;
} else if (isCausedByUnknownHostException(throwable)) {
// UnknownHostException implies the device doesn't have network connectivity.
// UnknownHostException.getMessage() may return a string that's more verbose than desired
// for
// logging an expected failure mode. Conversely, android.util.Log.getStackTraceString has
// special handling to return the empty string, which can result in logging that doesn't
// indicate the failure mode at all. Hence we special case this exception to always return a
// concise but useful message.
return "UnknownHostException (no network)";
} else if (!logStackTraces) {
return throwable.getMessage();
} else {
return android.util.Log.getStackTraceString(throwable).trim().replace("\t", " ");
}
}
}

View file

@ -55,6 +55,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.security.NetworkSecurityPolicy;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@ -195,7 +196,7 @@ public final class Util {
*/
@Nullable
public static ComponentName startForegroundService(Context context, Intent intent) {
if (Util.SDK_INT >= 26) {
if (SDK_INT >= 26) {
return context.startForegroundService(intent);
} else {
return context.startService(intent);
@ -211,12 +212,12 @@ public final class Util {
* @return Whether a permission request was made.
*/
public static boolean maybeRequestReadExternalStoragePermission(Activity activity, Uri... uris) {
if (Util.SDK_INT < 23) {
if (SDK_INT < 23) {
return false;
}
for (Uri uri : uris) {
if (isLocalFileUri(uri)) {
return requestExternalStoragePermission(activity);
if (maybeRequestReadExternalStoragePermission(activity, uri)) {
return true;
}
}
return false;
@ -234,25 +235,46 @@ public final class Util {
*/
public static boolean maybeRequestReadExternalStoragePermission(
Activity activity, MediaItem... mediaItems) {
if (Util.SDK_INT < 23) {
if (SDK_INT < 23) {
return false;
}
for (MediaItem mediaItem : mediaItems) {
if (mediaItem.localConfiguration == null) {
continue;
}
if (isLocalFileUri(mediaItem.localConfiguration.uri)) {
return requestExternalStoragePermission(activity);
if (maybeRequestReadExternalStoragePermission(activity, mediaItem.localConfiguration.uri)) {
return true;
}
for (int i = 0; i < mediaItem.localConfiguration.subtitleConfigurations.size(); i++) {
if (isLocalFileUri(mediaItem.localConfiguration.subtitleConfigurations.get(i).uri)) {
return requestExternalStoragePermission(activity);
List<MediaItem.SubtitleConfiguration> subtitleConfigs =
mediaItem.localConfiguration.subtitleConfigurations;
for (int i = 0; i < subtitleConfigs.size(); i++) {
if (maybeRequestReadExternalStoragePermission(activity, subtitleConfigs.get(i).uri)) {
return true;
}
}
}
return false;
}
private static boolean maybeRequestReadExternalStoragePermission(Activity activity, Uri uri) {
return SDK_INT >= 23
&& (isLocalFileUri(uri) || isMediaStoreExternalContentUri(uri))
&& requestExternalStoragePermission(activity);
}
private static boolean isMediaStoreExternalContentUri(Uri uri) {
if (!"content".equals(uri.getScheme()) || !MediaStore.AUTHORITY.equals(uri.getAuthority())) {
return false;
}
List<String> pathSegments = uri.getPathSegments();
if (pathSegments.isEmpty()) {
return false;
}
String firstPathSegment = pathSegments.get(0);
return MediaStore.VOLUME_EXTERNAL.equals(firstPathSegment)
|| MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(firstPathSegment);
}
/**
* Returns whether it may be possible to load the URIs of the given media items based on the
* network security policy's cleartext traffic permissions.
@ -261,7 +283,7 @@ public final class Util {
* @return Whether it may be possible to load the URIs of the given media items.
*/
public static boolean checkCleartextTrafficPermitted(MediaItem... mediaItems) {
if (Util.SDK_INT < 24) {
if (SDK_INT < 24) {
// We assume cleartext traffic is permitted.
return true;
}
@ -622,7 +644,7 @@ public final class Util {
normalizedTag = language;
}
normalizedTag = Ascii.toLowerCase(normalizedTag);
String mainLanguage = Util.splitAtFirst(normalizedTag, "-")[0];
String mainLanguage = splitAtFirst(normalizedTag, "-")[0];
if (languageTagReplacementMap == null) {
languageTagReplacementMap = createIsoLanguageReplacementMap();
}
@ -1633,9 +1655,9 @@ public final class Util {
case 7:
return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
case 8:
if (Util.SDK_INT >= 23) {
if (SDK_INT >= 23) {
return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else if (Util.SDK_INT >= 21) {
} else if (SDK_INT >= 21) {
// Equal to AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, which is hidden before Android M.
return AudioFormat.CHANNEL_OUT_5POINT1
| AudioFormat.CHANNEL_OUT_SIDE_LEFT
@ -1918,7 +1940,7 @@ public final class Util {
public static @ContentType int inferContentTypeForUriAndMimeType(
Uri uri, @Nullable String mimeType) {
if (mimeType == null) {
return Util.inferContentType(uri);
return inferContentType(uri);
}
switch (mimeType) {
case MimeTypes.APPLICATION_MPD:
@ -2242,7 +2264,7 @@ public final class Util {
/** Returns the default {@link Locale.Category#DISPLAY DISPLAY} {@link Locale}. */
public static Locale getDefaultDisplayLocale() {
return Util.SDK_INT >= 24 ? Locale.getDefault(Locale.Category.DISPLAY) : Locale.getDefault();
return SDK_INT >= 24 ? Locale.getDefault(Locale.Category.DISPLAY) : Locale.getDefault();
}
/**
@ -2314,7 +2336,7 @@ public final class Util {
* @return Whether the app is running on an automotive device.
*/
public static boolean isAutomotive(Context context) {
return Util.SDK_INT >= 23
return SDK_INT >= 23
&& context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
@ -2332,7 +2354,7 @@ public final class Util {
*/
public static Point getCurrentDisplayModeSize(Context context) {
@Nullable Display defaultDisplay = null;
if (Util.SDK_INT >= 17) {
if (SDK_INT >= 17) {
@Nullable
DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
@ -2380,7 +2402,7 @@ public final class Util {
// vendor.display-size instead.
@Nullable
String displaySize =
Util.SDK_INT < 28
SDK_INT < 28
? getSystemProperty("sys.display-size")
: getSystemProperty("vendor.display-size");
// If we managed to read the display size, attempt to parse it.
@ -2401,17 +2423,17 @@ public final class Util {
}
// Sony Android TVs advertise support for 4k output via a system feature.
if ("Sony".equals(Util.MANUFACTURER)
&& Util.MODEL.startsWith("BRAVIA")
if ("Sony".equals(MANUFACTURER)
&& MODEL.startsWith("BRAVIA")
&& context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) {
return new Point(3840, 2160);
}
}
Point displaySize = new Point();
if (Util.SDK_INT >= 23) {
if (SDK_INT >= 23) {
getDisplaySizeV23(display, displaySize);
} else if (Util.SDK_INT >= 17) {
} else if (SDK_INT >= 17) {
getDisplaySizeV17(display, displaySize);
} else {
getDisplaySizeV16(display, displaySize);
@ -2629,7 +2651,7 @@ public final class Util {
@RequiresApi(24)
private static String[] getSystemLocalesV24(Configuration config) {
return Util.split(config.getLocales().toLanguageTags(), ",");
return split(config.getLocales().toLanguageTags(), ",");
}
@RequiresApi(21)

View file

@ -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.ShuffleOrder.DefaultShuffleOrder;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
@ -65,6 +66,50 @@ public class TimelineTest {
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
}
@Test
public void timelineEquals() {
ImmutableList<TimelineWindowDefinition> timelineWindowDefinitions =
ImmutableList.of(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 111),
new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 222),
new TimelineWindowDefinition(/* periodCount= */ 3, /* id= */ 333));
Timeline timeline1 =
new FakeTimeline(timelineWindowDefinitions.toArray(new TimelineWindowDefinition[0]));
Timeline timeline2 =
new FakeTimeline(timelineWindowDefinitions.toArray(new TimelineWindowDefinition[0]));
assertThat(timeline1).isEqualTo(timeline2);
assertThat(timeline1.hashCode()).isEqualTo(timeline2.hashCode());
}
@Test
public void timelineEquals_includesShuffleOrder() {
ImmutableList<TimelineWindowDefinition> timelineWindowDefinitions =
ImmutableList.of(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 111),
new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 222),
new TimelineWindowDefinition(/* periodCount= */ 3, /* id= */ 333));
Timeline timeline =
new FakeTimeline(
new Object[0],
new DefaultShuffleOrder(timelineWindowDefinitions.size(), /* randomSeed= */ 5),
timelineWindowDefinitions.toArray(new TimelineWindowDefinition[0]));
Timeline timelineWithEquivalentShuffleOrder =
new FakeTimeline(
new Object[0],
new DefaultShuffleOrder(timelineWindowDefinitions.size(), /* randomSeed= */ 5),
timelineWindowDefinitions.toArray(new TimelineWindowDefinition[0]));
Timeline timelineWithDifferentShuffleOrder =
new FakeTimeline(
new Object[0],
new DefaultShuffleOrder(timelineWindowDefinitions.size(), /* randomSeed= */ 3),
timelineWindowDefinitions.toArray(new TimelineWindowDefinition[0]));
assertThat(timeline).isEqualTo(timelineWithEquivalentShuffleOrder);
assertThat(timeline.hashCode()).isEqualTo(timelineWithEquivalentShuffleOrder.hashCode());
assertThat(timeline).isNotEqualTo(timelineWithDifferentShuffleOrder);
}
@Test
public void windowEquals() {
MediaItem mediaItem = new MediaItem.Builder().setUri("uri").setTag(new Object()).build();

View file

@ -283,6 +283,7 @@ import java.util.concurrent.TimeoutException;
COMMAND_GET_TIMELINE,
COMMAND_GET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEM,
COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_GET_TRACKS,
COMMAND_GET_AUDIO_ATTRIBUTES,
@ -292,8 +293,7 @@ import java.util.concurrent.TimeoutException;
COMMAND_SET_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT,
COMMAND_SET_MEDIA_ITEM)
COMMAND_GET_TEXT)
.addIf(
COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported())
.build();
@ -422,6 +422,9 @@ import java.util.concurrent.TimeoutException;
public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) {
verifyApplicationThread();
internalPlayer.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled);
for (AudioOffloadListener listener : audioOffloadListeners) {
listener.onExperimentalOffloadSchedulingEnabledChanged(offloadSchedulingEnabled);
}
}
@Override
@ -696,6 +699,7 @@ import java.util.concurrent.TimeoutException;
@Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) {
verifyApplicationThread();
this.shuffleOrder = shuffleOrder;
Timeline timeline = createMaskingTimeline();
PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
@ -704,7 +708,6 @@ import java.util.concurrent.TimeoutException;
maskWindowPositionMsOrGetPeriodPositionUs(
timeline, getCurrentMediaItemIndex(), getCurrentPosition()));
pendingOperationAcks++;
this.shuffleOrder = shuffleOrder;
internalPlayer.setShuffleOrder(shuffleOrder);
updatePlaybackInfo(
newPlaybackInfo,
@ -1951,12 +1954,6 @@ import java.util.concurrent.TimeoutException;
updateAvailableCommands();
listeners.flushEvents();
if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) {
for (AudioOffloadListener listener : audioOffloadListeners) {
listener.onExperimentalOffloadSchedulingEnabledChanged(
newPlaybackInfo.offloadSchedulingEnabled);
}
}
if (previousPlaybackInfo.sleepingForOffload != newPlaybackInfo.sleepingForOffload) {
for (AudioOffloadListener listener : audioOffloadListeners) {
listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload);

View file

@ -809,10 +809,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
return;
}
this.offloadSchedulingEnabled = offloadSchedulingEnabled;
@Player.State int state = playbackInfo.playbackState;
if (offloadSchedulingEnabled || state == Player.STATE_ENDED || state == Player.STATE_IDLE) {
playbackInfo = playbackInfo.copyWithOffloadSchedulingEnabled(offloadSchedulingEnabled);
} else {
if (!offloadSchedulingEnabled && playbackInfo.sleepingForOffload) {
// We need to wake the player up if offload scheduling is disabled and we are sleeping.
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
}
@ -952,12 +950,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void doSomeWork() throws ExoPlaybackException, IOException {
long operationStartTimeMs = clock.uptimeMillis();
// Remove other pending DO_SOME_WORK requests that are handled by this invocation.
handler.removeMessages(MSG_DO_SOME_WORK);
updatePeriods();
if (playbackInfo.playbackState == Player.STATE_IDLE
|| playbackInfo.playbackState == Player.STATE_ENDED) {
// Remove all messages. Prepare (in case of IDLE) or seek (in case of ENDED) will resume.
handler.removeMessages(MSG_DO_SOME_WORK);
// Nothing to do. Prepare (in case of IDLE) or seek (in case of ENDED) will resume.
return;
}
@ -1070,24 +1070,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
throw new IllegalStateException("Playback stuck buffering and not loading");
}
if (offloadSchedulingEnabled != playbackInfo.offloadSchedulingEnabled) {
playbackInfo = playbackInfo.copyWithOffloadSchedulingEnabled(offloadSchedulingEnabled);
}
boolean sleepingForOffload = false;
if ((shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY)
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
sleepingForOffload = !maybeScheduleWakeup(operationStartTimeMs, ACTIVE_INTERVAL_MS);
} else if (enabledRendererCount != 0 && playbackInfo.playbackState != Player.STATE_ENDED) {
scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
} else {
handler.removeMessages(MSG_DO_SOME_WORK);
}
boolean isPlaying = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY;
boolean sleepingForOffload = offloadSchedulingEnabled && requestForRendererSleep && isPlaying;
if (playbackInfo.sleepingForOffload != sleepingForOffload) {
playbackInfo = playbackInfo.copyWithSleepingForOffload(sleepingForOffload);
}
requestForRendererSleep = false; // A sleep request is only valid for the current doSomeWork.
if (sleepingForOffload || playbackInfo.playbackState == Player.STATE_ENDED) {
// No need to schedule next work.
return;
} else if (isPlaying || playbackInfo.playbackState == Player.STATE_BUFFERING) {
// We are actively playing or waiting for data to be ready. Schedule next work quickly.
scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
} else if (playbackInfo.playbackState == Player.STATE_READY && enabledRendererCount != 0) {
// We are ready, but not playing. Schedule next work less often to handle non-urgent updates.
scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
}
TraceUtil.endSection();
}
@ -1117,19 +1117,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
handler.removeMessages(MSG_DO_SOME_WORK);
handler.sendEmptyMessageAtTime(MSG_DO_SOME_WORK, thisOperationStartTimeMs + intervalMs);
}
private boolean maybeScheduleWakeup(long operationStartTimeMs, long intervalMs) {
if (offloadSchedulingEnabled && requestForRendererSleep) {
return false;
}
scheduleNextWork(operationStartTimeMs, intervalMs);
return true;
}
private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
@ -1460,7 +1450,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* bufferedPositionUs= */ startPositionUs,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ startPositionUs,
offloadSchedulingEnabled,
/* sleepingForOffload= */ false);
if (releaseMediaSourceList) {
mediaSourceList.release();

View file

@ -70,8 +70,6 @@ import java.util.List;
public final @PlaybackSuppressionReason int playbackSuppressionReason;
/** The playback parameters. */
public final PlaybackParameters playbackParameters;
/** Whether offload scheduling is enabled for the main player loop. */
public final boolean offloadSchedulingEnabled;
/** Whether the main player loop is sleeping, while using offload scheduling. */
public final boolean sleepingForOffload;
@ -118,7 +116,6 @@ import java.util.List;
/* bufferedPositionUs= */ 0,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ 0,
/* offloadSchedulingEnabled= */ false,
/* sleepingForOffload= */ false);
}
@ -141,7 +138,6 @@ import java.util.List;
* @param bufferedPositionUs See {@link #bufferedPositionUs}.
* @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}.
* @param positionUs See {@link #positionUs}.
* @param offloadSchedulingEnabled See {@link #offloadSchedulingEnabled}.
* @param sleepingForOffload See {@link #sleepingForOffload}.
*/
public PlaybackInfo(
@ -162,7 +158,6 @@ import java.util.List;
long bufferedPositionUs,
long totalBufferedDurationUs,
long positionUs,
boolean offloadSchedulingEnabled,
boolean sleepingForOffload) {
this.timeline = timeline;
this.periodId = periodId;
@ -181,7 +176,6 @@ import java.util.List;
this.bufferedPositionUs = bufferedPositionUs;
this.totalBufferedDurationUs = totalBufferedDurationUs;
this.positionUs = positionUs;
this.offloadSchedulingEnabled = offloadSchedulingEnabled;
this.sleepingForOffload = sleepingForOffload;
}
@ -233,7 +227,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -263,7 +256,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -293,7 +285,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -323,7 +314,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -353,7 +343,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -383,7 +372,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -417,7 +405,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -447,38 +434,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
/**
* Copies playback info with new offloadSchedulingEnabled.
*
* @param offloadSchedulingEnabled New offloadSchedulingEnabled state. See {@link
* #offloadSchedulingEnabled}.
* @return Copied playback info with new offload scheduling state.
*/
@CheckResult
public PlaybackInfo copyWithOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) {
return new PlaybackInfo(
timeline,
periodId,
requestedContentPositionUs,
discontinuityStartPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
staticMetadata,
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
@ -508,7 +463,6 @@ import java.util.List;
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
offloadSchedulingEnabled,
sleepingForOffload);
}
}

View file

@ -29,7 +29,6 @@ import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.PlaybackParams;
import android.media.metrics.LogSessionId;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Pair;
@ -43,6 +42,8 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException;
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.MimeTypes;
import com.google.android.exoplayer2.util.Util;
@ -606,7 +607,8 @@ public final class DefaultAudioSink implements AudioSink {
enableAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams;
offloadMode = Util.SDK_INT >= 29 ? builder.offloadMode : OFFLOAD_MODE_DISABLED;
audioTrackBufferSizeProvider = builder.audioTrackBufferSizeProvider;
releasingConditionVariable = new ConditionVariable(true);
releasingConditionVariable = new ConditionVariable(Clock.DEFAULT);
releasingConditionVariable.open();
audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener());
channelMappingAudioProcessor = new ChannelMappingAudioProcessor();
trimmingAudioProcessor = new TrimmingAudioProcessor();
@ -831,13 +833,15 @@ public final class DefaultAudioSink implements AudioSink {
}
}
private void initializeAudioTrack() throws InitializationException {
// If we're asynchronously releasing a previous audio track then we block until it has been
private boolean initializeAudioTrack() throws InitializationException {
// If we're asynchronously releasing a previous audio track then we wait until it has been
// released. This guarantees that we cannot end up in a state where we have multiple audio
// track instances. Without this guarantee it would be possible, in extreme cases, to exhaust
// the shared memory that's available for audio track buffers. This would in turn cause the
// initialization of the audio track to fail.
releasingConditionVariable.block();
if (!releasingConditionVariable.isOpen()) {
return false;
}
audioTrack = buildAudioTrackWithRetry();
if (isOffloadedPlayback(audioTrack)) {
@ -865,6 +869,7 @@ public final class DefaultAudioSink implements AudioSink {
}
startMediaTimeUsNeedsInit = true;
return true;
}
@Override
@ -921,7 +926,10 @@ public final class DefaultAudioSink implements AudioSink {
if (!isAudioTrackInitialized()) {
try {
initializeAudioTrack();
if (!initializeAudioTrack()) {
// Not yet ready for initialization of a new AudioTrack.
return false;
}
} catch (InitializationException e) {
if (e.isRecoverable) {
throw e; // Do not delay the exception if it can be recovered at higher level.

View file

@ -315,7 +315,9 @@ public final class MediaCodecInfo {
}
for (CodecProfileLevel profileLevel : profileLevels) {
if (profileLevel.profile == profile && profileLevel.level >= level) {
if (profileLevel.profile == profile
&& profileLevel.level >= level
&& !needsProfileExcludedWorkaround(mimeType, profile)) {
return true;
}
}
@ -829,4 +831,15 @@ public final class MediaCodecInfo {
}
return true;
}
/**
* Whether a profile is excluded from the list of supported profiles. This may happen when a
* device declares support for a profile it doesn't actually support.
*/
private static boolean needsProfileExcludedWorkaround(String mimeType, int profile) {
// See https://github.com/google/ExoPlayer/issues/3537
return MimeTypes.VIDEO_H265.equals(mimeType)
&& CodecProfileLevel.HEVCProfileMain10 == profile
&& ("sailfish".equals(Util.DEVICE) || "marlin".equals(Util.DEVICE));
}
}

View file

@ -274,6 +274,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
*/
public DefaultMediaSourceFactory setDataSourceFactory(DataSource.Factory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory;
delegateFactoryLoader.setDataSourceFactory(dataSourceFactory);
return this;
}
@ -576,6 +577,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
this.dataSourceFactory = dataSourceFactory;
// TODO(b/233577470): Call MediaSource.Factory.setDataSourceFactory on each value when it
// exists on the interface.
mediaSourceFactorySuppliers.clear();
mediaSourceFactories.clear();
}
}
@ -609,6 +611,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
}
@Nullable Supplier<MediaSource.Factory> mediaSourceFactorySupplier = null;
DataSource.Factory dataSourceFactory = checkNotNull(this.dataSourceFactory);
try {
Class<? extends MediaSource.Factory> clazz;
switch (contentType) {
@ -616,20 +619,20 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
clazz =
Class.forName("com.google.android.exoplayer2.source.dash.DashMediaSource$Factory")
.asSubclass(MediaSource.Factory.class);
mediaSourceFactorySupplier = () -> newInstance(clazz, checkNotNull(dataSourceFactory));
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
break;
case C.CONTENT_TYPE_SS:
clazz =
Class.forName(
"com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory")
.asSubclass(MediaSource.Factory.class);
mediaSourceFactorySupplier = () -> newInstance(clazz, checkNotNull(dataSourceFactory));
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
break;
case C.CONTENT_TYPE_HLS:
clazz =
Class.forName("com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory")
.asSubclass(MediaSource.Factory.class);
mediaSourceFactorySupplier = () -> newInstance(clazz, checkNotNull(dataSourceFactory));
mediaSourceFactorySupplier = () -> newInstance(clazz, dataSourceFactory);
break;
case C.CONTENT_TYPE_RTSP:
clazz =
@ -639,9 +642,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
break;
case C.CONTENT_TYPE_OTHER:
mediaSourceFactorySupplier =
() ->
new ProgressiveMediaSource.Factory(
checkNotNull(dataSourceFactory), extractorsFactory);
() -> new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
break;
default:
// Do nothing.

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max;
import static java.lang.Math.min;
@ -134,7 +135,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private boolean seenFirstTrackSelection;
private boolean notifyDiscontinuity;
private int enabledTrackCount;
private long length;
private boolean isLengthKnown;
private long lastSeekPositionUs;
private long pendingResetPositionUs;
@ -192,15 +193,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
onContinueLoadingRequestedRunnable =
() -> {
if (!released) {
Assertions.checkNotNull(callback)
.onContinueLoadingRequested(ProgressiveMediaPeriod.this);
checkNotNull(callback).onContinueLoadingRequested(ProgressiveMediaPeriod.this);
}
};
handler = Util.createHandlerForCurrentLooper();
sampleQueueTrackIds = new TrackId[0];
sampleQueues = new SampleQueue[0];
pendingResetPositionUs = C.TIME_UNSET;
length = C.LENGTH_UNSET;
durationUs = C.TIME_UNSET;
dataType = C.DATA_TYPE_MEDIA;
}
@ -366,7 +365,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public long getNextLoadPositionUs() {
return enabledTrackCount == 0 ? C.TIME_END_OF_SOURCE : getBufferedPositionUs();
return getBufferedPositionUs();
}
@Override
@ -382,8 +381,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public long getBufferedPositionUs() {
assertPrepared();
boolean[] trackIsAudioVideoFlags = trackState.trackIsAudioVideoFlags;
if (loadingFinished) {
if (loadingFinished || enabledTrackCount == 0) {
return C.TIME_END_OF_SOURCE;
} else if (isPendingReset()) {
return pendingResetPositionUs;
@ -393,14 +391,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Ignore non-AV tracks, which may be sparse or poorly interleaved.
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) {
if (trackIsAudioVideoFlags[i] && !sampleQueues[i].isLastSampleQueued()) {
if (trackState.trackIsAudioVideoFlags[i]
&& trackState.trackEnabledStates[i]
&& !sampleQueues[i].isLastSampleQueued()) {
largestQueuedTimestampUs =
min(largestQueuedTimestampUs, sampleQueues[i].getLargestQueuedTimestampUs());
}
}
}
if (largestQueuedTimestampUs == Long.MAX_VALUE) {
largestQueuedTimestampUs = getLargestQueuedTimestampUs();
largestQueuedTimestampUs = getLargestQueuedTimestampUs(/* includeDisabledTracks= */ false);
}
return largestQueuedTimestampUs == Long.MIN_VALUE
? lastSeekPositionUs
@ -536,7 +536,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.reset();
}
Assertions.checkNotNull(callback).onContinueLoadingRequested(this);
checkNotNull(callback).onContinueLoadingRequested(this);
}
private boolean suppressRead() {
@ -550,7 +550,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
if (durationUs == C.TIME_UNSET && seekMap != null) {
boolean isSeekable = seekMap.isSeekable();
long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
long largestQueuedTimestampUs =
getLargestQueuedTimestampUs(/* includeDisabledTracks= */ true);
durationUs =
largestQueuedTimestampUs == Long.MIN_VALUE
? 0
@ -577,9 +578,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* trackSelectionData= */ null,
/* mediaStartTimeUs= */ loadable.seekTimeUs,
durationUs);
copyLengthFromLoader(loadable);
loadingFinished = true;
Assertions.checkNotNull(callback).onContinueLoadingRequested(this);
checkNotNull(callback).onContinueLoadingRequested(this);
}
@Override
@ -606,12 +606,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* mediaStartTimeUs= */ loadable.seekTimeUs,
durationUs);
if (!released) {
copyLengthFromLoader(loadable);
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.reset();
}
if (enabledTrackCount > 0) {
Assertions.checkNotNull(callback).onContinueLoadingRequested(this);
checkNotNull(callback).onContinueLoadingRequested(this);
}
}
}
@ -623,7 +622,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
long loadDurationMs,
IOException error,
int errorCount) {
copyLengthFromLoader(loadable);
StatsDataSource dataSource = loadable.dataSource;
LoadEventInfo loadEventInfo =
new LoadEventInfo(
@ -709,6 +707,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Internal methods.
private void onLengthKnown() {
handler.post(() -> isLengthKnown = true);
}
private TrackOutput prepareTrackOutput(TrackId id) {
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) {
@ -732,7 +734,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void setSeekMap(SeekMap seekMap) {
this.seekMap = icyHeaders == null ? seekMap : new Unseekable(/* durationUs= */ C.TIME_UNSET);
durationUs = seekMap.getDurationUs();
isLive = length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET;
isLive = !isLengthKnown && seekMap.getDurationUs() == C.TIME_UNSET;
dataType = isLive ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE : C.DATA_TYPE_MEDIA;
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
if (!prepared) {
@ -754,7 +756,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
TrackGroup[] trackArray = new TrackGroup[trackCount];
boolean[] trackIsAudioVideoFlags = new boolean[trackCount];
for (int i = 0; i < trackCount; i++) {
Format trackFormat = Assertions.checkNotNull(sampleQueues[i].getUpstreamFormat());
Format trackFormat = checkNotNull(sampleQueues[i].getUpstreamFormat());
@Nullable String mimeType = trackFormat.sampleMimeType;
boolean isAudio = MimeTypes.isAudio(mimeType);
boolean isAudioVideo = isAudio || MimeTypes.isVideo(mimeType);
@ -785,13 +787,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
trackState = new TrackState(new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
prepared = true;
Assertions.checkNotNull(callback).onPrepared(this);
}
private void copyLengthFromLoader(ExtractingLoadable loadable) {
if (length == C.LENGTH_UNSET) {
length = loadable.length;
}
checkNotNull(callback).onPrepared(this);
}
private void startLoading() {
@ -806,7 +802,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return;
}
loadable.setLoadPosition(
Assertions.checkNotNull(seekMap).getSeekPoints(pendingResetPositionUs).first.position,
checkNotNull(seekMap).getSeekPoints(pendingResetPositionUs).first.position,
pendingResetPositionUs);
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.setStartTimeUs(pendingResetPositionUs);
@ -839,7 +835,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* retry.
*/
private boolean configureRetry(ExtractingLoadable loadable, int currentExtractedSampleCount) {
if (length != C.LENGTH_UNSET || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET)) {
if (isLengthKnown || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET)) {
// We're playing an on-demand stream. Resume the current loadable, which will
// request data starting from the point it left off.
extractedSamplesCountAtStartOfLoad = currentExtractedSampleCount;
@ -903,11 +899,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return extractedSamplesCount;
}
private long getLargestQueuedTimestampUs() {
private long getLargestQueuedTimestampUs(boolean includeDisabledTracks) {
long largestQueuedTimestampUs = Long.MIN_VALUE;
for (SampleQueue sampleQueue : sampleQueues) {
largestQueuedTimestampUs =
max(largestQueuedTimestampUs, sampleQueue.getLargestQueuedTimestampUs());
for (int i = 0; i < sampleQueues.length; i++) {
if (includeDisabledTracks || checkNotNull(trackState).trackEnabledStates[i]) {
largestQueuedTimestampUs =
max(largestQueuedTimestampUs, sampleQueues[i].getLargestQueuedTimestampUs());
}
}
return largestQueuedTimestampUs;
}
@ -919,8 +917,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@EnsuresNonNull({"trackState", "seekMap"})
private void assertPrepared() {
Assertions.checkState(prepared);
Assertions.checkNotNull(trackState);
Assertions.checkNotNull(seekMap);
checkNotNull(trackState);
checkNotNull(seekMap);
}
private final class SampleStreamImpl implements SampleStream {
@ -969,7 +967,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private boolean pendingExtractorSeek;
private long seekTimeUs;
private DataSpec dataSpec;
private long length;
@Nullable private TrackOutput icyTrackOutput;
private boolean seenIcyMetadata;
@ -987,7 +984,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.loadCondition = loadCondition;
this.positionHolder = new PositionHolder();
this.pendingExtractorSeek = true;
this.length = C.LENGTH_UNSET;
loadTaskId = LoadEventInfo.getNewId();
dataSpec = buildDataSpec(/* position= */ 0);
}
@ -1006,9 +1002,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
try {
long position = positionHolder.position;
dataSpec = buildDataSpec(position);
length = dataSource.open(dataSpec);
long length = dataSource.open(dataSpec);
if (length != C.LENGTH_UNSET) {
length += position;
onLengthKnown();
}
icyHeaders = IcyHeaders.parse(dataSource.getResponseHeaders());
DataSource extractorDataSource = dataSource;
@ -1064,9 +1061,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public void onIcyMetadata(ParsableByteArray metadata) {
// Always output the first ICY metadata at the start time. This helps minimize any delay
// between the start of playback and the first ICY metadata event.
long timeUs = !seenIcyMetadata ? seekTimeUs : max(getLargestQueuedTimestampUs(), seekTimeUs);
long timeUs =
!seenIcyMetadata
? seekTimeUs
: max(getLargestQueuedTimestampUs(/* includeDisabledTracks= */ true), seekTimeUs);
int length = metadata.bytesLeft();
TrackOutput icyTrackOutput = Assertions.checkNotNull(this.icyTrackOutput);
TrackOutput icyTrackOutput = checkNotNull(this.icyTrackOutput);
icyTrackOutput.sampleData(metadata, length);
icyTrackOutput.sampleMetadata(
timeUs, C.BUFFER_FLAG_KEY_FRAME, length, /* offset= */ 0, /* cryptoData= */ null);

View file

@ -35,7 +35,7 @@ public interface TextOutput {
* Called when there is a change in the {@link CueGroup}.
*
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* in the cues You should only implement one or the other.
* in the cues. You should only implement one or the other.
*/
void onCues(CueGroup cueGroup);
}

View file

@ -626,7 +626,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
surface = placeholderSurface;
} else {
MediaCodecInfo codecInfo = getCodecInfo();
if (codecInfo != null && shouldUseDummySurface(codecInfo)) {
if (codecInfo != null && shouldUsePlaceholderSurface(codecInfo)) {
placeholderSurface = PlaceholderSurface.newInstanceV17(context, codecInfo.secure);
surface = placeholderSurface;
}
@ -672,7 +672,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
return surface != null || shouldUseDummySurface(codecInfo);
return surface != null || shouldUsePlaceholderSurface(codecInfo);
}
@Override
@ -703,7 +703,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
deviceNeedsNoPostProcessWorkaround,
tunneling ? tunnelingAudioSessionId : C.AUDIO_SESSION_ID_UNSET);
if (surface == null) {
if (!shouldUseDummySurface(codecInfo)) {
if (!shouldUsePlaceholderSurface(codecInfo)) {
throw new IllegalStateException();
}
if (placeholderSurface == null) {
@ -1330,7 +1330,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeNotifyRenderedFirstFrame();
}
private boolean shouldUseDummySurface(MediaCodecInfo codecInfo) {
private boolean shouldUsePlaceholderSurface(MediaCodecInfo codecInfo) {
return Util.SDK_INT >= 23
&& !tunneling
&& !codecNeedsSetOutputSurfaceWorkaround(codecInfo.name)
@ -1569,7 +1569,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
if (haveUnknownDimensions) {
Log.w(TAG, "Resolutions unknown. Codec max resolution: " + maxWidth + "x" + maxHeight);
Point codecMaxSize = getCodecMaxSize(codecInfo, format);
@Nullable Point codecMaxSize = getCodecMaxSize(codecInfo, format);
if (codecMaxSize != null) {
maxWidth = max(maxWidth, codecMaxSize.x);
maxHeight = max(maxHeight, codecMaxSize.y);
@ -1597,8 +1597,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The {@link Format} for which the codec is being configured.
* @return The maximum video size to use, or null if the size of {@code format} should be used.
* @return The maximum video size to use, or {@code null} if the size of {@code format} should be
* used.
*/
@Nullable
private static Point getCodecMaxSize(MediaCodecInfo codecInfo, Format format) {
boolean isVerticalVideo = format.height > format.width;
int formatLongEdgePx = isVerticalVideo ? format.height : format.width;

View file

@ -53,7 +53,6 @@ import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.play
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPositionDiscontinuity;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilReceiveOffloadSchedulingEnabledNewState;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilSleepingForOffload;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilTimelineChanged;
import static com.google.android.exoplayer2.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
@ -62,6 +61,7 @@ import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSample
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US;
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
import static com.google.android.exoplayer2.testutil.TestUtil.assertTimelinesSame;
import static com.google.android.exoplayer2.testutil.TestUtil.timelinesAreSame;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertThrows;
@ -113,6 +113,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
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.ShuffleOrder;
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
@ -141,7 +142,6 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit
import com.google.android.exoplayer2.testutil.FakeTrackSelection;
import com.google.android.exoplayer2.testutil.FakeTrackSelector;
import com.google.android.exoplayer2.testutil.FakeVideoRenderer;
import com.google.android.exoplayer2.testutil.NoUidTimeline;
import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.Allocation;
@ -6502,6 +6502,53 @@ public final class ExoPlayerTest {
assertThat(positionAfterSetShuffleOrder.get()).isAtLeast(5000);
}
@Test
public void setShuffleOrder_notifiesTimelineChanged() throws Exception {
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
// No callback expected for this call, because the (empty) timeline doesn't change. We start
// with a deterministic shuffle order, to ensure when we call setShuffleOrder again below the
// order is definitely different (otherwise the test is flaky when the existing shuffle order
// matches the shuffle order passed in below).
player.setShuffleOrder(new FakeShuffleOrder(0));
player.setMediaSources(
ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource()));
Player.Listener mockListener = mock(Player.Listener.class);
player.addListener(mockListener);
player.prepare();
TestPlayerRunHelper.playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 5000);
player.play();
ShuffleOrder.DefaultShuffleOrder newShuffleOrder =
new ShuffleOrder.DefaultShuffleOrder(player.getMediaItemCount(), /* randomSeed= */ 5);
player.setShuffleOrder(newShuffleOrder);
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
ArgumentCaptor<Timeline> timelineCaptor = ArgumentCaptor.forClass(Timeline.class);
verify(mockListener)
.onTimelineChanged(
timelineCaptor.capture(), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
Timeline capturedTimeline = Iterables.getOnlyElement(timelineCaptor.getAllValues());
List<Integer> newShuffleOrderIndexes = new ArrayList<>(newShuffleOrder.getLength());
for (int i = newShuffleOrder.getFirstIndex();
i != C.INDEX_UNSET;
i = newShuffleOrder.getNextIndex(i)) {
newShuffleOrderIndexes.add(i);
}
List<Integer> capturedTimelineShuffleIndexes = new ArrayList<>(newShuffleOrder.getLength());
for (int i = capturedTimeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true);
i != C.INDEX_UNSET;
i =
capturedTimeline.getNextWindowIndex(
i, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true)) {
capturedTimelineShuffleIndexes.add(i);
}
assertThat(capturedTimelineShuffleIndexes).isEqualTo(newShuffleOrderIndexes);
}
@Test
public void setMediaSources_empty_whenEmpty_correctMaskingMediaItemIndex() throws Exception {
final int[] currentMediaItemIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
@ -9625,47 +9672,16 @@ public final class ExoPlayerTest {
}
@Test
public void enableOffloadSchedulingWhileIdle_isToggled_isReported() throws Exception {
public void enableOffloadScheduling_isReported() throws Exception {
ExoPlayer player = new TestExoPlayerBuilder(context).build();
ExoPlayer.AudioOffloadListener mockListener = mock(ExoPlayer.AudioOffloadListener.class);
player.addAudioOffloadListener(mockListener);
player.experimentalSetOffloadSchedulingEnabled(true);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isTrue();
verify(mockListener).onExperimentalOffloadSchedulingEnabledChanged(true);
player.experimentalSetOffloadSchedulingEnabled(false);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse();
}
@Test
public void enableOffloadSchedulingWhilePlaying_isToggled_isReported() throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();
ExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(sleepRenderer).build();
Timeline timeline = new FakeTimeline();
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.prepare();
player.play();
player.experimentalSetOffloadSchedulingEnabled(true);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isTrue();
player.experimentalSetOffloadSchedulingEnabled(false);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse();
}
@Test
public void enableOffloadSchedulingWhileSleepingForOffload_isDisabled_isReported()
throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();
ExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(sleepRenderer).build();
Timeline timeline = new FakeTimeline();
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.experimentalSetOffloadSchedulingEnabled(true);
player.prepare();
player.play();
runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
player.experimentalSetOffloadSchedulingEnabled(false);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse();
verify(mockListener).onExperimentalOffloadSchedulingEnabledChanged(false);
}
@Test
@ -12286,6 +12302,6 @@ public final class ExoPlayerTest {
* Returns an argument matcher for {@link Timeline} instances that ignores period and window uids.
*/
private static ArgumentMatcher<Timeline> noUid(Timeline timeline) {
return argument -> new NoUidTimeline(timeline).equals(new NoUidTimeline(argument));
return argument -> timelinesAreSame(argument, timeline);
}
}

View file

@ -1106,7 +1106,6 @@ public final class MediaPeriodQueueTest {
/* bufferedPositionUs= */ 0,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ 0,
/* offloadSchedulingEnabled= */ false,
/* sleepingForOffload= */ false);
}

View file

@ -57,6 +57,7 @@ public class Mp4PlaybackTest {
"sample_eac3joc.mp4",
"sample_fragmented.mp4",
"sample_fragmented_seekable.mp4",
"sample_fragmented_large_bitrates.mp4",
"sample_fragmented_sei.mp4",
"sample_mdat_too_long.mp4",
"sample.mp4",

View file

@ -597,6 +597,9 @@ public class DashManifestParser extends DefaultHandler
case "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":
uuid = C.WIDEVINE_UUID;
break;
case "urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e":
uuid = C.CLEARKEY_UUID;
break;
default:
break;
}
@ -604,7 +607,9 @@ public class DashManifestParser extends DefaultHandler
do {
xpp.next();
if (XmlPullParserUtil.isStartTag(xpp, "ms:laurl")) {
if (XmlPullParserUtil.isStartTag(xpp, "clearkey:Laurl") && xpp.next() == XmlPullParser.TEXT) {
licenseServerUrl = xpp.getText();
} else if (XmlPullParserUtil.isStartTag(xpp, "ms:laurl")) {
licenseServerUrl = xpp.getAttributeValue(null, "licenseUrl");
} else if (data == null
&& XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
@ -851,6 +856,7 @@ public class DashManifestParser extends DefaultHandler
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
drmSchemeDatas.addAll(extraDrmSchemeDatas);
if (!drmSchemeDatas.isEmpty()) {
fillInClearKeyInformation(drmSchemeDatas);
filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
formatBuilder.setDrmInitData(new DrmInitData(drmSchemeType, drmSchemeDatas));
}
@ -1658,6 +1664,32 @@ public class DashManifestParser extends DefaultHandler
}
}
private static void fillInClearKeyInformation(ArrayList<SchemeData> schemeDatas) {
// Find and remove ClearKey information.
@Nullable String clearKeyLicenseServerUrl = null;
for (int i = 0; i < schemeDatas.size(); i++) {
SchemeData schemeData = schemeDatas.get(i);
if (C.CLEARKEY_UUID.equals(schemeData.uuid) && schemeData.licenseServerUrl != null) {
clearKeyLicenseServerUrl = schemeData.licenseServerUrl;
schemeDatas.remove(i);
break;
}
}
if (clearKeyLicenseServerUrl == null) {
return;
}
// Fill in the ClearKey information into the existing PSSH schema data if applicable.
for (int i = 0; i < schemeDatas.size(); i++) {
SchemeData schemeData = schemeDatas.get(i);
if (C.COMMON_PSSH_UUID.equals(schemeData.uuid) && schemeData.licenseServerUrl == null) {
schemeDatas.set(
i,
new SchemeData(
C.CLEARKEY_UUID, clearKeyLicenseServerUrl, schemeData.mimeType, schemeData.data));
}
}
}
/**
* Derives a sample mimeType from a container mimeType and codecs attribute.
*

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.dash;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@ -22,9 +23,13 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.robolectric.RobolectricUtil;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -82,4 +87,53 @@ public class DefaultMediaSourceFactoryTest {
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_DASH);
}
@Test
public void createMediaSource_withSetDataSourceFactory_usesDataSourceFactory() throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext())
.setDataSourceFactory(() -> fakeDataSource);
prepareDashUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
@Test
public void
createMediaSource_usingDefaultDataSourceFactoryAndSetDataSourceFactory_usesUpdatesDataSourceFactory()
throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext());
// Use default DataSource.Factory first.
prepareDashUrlAndWaitForPrepareError(defaultMediaSourceFactory);
defaultMediaSourceFactory.setDataSourceFactory(() -> fakeDataSource);
prepareDashUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
private static void prepareDashUrlAndWaitForPrepareError(
DefaultMediaSourceFactory defaultMediaSourceFactory) throws Exception {
MediaSource mediaSource =
defaultMediaSourceFactory.createMediaSource(MediaItem.fromUri(URI_MEDIA + "/file.mpd"));
getInstrumentation()
.runOnMainSync(
() ->
mediaSource.prepareSource(
(source, timeline) -> {}, /* mediaTransferListener= */ null, PlayerId.UNSET));
// We don't expect this to prepare successfully.
RobolectricUtil.runMainLooperUntil(
() -> {
try {
mediaSource.maybeThrowSourceInfoRefreshError();
return false;
} catch (IOException e) {
return true;
}
});
}
}

View file

@ -23,6 +23,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.source.dash.manifest.Representation.MultiSegmentRepresentation;
import com.google.android.exoplayer2.source.dash.manifest.Representation.SingleSegmentRepresentation;
@ -79,6 +80,8 @@ public class DashManifestParserTest {
"media/mpd/sample_mpd_service_description_low_latency_only_playback_rates";
private static final String SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY_ONLY_TARGET_LATENCY =
"media/mpd/sample_mpd_service_description_low_latency_only_target_latency";
private static final String SAMPLE_MPD_CLEAR_KEY_LICENSE_URL =
"media/mpd/sample_mpd_clear_key_license_url";
private static final String NEXT_TAG_NAME = "Next";
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
@ -880,6 +883,37 @@ public class DashManifestParserTest {
assertThat(manifest.serviceDescription).isNull();
}
@Test
public void contentProtections_withClearKeyLicenseUrl() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(), SAMPLE_MPD_CLEAR_KEY_LICENSE_URL));
assertThat(manifest.getPeriodCount()).isEqualTo(1);
Period period = manifest.getPeriod(0);
assertThat(period.adaptationSets).hasSize(2);
AdaptationSet adaptationSet0 = period.adaptationSets.get(0);
AdaptationSet adaptationSet1 = period.adaptationSets.get(1);
assertThat(adaptationSet0.representations).hasSize(1);
assertThat(adaptationSet1.representations).hasSize(1);
Representation representation0 = adaptationSet0.representations.get(0);
Representation representation1 = adaptationSet1.representations.get(0);
assertThat(representation0.format.drmInitData.schemeType).isEqualTo("cenc");
assertThat(representation1.format.drmInitData.schemeType).isEqualTo("cenc");
assertThat(representation0.format.drmInitData.schemeDataCount).isEqualTo(1);
assertThat(representation1.format.drmInitData.schemeDataCount).isEqualTo(1);
DrmInitData.SchemeData schemeData0 = representation0.format.drmInitData.get(0);
DrmInitData.SchemeData schemeData1 = representation1.format.drmInitData.get(0);
assertThat(schemeData0.uuid).isEqualTo(C.CLEARKEY_UUID);
assertThat(schemeData1.uuid).isEqualTo(C.CLEARKEY_UUID);
assertThat(schemeData0.licenseServerUrl).isEqualTo("https://testserver1.test/AcquireLicense");
assertThat(schemeData1.licenseServerUrl).isEqualTo("https://testserver2.test/AcquireLicense");
}
private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) {
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null));
}

View file

@ -45,6 +45,7 @@ import com.google.android.exoplayer2.video.DolbyVisionConfig;
import com.google.android.exoplayer2.video.HevcConfig;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
@ -1303,7 +1304,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
if (esdsData != null) {
formatBuilder.setAverageBitrate(esdsData.bitrate).setPeakBitrate(esdsData.peakBitrate);
formatBuilder
.setAverageBitrate(Ints.saturatedCast(esdsData.bitrate))
.setPeakBitrate(Ints.saturatedCast(esdsData.peakBitrate));
}
out.format = formatBuilder.build();
@ -1609,7 +1612,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
.setLanguage(language);
if (esdsData != null) {
formatBuilder.setAverageBitrate(esdsData.bitrate).setPeakBitrate(esdsData.peakBitrate);
formatBuilder
.setAverageBitrate(Ints.saturatedCast(esdsData.bitrate))
.setPeakBitrate(Ints.saturatedCast(esdsData.peakBitrate));
}
out.format = formatBuilder.build();
@ -1659,7 +1664,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
parent.skipBytes(2);
}
if ((flags & 0x40 /* URL_Flag */) != 0) {
parent.skipBytes(parent.readUnsignedShort());
parent.skipBytes(parent.readUnsignedByte());
}
if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
parent.skipBytes(2);
@ -1683,8 +1688,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
parent.skipBytes(4);
int peakBitrate = parent.readUnsignedIntToInt();
int bitrate = parent.readUnsignedIntToInt();
long peakBitrate = parent.readUnsignedInt();
long bitrate = parent.readUnsignedInt();
// Start of the DecoderSpecificInfo.
parent.skipBytes(1); // DecoderSpecificInfo tag
@ -1943,14 +1948,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private static final class EsdsData {
private final @NullableType String mimeType;
private final byte @NullableType [] initializationData;
private final int bitrate;
private final int peakBitrate;
private final long bitrate;
private final long peakBitrate;
public EsdsData(
@NullableType String mimeType,
byte @NullableType [] initializationData,
int bitrate,
int peakBitrate) {
long bitrate,
long peakBitrate) {
this.mimeType = mimeType;
this.initializationData = initializationData;
this.bitrate = bitrate;

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util;
import static java.lang.Math.min;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.nio.ByteBuffer;
import java.util.Arrays;
@ -781,40 +782,105 @@ public final class NalUnitUtil {
}
}
/**
* Skips any short term reference picture sets contained in a SPS.
*
* <p>Note: The st_ref_pic_set parsing in this method is simplified for the case where they're
* contained in a SPS, and would need generalizing for use elsewhere.
*/
private static void skipShortTermReferencePictureSets(ParsableNalUnitBitArray bitArray) {
int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt();
boolean interRefPicSetPredictionFlag = false;
int numNegativePics;
int numPositivePics;
// As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous
// one, so we just keep track of that rather than storing the whole array.
// RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS.
int previousNumDeltaPocs = 0;
// As this method applies in a SPS, each short term reference picture set only accesses data
// from the previous one. This is because RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1), and
// delta_idx_minus1 is always zero in a SPS. Hence we just keep track of variables from the
// previous one as we iterate.
int previousNumNegativePics = C.INDEX_UNSET;
int previousNumPositivePics = C.INDEX_UNSET;
int[] previousDeltaPocS0 = new int[0];
int[] previousDeltaPocS1 = new int[0];
for (int stRpsIdx = 0; stRpsIdx < numShortTermRefPicSets; stRpsIdx++) {
if (stRpsIdx != 0) {
interRefPicSetPredictionFlag = bitArray.readBit();
}
int numNegativePics;
int numPositivePics;
int[] deltaPocS0;
int[] deltaPocS1;
boolean interRefPicSetPredictionFlag = stRpsIdx != 0 && bitArray.readBit();
if (interRefPicSetPredictionFlag) {
bitArray.skipBit(); // delta_rps_sign
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
int previousNumDeltaPocs = previousNumNegativePics + previousNumPositivePics;
int deltaRpsSign = bitArray.readBit() ? 1 : 0;
int absDeltaRps = bitArray.readUnsignedExpGolombCodedInt() + 1;
int deltaRps = (1 - 2 * deltaRpsSign) * absDeltaRps;
boolean[] useDeltaFlags = new boolean[previousNumDeltaPocs + 1];
for (int j = 0; j <= previousNumDeltaPocs; j++) {
if (!bitArray.readBit()) { // used_by_curr_pic_flag[j]
bitArray.skipBit(); // use_delta_flag[j]
useDeltaFlags[j] = bitArray.readBit();
} else {
// When use_delta_flag[j] is not present, its value is 1.
useDeltaFlags[j] = true;
}
}
// Derive numNegativePics, numPositivePics, deltaPocS0 and deltaPocS1 as per Rec. ITU-T
// H.265 v6 (06/2019) Section 7.4.8
int i = 0;
deltaPocS0 = new int[previousNumDeltaPocs + 1];
deltaPocS1 = new int[previousNumDeltaPocs + 1];
for (int j = previousNumPositivePics - 1; j >= 0; j--) {
int dPoc = previousDeltaPocS1[j] + deltaRps;
if (dPoc < 0 && useDeltaFlags[previousNumNegativePics + j]) {
deltaPocS0[i++] = dPoc;
}
}
if (deltaRps < 0 && useDeltaFlags[previousNumDeltaPocs]) {
deltaPocS0[i++] = deltaRps;
}
for (int j = 0; j < previousNumNegativePics; j++) {
int dPoc = previousDeltaPocS0[j] + deltaRps;
if (dPoc < 0 && useDeltaFlags[j]) {
deltaPocS0[i++] = dPoc;
}
}
numNegativePics = i;
deltaPocS0 = Arrays.copyOf(deltaPocS0, numNegativePics);
i = 0;
for (int j = previousNumNegativePics - 1; j >= 0; j--) {
int dPoc = previousDeltaPocS0[j] + deltaRps;
if (dPoc > 0 && useDeltaFlags[j]) {
deltaPocS1[i++] = dPoc;
}
}
if (deltaRps > 0 && useDeltaFlags[previousNumDeltaPocs]) {
deltaPocS1[i++] = deltaRps;
}
for (int j = 0; j < previousNumPositivePics; j++) {
int dPoc = previousDeltaPocS1[j] + deltaRps;
if (dPoc > 0 && useDeltaFlags[previousNumNegativePics + j]) {
deltaPocS1[i++] = dPoc;
}
}
numPositivePics = i;
deltaPocS1 = Arrays.copyOf(deltaPocS1, numPositivePics);
} else {
numNegativePics = bitArray.readUnsignedExpGolombCodedInt();
numPositivePics = bitArray.readUnsignedExpGolombCodedInt();
previousNumDeltaPocs = numNegativePics + numPositivePics;
deltaPocS0 = new int[numNegativePics];
for (int i = 0; i < numNegativePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
deltaPocS0[i] = bitArray.readUnsignedExpGolombCodedInt() + 1;
bitArray.skipBit(); // used_by_curr_pic_s0_flag[i]
}
deltaPocS1 = new int[numPositivePics];
for (int i = 0; i < numPositivePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
deltaPocS1[i] = bitArray.readUnsignedExpGolombCodedInt() + 1;
bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
}
}
previousNumNegativePics = numNegativePics;
previousNumPositivePics = numPositivePics;
previousDeltaPocS0 = deltaPocS0;
previousDeltaPocS1 = deltaPocS1;
}
}

View file

@ -122,6 +122,15 @@ public final class FragmentedMp4ExtractorTest {
simulationConfig);
}
/** https://github.com/google/ExoPlayer/issues/10381 */
@Test
public void sampleWithLargeBitrates() throws Exception {
ExtractorAsserts.assertBehavior(
getExtractorFactory(ImmutableList.of()),
"media/mp4/sample_fragmented_large_bitrates.mp4",
simulationConfig);
}
private static ExtractorFactory getExtractorFactory(final List<Format> closedCaptionFormats) {
return () ->
new FragmentedMp4Extractor(

View file

@ -168,6 +168,32 @@ public final class NalUnitUtilTest {
assertDiscardToSpsMatchesExpected("FF00000001660000000167FF", "0000000167FF");
}
/** Regression test for https://github.com/google/ExoPlayer/issues/10316. */
@Test
public void parseH265SpsNalUnitPayload_exoghi_10316() {
byte[] spsNalUnitPayload =
new byte[] {
1, 2, 32, 0, 0, 3, 0, -112, 0, 0, 3, 0, 0, 3, 0, -106, -96, 1, -32, 32, 2, 28, 77, -98,
87, -110, 66, -111, -123, 22, 74, -86, -53, -101, -98, -68, -28, 9, 119, -21, -103, 120,
-16, 22, -95, 34, 1, 54, -62, 0, 0, 7, -46, 0, 0, -69, -127, -12, 85, -17, 126, 0, -29,
-128, 28, 120, 1, -57, 0, 56, -15
};
NalUnitUtil.H265SpsData spsData =
NalUnitUtil.parseH265SpsNalUnitPayload(spsNalUnitPayload, 0, spsNalUnitPayload.length);
assertThat(spsData.constraintBytes).isEqualTo(new int[] {144, 0, 0, 0, 0, 0});
assertThat(spsData.generalLevelIdc).isEqualTo(150);
assertThat(spsData.generalProfileCompatibilityFlags).isEqualTo(4);
assertThat(spsData.generalProfileIdc).isEqualTo(2);
assertThat(spsData.generalProfileSpace).isEqualTo(0);
assertThat(spsData.generalTierFlag).isFalse();
assertThat(spsData.height).isEqualTo(2160);
assertThat(spsData.pixelWidthHeightRatio).isEqualTo(1);
assertThat(spsData.seqParameterSetId).isEqualTo(0);
assertThat(spsData.width).isEqualTo(3840);
}
private static byte[] buildTestData() {
byte[] data = new byte[20];
for (int i = 0; i < data.length; i++) {

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.hls;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@ -22,9 +23,13 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.robolectric.RobolectricUtil;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -82,4 +87,53 @@ public class DefaultMediaSourceFactoryTest {
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_HLS);
}
@Test
public void createMediaSource_withSetDataSourceFactory_usesDataSourceFactory() throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext())
.setDataSourceFactory(() -> fakeDataSource);
prepareHlsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
@Test
public void
createMediaSource_usingDefaultDataSourceFactoryAndSetDataSourceFactory_usesUpdatesDataSourceFactory()
throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext());
// Use default DataSource.Factory first.
prepareHlsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
defaultMediaSourceFactory.setDataSourceFactory(() -> fakeDataSource);
prepareHlsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
private static void prepareHlsUrlAndWaitForPrepareError(
DefaultMediaSourceFactory defaultMediaSourceFactory) throws Exception {
MediaSource mediaSource =
defaultMediaSourceFactory.createMediaSource(MediaItem.fromUri(URI_MEDIA + "/file.m3u8"));
getInstrumentation()
.runOnMainSync(
() ->
mediaSource.prepareSource(
(source, timeline) -> {}, /* mediaTransferListener= */ null, PlayerId.UNSET));
// We don't expect this to prepare successfully.
RobolectricUtil.runMainLooperUntil(
() -> {
try {
mediaSource.maybeThrowSourceInfoRefreshError();
return false;
} catch (IOException e) {
return true;
}
});
}
}

View file

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.source.rtsp.reader;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import com.google.android.exoplayer2.C;
@ -51,6 +53,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** The combined size of a sample that is fragmented into multiple RTP packets. */
private int fragmentedSampleSizeBytes;
private long fragmentedSampleTimeUs;
private long startTimeOffsetUs;
/**
* Whether the first packet of one VP8 frame is received. A VP8 frame can be split into two RTP
@ -67,6 +71,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
firstReceivedTimestamp = C.TIME_UNSET;
previousSequenceNumber = C.INDEX_UNSET;
fragmentedSampleSizeBytes = C.LENGTH_UNSET;
fragmentedSampleTimeUs = C.TIME_UNSET;
// The start time offset must be 0 until the first seek.
startTimeOffsetUs = 0;
gotFirstPacketOfVp8Frame = false;
@ -81,7 +86,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@Override
public void onReceivingFirstPacket(long timestamp, int sequenceNumber) {}
public void onReceivingFirstPacket(long timestamp, int sequenceNumber) {
checkState(firstReceivedTimestamp == C.TIME_UNSET);
firstReceivedTimestamp = timestamp;
}
@Override
public void consume(
@ -113,21 +121,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int fragmentSize = data.bytesLeft();
trackOutput.sampleData(data, fragmentSize);
fragmentedSampleSizeBytes += fragmentSize;
if (fragmentedSampleSizeBytes == C.LENGTH_UNSET) {
fragmentedSampleSizeBytes = fragmentSize;
} else {
fragmentedSampleSizeBytes += fragmentSize;
}
fragmentedSampleTimeUs = toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp);
if (rtpMarker) {
if (firstReceivedTimestamp == C.TIME_UNSET) {
firstReceivedTimestamp = timestamp;
}
long timeUs = toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp);
trackOutput.sampleMetadata(
timeUs,
isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0,
fragmentedSampleSizeBytes,
/* offset= */ 0,
/* cryptoData= */ null);
fragmentedSampleSizeBytes = C.LENGTH_UNSET;
gotFirstPacketOfVp8Frame = false;
outputSampleMetadataForFragmentedPackets();
}
previousSequenceNumber = sequenceNumber;
}
@ -147,18 +150,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private boolean validateVp8Descriptor(ParsableByteArray payload, int packetSequenceNumber) {
// VP8 Payload Descriptor is defined in RFC7741 Section 4.2.
int header = payload.readUnsignedByte();
if (!gotFirstPacketOfVp8Frame) {
// TODO(b/198620566) Consider using ParsableBitArray.
// For start of VP8 partition S=1 and PID=0 as per RFC7741 Section 4.2.
if ((header & 0x10) != 0x1 || (header & 0x07) != 0) {
Log.w(TAG, "RTP packet is not the start of a new VP8 partition, skipping.");
return false;
// TODO(b/198620566) Consider using ParsableBitArray.
// For start of VP8 partition S=1 and PID=0 as per RFC7741 Section 4.2.
if ((header & 0x10) == 0x10 && (header & 0x07) == 0) {
if (gotFirstPacketOfVp8Frame && fragmentedSampleSizeBytes > 0) {
// Received new VP8 fragment, output data of previous fragment to decoder.
outputSampleMetadataForFragmentedPackets();
}
gotFirstPacketOfVp8Frame = true;
} else {
} else if (gotFirstPacketOfVp8Frame) {
// Check that this packet is in the sequence of the previous packet.
int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber);
if (packetSequenceNumber != expectedSequenceNumber) {
if (packetSequenceNumber < expectedSequenceNumber) {
Log.w(
TAG,
Util.formatInvariant(
@ -167,6 +170,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
expectedSequenceNumber, packetSequenceNumber));
return false;
}
} else {
Log.w(TAG, "RTP packet is not the start of a new VP8 partition, skipping.");
return false;
}
// Check if optional X header is present.
@ -195,6 +201,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return true;
}
/**
* Outputs sample metadata of the received fragmented packets.
*
* <p>Call this method only after receiving an end of a VP8 partition.
*/
private void outputSampleMetadataForFragmentedPackets() {
checkNotNull(trackOutput)
.sampleMetadata(
fragmentedSampleTimeUs,
isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0,
fragmentedSampleSizeBytes,
/* offset= */ 0,
/* cryptoData= */ null);
fragmentedSampleSizeBytes = 0;
fragmentedSampleTimeUs = C.TIME_UNSET;
gotFirstPacketOfVp8Frame = false;
}
private static long toSampleUs(
long startTimeOffsetUs, long rtpTimestamp, long firstReceivedRtpTimestamp) {
return startTimeOffsetUs

View file

@ -0,0 +1,203 @@
/*
* Copyright 2022 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.rtsp.reader;
import static com.google.android.exoplayer2.util.Util.getBytesFromHexString;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.rtsp.RtpPacket;
import com.google.android.exoplayer2.source.rtsp.RtpPayloadFormat;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Bytes;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link RtpVp8Reader}. */
@RunWith(AndroidJUnit4.class)
public final class RtpVp8ReaderTest {
/** VP9 uses a 90 KHz media clock (RFC7741 Section 4.1). */
private static final long MEDIA_CLOCK_FREQUENCY = 90_000;
private static final byte[] PARTITION_1 = getBytesFromHexString("000102030405060708090A0B0C0D0E");
// 000102030405060708090A
private static final byte[] PARTITION_1_FRAGMENT_1 =
Arrays.copyOf(PARTITION_1, /* newLength= */ 11);
// 0B0C0D0E
private static final byte[] PARTITION_1_FRAGMENT_2 =
Arrays.copyOfRange(PARTITION_1, /* from= */ 11, /* to= */ 15);
private static final long PARTITION_1_RTP_TIMESTAMP = 2599168056L;
private static final RtpPacket PACKET_PARTITION_1_FRAGMENT_1 =
new RtpPacket.Builder()
.setTimestamp(PARTITION_1_RTP_TIMESTAMP)
.setSequenceNumber(40289)
.setMarker(false)
.setPayloadData(Bytes.concat(getBytesFromHexString("10"), PARTITION_1_FRAGMENT_1))
.build();
private static final RtpPacket PACKET_PARTITION_1_FRAGMENT_2 =
new RtpPacket.Builder()
.setTimestamp(PARTITION_1_RTP_TIMESTAMP)
.setSequenceNumber(40290)
.setMarker(false)
.setPayloadData(Bytes.concat(getBytesFromHexString("00"), PARTITION_1_FRAGMENT_2))
.build();
private static final byte[] PARTITION_2 = getBytesFromHexString("0D0C0B0A09080706050403020100");
// 0D0C0B0A090807060504
private static final byte[] PARTITION_2_FRAGMENT_1 =
Arrays.copyOf(PARTITION_2, /* newLength= */ 10);
// 03020100
private static final byte[] PARTITION_2_FRAGMENT_2 =
Arrays.copyOfRange(PARTITION_2, /* from= */ 10, /* to= */ 14);
private static final long PARTITION_2_RTP_TIMESTAMP = 2599168344L;
private static final RtpPacket PACKET_PARTITION_2_FRAGMENT_1 =
new RtpPacket.Builder()
.setTimestamp(PARTITION_2_RTP_TIMESTAMP)
.setSequenceNumber(40291)
.setMarker(false)
.setPayloadData(Bytes.concat(getBytesFromHexString("10"), PARTITION_2_FRAGMENT_1))
.build();
private static final RtpPacket PACKET_PARTITION_2_FRAGMENT_2 =
new RtpPacket.Builder()
.setTimestamp(PARTITION_2_RTP_TIMESTAMP)
.setSequenceNumber(40292)
.setMarker(true)
.setPayloadData(
Bytes.concat(
getBytesFromHexString("80"),
// Optional header.
getBytesFromHexString("D6AA953961"),
PARTITION_2_FRAGMENT_2))
.build();
private static final long PARTITION_2_PRESENTATION_TIMESTAMP_US =
Util.scaleLargeTimestamp(
(PARTITION_2_RTP_TIMESTAMP - PARTITION_1_RTP_TIMESTAMP),
/* multiplier= */ C.MICROS_PER_SECOND,
/* divisor= */ MEDIA_CLOCK_FREQUENCY);
private FakeExtractorOutput extractorOutput;
@Before
public void setUp() {
extractorOutput =
new FakeExtractorOutput(
(id, type) -> new FakeTrackOutput(/* deduplicateConsecutiveFormats= */ true));
}
@Test
public void consume_validPackets() {
RtpVp8Reader vp8Reader = createVp8Reader();
vp8Reader.createTracks(extractorOutput, /* trackId= */ 0);
vp8Reader.onReceivingFirstPacket(
PACKET_PARTITION_1_FRAGMENT_1.timestamp, PACKET_PARTITION_1_FRAGMENT_1.sequenceNumber);
consume(vp8Reader, PACKET_PARTITION_1_FRAGMENT_1);
consume(vp8Reader, PACKET_PARTITION_1_FRAGMENT_2);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_1);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_2);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
assertThat(trackOutput.getSampleCount()).isEqualTo(2);
assertThat(trackOutput.getSampleData(0)).isEqualTo(PARTITION_1);
assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0);
assertThat(trackOutput.getSampleData(1)).isEqualTo(PARTITION_2);
assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(PARTITION_2_PRESENTATION_TIMESTAMP_US);
}
@Test
public void consume_fragmentedFrameMissingFirstFragment() {
RtpVp8Reader vp8Reader = createVp8Reader();
vp8Reader.createTracks(extractorOutput, /* trackId= */ 0);
// First packet timing information is transmitted over RTSP, not RTP.
vp8Reader.onReceivingFirstPacket(
PACKET_PARTITION_1_FRAGMENT_1.timestamp, PACKET_PARTITION_1_FRAGMENT_1.sequenceNumber);
consume(vp8Reader, PACKET_PARTITION_1_FRAGMENT_2);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_1);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_2);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
assertThat(trackOutput.getSampleCount()).isEqualTo(1);
assertThat(trackOutput.getSampleData(0)).isEqualTo(PARTITION_2);
assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(PARTITION_2_PRESENTATION_TIMESTAMP_US);
}
@Test
public void consume_fragmentedFrameMissingBoundaryFragment() {
RtpVp8Reader vp8Reader = createVp8Reader();
vp8Reader.createTracks(extractorOutput, /* trackId= */ 0);
vp8Reader.onReceivingFirstPacket(
PACKET_PARTITION_1_FRAGMENT_1.timestamp, PACKET_PARTITION_1_FRAGMENT_1.sequenceNumber);
consume(vp8Reader, PACKET_PARTITION_1_FRAGMENT_1);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_1);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_2);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
assertThat(trackOutput.getSampleCount()).isEqualTo(2);
assertThat(trackOutput.getSampleData(0)).isEqualTo(PARTITION_1_FRAGMENT_1);
assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0);
assertThat(trackOutput.getSampleData(1)).isEqualTo(PARTITION_2);
assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(PARTITION_2_PRESENTATION_TIMESTAMP_US);
}
@Test
public void consume_outOfOrderFragmentedFrame() {
RtpVp8Reader vp8Reader = createVp8Reader();
vp8Reader.createTracks(extractorOutput, /* trackId= */ 0);
vp8Reader.onReceivingFirstPacket(
PACKET_PARTITION_1_FRAGMENT_1.timestamp, PACKET_PARTITION_1_FRAGMENT_1.sequenceNumber);
consume(vp8Reader, PACKET_PARTITION_1_FRAGMENT_1);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_1);
consume(vp8Reader, PACKET_PARTITION_1_FRAGMENT_2);
consume(vp8Reader, PACKET_PARTITION_2_FRAGMENT_2);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
assertThat(trackOutput.getSampleCount()).isEqualTo(2);
assertThat(trackOutput.getSampleData(0)).isEqualTo(PARTITION_1_FRAGMENT_1);
assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0);
assertThat(trackOutput.getSampleData(1)).isEqualTo(PARTITION_2);
assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(PARTITION_2_PRESENTATION_TIMESTAMP_US);
}
private static RtpVp8Reader createVp8Reader() {
return new RtpVp8Reader(
new RtpPayloadFormat(
new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_VP8).build(),
/* rtpPayloadType= */ 96,
/* clockRate= */ (int) MEDIA_CLOCK_FREQUENCY,
/* fmtpParameters= */ ImmutableMap.of()));
}
private static void consume(RtpVp8Reader vp8Reader, RtpPacket rtpPacket) {
vp8Reader.consume(
new ParsableByteArray(rtpPacket.payloadData),
rtpPacket.timestamp,
rtpPacket.sequenceNumber,
rtpPacket.marker);
}
}

View file

@ -29,6 +29,7 @@ dependencies {
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
testImplementation project(modulePrefix + 'robolectricutils')
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.smoothstreaming;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@ -22,9 +23,13 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.robolectric.RobolectricUtil;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -93,4 +98,53 @@ public class DefaultMediaSourceFactoryTest {
assertThat(supportedTypes).asList().containsExactly(C.CONTENT_TYPE_OTHER, C.CONTENT_TYPE_SS);
}
@Test
public void createMediaSource_withSetDataSourceFactory_usesDataSourceFactory() throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext())
.setDataSourceFactory(() -> fakeDataSource);
prepareSsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
@Test
public void
createMediaSource_usingDefaultDataSourceFactoryAndSetDataSourceFactory_usesUpdatesDataSourceFactory()
throws Exception {
FakeDataSource fakeDataSource = new FakeDataSource();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext());
// Use default DataSource.Factory first.
prepareSsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
defaultMediaSourceFactory.setDataSourceFactory(() -> fakeDataSource);
prepareSsUrlAndWaitForPrepareError(defaultMediaSourceFactory);
assertThat(fakeDataSource.getAndClearOpenedDataSpecs()).asList().isNotEmpty();
}
private static void prepareSsUrlAndWaitForPrepareError(
DefaultMediaSourceFactory defaultMediaSourceFactory) throws Exception {
MediaSource mediaSource =
defaultMediaSourceFactory.createMediaSource(MediaItem.fromUri(URI_MEDIA + "/file.ism"));
getInstrumentation()
.runOnMainSync(
() ->
mediaSource.prepareSource(
(source, timeline) -> {}, /* mediaTransferListener= */ null, PlayerId.UNSET));
// We don't expect this to prepare successfully.
RobolectricUtil.runMainLooperUntil(
() -> {
try {
mediaSource.maybeThrowSourceInfoRefreshError();
return false;
} catch (IOException e) {
return true;
}
});
}
}

View file

@ -1817,7 +1817,13 @@ public class StyledPlayerControlView extends FrameLayout {
if (position < playbackSpeedTexts.length) {
holder.textView.setText(playbackSpeedTexts[position]);
}
holder.checkView.setVisibility(position == selectedIndex ? VISIBLE : INVISIBLE);
if (position == selectedIndex) {
holder.itemView.setSelected(true);
holder.checkView.setVisibility(VISIBLE);
} else {
holder.itemView.setSelected(false);
holder.checkView.setVisibility(INVISIBLE);
}
holder.itemView.setOnClickListener(
v -> {
if (position != selectedIndex) {

386
media3-migration.sh Normal file
View file

@ -0,0 +1,386 @@
#!/bin/bash
# Copyright (C) 2022 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.
##
shopt -s extglob
PACKAGE_MAPPINGS='com.google.android.exoplayer2 com.google.android.exoplayer2
com.google.android.exoplayer2.analytics com.google.android.exoplayer2.analytics
com.google.android.exoplayer2.audio com.google.android.exoplayer2.audio
com.google.android.exoplayer2.castdemo com.google.android.exoplayer2.castdemo
com.google.android.exoplayer2.database com.google.android.exoplayer2.database
com.google.android.exoplayer2.decoder com.google.android.exoplayer2.decoder
com.google.android.exoplayer2.demo com.google.android.exoplayer2.demo
com.google.android.exoplayer2.drm com.google.android.exoplayer2.drm
com.google.android.exoplayer2.ext.av1 com.google.android.exoplayer2.ext.av1
com.google.android.exoplayer2.ext.cast com.google.android.exoplayer2.ext.cast
com.google.android.exoplayer2.ext.cronet com.google.android.exoplayer2.ext.cronet
com.google.android.exoplayer2.ext.ffmpeg com.google.android.exoplayer2.ext.ffmpeg
com.google.android.exoplayer2.ext.flac com.google.android.exoplayer2.ext.flac
com.google.android.exoplayer2.ext.ima com.google.android.exoplayer2.ext.ima
com.google.android.exoplayer2.ext.leanback com.google.android.exoplayer2.ext.leanback
com.google.android.exoplayer2.ext.okhttp com.google.android.exoplayer2.ext.okhttp
com.google.android.exoplayer2.ext.opus com.google.android.exoplayer2.ext.opus
com.google.android.exoplayer2.ext.rtmp com.google.android.exoplayer2.ext.rtmp
com.google.android.exoplayer2.ext.vp9 com.google.android.exoplayer2.ext.vp9
com.google.android.exoplayer2.ext.workmanager com.google.android.exoplayer2.ext.workmanager
com.google.android.exoplayer2.extractor com.google.android.exoplayer2.extractor
com.google.android.exoplayer2.gldemo com.google.android.exoplayer2.gldemo
com.google.android.exoplayer2.mediacodec com.google.android.exoplayer2.mediacodec
com.google.android.exoplayer2.metadata com.google.android.exoplayer2.metadata
com.google.android.exoplayer2.offline com.google.android.exoplayer2.offline
com.google.android.exoplayer2.playbacktests com.google.android.exoplayer2.playbacktests
com.google.android.exoplayer2.robolectric com.google.android.exoplayer2.robolectric
com.google.android.exoplayer2.scheduler com.google.android.exoplayer2.scheduler
com.google.android.exoplayer2.source com.google.android.exoplayer2.source
com.google.android.exoplayer2.source.dash com.google.android.exoplayer2.source.dash
com.google.android.exoplayer2.source.hls com.google.android.exoplayer2.source.hls
com.google.android.exoplayer2.source.rtsp com.google.android.exoplayer2.source.rtsp
com.google.android.exoplayer2.source.smoothstreaming com.google.android.exoplayer2.source.smoothstreaming
com.google.android.exoplayer2.surfacedemo com.google.android.exoplayer2.surfacedemo
com.google.android.exoplayer2.testdata com.google.android.exoplayer2.testdata
com.google.android.exoplayer2.testutil com.google.android.exoplayer2.testutil
com.google.android.exoplayer2.text com.google.android.exoplayer2.text
com.google.android.exoplayer2.trackselection com.google.android.exoplayer2.trackselection
com.google.android.exoplayer2.transformer com.google.android.exoplayer2.transformer
com.google.android.exoplayer2.transformerdemo com.google.android.exoplayer2.transformerdemo
com.google.android.exoplayer2.ui com.google.android.exoplayer2.ui
com.google.android.exoplayer2.upstream com.google.android.exoplayer2.upstream
com.google.android.exoplayer2.upstream.cache com.google.android.exoplayer2.upstream.cache
com.google.android.exoplayer2.upstream.crypto com.google.android.exoplayer2.upstream.crypto
com.google.android.exoplayer2.util com.google.android.exoplayer2.util
com.google.android.exoplayer2.util com.google.android.exoplayer2.util
com.google.android.exoplayer2.video com.google.android.exoplayer2.video'
CLASS_RENAMINGS='com.google.android.exoplayer2.ui.StyledPlayerView com.google.android.exoplayer2.ui.PlayerView
StyledPlayerView PlayerView
com.google.android.exoplayer2.ui.StyledPlayerControlView com.google.android.exoplayer2.ui.PlayerControlView
StyledPlayerControlView PlayerControlView
com.google.android.exoplayer2.ExoPlayerLibraryInfo androidx.media3.common.MediaLibraryInfo
ExoPlayerLibraryInfo MediaLibraryInfo
com.google.android.exoplayer2.SimpleExoPlayer com.google.android.exoplayer2.ExoPlayer
SimpleExoPlayer ExoPlayer'
CLASS_MAPPINGS='com.google.android.exoplayer2.text.span androidx.media3.common.text HorizontalTextInVerticalContextSpan LanguageFeatureSpan RubySpan SpanUtil TextAnnotation TextEmphasisSpan
com.google.android.exoplayer2.text androidx.media3.common.text CueGroup Cue
com.google.android.exoplayer2.text com.google.android.exoplayer2.text ExoplayerCuesDecoder SubtitleDecoderFactory TextOutput TextRenderer
com.google.android.exoplayer2.upstream.crypto com.google.android.exoplayer2.upstream AesCipherDataSource AesCipherDataSink AesFlushingCipher
com.google.android.exoplayer2.util com.google.android.exoplayer2.util AtomicFile Assertions BundleableUtil BundleUtil Clock ClosedSource CodecSpecificDataUtil ColorParser ConditionVariable Consumer CopyOnWriteMultiset EGLSurfaceTexture GlProgram GlUtil HandlerWrapper LibraryLoader ListenerSet Log LongArray MediaFormatUtil NetworkTypeObserver NonNullApi NotificationUtil ParsableBitArray ParsableByteArray RepeatModeUtil RunnableFutureTask SystemClock SystemHandlerWrapper TimedValueQueue TimestampAdjuster TraceUtil UnknownNull UnstableApi UriUtil Util XmlPullParserUtil
com.google.android.exoplayer2.util androidx.media3.common ErrorMessageProvider FlagSet FileTypes MimeTypes PriorityTaskManager
com.google.android.exoplayer2.metadata androidx.media3.common Metadata
com.google.android.exoplayer2.metadata com.google.android.exoplayer2.metadata MetadataDecoderFactory MetadataOutput MetadataRenderer
com.google.android.exoplayer2.audio androidx.media3.common AudioAttributes AuxEffectInfo
com.google.android.exoplayer2.ui androidx.media3.common AdOverlayInfo AdViewProvider
com.google.android.exoplayer2.source.ads androidx.media3.common AdPlaybackState
com.google.android.exoplayer2.source androidx.media3.common MediaPeriodId TrackGroup
com.google.android.exoplayer2.offline androidx.media3.common StreamKey
com.google.android.exoplayer2.ui com.google.android.exoplayer2.offline DownloadNotificationHelper
com.google.android.exoplayer2.trackselection androidx.media3.common TrackSelectionParameters TrackSelectionOverride
com.google.android.exoplayer2.video androidx.media3.common ColorInfo VideoSize
com.google.android.exoplayer2.upstream androidx.media3.common DataReader
com.google.android.exoplayer2.upstream com.google.android.exoplayer2.upstream Allocation Allocator BandwidthMeter CachedRegionTracker DefaultAllocator DefaultBandwidthMeter DefaultLoadErrorHandlingPolicy Loader LoaderErrorThrower ParsingLoadable SlidingPercentile TimeToFirstByteEstimator
com.google.android.exoplayer2.audio com.google.android.exoplayer2.extractor AacUtil Ac3Util Ac4Util DtsUtil MpegAudioUtil OpusUtil WavUtil
com.google.android.exoplayer2.util com.google.android.exoplayer2.extractor NalUnitUtil ParsableNalUnitBitArray
com.google.android.exoplayer2.video com.google.android.exoplayer2.extractor AvcConfig DolbyVisionConfig HevcConfig
com.google.android.exoplayer2.decoder com.google.android.exoplayer2 DecoderCounters DecoderReuseEvaluation
com.google.android.exoplayer2.util com.google.android.exoplayer2 MediaClock StandaloneMediaClock
com.google.android.exoplayer2 com.google.android.exoplayer2 FormatHolder PlayerMessage
com.google.android.exoplayer2 androidx.media3.common BasePlayer BundleListRetriever Bundleable ControlDispatcher C DefaultControlDispatcher DeviceInfo ErrorMessageProvider ExoPlayerLibraryInfo Format ForwardingPlayer HeartRating IllegalSeekPositionException MediaItem MediaMetadata ParserException PercentageRating PlaybackException PlaybackParameters Player PositionInfo Rating StarRating ThumbRating Timeline Tracks
com.google.android.exoplayer2.drm androidx.media3.common DrmInitData'
DEPENDENCY_MAPPINGS='exoplayer media3-exoplayer
exoplayer-common media3-common
exoplayer-core media3-exoplayer
exoplayer-dash media3-exoplayer-dash
exoplayer-database media3-database
exoplayer-datasource media-datasource
exoplayer-decoder media3-decoder
exoplayer-extractor media3-extractor
exoplayer-hls media3-exoplayer-hls
exoplayer-robolectricutils media3-test-utils-robolectric
exoplayer-rtsp media3-exoplayer-rtsp
exoplayer-smoothstreaming media3-exoplayer-smoothstreaming
exoplayer-testutils media3-test-utils
exoplayer-transformer media3-transformer
exoplayer-ui media3-ui
extension-cast media3-cast
extension-cronet media3-datasource-cronet
extension-ima media3-exoplayer-ima
extension-leanback media3-ui-leanback
extension-okhttp media3-datasource-okhttp
extension-rtmp media3-datasource-rtmp
extension-workmanager media3-exoplayer-workmanager'
# Rewrites classes, packages and dependencies from the legacy ExoPlayer package structure
# to androidx.media3 structure.
MEDIA3_VERSION="1.0.0-beta02"
LEGACY_PEER_VERSION="2.18.1"
function usage() {
echo "usage: $0 [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]"
echo " PROJECT_ROOT: path to your project root (location of 'gradlew')"
echo " -p: list package mappings and then exit"
echo " -c: list class mappings (precedence over package mappings) and then exit"
echo " -d: list dependency mappings and then exit"
echo " -m: migrate packages, classes and dependencies to AndroidX Media3"
echo " -l: list files that will be considered for rewrite and then exit"
echo " -x: exclude the path from the list of file to be changed: 'app/src/test'"
echo " -f: force the action even when validation fails"
echo " -v: print the exoplayer2/media3 version strings of this script and exit"
echo " --noclean : Do not call './gradlew clean' in project directory."
echo " -h, --help: show this help text"
}
function print_pairs {
while read -r line;
do
IFS=' ' read -ra PAIR <<< "$line"
printf "%-55s %-30s\n" "${PAIR[0]}" "${PAIR[1]}"
done <<< "$(echo "$@")"
}
function print_class_mappings {
while read -r mapping;
do
old=$(echo "$mapping" | cut -d ' ' -f1)
new=$(echo "$mapping" | cut -d ' ' -f2)
classes=$(echo "$mapping" | cut -d ' ' -f3-)
for clazz in $classes;
do
printf "%-80s %-30s\n" "$old.$clazz" "$new.$clazz"
done
done <<< "$(echo "$CLASS_MAPPINGS" | sort)"
}
ERROR_COUNTER=0
VALIDATION_ERRORS=''
function add_validation_error {
let ERROR_COUNTER++
VALIDATION_ERRORS+="\033[31m[$ERROR_COUNTER] ->\033[0m ${1}"
}
function validate_exoplayer_version() {
has_exoplayer_dependency=''
while read -r file;
do
local version
version=$(grep -m 1 "com\.google\.android\.exoplayer:" "$file" | cut -d ":" -f3 | tr -d \" | tr -d \')
if [[ ! -z $version ]] && [[ ! "$version" =~ $LEGACY_PEER_VERSION ]];
then
add_validation_error "The version does not match '$LEGACY_PEER_VERSION'. \
Update to '$LEGACY_PEER_VERSION' or use the migration script matching your \
current version. Current version '$version' found in\n $file\n"
fi
done <<< "$(find . -type f -name "build.gradle")"
}
function validate_string_not_contained {
local pattern=$1 # regex
local failure_message=$2
while read -r file;
do
if grep -q -e "$pattern" "$file";
then
add_validation_error "$failure_message:\n $file\n"
fi
done <<< "$files"
}
function validate_string_patterns {
validate_string_not_contained \
'com\.google\.android\.exoplayer2\..*\*' \
'Replace wildcard import statements with fully qualified import statements';
validate_string_not_contained \
'com\.google\.android\.exoplayer2\.ui\.PlayerView' \
'Migrate PlayerView to StyledPlayerView before migrating';
validate_string_not_contained \
'LegacyPlayerView' \
'Migrate LegacyPlayerView to StyledPlayerView before migrating';
validate_string_not_contained \
'com\.google\.android\.exoplayer2\.ext\.mediasession' \
'The MediaSessionConnector is integrated in androidx.media3.session.MediaSession'
}
SED_CMD_INPLACE='sed -i '
if [[ "$OSTYPE" == "darwin"* ]]; then
SED_CMD_INPLACE="sed -i '' "
fi
MIGRATE_FILES='1'
LIST_FILES_ONLY='1'
PRINT_CLASS_MAPPINGS='1'
PRINT_PACKAGE_MAPPINGS='1'
PRINT_DEPENDENCY_MAPPINGS='1'
PRINT_VERSION='1'
NO_CLEAN='1'
FORCE='1'
IGNORE_VERSION='1'
EXCLUDED_PATHS=''
while [[ $1 =~ ^-.* ]];
do
case "$1" in
-m ) MIGRATE_FILES='';;
-l ) LIST_FILES_ONLY='';;
-c ) PRINT_CLASS_MAPPINGS='';;
-p ) PRINT_PACKAGE_MAPPINGS='';;
-d ) PRINT_DEPENDENCY_MAPPINGS='';;
-v ) PRINT_VERSION='';;
-f ) FORCE='';;
-x ) shift; EXCLUDED_PATHS="$(printf "%s\n%s" $EXCLUDED_PATHS $1)";;
--noclean ) NO_CLEAN='';;
* ) usage && exit 1;;
esac
shift
done
if [[ -z $PRINT_DEPENDENCY_MAPPINGS ]];
then
print_pairs "$DEPENDENCY_MAPPINGS"
exit 0
elif [[ -z $PRINT_PACKAGE_MAPPINGS ]];
then
print_pairs "$PACKAGE_MAPPINGS"
exit 0
elif [[ -z $PRINT_CLASS_MAPPINGS ]];
then
print_class_mappings
exit 0
elif [[ -z $PRINT_VERSION ]];
then
echo "$LEGACY_PEER_VERSION -> $MEDIA3_VERSION. This script is written to migrate from ExoPlayer $LEGACY_PEER_VERSION to AndroidX Media3 $MEDIA3_VERSION"
exit 0
elif [[ -z $1 ]];
then
usage
exit 1
fi
if [[ ! -f $1/gradlew ]];
then
echo "directory seems not to exist or is not a gradle project (missing 'gradlew')"
usage
exit 1
fi
PROJECT_ROOT=$1
cd "$PROJECT_ROOT"
# Create the set of files to transform
exclusion="/build/|/.idea/|/res/drawable|/res/color|/res/mipmap|/res/values|"
if [[ ! -z $EXCLUDED_PATHS ]];
then
while read -r path;
do
exclusion="$exclusion./$path|"
done <<< "$EXCLUDED_PATHS"
fi
files=$(find . -name '*\.java' -o -name '*\.kt' -o -name '*\.xml' | grep -Ev "'$exclusion'")
# Validate project and exit in case of validation errors
validate_string_patterns
validate_exoplayer_version "$PROJECT_ROOT"
if [[ ! -z $FORCE && ! -z "$VALIDATION_ERRORS" ]];
then
echo "============================================="
echo "Validation errors (use -f to force execution)"
echo "---------------------------------------------"
echo -e "$VALIDATION_ERRORS"
exit 1
fi
if [[ -z $LIST_FILES_ONLY ]];
then
echo "$files" | cut -c 3-
find . -type f -name 'build\.gradle' | cut -c 3-
exit 0
fi
# start migration after successful validation or when forced to disregard validation
# errors
if [[ ! -z "$MIGRATE_FILES" ]];
then
echo "nothing to do"
usage
exit 0
fi
PWD=$(pwd)
if [[ ! -z $NO_CLEAN ]];
then
cd "$PROJECT_ROOT"
./gradlew clean
cd "$PWD"
fi
# create expressions for class renamings
renaming_expressions=''
while read -r renaming;
do
src=$(echo "$renaming" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
dest=$(echo "$renaming" | cut -d ' ' -f2)
renaming_expressions+="-e s/$src/$dest/g "
done <<< "$CLASS_RENAMINGS"
# create expressions for class mappings
classes_expressions=''
while read -r mapping;
do
src=$(echo "$mapping" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
dest=$(echo "$mapping" | cut -d ' ' -f2)
classes=$(echo "$mapping" | cut -d ' ' -f3-)
for clazz in $classes;
do
classes_expressions+="-e s/$src\.$clazz/$dest.$clazz/g "
done
done <<< "$CLASS_MAPPINGS"
# create expressions for package mappings
packages_expressions=''
while read -r mapping;
do
src=$(echo "$mapping" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
dest=$(echo "$mapping" | cut -d ' ' -f2)
packages_expressions+="-e s/$src/$dest/g "
done <<< "$PACKAGE_MAPPINGS"
# do search and replace with expressions in each selected file
while read -r file;
do
echo "migrating $file"
expr="$renaming_expressions $classes_expressions $packages_expressions"
$SED_CMD_INPLACE $expr $file
done <<< "$files"
# create expressions for dependencies in gradle files
EXOPLAYER_GROUP="com\.google\.android\.exoplayer"
MEDIA3_GROUP="androidx.media3"
dependency_expressions=""
while read -r mapping
do
OLD=$(echo "$mapping" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
NEW=$(echo "$mapping" | cut -d ' ' -f2)
dependency_expressions="$dependency_expressions -e s/$EXOPLAYER_GROUP:$OLD:.*\"/$MEDIA3_GROUP:$NEW:$MEDIA3_VERSION\"/g -e s/$EXOPLAYER_GROUP:$OLD:.*'/$MEDIA3_GROUP:$NEW:$MEDIA3_VERSION'/"
done <<< "$DEPENDENCY_MAPPINGS"
## do search and replace for dependencies in gradle files
while read -r build_file;
do
echo "migrating build file $build_file"
$SED_CMD_INPLACE $dependency_expressions $build_file
done <<< "$(find . -type f -name 'build\.gradle')"

View file

@ -182,41 +182,6 @@ public class TestPlayerRunHelper {
return checkNotNull(player.getPlayerError());
}
/**
* Runs tasks of the main {@link Looper} until {@link
* ExoPlayer.AudioOffloadListener#onExperimentalOffloadSchedulingEnabledChanged} is called or a
* playback error occurs.
*
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
*
* @param player The {@link Player}.
* @return The new offloadSchedulingEnabled state.
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static boolean runUntilReceiveOffloadSchedulingEnabledNewState(ExoPlayer player)
throws TimeoutException {
verifyMainTestThread(player);
AtomicReference<@NullableType Boolean> offloadSchedulingEnabledReceiver =
new AtomicReference<>();
ExoPlayer.AudioOffloadListener listener =
new ExoPlayer.AudioOffloadListener() {
@Override
public void onExperimentalOffloadSchedulingEnabledChanged(
boolean offloadSchedulingEnabled) {
offloadSchedulingEnabledReceiver.set(offloadSchedulingEnabled);
}
};
player.addAudioOffloadListener(listener);
runMainLooperUntil(
() -> offloadSchedulingEnabledReceiver.get() != null || player.getPlayerError() != null);
player.removeAudioOffloadListener(listener);
if (player.getPlayerError() != null) {
throw new IllegalStateException(player.getPlayerError());
}
return checkNotNull(offloadSchedulingEnabledReceiver.get());
}
/**
* Runs tasks of the main {@link Looper} until {@link
* ExoPlayer.AudioOffloadListener#onExperimentalSleepingForOffloadChanged(boolean)} is called or a

View file

@ -0,0 +1,339 @@
seekMap:
isSeekable = true
duration = 1067733
getPosition(0) = [[timeUs=66733, position=1325]]
getPosition(1) = [[timeUs=66733, position=1325]]
getPosition(533866) = [[timeUs=66733, position=1325]]
getPosition(1067733) = [[timeUs=66733, position=1325]]
numberOfTracks = 2
track 0:
total output bytes = 85933
sample count = 30
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
width = 1080
height = 720
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample 0:
time = 66733
flags = 1
data = length 38070, hash B58E1AEE
sample 1:
time = 200200
flags = 0
data = length 8340, hash 8AC449FF
sample 2:
time = 133466
flags = 0
data = length 1295, hash C0DA5090
sample 3:
time = 100100
flags = 0
data = length 469, hash D6E0A200
sample 4:
time = 166833
flags = 0
data = length 564, hash E5F56C5B
sample 5:
time = 333666
flags = 0
data = length 6075, hash 8756E49E
sample 6:
time = 266933
flags = 0
data = length 847, hash DCC2B618
sample 7:
time = 233566
flags = 0
data = length 455, hash B9CCE047
sample 8:
time = 300300
flags = 0
data = length 467, hash 69806D94
sample 9:
time = 467133
flags = 0
data = length 4549, hash 3944F501
sample 10:
time = 400400
flags = 0
data = length 1087, hash 491BF106
sample 11:
time = 367033
flags = 0
data = length 380, hash 5FED016A
sample 12:
time = 433766
flags = 0
data = length 455, hash 8A0610
sample 13:
time = 600600
flags = 0
data = length 5190, hash B9031D8
sample 14:
time = 533866
flags = 0
data = length 1071, hash 684E7DC8
sample 15:
time = 500500
flags = 0
data = length 653, hash 8494F326
sample 16:
time = 567233
flags = 0
data = length 485, hash 2CCC85F4
sample 17:
time = 734066
flags = 0
data = length 4884, hash D16B6A96
sample 18:
time = 667333
flags = 0
data = length 997, hash 164FF210
sample 19:
time = 633966
flags = 0
data = length 640, hash F664125B
sample 20:
time = 700700
flags = 0
data = length 491, hash B5930C7C
sample 21:
time = 867533
flags = 0
data = length 2989, hash 92CF4FCF
sample 22:
time = 800800
flags = 0
data = length 838, hash 294A3451
sample 23:
time = 767433
flags = 0
data = length 544, hash FCCE2DE6
sample 24:
time = 834166
flags = 0
data = length 329, hash A654FFA1
sample 25:
time = 1001000
flags = 0
data = length 1517, hash 5F7EBF8B
sample 26:
time = 934266
flags = 0
data = length 803, hash 7A5C4C1D
sample 27:
time = 900900
flags = 0
data = length 415, hash B31BBC3B
sample 28:
time = 967633
flags = 0
data = length 415, hash 850DFEA3
sample 29:
time = 1034366
flags = 0
data = length 619, hash AB5E56CA
track 1:
total output bytes = 18257
sample count = 46
format 0:
averageBitrate = 2147483647
peakBitrate = 2147483647
id = 2
sampleMimeType = audio/mp4a-latm
codecs = mp4a.40.2
channelCount = 1
sampleRate = 44100
language = und
initializationData:
data = length 5, hash 2B7623A
sample 0:
time = 0
flags = 1
data = length 18, hash 96519432
sample 1:
time = 23219
flags = 1
data = length 4, hash EE9DF
sample 2:
time = 46439
flags = 1
data = length 4, hash EEDBF
sample 3:
time = 69659
flags = 1
data = length 157, hash E2F078F4
sample 4:
time = 92879
flags = 1
data = length 371, hash B9471F94
sample 5:
time = 116099
flags = 1
data = length 373, hash 2AB265CB
sample 6:
time = 139319
flags = 1
data = length 402, hash 1295477C
sample 7:
time = 162539
flags = 1
data = length 455, hash 2D8146C8
sample 8:
time = 185759
flags = 1
data = length 434, hash F2C5D287
sample 9:
time = 208979
flags = 1
data = length 450, hash 84143FCD
sample 10:
time = 232199
flags = 1
data = length 429, hash EF769D50
sample 11:
time = 255419
flags = 1
data = length 450, hash EC3DE692
sample 12:
time = 278639
flags = 1
data = length 447, hash 3E519E13
sample 13:
time = 301859
flags = 1
data = length 457, hash 1E4F23A0
sample 14:
time = 325079
flags = 1
data = length 447, hash A439EA97
sample 15:
time = 348299
flags = 1
data = length 456, hash 1E9034C6
sample 16:
time = 371519
flags = 1
data = length 398, hash 99DB7345
sample 17:
time = 394739
flags = 1
data = length 474, hash 3F05F10A
sample 18:
time = 417959
flags = 1
data = length 416, hash C105EE09
sample 19:
time = 441179
flags = 1
data = length 454, hash 5FDBE458
sample 20:
time = 464399
flags = 1
data = length 438, hash 41A93AC3
sample 21:
time = 487619
flags = 1
data = length 443, hash 10FDA652
sample 22:
time = 510839
flags = 1
data = length 412, hash 1F791E25
sample 23:
time = 534058
flags = 1
data = length 482, hash A6D983D
sample 24:
time = 557278
flags = 1
data = length 386, hash BED7392F
sample 25:
time = 580498
flags = 1
data = length 463, hash 5309F8C9
sample 26:
time = 603718
flags = 1
data = length 394, hash 21C7321F
sample 27:
time = 626938
flags = 1
data = length 489, hash 71B4730D
sample 28:
time = 650158
flags = 1
data = length 403, hash D9C6DE89
sample 29:
time = 673378
flags = 1
data = length 447, hash 9B14B73B
sample 30:
time = 696598
flags = 1
data = length 439, hash 4760D35B
sample 31:
time = 719818
flags = 1
data = length 463, hash 1601F88D
sample 32:
time = 743038
flags = 1
data = length 423, hash D4AE6773
sample 33:
time = 766258
flags = 1
data = length 497, hash A3C674D3
sample 34:
time = 789478
flags = 1
data = length 419, hash D3734A1F
sample 35:
time = 812698
flags = 1
data = length 474, hash DFB41F9
sample 36:
time = 835918
flags = 1
data = length 413, hash 53E7CB9F
sample 37:
time = 859138
flags = 1
data = length 445, hash D15B0E39
sample 38:
time = 882358
flags = 1
data = length 453, hash 77ED81E4
sample 39:
time = 905578
flags = 1
data = length 545, hash 3321AEB9
sample 40:
time = 928798
flags = 1
data = length 317, hash F557D0E
sample 41:
time = 952018
flags = 1
data = length 537, hash ED58CF7B
sample 42:
time = 975238
flags = 1
data = length 458, hash 51CDAA10
sample 43:
time = 998458
flags = 1
data = length 465, hash CBA1EFD7
sample 44:
time = 1021678
flags = 1
data = length 446, hash D6735B8A
sample 45:
time = 1044897
flags = 1
data = length 10, hash A453EEBE
tracksEnded = true

View file

@ -0,0 +1,279 @@
seekMap:
isSeekable = true
duration = 1067733
getPosition(0) = [[timeUs=66733, position=1325]]
getPosition(1) = [[timeUs=66733, position=1325]]
getPosition(533866) = [[timeUs=66733, position=1325]]
getPosition(1067733) = [[timeUs=66733, position=1325]]
numberOfTracks = 2
track 0:
total output bytes = 85933
sample count = 30
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
width = 1080
height = 720
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample 0:
time = 66733
flags = 1
data = length 38070, hash B58E1AEE
sample 1:
time = 200200
flags = 0
data = length 8340, hash 8AC449FF
sample 2:
time = 133466
flags = 0
data = length 1295, hash C0DA5090
sample 3:
time = 100100
flags = 0
data = length 469, hash D6E0A200
sample 4:
time = 166833
flags = 0
data = length 564, hash E5F56C5B
sample 5:
time = 333666
flags = 0
data = length 6075, hash 8756E49E
sample 6:
time = 266933
flags = 0
data = length 847, hash DCC2B618
sample 7:
time = 233566
flags = 0
data = length 455, hash B9CCE047
sample 8:
time = 300300
flags = 0
data = length 467, hash 69806D94
sample 9:
time = 467133
flags = 0
data = length 4549, hash 3944F501
sample 10:
time = 400400
flags = 0
data = length 1087, hash 491BF106
sample 11:
time = 367033
flags = 0
data = length 380, hash 5FED016A
sample 12:
time = 433766
flags = 0
data = length 455, hash 8A0610
sample 13:
time = 600600
flags = 0
data = length 5190, hash B9031D8
sample 14:
time = 533866
flags = 0
data = length 1071, hash 684E7DC8
sample 15:
time = 500500
flags = 0
data = length 653, hash 8494F326
sample 16:
time = 567233
flags = 0
data = length 485, hash 2CCC85F4
sample 17:
time = 734066
flags = 0
data = length 4884, hash D16B6A96
sample 18:
time = 667333
flags = 0
data = length 997, hash 164FF210
sample 19:
time = 633966
flags = 0
data = length 640, hash F664125B
sample 20:
time = 700700
flags = 0
data = length 491, hash B5930C7C
sample 21:
time = 867533
flags = 0
data = length 2989, hash 92CF4FCF
sample 22:
time = 800800
flags = 0
data = length 838, hash 294A3451
sample 23:
time = 767433
flags = 0
data = length 544, hash FCCE2DE6
sample 24:
time = 834166
flags = 0
data = length 329, hash A654FFA1
sample 25:
time = 1001000
flags = 0
data = length 1517, hash 5F7EBF8B
sample 26:
time = 934266
flags = 0
data = length 803, hash 7A5C4C1D
sample 27:
time = 900900
flags = 0
data = length 415, hash B31BBC3B
sample 28:
time = 967633
flags = 0
data = length 415, hash 850DFEA3
sample 29:
time = 1034366
flags = 0
data = length 619, hash AB5E56CA
track 1:
total output bytes = 13359
sample count = 31
format 0:
averageBitrate = 2147483647
peakBitrate = 2147483647
id = 2
sampleMimeType = audio/mp4a-latm
codecs = mp4a.40.2
channelCount = 1
sampleRate = 44100
language = und
initializationData:
data = length 5, hash 2B7623A
sample 0:
time = 348299
flags = 1
data = length 456, hash 1E9034C6
sample 1:
time = 371519
flags = 1
data = length 398, hash 99DB7345
sample 2:
time = 394739
flags = 1
data = length 474, hash 3F05F10A
sample 3:
time = 417959
flags = 1
data = length 416, hash C105EE09
sample 4:
time = 441179
flags = 1
data = length 454, hash 5FDBE458
sample 5:
time = 464399
flags = 1
data = length 438, hash 41A93AC3
sample 6:
time = 487619
flags = 1
data = length 443, hash 10FDA652
sample 7:
time = 510839
flags = 1
data = length 412, hash 1F791E25
sample 8:
time = 534058
flags = 1
data = length 482, hash A6D983D
sample 9:
time = 557278
flags = 1
data = length 386, hash BED7392F
sample 10:
time = 580498
flags = 1
data = length 463, hash 5309F8C9
sample 11:
time = 603718
flags = 1
data = length 394, hash 21C7321F
sample 12:
time = 626938
flags = 1
data = length 489, hash 71B4730D
sample 13:
time = 650158
flags = 1
data = length 403, hash D9C6DE89
sample 14:
time = 673378
flags = 1
data = length 447, hash 9B14B73B
sample 15:
time = 696598
flags = 1
data = length 439, hash 4760D35B
sample 16:
time = 719818
flags = 1
data = length 463, hash 1601F88D
sample 17:
time = 743038
flags = 1
data = length 423, hash D4AE6773
sample 18:
time = 766258
flags = 1
data = length 497, hash A3C674D3
sample 19:
time = 789478
flags = 1
data = length 419, hash D3734A1F
sample 20:
time = 812698
flags = 1
data = length 474, hash DFB41F9
sample 21:
time = 835918
flags = 1
data = length 413, hash 53E7CB9F
sample 22:
time = 859138
flags = 1
data = length 445, hash D15B0E39
sample 23:
time = 882358
flags = 1
data = length 453, hash 77ED81E4
sample 24:
time = 905578
flags = 1
data = length 545, hash 3321AEB9
sample 25:
time = 928798
flags = 1
data = length 317, hash F557D0E
sample 26:
time = 952018
flags = 1
data = length 537, hash ED58CF7B
sample 27:
time = 975238
flags = 1
data = length 458, hash 51CDAA10
sample 28:
time = 998458
flags = 1
data = length 465, hash CBA1EFD7
sample 29:
time = 1021678
flags = 1
data = length 446, hash D6735B8A
sample 30:
time = 1044897
flags = 1
data = length 10, hash A453EEBE
tracksEnded = true

View file

@ -0,0 +1,219 @@
seekMap:
isSeekable = true
duration = 1067733
getPosition(0) = [[timeUs=66733, position=1325]]
getPosition(1) = [[timeUs=66733, position=1325]]
getPosition(533866) = [[timeUs=66733, position=1325]]
getPosition(1067733) = [[timeUs=66733, position=1325]]
numberOfTracks = 2
track 0:
total output bytes = 85933
sample count = 30
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
width = 1080
height = 720
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample 0:
time = 66733
flags = 1
data = length 38070, hash B58E1AEE
sample 1:
time = 200200
flags = 0
data = length 8340, hash 8AC449FF
sample 2:
time = 133466
flags = 0
data = length 1295, hash C0DA5090
sample 3:
time = 100100
flags = 0
data = length 469, hash D6E0A200
sample 4:
time = 166833
flags = 0
data = length 564, hash E5F56C5B
sample 5:
time = 333666
flags = 0
data = length 6075, hash 8756E49E
sample 6:
time = 266933
flags = 0
data = length 847, hash DCC2B618
sample 7:
time = 233566
flags = 0
data = length 455, hash B9CCE047
sample 8:
time = 300300
flags = 0
data = length 467, hash 69806D94
sample 9:
time = 467133
flags = 0
data = length 4549, hash 3944F501
sample 10:
time = 400400
flags = 0
data = length 1087, hash 491BF106
sample 11:
time = 367033
flags = 0
data = length 380, hash 5FED016A
sample 12:
time = 433766
flags = 0
data = length 455, hash 8A0610
sample 13:
time = 600600
flags = 0
data = length 5190, hash B9031D8
sample 14:
time = 533866
flags = 0
data = length 1071, hash 684E7DC8
sample 15:
time = 500500
flags = 0
data = length 653, hash 8494F326
sample 16:
time = 567233
flags = 0
data = length 485, hash 2CCC85F4
sample 17:
time = 734066
flags = 0
data = length 4884, hash D16B6A96
sample 18:
time = 667333
flags = 0
data = length 997, hash 164FF210
sample 19:
time = 633966
flags = 0
data = length 640, hash F664125B
sample 20:
time = 700700
flags = 0
data = length 491, hash B5930C7C
sample 21:
time = 867533
flags = 0
data = length 2989, hash 92CF4FCF
sample 22:
time = 800800
flags = 0
data = length 838, hash 294A3451
sample 23:
time = 767433
flags = 0
data = length 544, hash FCCE2DE6
sample 24:
time = 834166
flags = 0
data = length 329, hash A654FFA1
sample 25:
time = 1001000
flags = 0
data = length 1517, hash 5F7EBF8B
sample 26:
time = 934266
flags = 0
data = length 803, hash 7A5C4C1D
sample 27:
time = 900900
flags = 0
data = length 415, hash B31BBC3B
sample 28:
time = 967633
flags = 0
data = length 415, hash 850DFEA3
sample 29:
time = 1034366
flags = 0
data = length 619, hash AB5E56CA
track 1:
total output bytes = 6804
sample count = 16
format 0:
averageBitrate = 2147483647
peakBitrate = 2147483647
id = 2
sampleMimeType = audio/mp4a-latm
codecs = mp4a.40.2
channelCount = 1
sampleRate = 44100
language = und
initializationData:
data = length 5, hash 2B7623A
sample 0:
time = 696598
flags = 1
data = length 439, hash 4760D35B
sample 1:
time = 719818
flags = 1
data = length 463, hash 1601F88D
sample 2:
time = 743038
flags = 1
data = length 423, hash D4AE6773
sample 3:
time = 766258
flags = 1
data = length 497, hash A3C674D3
sample 4:
time = 789478
flags = 1
data = length 419, hash D3734A1F
sample 5:
time = 812698
flags = 1
data = length 474, hash DFB41F9
sample 6:
time = 835918
flags = 1
data = length 413, hash 53E7CB9F
sample 7:
time = 859138
flags = 1
data = length 445, hash D15B0E39
sample 8:
time = 882358
flags = 1
data = length 453, hash 77ED81E4
sample 9:
time = 905578
flags = 1
data = length 545, hash 3321AEB9
sample 10:
time = 928798
flags = 1
data = length 317, hash F557D0E
sample 11:
time = 952018
flags = 1
data = length 537, hash ED58CF7B
sample 12:
time = 975238
flags = 1
data = length 458, hash 51CDAA10
sample 13:
time = 998458
flags = 1
data = length 465, hash CBA1EFD7
sample 14:
time = 1021678
flags = 1
data = length 446, hash D6735B8A
sample 15:
time = 1044897
flags = 1
data = length 10, hash A453EEBE
tracksEnded = true

View file

@ -0,0 +1,159 @@
seekMap:
isSeekable = true
duration = 1067733
getPosition(0) = [[timeUs=66733, position=1325]]
getPosition(1) = [[timeUs=66733, position=1325]]
getPosition(533866) = [[timeUs=66733, position=1325]]
getPosition(1067733) = [[timeUs=66733, position=1325]]
numberOfTracks = 2
track 0:
total output bytes = 85933
sample count = 30
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
width = 1080
height = 720
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample 0:
time = 66733
flags = 1
data = length 38070, hash B58E1AEE
sample 1:
time = 200200
flags = 0
data = length 8340, hash 8AC449FF
sample 2:
time = 133466
flags = 0
data = length 1295, hash C0DA5090
sample 3:
time = 100100
flags = 0
data = length 469, hash D6E0A200
sample 4:
time = 166833
flags = 0
data = length 564, hash E5F56C5B
sample 5:
time = 333666
flags = 0
data = length 6075, hash 8756E49E
sample 6:
time = 266933
flags = 0
data = length 847, hash DCC2B618
sample 7:
time = 233566
flags = 0
data = length 455, hash B9CCE047
sample 8:
time = 300300
flags = 0
data = length 467, hash 69806D94
sample 9:
time = 467133
flags = 0
data = length 4549, hash 3944F501
sample 10:
time = 400400
flags = 0
data = length 1087, hash 491BF106
sample 11:
time = 367033
flags = 0
data = length 380, hash 5FED016A
sample 12:
time = 433766
flags = 0
data = length 455, hash 8A0610
sample 13:
time = 600600
flags = 0
data = length 5190, hash B9031D8
sample 14:
time = 533866
flags = 0
data = length 1071, hash 684E7DC8
sample 15:
time = 500500
flags = 0
data = length 653, hash 8494F326
sample 16:
time = 567233
flags = 0
data = length 485, hash 2CCC85F4
sample 17:
time = 734066
flags = 0
data = length 4884, hash D16B6A96
sample 18:
time = 667333
flags = 0
data = length 997, hash 164FF210
sample 19:
time = 633966
flags = 0
data = length 640, hash F664125B
sample 20:
time = 700700
flags = 0
data = length 491, hash B5930C7C
sample 21:
time = 867533
flags = 0
data = length 2989, hash 92CF4FCF
sample 22:
time = 800800
flags = 0
data = length 838, hash 294A3451
sample 23:
time = 767433
flags = 0
data = length 544, hash FCCE2DE6
sample 24:
time = 834166
flags = 0
data = length 329, hash A654FFA1
sample 25:
time = 1001000
flags = 0
data = length 1517, hash 5F7EBF8B
sample 26:
time = 934266
flags = 0
data = length 803, hash 7A5C4C1D
sample 27:
time = 900900
flags = 0
data = length 415, hash B31BBC3B
sample 28:
time = 967633
flags = 0
data = length 415, hash 850DFEA3
sample 29:
time = 1034366
flags = 0
data = length 619, hash AB5E56CA
track 1:
total output bytes = 10
sample count = 1
format 0:
averageBitrate = 2147483647
peakBitrate = 2147483647
id = 2
sampleMimeType = audio/mp4a-latm
codecs = mp4a.40.2
channelCount = 1
sampleRate = 44100
language = und
initializationData:
data = length 5, hash 2B7623A
sample 0:
time = 1044897
flags = 1
data = length 10, hash A453EEBE
tracksEnded = true

View file

@ -0,0 +1,339 @@
seekMap:
isSeekable = true
duration = 1067733
getPosition(0) = [[timeUs=66733, position=1325]]
getPosition(1) = [[timeUs=66733, position=1325]]
getPosition(533866) = [[timeUs=66733, position=1325]]
getPosition(1067733) = [[timeUs=66733, position=1325]]
numberOfTracks = 2
track 0:
total output bytes = 85933
sample count = 30
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
width = 1080
height = 720
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample 0:
time = 66733
flags = 1
data = length 38070, hash B58E1AEE
sample 1:
time = 200200
flags = 0
data = length 8340, hash 8AC449FF
sample 2:
time = 133466
flags = 0
data = length 1295, hash C0DA5090
sample 3:
time = 100100
flags = 0
data = length 469, hash D6E0A200
sample 4:
time = 166833
flags = 0
data = length 564, hash E5F56C5B
sample 5:
time = 333666
flags = 0
data = length 6075, hash 8756E49E
sample 6:
time = 266933
flags = 0
data = length 847, hash DCC2B618
sample 7:
time = 233566
flags = 0
data = length 455, hash B9CCE047
sample 8:
time = 300300
flags = 0
data = length 467, hash 69806D94
sample 9:
time = 467133
flags = 0
data = length 4549, hash 3944F501
sample 10:
time = 400400
flags = 0
data = length 1087, hash 491BF106
sample 11:
time = 367033
flags = 0
data = length 380, hash 5FED016A
sample 12:
time = 433766
flags = 0
data = length 455, hash 8A0610
sample 13:
time = 600600
flags = 0
data = length 5190, hash B9031D8
sample 14:
time = 533866
flags = 0
data = length 1071, hash 684E7DC8
sample 15:
time = 500500
flags = 0
data = length 653, hash 8494F326
sample 16:
time = 567233
flags = 0
data = length 485, hash 2CCC85F4
sample 17:
time = 734066
flags = 0
data = length 4884, hash D16B6A96
sample 18:
time = 667333
flags = 0
data = length 997, hash 164FF210
sample 19:
time = 633966
flags = 0
data = length 640, hash F664125B
sample 20:
time = 700700
flags = 0
data = length 491, hash B5930C7C
sample 21:
time = 867533
flags = 0
data = length 2989, hash 92CF4FCF
sample 22:
time = 800800
flags = 0
data = length 838, hash 294A3451
sample 23:
time = 767433
flags = 0
data = length 544, hash FCCE2DE6
sample 24:
time = 834166
flags = 0
data = length 329, hash A654FFA1
sample 25:
time = 1001000
flags = 0
data = length 1517, hash 5F7EBF8B
sample 26:
time = 934266
flags = 0
data = length 803, hash 7A5C4C1D
sample 27:
time = 900900
flags = 0
data = length 415, hash B31BBC3B
sample 28:
time = 967633
flags = 0
data = length 415, hash 850DFEA3
sample 29:
time = 1034366
flags = 0
data = length 619, hash AB5E56CA
track 1:
total output bytes = 18257
sample count = 46
format 0:
averageBitrate = 2147483647
peakBitrate = 2147483647
id = 2
sampleMimeType = audio/mp4a-latm
codecs = mp4a.40.2
channelCount = 1
sampleRate = 44100
language = und
initializationData:
data = length 5, hash 2B7623A
sample 0:
time = 0
flags = 1
data = length 18, hash 96519432
sample 1:
time = 23219
flags = 1
data = length 4, hash EE9DF
sample 2:
time = 46439
flags = 1
data = length 4, hash EEDBF
sample 3:
time = 69659
flags = 1
data = length 157, hash E2F078F4
sample 4:
time = 92879
flags = 1
data = length 371, hash B9471F94
sample 5:
time = 116099
flags = 1
data = length 373, hash 2AB265CB
sample 6:
time = 139319
flags = 1
data = length 402, hash 1295477C
sample 7:
time = 162539
flags = 1
data = length 455, hash 2D8146C8
sample 8:
time = 185759
flags = 1
data = length 434, hash F2C5D287
sample 9:
time = 208979
flags = 1
data = length 450, hash 84143FCD
sample 10:
time = 232199
flags = 1
data = length 429, hash EF769D50
sample 11:
time = 255419
flags = 1
data = length 450, hash EC3DE692
sample 12:
time = 278639
flags = 1
data = length 447, hash 3E519E13
sample 13:
time = 301859
flags = 1
data = length 457, hash 1E4F23A0
sample 14:
time = 325079
flags = 1
data = length 447, hash A439EA97
sample 15:
time = 348299
flags = 1
data = length 456, hash 1E9034C6
sample 16:
time = 371519
flags = 1
data = length 398, hash 99DB7345
sample 17:
time = 394739
flags = 1
data = length 474, hash 3F05F10A
sample 18:
time = 417959
flags = 1
data = length 416, hash C105EE09
sample 19:
time = 441179
flags = 1
data = length 454, hash 5FDBE458
sample 20:
time = 464399
flags = 1
data = length 438, hash 41A93AC3
sample 21:
time = 487619
flags = 1
data = length 443, hash 10FDA652
sample 22:
time = 510839
flags = 1
data = length 412, hash 1F791E25
sample 23:
time = 534058
flags = 1
data = length 482, hash A6D983D
sample 24:
time = 557278
flags = 1
data = length 386, hash BED7392F
sample 25:
time = 580498
flags = 1
data = length 463, hash 5309F8C9
sample 26:
time = 603718
flags = 1
data = length 394, hash 21C7321F
sample 27:
time = 626938
flags = 1
data = length 489, hash 71B4730D
sample 28:
time = 650158
flags = 1
data = length 403, hash D9C6DE89
sample 29:
time = 673378
flags = 1
data = length 447, hash 9B14B73B
sample 30:
time = 696598
flags = 1
data = length 439, hash 4760D35B
sample 31:
time = 719818
flags = 1
data = length 463, hash 1601F88D
sample 32:
time = 743038
flags = 1
data = length 423, hash D4AE6773
sample 33:
time = 766258
flags = 1
data = length 497, hash A3C674D3
sample 34:
time = 789478
flags = 1
data = length 419, hash D3734A1F
sample 35:
time = 812698
flags = 1
data = length 474, hash DFB41F9
sample 36:
time = 835918
flags = 1
data = length 413, hash 53E7CB9F
sample 37:
time = 859138
flags = 1
data = length 445, hash D15B0E39
sample 38:
time = 882358
flags = 1
data = length 453, hash 77ED81E4
sample 39:
time = 905578
flags = 1
data = length 545, hash 3321AEB9
sample 40:
time = 928798
flags = 1
data = length 317, hash F557D0E
sample 41:
time = 952018
flags = 1
data = length 537, hash ED58CF7B
sample 42:
time = 975238
flags = 1
data = length 458, hash 51CDAA10
sample 43:
time = 998458
flags = 1
data = length 465, hash CBA1EFD7
sample 44:
time = 1021678
flags = 1
data = length 446, hash D6735B8A
sample 45:
time = 1044897
flags = 1
data = length 10, hash A453EEBE
tracksEnded = true

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Includes ContentProtection elements with additional ClearKey license URLs.
Covers all possible locations (in AdaptationSet and Representation) and possible orders of these
ContentProtection elements (CENC first or ClearKey first).
-->
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:DASH:schema:MPD:2011" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="static" availabilityStartTime="2016-10-14T17:00:17" xmlns:cenc="urn:mpeg:cenc:2013" xmlns:clearkey="http://dashif.org/guidelines/clearKey">
<Period start="PT0.000S" duration="PT0H5M50S">
<SegmentTemplate startNumber="0" timescale="1000" media="sq/$Number$">
<SegmentTimeline>
<S d="2002" t="6009" r="2"/>
</SegmentTimeline>
</SegmentTemplate>
<AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
<Representation id="140" codecs="mp4a.40.2" audioSamplingRate="48000" startWithSAP="1" bandwidth="144000">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="9eb4050d-e44b-4802-932e-27d75083e266" />
<ContentProtection value="ClearKey1.0" schemeIdUri="urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e">
<clearkey:Laurl Lic_type="EME-1.0">https://testserver1.test/AcquireLicense</clearkey:Laurl>
</ContentProtection>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" mimeType="video/mp4" subsegmentAlignment="true">
<ContentProtection value="ClearKey1.0" schemeIdUri="urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e">
<clearkey:Laurl Lic_type="EME-1.0">https://testserver2.test/AcquireLicense</clearkey:Laurl>
</ContentProtection>
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="9eb4050d-e44b-4802-932e-27d75083e266" />
<Representation id="133" codecs="avc1.4d4015" width="426" height="240" startWithSAP="1" bandwidth="258000" frameRate="30" />
</AdaptationSet>
</Period>
</MPD>

View file

@ -0,0 +1,82 @@
MediaCodecAdapter (exotest.audio.aac):
buffers.length = 47
buffers[0] = length 18, hash 96519432
buffers[1] = length 4, hash EE9DF
buffers[2] = length 4, hash EEDBF
buffers[3] = length 157, hash E2F078F4
buffers[4] = length 371, hash B9471F94
buffers[5] = length 373, hash 2AB265CB
buffers[6] = length 402, hash 1295477C
buffers[7] = length 455, hash 2D8146C8
buffers[8] = length 434, hash F2C5D287
buffers[9] = length 450, hash 84143FCD
buffers[10] = length 429, hash EF769D50
buffers[11] = length 450, hash EC3DE692
buffers[12] = length 447, hash 3E519E13
buffers[13] = length 457, hash 1E4F23A0
buffers[14] = length 447, hash A439EA97
buffers[15] = length 456, hash 1E9034C6
buffers[16] = length 398, hash 99DB7345
buffers[17] = length 474, hash 3F05F10A
buffers[18] = length 416, hash C105EE09
buffers[19] = length 454, hash 5FDBE458
buffers[20] = length 438, hash 41A93AC3
buffers[21] = length 443, hash 10FDA652
buffers[22] = length 412, hash 1F791E25
buffers[23] = length 482, hash A6D983D
buffers[24] = length 386, hash BED7392F
buffers[25] = length 463, hash 5309F8C9
buffers[26] = length 394, hash 21C7321F
buffers[27] = length 489, hash 71B4730D
buffers[28] = length 403, hash D9C6DE89
buffers[29] = length 447, hash 9B14B73B
buffers[30] = length 439, hash 4760D35B
buffers[31] = length 463, hash 1601F88D
buffers[32] = length 423, hash D4AE6773
buffers[33] = length 497, hash A3C674D3
buffers[34] = length 419, hash D3734A1F
buffers[35] = length 474, hash DFB41F9
buffers[36] = length 413, hash 53E7CB9F
buffers[37] = length 445, hash D15B0E39
buffers[38] = length 453, hash 77ED81E4
buffers[39] = length 545, hash 3321AEB9
buffers[40] = length 317, hash F557D0E
buffers[41] = length 537, hash ED58CF7B
buffers[42] = length 458, hash 51CDAA10
buffers[43] = length 465, hash CBA1EFD7
buffers[44] = length 446, hash D6735B8A
buffers[45] = length 10, hash A453EEBE
buffers[46] = length 0, hash 1
MediaCodecAdapter (exotest.video.avc):
buffers.length = 31
buffers[0] = length 38070, hash B58E1AEE
buffers[1] = length 8340, hash 8AC449FF
buffers[2] = length 1295, hash C0DA5090
buffers[3] = length 469, hash D6E0A200
buffers[4] = length 564, hash E5F56C5B
buffers[5] = length 6075, hash 8756E49E
buffers[6] = length 847, hash DCC2B618
buffers[7] = length 455, hash B9CCE047
buffers[8] = length 467, hash 69806D94
buffers[9] = length 4549, hash 3944F501
buffers[10] = length 1087, hash 491BF106
buffers[11] = length 380, hash 5FED016A
buffers[12] = length 455, hash 8A0610
buffers[13] = length 5190, hash B9031D8
buffers[14] = length 1071, hash 684E7DC8
buffers[15] = length 653, hash 8494F326
buffers[16] = length 485, hash 2CCC85F4
buffers[17] = length 4884, hash D16B6A96
buffers[18] = length 997, hash 164FF210
buffers[19] = length 640, hash F664125B
buffers[20] = length 491, hash B5930C7C
buffers[21] = length 2989, hash 92CF4FCF
buffers[22] = length 838, hash 294A3451
buffers[23] = length 544, hash FCCE2DE6
buffers[24] = length 329, hash A654FFA1
buffers[25] = length 1517, hash 5F7EBF8B
buffers[26] = length 803, hash 7A5C4C1D
buffers[27] = length 415, hash B31BBC3B
buffers[28] = length 415, hash 850DFEA3
buffers[29] = length 619, hash AB5E56CA
buffers[30] = length 0, hash 1

View file

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.testutil;
import static com.google.android.exoplayer2.testutil.TestUtil.timelinesAreSame;
import android.os.Looper;
import android.view.Surface;
import androidx.annotation.Nullable;
@ -763,7 +765,7 @@ public abstract class Action {
@Nullable Timeline expectedTimeline,
@Player.TimelineChangeReason int expectedReason) {
super(tag, "WaitForTimelineChanged");
this.expectedTimeline = expectedTimeline != null ? new NoUidTimeline(expectedTimeline) : null;
this.expectedTimeline = expectedTimeline;
this.ignoreExpectedReason = false;
this.expectedReason = expectedReason;
}
@ -795,7 +797,7 @@ public abstract class Action {
@Override
public void onTimelineChanged(
Timeline timeline, @Player.TimelineChangeReason int reason) {
if ((expectedTimeline == null || new NoUidTimeline(timeline).equals(expectedTimeline))
if ((expectedTimeline == null || timelinesAreSame(timeline, expectedTimeline))
&& (ignoreExpectedReason || expectedReason == reason)) {
player.removeListener(this);
nextAction.schedule(player, trackSelector, surface, handler);
@ -803,8 +805,8 @@ public abstract class Action {
}
};
player.addListener(listener);
Timeline currentTimeline = new NoUidTimeline(player.getCurrentTimeline());
if (currentTimeline.equals(expectedTimeline)) {
if (expectedTimeline != null
&& timelinesAreSame(player.getCurrentTimeline(), expectedTimeline)) {
player.removeListener(listener);
nextAction.schedule(player, trackSelector, surface, handler);
}

View file

@ -42,6 +42,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -534,11 +535,8 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul
* @param timelines A list of expected {@link Timeline}s.
*/
public void assertTimelinesSame(Timeline... timelines) {
assertThat(this.timelines).hasSize(timelines.length);
for (int i = 0; i < timelines.length; i++) {
assertThat(new NoUidTimeline(timelines[i]))
.isEqualTo(new NoUidTimeline(this.timelines.get(i)));
}
TestUtil.assertTimelinesSame(
ImmutableList.copyOf(this.timelines), ImmutableList.copyOf(timelines));
}
/**

View file

@ -25,6 +25,7 @@ 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.source.ShuffleOrder;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
@ -273,7 +274,7 @@ public final class FakeTimeline extends Timeline {
private final TimelineWindowDefinition[] windowDefinitions;
private final Object[] manifests;
private final int[] periodOffsets;
private final FakeShuffleOrder fakeShuffleOrder;
private final ShuffleOrder shuffleOrder;
/**
* Returns an ad playback state with the specified number of ads in each of the specified ad
@ -393,6 +394,19 @@ public final class FakeTimeline extends Timeline {
* @param windowDefinitions A list of {@link TimelineWindowDefinition}s.
*/
public FakeTimeline(Object[] manifests, TimelineWindowDefinition... windowDefinitions) {
this(manifests, new FakeShuffleOrder(windowDefinitions.length), windowDefinitions);
}
/**
* Creates a fake timeline with the given window definitions and {@link
* com.google.android.exoplayer2.source.ShuffleOrder}.
*
* @param windowDefinitions A list of {@link TimelineWindowDefinition}s.
*/
public FakeTimeline(
Object[] manifests,
ShuffleOrder shuffleOrder,
TimelineWindowDefinition... windowDefinitions) {
this.manifests = new Object[windowDefinitions.length];
System.arraycopy(manifests, 0, this.manifests, 0, min(this.manifests.length, manifests.length));
this.windowDefinitions = windowDefinitions;
@ -401,7 +415,7 @@ public final class FakeTimeline extends Timeline {
for (int i = 0; i < windowDefinitions.length; i++) {
periodOffsets[i + 1] = periodOffsets[i] + windowDefinitions[i].periodCount;
}
fakeShuffleOrder = new FakeShuffleOrder(windowDefinitions.length);
this.shuffleOrder = shuffleOrder;
}
@Override
@ -420,7 +434,7 @@ public final class FakeTimeline extends Timeline {
? getFirstWindowIndex(shuffleModeEnabled)
: C.INDEX_UNSET;
}
return shuffleModeEnabled ? fakeShuffleOrder.getNextIndex(windowIndex) : windowIndex + 1;
return shuffleModeEnabled ? shuffleOrder.getNextIndex(windowIndex) : windowIndex + 1;
}
@Override
@ -434,20 +448,20 @@ public final class FakeTimeline extends Timeline {
? getLastWindowIndex(shuffleModeEnabled)
: C.INDEX_UNSET;
}
return shuffleModeEnabled ? fakeShuffleOrder.getPreviousIndex(windowIndex) : windowIndex - 1;
return shuffleModeEnabled ? shuffleOrder.getPreviousIndex(windowIndex) : windowIndex - 1;
}
@Override
public int getLastWindowIndex(boolean shuffleModeEnabled) {
return shuffleModeEnabled
? fakeShuffleOrder.getLastIndex()
? shuffleOrder.getLastIndex()
: super.getLastWindowIndex(/* shuffleModeEnabled= */ false);
}
@Override
public int getFirstWindowIndex(boolean shuffleModeEnabled) {
return shuffleModeEnabled
? fakeShuffleOrder.getFirstIndex()
? shuffleOrder.getFirstIndex()
: super.getFirstWindowIndex(/* shuffleModeEnabled= */ false);
}

View file

@ -1,49 +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.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ForwardingTimeline;
/**
* A timeline which wraps another timeline and overrides all window and period uids to 0. This is
* useful for testing timeline equality without taking uids into account.
*/
public class NoUidTimeline extends ForwardingTimeline {
/**
* Creates an instance.
*
* @param timeline The underlying timeline.
*/
public NoUidTimeline(Timeline timeline) {
super(timeline);
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
timeline.getWindow(windowIndex, window, defaultPositionProjectionUs);
window.uid = 0;
return window;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
timeline.getPeriod(periodIndex, period, setIds);
period.uid = 0;
return period;
}
}

View file

@ -68,6 +68,7 @@ public class StubPlayer extends BasePlayer {
}
@Override
@Nullable
public PlaybackException getPlayerError() {
throw new UnsupportedOperationException();
}

View file

@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Bytes;
import com.google.common.truth.Correspondence;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -197,19 +198,33 @@ public class TestUtil {
/**
* Asserts that the actual timelines are the same to the expected timelines. This assert differs
* from testing equality by not comparing period ids which may be different due to id mapping of
* child source period ids.
* from testing equality by not comparing:
*
* <ul>
* <li>Period IDs, which may be different due to ID mapping of child source period IDs.
* <li>Shuffle order, which by default is random and non-deterministic.
* </ul>
*
* @param actualTimelines A list of actual {@link Timeline timelines}.
* @param expectedTimelines A list of expected {@link Timeline timelines}.
*/
public static void assertTimelinesSame(
List<Timeline> actualTimelines, List<Timeline> expectedTimelines) {
assertThat(actualTimelines).hasSize(expectedTimelines.size());
for (int i = 0; i < actualTimelines.size(); i++) {
assertThat(new NoUidTimeline(actualTimelines.get(i)))
.isEqualTo(new NoUidTimeline(expectedTimelines.get(i)));
}
assertThat(actualTimelines)
.comparingElementsUsing(
Correspondence.from(
TestUtil::timelinesAreSame, "is equal to (ignoring Window.uid and Period.uid)"))
.containsExactlyElementsIn(expectedTimelines)
.inOrder();
}
/**
* Returns true if {@code thisTimeline} is equal to {@code thatTimeline}, ignoring {@link
* Timeline.Window#uid} and {@link Timeline.Period#uid} values, and shuffle order.
*/
public static boolean timelinesAreSame(Timeline thisTimeline, Timeline thatTimeline) {
return new NoUidOrShufflingTimeline(thisTimeline)
.equals(new NoUidOrShufflingTimeline(thatTimeline));
}
/**
@ -492,4 +507,68 @@ public class TestUtil {
return list;
}
private static final class NoUidOrShufflingTimeline extends Timeline {
private final Timeline delegate;
public NoUidOrShufflingTimeline(Timeline timeline) {
this.delegate = timeline;
}
@Override
public int getWindowCount() {
return delegate.getWindowCount();
}
@Override
public int getNextWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) {
return delegate.getNextWindowIndex(windowIndex, repeatMode, /* shuffleModeEnabled= */ false);
}
@Override
public int getPreviousWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) {
return delegate.getPreviousWindowIndex(
windowIndex, repeatMode, /* shuffleModeEnabled= */ false);
}
@Override
public int getLastWindowIndex(boolean shuffleModeEnabled) {
return delegate.getLastWindowIndex(/* shuffleModeEnabled= */ false);
}
@Override
public int getFirstWindowIndex(boolean shuffleModeEnabled) {
return delegate.getFirstWindowIndex(/* shuffleModeEnabled= */ false);
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
delegate.getWindow(windowIndex, window, defaultPositionProjectionUs);
window.uid = 0;
return window;
}
@Override
public int getPeriodCount() {
return delegate.getPeriodCount();
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
delegate.getPeriod(periodIndex, period, setIds);
period.uid = 0;
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
return delegate.getIndexOfPeriod(uid);
}
@Override
public Object getUidOfPeriod(int periodIndex) {
return 0;
}
}
}