Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c4b346e4
authored
Oct 14, 2020
by
christosts
Committed by
Oliver Woodman
Oct 17, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Integrate playback speed control in ExoPlayerImplInternal
Issue: #4904 PiperOrigin-RevId: 337048010
parent
f00584b0
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
546 additions
and
10 deletions
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
View file @
c4b346e4
...
...
@@ -865,6 +865,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodHolder
loadingPeriod
=
queue
.
getLoadingPeriod
();
playbackInfo
.
bufferedPositionUs
=
loadingPeriod
.
getBufferedPositionUs
();
playbackInfo
.
totalBufferedDurationUs
=
getTotalBufferedDurationUs
();
// Adjust live playback speed to new position.
if
(
playbackInfo
.
playWhenReady
&&
isCurrentPeriodInMovingLiveWindow
()
&&
playbackInfo
.
playbackParameters
.
speed
==
1
f
)
{
float
adjustedSpeed
=
livePlaybackSpeedControl
.
adjustPlaybackSpeed
(
getCurrentLiveOffsetUs
());
if
(
mediaClock
.
getPlaybackParameters
().
speed
!=
adjustedSpeed
)
{
mediaClock
.
setPlaybackParameters
(
playbackInfo
.
playbackParameters
.
withSpeed
(
adjustedSpeed
));
}
}
}
private
void
doSomeWork
()
throws
ExoPlaybackException
,
IOException
{
...
...
@@ -992,6 +1002,34 @@ import java.util.concurrent.atomic.AtomicBoolean;
TraceUtil
.
endSection
();
}
private
long
getCurrentLiveOffsetUs
()
{
return
getLiveOffsetUs
(
playbackInfo
.
timeline
,
playbackInfo
.
periodId
.
periodUid
,
playbackInfo
.
positionUs
);
}
private
long
getLiveOffsetUs
(
Timeline
timeline
,
Object
periodUid
,
long
periodPositionUs
)
{
int
windowIndex
=
timeline
.
getPeriodByUid
(
periodUid
,
period
).
windowIndex
;
timeline
.
getWindow
(
windowIndex
,
window
);
if
(
window
.
windowStartTimeMs
==
C
.
TIME_UNSET
||
!
window
.
isLive
||
!
window
.
isDynamic
)
{
return
C
.
TIME_UNSET
;
}
return
C
.
msToUs
(
window
.
getCurrentUnixTimeMs
()
-
window
.
windowStartTimeMs
)
-
(
periodPositionUs
+
period
.
getPositionInWindowUs
());
}
private
boolean
isCurrentPeriodInMovingLiveWindow
()
{
return
isInMovingLiveWindow
(
playbackInfo
.
timeline
,
playbackInfo
.
periodId
);
}
private
boolean
isInMovingLiveWindow
(
Timeline
timeline
,
MediaPeriodId
mediaPeriodId
)
{
if
(
mediaPeriodId
.
isAd
()
||
timeline
.
isEmpty
())
{
return
false
;
}
int
windowIndex
=
timeline
.
getPeriodByUid
(
mediaPeriodId
.
periodUid
,
period
).
windowIndex
;
timeline
.
getWindow
(
windowIndex
,
window
);
return
window
.
isLive
&&
window
.
isDynamic
;
}
private
void
scheduleNextWork
(
long
thisOperationStartTimeMs
,
long
intervalMs
)
{
handler
.
removeMessages
(
MSG_DO_SOME_WORK
);
handler
.
sendEmptyMessageAtTime
(
MSG_DO_SOME_WORK
,
thisOperationStartTimeMs
+
intervalMs
);
...
...
@@ -1095,6 +1133,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* forceBufferingState= */
playbackInfo
.
playbackState
==
Player
.
STATE_ENDED
);
seekPositionAdjusted
|=
periodPositionUs
!=
newPeriodPositionUs
;
periodPositionUs
=
newPeriodPositionUs
;
updateLivePlaybackSpeedControl
(
/* newTimeline= */
playbackInfo
.
timeline
,
/* newPeriodId= */
periodId
,
/* oldTimeline= */
playbackInfo
.
timeline
,
/* oldPeriodId= */
playbackInfo
.
periodId
,
/* positionForTargetOffsetOverrideUs= */
requestedContentPosition
);
}
}
finally
{
playbackInfo
=
...
...
@@ -1646,14 +1690,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
return
true
;
}
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
MediaPeriodHolder
loadingHolder
=
queue
.
getLoadingPeriod
();
int
windowIndex
=
playbackInfo
.
timeline
.
getPeriodByUid
(
queue
.
getPlayingPeriod
().
uid
,
period
).
windowIndex
;
playbackInfo
.
timeline
.
getWindow
(
windowIndex
,
window
);
long
targetLiveOffsetUs
=
window
.
isLive
&&
window
.
isDynamic
isInMovingLiveWindow
(
playbackInfo
.
timeline
,
queue
.
getPlayingPeriod
().
info
.
id
)
?
livePlaybackSpeedControl
.
getTargetLiveOffsetUs
()
:
C
.
TIME_UNSET
;
MediaPeriodHolder
loadingHolder
=
queue
.
getLoadingPeriod
();
boolean
bufferedToEnd
=
loadingHolder
.
isFullyBuffered
()
&&
loadingHolder
.
info
.
isFinal
;
return
bufferedToEnd
||
loadControl
.
shouldStartPlayback
(
...
...
@@ -1720,6 +1761,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
newPositionUs
=
seekToPeriodPosition
(
newPeriodId
,
newPositionUs
,
forceBufferingState
);
}
}
finally
{
updateLivePlaybackSpeedControl
(
/* newTimeline= */
timeline
,
newPeriodId
,
/* oldTimeline= */
playbackInfo
.
timeline
,
/* oldPeriodId= */
playbackInfo
.
periodId
,
/* positionForTargetOffsetOverrideUs */
positionUpdate
.
setTargetLiveOffset
?
newPositionUs
:
C
.
TIME_UNSET
);
if
(
periodPositionChanged
||
newRequestedContentPositionUs
!=
playbackInfo
.
requestedContentPositionUs
)
{
playbackInfo
=
...
...
@@ -1737,6 +1786,36 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
private
void
updateLivePlaybackSpeedControl
(
Timeline
newTimeline
,
MediaPeriodId
newPeriodId
,
Timeline
oldTimeline
,
MediaPeriodId
oldPeriodId
,
long
positionForTargetOffsetOverrideUs
)
{
if
(
newTimeline
.
isEmpty
()
||
!
isInMovingLiveWindow
(
newTimeline
,
newPeriodId
))
{
// Live playback speed control is unused.
return
;
}
int
windowIndex
=
newTimeline
.
getPeriodByUid
(
newPeriodId
.
periodUid
,
period
).
windowIndex
;
newTimeline
.
getWindow
(
windowIndex
,
window
);
livePlaybackSpeedControl
.
updateLiveConfiguration
(
window
.
mediaItem
.
liveConfiguration
);
if
(
positionForTargetOffsetOverrideUs
!=
C
.
TIME_UNSET
)
{
livePlaybackSpeedControl
.
overrideTargetLiveOffsetUs
(
getLiveOffsetUs
(
newTimeline
,
newPeriodId
.
periodUid
,
positionForTargetOffsetOverrideUs
));
}
else
{
Object
windowUid
=
window
.
uid
;
@Nullable
Object
oldWindowUid
=
null
;
if
(!
oldTimeline
.
isEmpty
())
{
int
oldWindowIndex
=
oldTimeline
.
getPeriodByUid
(
oldPeriodId
.
periodUid
,
period
).
windowIndex
;
oldWindowUid
=
oldTimeline
.
getWindow
(
oldWindowIndex
,
window
).
uid
;
}
if
(!
Util
.
areEqual
(
oldWindowUid
,
windowUid
))
{
// Reset overridden target live offset to media values if window changes.
livePlaybackSpeedControl
.
overrideTargetLiveOffsetUs
(
C
.
TIME_UNSET
);
}
}
}
private
long
getMaxRendererReadPositionUs
()
{
MediaPeriodHolder
readingHolder
=
queue
.
getReadingPeriod
();
if
(
readingHolder
==
null
)
{
...
...
@@ -1936,6 +2015,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
?
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
:
Player
.
DISCONTINUITY_REASON_AD_INSERTION
;
playbackInfoUpdate
.
setPositionDiscontinuity
(
discontinuityReason
);
updateLivePlaybackSpeedControl
(
/* newTimeline= */
playbackInfo
.
timeline
,
/* newPeriodId= */
newPlayingPeriodHolder
.
info
.
id
,
/* oldTimeline= */
playbackInfo
.
timeline
,
/* oldPeriodId= */
oldPlayingPeriodHolder
.
info
.
id
,
/* positionForTargetOffsetOverrideUs= */
C
.
TIME_UNSET
);
resetPendingPauseAtEndOfPeriod
();
updatePlaybackPositions
();
advancedPlayingPeriod
=
true
;
...
...
@@ -2281,7 +2366,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* periodPositionUs= */
0
,
/* requestedContentPositionUs= */
C
.
TIME_UNSET
,
/* forceBufferingState= */
false
,
/* endPlayback= */
true
);
/* endPlayback= */
true
,
/* setTargetLiveOffset= */
false
);
}
MediaPeriodId
oldPeriodId
=
playbackInfo
.
periodId
;
Object
newPeriodUid
=
oldPeriodId
.
periodUid
;
...
...
@@ -2295,6 +2381,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
int
startAtDefaultPositionWindowIndex
=
C
.
INDEX_UNSET
;
boolean
forceBufferingState
=
false
;
boolean
endPlayback
=
false
;
boolean
setTargetLiveOffset
=
false
;
if
(
pendingInitialSeekPosition
!=
null
)
{
// Resolve initial seek position.
@Nullable
...
...
@@ -2319,6 +2406,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
else
{
newPeriodUid
=
periodPosition
.
first
;
newContentPositionUs
=
periodPosition
.
second
;
// Use explicit initial seek as new target live offset.
setTargetLiveOffset
=
true
;
}
forceBufferingState
=
playbackInfo
.
playbackState
==
Player
.
STATE_ENDED
;
}
...
...
@@ -2362,6 +2451,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
timeline
.
getPeriodPosition
(
window
,
period
,
windowIndex
,
windowPositionUs
);
newPeriodUid
=
periodPosition
.
first
;
newContentPositionUs
=
periodPosition
.
second
;
// Use an explicitly requested content position as new target live offset.
setTargetLiveOffset
=
true
;
}
}
...
...
@@ -2410,7 +2501,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
return
new
PositionUpdateForPlaylistChange
(
newPeriodId
,
periodPositionUs
,
newContentPositionUs
,
forceBufferingState
,
endPlayback
);
newPeriodId
,
periodPositionUs
,
newContentPositionUs
,
forceBufferingState
,
endPlayback
,
setTargetLiveOffset
);
}
private
static
boolean
shouldUseRequestedContentPosition
(
...
...
@@ -2673,18 +2769,21 @@ import java.util.concurrent.atomic.AtomicBoolean;
public
final
long
requestedContentPositionUs
;
public
final
boolean
forceBufferingState
;
public
final
boolean
endPlayback
;
public
final
boolean
setTargetLiveOffset
;
public
PositionUpdateForPlaylistChange
(
MediaPeriodId
periodId
,
long
periodPositionUs
,
long
requestedContentPositionUs
,
boolean
forceBufferingState
,
boolean
endPlayback
)
{
boolean
endPlayback
,
boolean
setTargetLiveOffset
)
{
this
.
periodId
=
periodId
;
this
.
periodPositionUs
=
periodPositionUs
;
this
.
requestedContentPositionUs
=
requestedContentPositionUs
;
this
.
forceBufferingState
=
forceBufferingState
;
this
.
endPlayback
=
endPlayback
;
this
.
setTargetLiveOffset
=
setTargetLiveOffset
;
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
View file @
c4b346e4
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
;
import
androidx.annotation.CheckResult
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
...
...
@@ -70,6 +71,17 @@ public final class PlaybackParameters {
return
timeMs
*
scaledUsPerMs
;
}
/**
* Returns a copy with the given speed.
*
* @param speed The new speed.
* @return The copied playback parameters.
*/
@CheckResult
public
PlaybackParameters
withSpeed
(
float
speed
)
{
return
new
PlaybackParameters
(
speed
,
pitch
);
}
@Override
public
boolean
equals
(
@Nullable
Object
obj
)
{
if
(
this
==
obj
)
{
...
...
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
View file @
c4b346e4
...
...
@@ -111,6 +111,7 @@ import com.google.android.exoplayer2.util.Clock;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.common.collect.ImmutableList
;
import
com.google.common.collect.Lists
;
import
com.google.common.collect.Range
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
...
...
@@ -8412,6 +8413,428 @@ public final class ExoPlayerTest {
.
onStaticMetadataChanged
(
ImmutableList
.
of
(
videoFormat
.
metadata
,
audioFormat
.
metadata
));
}
@Test
public
void
targetLiveOffsetInMedia_adjustsLiveOffsetToTargetOffset
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
timeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
Player
.
EventListener
mockListener
=
mock
(
Player
.
EventListener
.
class
);
player
.
addListener
(
mockListener
);
player
.
pause
();
player
.
setMediaSource
(
new
FakeMediaSource
(
timeline
));
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
long
liveOffsetAtStart
=
player
.
getCurrentLiveOffset
();
// Verify test setup (now = 20 seconds in live window, default start position = 8 seconds).
assertThat
(
liveOffsetAtStart
).
isIn
(
Range
.
closed
(
11_900L
,
12_100L
));
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert that player adjusted live offset to the media value.
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
8_900L
,
9_100L
));
// Assert that none of these playback speed changes were reported.
verify
(
mockListener
,
never
()).
onPlaybackParametersChanged
(
any
());
}
@Test
public
void
targetLiveOffsetInMedia_withInitialSeek_adjustsLiveOffsetToInitialSeek
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
timeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
player
.
pause
();
player
.
seekTo
(
18_000
);
player
.
setMediaSource
(
new
FakeMediaSource
(
timeline
),
/* resetPosition= */
false
);
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
long
liveOffsetAtStart
=
player
.
getCurrentLiveOffset
();
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Target should have been permanently adjusted to 2 seconds.
// (initial now = 20 seconds in live window, initial seek to 18 seconds)
assertThat
(
liveOffsetAtStart
).
isIn
(
Range
.
closed
(
1_900L
,
2_100L
));
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
1_900L
,
2_100L
));
}
@Test
public
void
targetLiveOffsetInMedia_withUserSeek_adjustsLiveOffsetToSeek
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
timeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
player
.
pause
();
player
.
setMediaSource
(
new
FakeMediaSource
(
timeline
));
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
long
liveOffsetAtStart
=
player
.
getCurrentLiveOffset
();
// Verify test setup (now = 20 seconds in live window, default start position = 8 seconds).
assertThat
(
liveOffsetAtStart
).
isIn
(
Range
.
closed
(
11_900L
,
12_100L
));
// Seek to a live offset of 2 seconds.
player
.
seekTo
(
18_000
);
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert the live offset adjustment was permanent.
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
1_900L
,
2_100L
));
}
@Test
public
void
targetLiveOffsetInMedia_withTimelineUpdate_adjustsLiveOffsetToLatestTimeline
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
initialTimeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
Timeline
updatedTimeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
+
50_000
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
4_000
).
build
()));
FakeMediaSource
fakeMediaSource
=
new
FakeMediaSource
(
initialTimeline
);
player
.
pause
();
player
.
setMediaSource
(
fakeMediaSource
);
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
long
liveOffsetAtStart
=
player
.
getCurrentLiveOffset
();
// Verify test setup (now = 20 seconds in live window, default start position = 8 seconds).
assertThat
(
liveOffsetAtStart
).
isIn
(
Range
.
closed
(
11_900L
,
12_100L
));
// Play a bit and update configuration.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
55_000
);
fakeMediaSource
.
setNewSourceInfo
(
updatedTimeline
);
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert that adjustment uses target offset from the updated timeline.
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
3_900L
,
4_100L
));
}
@Test
public
void
targetLiveOffsetInMedia_withSetPlaybackParameters_usesPlaybackParameterSpeed
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
timeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
20
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
Player
.
EventListener
mockListener
=
mock
(
Player
.
EventListener
.
class
);
player
.
addListener
(
mockListener
);
player
.
pause
();
player
.
setMediaSource
(
new
FakeMediaSource
(
timeline
));
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
long
liveOffsetAtStart
=
player
.
getCurrentLiveOffset
();
// Verify test setup (now = 20 seconds in live window, default start position = 20 seconds).
assertThat
(
liveOffsetAtStart
).
isIn
(
Range
.
closed
(-
100L
,
100L
));
player
.
setPlaybackParameters
(
new
PlaybackParameters
(
/* speed */
2.0f
));
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert that the player didn't adjust the live offset to the media value (9 seconds) and
// instead played the media with double speed (resulting in a negative live offset).
assertThat
(
liveOffsetAtEnd
).
isLessThan
(
0
);
// Assert that user-set speed was reported
verify
(
mockListener
).
onPlaybackParametersChanged
(
new
PlaybackParameters
(
2.0f
));
}
@Test
public
void
targetLiveOffsetInMedia_afterAutomaticPeriodTransition_adjustsLiveOffsetToTargetOffset
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
10_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
liveTimeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
player
.
pause
();
player
.
addMediaSource
(
new
FakeMediaSource
(
new
FakeTimeline
(
/* windowCount= */
1
)));
player
.
addMediaSource
(
new
FakeMediaSource
(
liveTimeline
));
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
1
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert that player adjusted live offset to the media value.
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
8_900L
,
9_100L
));
}
@Test
public
void
targetLiveOffsetInMedia_afterSeekToDefaultPositionInOtherStream_adjustsLiveOffsetToMediaOffset
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
liveTimeline1
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
Timeline
liveTimeline2
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
4_000
).
build
()));
player
.
pause
();
player
.
addMediaSource
(
new
FakeMediaSource
(
liveTimeline1
));
player
.
addMediaSource
(
new
FakeMediaSource
(
liveTimeline2
));
// Ensure we override the target live offset to a seek position in the first live stream.
player
.
seekTo
(
10_000
);
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
// Seek to default position in second stream.
player
.
next
();
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
1
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert that player adjusted live offset to the media value.
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
3_900L
,
4_100L
));
}
@Test
public
void
targetLiveOffsetInMedia_afterSeekToSpecificPositionInOtherStream_adjustsLiveOffsetToSeekPosition
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
liveTimeline1
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
9_000
).
build
()));
Timeline
liveTimeline2
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
4_000
).
build
()));
player
.
pause
();
player
.
addMediaSource
(
new
FakeMediaSource
(
liveTimeline1
));
player
.
addMediaSource
(
new
FakeMediaSource
(
liveTimeline2
));
// Ensure we override the target live offset to a seek position in the first live stream.
player
.
seekTo
(
10_000
);
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
// Seek to specific position in second stream (at 2 seconds live offset).
player
.
seekTo
(
/* windowIndex= */
1
,
/* positionMs= */
18_000
);
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
1
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert that player adjusted live offset to the seek.
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
1_900L
,
2_100L
));
}
@Test
public
void
noTargetLiveOffsetInMedia_doesNotAdjustLiveOffset
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
)
.
setClock
(
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
nowUnixTimeMs
))
.
build
();
Timeline
liveTimelineWithoutTargetLiveOffset
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
1
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* isLive= */
true
,
/* isPlaceholder= */
false
,
/* durationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* defaultPositionUs= */
8
*
C
.
MICROS_PER_SECOND
,
/* windowOffsetInFirstPeriodUs= */
C
.
msToUs
(
windowStartUnixTimeMs
),
AdPlaybackState
.
NONE
,
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
build
()));
player
.
pause
();
player
.
setMediaSource
(
new
FakeMediaSource
(
liveTimelineWithoutTargetLiveOffset
));
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
long
liveOffsetAtStart
=
player
.
getCurrentLiveOffset
();
// Verify test setup (now = 20 seconds in live window, default start position = 8 seconds).
assertThat
(
liveOffsetAtStart
).
isIn
(
Range
.
closed
(
11_900L
,
12_100L
));
// Play until close to the end of the available live window.
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
999_000
);
long
liveOffsetAtEnd
=
player
.
getCurrentLiveOffset
();
player
.
release
();
// Assert that live offset is still the same (i.e. unadjusted).
assertThat
(
liveOffsetAtEnd
).
isIn
(
Range
.
closed
(
11_900L
,
12_100L
));
}
// Internal methods.
private
static
ActionSchedule
.
Builder
addSurfaceSwitch
(
ActionSchedule
.
Builder
builder
)
{
...
...
testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java
View file @
c4b346e4
...
...
@@ -314,8 +314,10 @@ public final class FakeTimeline extends Timeline {
windowDefinition
.
mediaItem
,
manifests
[
windowIndex
],
/* presentationStartTimeMs= */
C
.
TIME_UNSET
,
/* windowStartTimeMs= */
C
.
TIME_UNSET
,
/* elapsedRealtimeEpochOffsetMs= */
C
.
TIME_UNSET
,
/* windowStartTimeMs= */
windowDefinition
.
isLive
?
C
.
usToMs
(
windowDefinition
.
windowOffsetInFirstPeriodUs
)
:
C
.
TIME_UNSET
,
/* elapsedRealtimeEpochOffsetMs= */
windowDefinition
.
isLive
?
0
:
C
.
TIME_UNSET
,
windowDefinition
.
isSeekable
,
windowDefinition
.
isDynamic
,
windowDefinition
.
isLive
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment