mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Move playback state, isLoading, and track selector result to PlaybackInfo.
This is a no-op change replacing the local variables in ExoPlayerImplInternal with the new ones in PlaybackInfo. *** Use playbackState, isLoading and trackSelectorResult from playbackInfo in ExoPlayerImpl. *** Move duplicated listener notification in ExoPlayerImpl to new method. Also split reset method in one parts which creates the new playback info and one part which notifies the listeners. The increment of the pending operation counter needs to happen in between. *** Use only one pending operation counter in ExoPlayerImpl. This also allows to move onSeekProcessed into the notification chain. *** Replace playback info changing messages to ExoPlayerImpl by single message type. As they are all handled in the same way, they can be summarized to one message. *** Only send playback info change notifications once per playback thread message. This ensures that all concurrent changes actually reach ExoPlayerImpl concurrently. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=178907165
This commit is contained in:
parent
a5cd0b87bc
commit
2cbf0ef0ab
5 changed files with 460 additions and 286 deletions
|
|
@ -56,18 +56,15 @@ public final class ExoPlayerTest extends TestCase {
|
|||
* error.
|
||||
*/
|
||||
public void testPlayEmptyTimeline() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 0);
|
||||
Timeline timeline = Timeline.EMPTY;
|
||||
FakeRenderer renderer = new FakeRenderer();
|
||||
// TODO(b/69665207): Without waiting for the timeline update, this test is flaky as the timeline
|
||||
// update happens after the transition to STATE_ENDED and the test runner may already have been
|
||||
// stopped. Remove action schedule as soon as state changes are part of the masking and the
|
||||
// correct order of events is restored.
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testPlayEmptyTimeline")
|
||||
.waitForTimelineChanged(timeline)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline).setRenderers(renderer).setActionSchedule(actionSchedule)
|
||||
.build().start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
|
||||
ExoPlayerTestRunner testRunner =
|
||||
new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setRenderers(renderer)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertNoPositionDiscontinuities();
|
||||
testRunner.assertTimelinesEqual(timeline);
|
||||
assertEquals(0, renderer.formatReadCount);
|
||||
|
|
@ -307,21 +304,28 @@ public final class ExoPlayerTest extends TestCase {
|
|||
|
||||
public void testSeekProcessedCallback() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testSeekProcessedCallback")
|
||||
// Initial seek before timeline preparation started. Expect immediate seek processed while
|
||||
// the player is still in STATE_IDLE.
|
||||
.pause().seek(5)
|
||||
// Wait until the media source starts preparing and issue more initial seeks. Expect only
|
||||
// one seek processed after the source has been prepared.
|
||||
.waitForPlaybackState(Player.STATE_BUFFERING).seek(2).seek(10)
|
||||
// Wait until media source prepared and re-seek to same position. Expect a seek processed
|
||||
// while still being in STATE_READY.
|
||||
.waitForPlaybackState(Player.STATE_READY).seek(10)
|
||||
// Start playback and wait until playback reaches second window.
|
||||
.play().waitForPositionDiscontinuity()
|
||||
// Seek twice in concession, expecting the first seek to be replaced (and thus except only
|
||||
// on seek processed callback).
|
||||
.seek(5).seek(60).build();
|
||||
ActionSchedule actionSchedule =
|
||||
new ActionSchedule.Builder("testSeekProcessedCallback")
|
||||
// Initial seek. Expect immediate seek processed.
|
||||
.pause()
|
||||
.seek(5)
|
||||
.waitForSeekProcessed()
|
||||
// Multiple overlapping seeks while the player is still preparing. Expect only one seek
|
||||
// processed.
|
||||
.seek(2)
|
||||
.seek(10)
|
||||
// Wait until media source prepared and re-seek to same position. Expect a seek
|
||||
// processed while still being in STATE_READY.
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.seek(10)
|
||||
// Start playback and wait until playback reaches second window.
|
||||
.play()
|
||||
.waitForPositionDiscontinuity()
|
||||
// Seek twice in concession, expecting the first seek to be replaced (and thus except
|
||||
// only on seek processed callback).
|
||||
.seek(5)
|
||||
.seek(60)
|
||||
.build();
|
||||
final List<Integer> playbackStatesWhenSeekProcessed = new ArrayList<>();
|
||||
Player.EventListener eventListener = new Player.DefaultEventListener() {
|
||||
private int currentPlaybackState = Player.STATE_IDLE;
|
||||
|
|
@ -340,7 +344,7 @@ public final class ExoPlayerTest extends TestCase {
|
|||
.setTimeline(timeline).setEventListener(eventListener).setActionSchedule(actionSchedule)
|
||||
.build().start().blockUntilEnded(TIMEOUT_MS);
|
||||
assertEquals(4, playbackStatesWhenSeekProcessed.size());
|
||||
assertEquals(Player.STATE_IDLE, (int) playbackStatesWhenSeekProcessed.get(0));
|
||||
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(0));
|
||||
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(1));
|
||||
assertEquals(Player.STATE_READY, (int) playbackStatesWhenSeekProcessed.get(2));
|
||||
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(3));
|
||||
|
|
@ -804,19 +808,24 @@ public final class ExoPlayerTest extends TestCase {
|
|||
|
||||
public void testStopDuringPreparationOverwritesPreparation() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopOverwritesPrepare")
|
||||
.waitForPlaybackState(Player.STATE_BUFFERING)
|
||||
.stop(true)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
ActionSchedule actionSchedule =
|
||||
new ActionSchedule.Builder("testStopOverwritesPrepare")
|
||||
.waitForPlaybackState(Player.STATE_BUFFERING)
|
||||
.seek(0)
|
||||
.stop(true)
|
||||
.waitForSeekProcessed()
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner =
|
||||
new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertTimelinesEqual(Timeline.EMPTY);
|
||||
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED);
|
||||
testRunner.assertNoPositionDiscontinuities();
|
||||
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
|
||||
}
|
||||
|
||||
public void testStopAndSeekAfterStopDoesNotResetTimeline() throws Exception {
|
||||
|
|
@ -855,8 +864,9 @@ public final class ExoPlayerTest extends TestCase {
|
|||
.waitForPlaybackState(Player.STATE_IDLE)
|
||||
.prepareSource(
|
||||
new FakeMediaSource(timeline, /* manifest= */ null),
|
||||
/* resetPosition= */ false,
|
||||
/* resetPosition= */ true,
|
||||
/* resetState= */ false)
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner =
|
||||
new ExoPlayerTestRunner.Builder()
|
||||
|
|
|
|||
|
|
@ -42,24 +42,19 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
private final Renderer[] renderers;
|
||||
private final TrackSelector trackSelector;
|
||||
private final TrackSelectionArray emptyTrackSelections;
|
||||
private final TrackSelectorResult emptyTrackSelectorResult;
|
||||
private final Handler eventHandler;
|
||||
private final ExoPlayerImplInternal internalPlayer;
|
||||
private final CopyOnWriteArraySet<Player.EventListener> listeners;
|
||||
private final Timeline.Window window;
|
||||
private final Timeline.Period period;
|
||||
|
||||
private boolean tracksSelected;
|
||||
private boolean playWhenReady;
|
||||
private @RepeatMode int repeatMode;
|
||||
private boolean shuffleModeEnabled;
|
||||
private int playbackState;
|
||||
private int pendingSeekAcks;
|
||||
private int pendingPrepareOrStopAcks;
|
||||
private boolean waitingForInitialTimeline;
|
||||
private boolean isLoading;
|
||||
private TrackGroupArray trackGroups;
|
||||
private TrackSelectionArray trackSelections;
|
||||
private int pendingOperationAcks;
|
||||
private boolean hasPendingPrepare;
|
||||
private boolean hasPendingSeek;
|
||||
private PlaybackParameters playbackParameters;
|
||||
|
||||
// Playback information when there is no pending seek/set source operation.
|
||||
|
|
@ -87,13 +82,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
this.playWhenReady = false;
|
||||
this.repeatMode = Player.REPEAT_MODE_OFF;
|
||||
this.shuffleModeEnabled = false;
|
||||
this.playbackState = Player.STATE_IDLE;
|
||||
this.listeners = new CopyOnWriteArraySet<>();
|
||||
emptyTrackSelections = new TrackSelectionArray(new TrackSelection[renderers.length]);
|
||||
emptyTrackSelectorResult =
|
||||
new TrackSelectorResult(
|
||||
TrackGroupArray.EMPTY,
|
||||
new boolean[renderers.length],
|
||||
new TrackSelectionArray(new TrackSelection[renderers.length]),
|
||||
null,
|
||||
new RendererConfiguration[renderers.length]);
|
||||
window = new Timeline.Window();
|
||||
period = new Timeline.Period();
|
||||
trackGroups = TrackGroupArray.EMPTY;
|
||||
trackSelections = emptyTrackSelections;
|
||||
playbackParameters = PlaybackParameters.DEFAULT;
|
||||
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
|
||||
eventHandler = new Handler(eventLooper) {
|
||||
|
|
@ -102,9 +100,19 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
ExoPlayerImpl.this.handleEvent(msg);
|
||||
}
|
||||
};
|
||||
playbackInfo = new PlaybackInfo(Timeline.EMPTY, null, 0, 0);
|
||||
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady,
|
||||
repeatMode, shuffleModeEnabled, eventHandler, this);
|
||||
playbackInfo =
|
||||
new PlaybackInfo(Timeline.EMPTY, /* startPositionUs= */ 0, emptyTrackSelectorResult);
|
||||
internalPlayer =
|
||||
new ExoPlayerImplInternal(
|
||||
renderers,
|
||||
trackSelector,
|
||||
emptyTrackSelectorResult,
|
||||
loadControl,
|
||||
playWhenReady,
|
||||
repeatMode,
|
||||
shuffleModeEnabled,
|
||||
eventHandler,
|
||||
this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -124,7 +132,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public int getPlaybackState() {
|
||||
return playbackState;
|
||||
return playbackInfo.playbackState;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -134,10 +142,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
|
||||
waitingForInitialTimeline = true;
|
||||
pendingPrepareOrStopAcks++;
|
||||
reset(resetPosition, resetState);
|
||||
PlaybackInfo playbackInfo =
|
||||
getResetPlaybackInfo(
|
||||
resetPosition, resetState, /* playbackState= */ Player.STATE_BUFFERING);
|
||||
// Trigger internal prepare first before updating the playback info and notifying external
|
||||
// listeners to ensure that new operations issued in the listener notifications reach the
|
||||
// player after this prepare. The internal player can't change the playback info immediately
|
||||
// because it uses a callback.
|
||||
hasPendingPrepare = true;
|
||||
pendingOperationAcks++;
|
||||
internalPlayer.prepare(mediaSource, resetPosition);
|
||||
updatePlaybackInfo(
|
||||
playbackInfo,
|
||||
/* positionDiscontinuity= */ false,
|
||||
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
|
||||
TIMELINE_CHANGE_REASON_RESET,
|
||||
/* seekProcessed= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -146,7 +166,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
this.playWhenReady = playWhenReady;
|
||||
internalPlayer.setPlayWhenReady(playWhenReady);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
||||
listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -190,7 +210,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
return isLoading;
|
||||
return playbackInfo.isLoading;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -214,19 +234,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) {
|
||||
throw new IllegalSeekPositionException(timeline, windowIndex, positionMs);
|
||||
}
|
||||
hasPendingSeek = true;
|
||||
pendingOperationAcks++;
|
||||
if (isPlayingAd()) {
|
||||
// TODO: Investigate adding support for seeking during ads. This is complicated to do in
|
||||
// general because the midroll ad preceding the seek destination must be played before the
|
||||
// content position can be played, if a different ad is playing at the moment.
|
||||
Log.w(TAG, "seekTo ignored because an ad is playing");
|
||||
if (pendingSeekAcks == 0) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onSeekProcessed();
|
||||
}
|
||||
}
|
||||
eventHandler
|
||||
.obtainMessage(
|
||||
ExoPlayerImplInternal.MSG_PLAYBACK_INFO_CHANGED,
|
||||
/* operationAcks */ 1,
|
||||
/* positionDiscontinuityReason */ C.INDEX_UNSET,
|
||||
playbackInfo)
|
||||
.sendToTarget();
|
||||
return;
|
||||
}
|
||||
pendingSeekAcks++;
|
||||
maskingWindowIndex = windowIndex;
|
||||
if (timeline.isEmpty()) {
|
||||
maskingWindowPositionMs = positionMs == C.TIME_UNSET ? 0 : positionMs;
|
||||
|
|
@ -273,9 +296,23 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
pendingPrepareOrStopAcks++;
|
||||
reset(/* resetPosition= */ reset, /* resetState= */ reset);
|
||||
PlaybackInfo playbackInfo =
|
||||
getResetPlaybackInfo(
|
||||
/* resetPosition= */ reset,
|
||||
/* resetState= */ reset,
|
||||
/* playbackState= */ Player.STATE_IDLE);
|
||||
// Trigger internal stop first before updating the playback info and notifying external
|
||||
// listeners to ensure that new operations issued in the listener notifications reach the
|
||||
// player after this stop. The internal player can't change the playback info immediately
|
||||
// because it uses a callback.
|
||||
pendingOperationAcks++;
|
||||
internalPlayer.stop(reset);
|
||||
updatePlaybackInfo(
|
||||
playbackInfo,
|
||||
/* positionDiscontinuity= */ false,
|
||||
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
|
||||
TIMELINE_CHANGE_REASON_RESET,
|
||||
/* seekProcessed= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -421,12 +458,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
return trackGroups;
|
||||
return playbackInfo.trackSelectorResult.groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
return trackSelections;
|
||||
return playbackInfo.trackSelectorResult.selections;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -442,51 +479,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
// Not private so it can be called from an inner class without going through a thunk method.
|
||||
/* package */ void handleEvent(Message msg) {
|
||||
switch (msg.what) {
|
||||
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
|
||||
playbackState = msg.arg1;
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_PLAYBACK_INFO_CHANGED:
|
||||
handlePlaybackInfo(
|
||||
(PlaybackInfo) msg.obj,
|
||||
/* operationAcks= */ msg.arg1,
|
||||
/* positionDiscontinuity= */ msg.arg2 != C.INDEX_UNSET,
|
||||
/* positionDiscontinuityReason= */ msg.arg2);
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_LOADING_CHANGED: {
|
||||
isLoading = msg.arg1 != 0;
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onLoadingChanged(isLoading);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
|
||||
int prepareOrStopAcks = msg.arg1;
|
||||
handlePlaybackInfo((PlaybackInfo) msg.obj, prepareOrStopAcks, 0, false,
|
||||
/* ignored */ DISCONTINUITY_REASON_INTERNAL);
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: {
|
||||
if (pendingPrepareOrStopAcks == 0) {
|
||||
TrackSelectorResult trackSelectorResult = (TrackSelectorResult) msg.obj;
|
||||
tracksSelected = true;
|
||||
trackGroups = trackSelectorResult.groups;
|
||||
trackSelections = trackSelectorResult.selections;
|
||||
trackSelector.onSelectionActivated(trackSelectorResult.info);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTracksChanged(trackGroups, trackSelections);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
||||
boolean seekPositionAdjusted = msg.arg1 != 0;
|
||||
handlePlaybackInfo((PlaybackInfo) msg.obj, 0, 1, seekPositionAdjusted,
|
||||
DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: {
|
||||
@DiscontinuityReason int discontinuityReason = msg.arg1;
|
||||
handlePlaybackInfo((PlaybackInfo) msg.obj, 0, 0, true, discontinuityReason);
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: {
|
||||
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED:
|
||||
PlaybackParameters playbackParameters = (PlaybackParameters) msg.obj;
|
||||
if (!this.playbackParameters.equals(playbackParameters)) {
|
||||
this.playbackParameters = playbackParameters;
|
||||
|
|
@ -495,24 +495,24 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_ERROR: {
|
||||
case ExoPlayerImplInternal.MSG_ERROR:
|
||||
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPlayerError(exception);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePlaybackInfo(PlaybackInfo playbackInfo, int prepareOrStopAcks, int seekAcks,
|
||||
boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason) {
|
||||
pendingPrepareOrStopAcks -= prepareOrStopAcks;
|
||||
pendingSeekAcks -= seekAcks;
|
||||
if (pendingPrepareOrStopAcks == 0 && pendingSeekAcks == 0) {
|
||||
private void handlePlaybackInfo(
|
||||
PlaybackInfo playbackInfo,
|
||||
int operationAcks,
|
||||
boolean positionDiscontinuity,
|
||||
@DiscontinuityReason int positionDiscontinuityReason) {
|
||||
pendingOperationAcks -= operationAcks;
|
||||
if (pendingOperationAcks == 0) {
|
||||
if (playbackInfo.timeline == null) {
|
||||
// Replace internal null timeline with externally visible empty timeline.
|
||||
playbackInfo = playbackInfo.copyWithTimeline(Timeline.EMPTY, playbackInfo.manifest);
|
||||
|
|
@ -523,37 +523,32 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
playbackInfo.fromNewPosition(
|
||||
playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs);
|
||||
}
|
||||
boolean timelineOrManifestChanged = this.playbackInfo.timeline != playbackInfo.timeline
|
||||
|| this.playbackInfo.manifest != playbackInfo.manifest;
|
||||
this.playbackInfo = playbackInfo;
|
||||
if (timelineOrManifestChanged || waitingForInitialTimeline) {
|
||||
if (playbackInfo.timeline.isEmpty()) {
|
||||
// Update the masking variables, which are used when the timeline becomes empty.
|
||||
maskingPeriodIndex = 0;
|
||||
maskingWindowIndex = 0;
|
||||
maskingWindowPositionMs = 0;
|
||||
}
|
||||
@Player.TimelineChangeReason int reason = waitingForInitialTimeline
|
||||
? Player.TIMELINE_CHANGE_REASON_PREPARED : Player.TIMELINE_CHANGE_REASON_DYNAMIC;
|
||||
waitingForInitialTimeline = false;
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTimelineChanged(playbackInfo.timeline, playbackInfo.manifest, reason);
|
||||
}
|
||||
}
|
||||
if (positionDiscontinuity) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity(positionDiscontinuityReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pendingSeekAcks == 0 && seekAcks > 0) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onSeekProcessed();
|
||||
if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare)
|
||||
&& playbackInfo.timeline.isEmpty()) {
|
||||
// Update the masking variables, which are used when the timeline becomes empty.
|
||||
maskingPeriodIndex = 0;
|
||||
maskingWindowIndex = 0;
|
||||
maskingWindowPositionMs = 0;
|
||||
}
|
||||
@Player.TimelineChangeReason
|
||||
int timelineChangeReason =
|
||||
hasPendingPrepare
|
||||
? Player.TIMELINE_CHANGE_REASON_PREPARED
|
||||
: Player.TIMELINE_CHANGE_REASON_DYNAMIC;
|
||||
boolean seekProcessed = hasPendingSeek;
|
||||
hasPendingPrepare = false;
|
||||
hasPendingSeek = false;
|
||||
updatePlaybackInfo(
|
||||
playbackInfo,
|
||||
positionDiscontinuity,
|
||||
positionDiscontinuityReason,
|
||||
timelineChangeReason,
|
||||
seekProcessed);
|
||||
}
|
||||
}
|
||||
|
||||
private void reset(boolean resetPosition, boolean resetState) {
|
||||
private PlaybackInfo getResetPlaybackInfo(
|
||||
boolean resetPosition, boolean resetState, int playbackState) {
|
||||
if (resetPosition) {
|
||||
maskingWindowIndex = 0;
|
||||
maskingPeriodIndex = 0;
|
||||
|
|
@ -563,22 +558,62 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
maskingPeriodIndex = getCurrentPeriodIndex();
|
||||
maskingWindowPositionMs = getCurrentPosition();
|
||||
}
|
||||
if (resetState) {
|
||||
if (!playbackInfo.timeline.isEmpty() || playbackInfo.manifest != null) {
|
||||
playbackInfo = playbackInfo.copyWithTimeline(Timeline.EMPTY, null);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTimelineChanged(playbackInfo.timeline, playbackInfo.manifest,
|
||||
Player.TIMELINE_CHANGE_REASON_RESET);
|
||||
}
|
||||
return new PlaybackInfo(
|
||||
resetState ? Timeline.EMPTY : playbackInfo.timeline,
|
||||
resetState ? null : playbackInfo.manifest,
|
||||
playbackInfo.periodId,
|
||||
playbackInfo.startPositionUs,
|
||||
playbackInfo.contentPositionUs,
|
||||
playbackState,
|
||||
/* isLoading= */ false,
|
||||
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult);
|
||||
}
|
||||
|
||||
private void updatePlaybackInfo(
|
||||
PlaybackInfo newPlaybackInfo,
|
||||
boolean positionDiscontinuity,
|
||||
@Player.DiscontinuityReason int positionDiscontinuityReason,
|
||||
@Player.TimelineChangeReason int timelineChangeReason,
|
||||
boolean seekProcessed) {
|
||||
boolean timelineOrManifestChanged =
|
||||
playbackInfo.timeline != newPlaybackInfo.timeline
|
||||
|| playbackInfo.manifest != newPlaybackInfo.manifest;
|
||||
boolean playbackStateChanged = playbackInfo.playbackState != newPlaybackInfo.playbackState;
|
||||
boolean isLoadingChanged = playbackInfo.isLoading != newPlaybackInfo.isLoading;
|
||||
boolean trackSelectorResultChanged =
|
||||
this.playbackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult;
|
||||
playbackInfo = newPlaybackInfo;
|
||||
if (timelineOrManifestChanged || timelineChangeReason == TIMELINE_CHANGE_REASON_PREPARED) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTimelineChanged(
|
||||
playbackInfo.timeline, playbackInfo.manifest, timelineChangeReason);
|
||||
}
|
||||
if (tracksSelected) {
|
||||
tracksSelected = false;
|
||||
trackGroups = TrackGroupArray.EMPTY;
|
||||
trackSelections = emptyTrackSelections;
|
||||
trackSelector.onSelectionActivated(null);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTracksChanged(trackGroups, trackSelections);
|
||||
}
|
||||
}
|
||||
if (positionDiscontinuity) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity(positionDiscontinuityReason);
|
||||
}
|
||||
}
|
||||
if (trackSelectorResultChanged) {
|
||||
trackSelector.onSelectionActivated(playbackInfo.trackSelectorResult.info);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTracksChanged(
|
||||
playbackInfo.trackSelectorResult.groups, playbackInfo.trackSelectorResult.selections);
|
||||
}
|
||||
}
|
||||
if (isLoadingChanged) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onLoadingChanged(playbackInfo.isLoading);
|
||||
}
|
||||
}
|
||||
if (playbackStateChanged) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState);
|
||||
}
|
||||
}
|
||||
if (seekProcessed) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onSeekProcessed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -593,7 +628,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
}
|
||||
|
||||
private boolean shouldMaskPosition() {
|
||||
return playbackInfo.timeline.isEmpty() || pendingSeekAcks > 0 || pendingPrepareOrStopAcks > 0;
|
||||
return playbackInfo.timeline.isEmpty() || pendingOperationAcks > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import android.util.Pair;
|
|||
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
|
||||
import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
|
||||
import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo;
|
||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||
import com.google.android.exoplayer2.source.ClippingMediaPeriod;
|
||||
import com.google.android.exoplayer2.source.EmptySampleStream;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
|
|
@ -51,14 +52,9 @@ import java.io.IOException;
|
|||
private static final String TAG = "ExoPlayerImplInternal";
|
||||
|
||||
// External messages
|
||||
public static final int MSG_STATE_CHANGED = 0;
|
||||
public static final int MSG_LOADING_CHANGED = 1;
|
||||
public static final int MSG_TRACKS_CHANGED = 2;
|
||||
public static final int MSG_SEEK_ACK = 3;
|
||||
public static final int MSG_POSITION_DISCONTINUITY = 4;
|
||||
public static final int MSG_SOURCE_INFO_REFRESHED = 5;
|
||||
public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 6;
|
||||
public static final int MSG_ERROR = 7;
|
||||
public static final int MSG_PLAYBACK_INFO_CHANGED = 0;
|
||||
public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 1;
|
||||
public static final int MSG_ERROR = 2;
|
||||
|
||||
// Internal messages
|
||||
private static final int MSG_PREPARE = 0;
|
||||
|
|
@ -99,6 +95,7 @@ import java.io.IOException;
|
|||
private final Renderer[] renderers;
|
||||
private final RendererCapabilities[] rendererCapabilities;
|
||||
private final TrackSelector trackSelector;
|
||||
private final TrackSelectorResult emptyTrackSelectorResult;
|
||||
private final LoadControl loadControl;
|
||||
private final Handler handler;
|
||||
private final HandlerThread internalPlaybackThread;
|
||||
|
|
@ -110,6 +107,7 @@ import java.io.IOException;
|
|||
private final long backBufferDurationUs;
|
||||
private final boolean retainBackBufferFromKeyframe;
|
||||
private final DefaultMediaClock mediaClock;
|
||||
private final PlaybackInfoUpdate playbackInfoUpdate;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private SeekParameters seekParameters;
|
||||
|
|
@ -120,8 +118,6 @@ import java.io.IOException;
|
|||
private boolean released;
|
||||
private boolean playWhenReady;
|
||||
private boolean rebuffering;
|
||||
private boolean isLoading;
|
||||
private int state;
|
||||
private @Player.RepeatMode int repeatMode;
|
||||
private boolean shuffleModeEnabled;
|
||||
private int customMessagesSent;
|
||||
|
|
@ -136,24 +132,34 @@ import java.io.IOException;
|
|||
private MediaPeriodHolder readingPeriodHolder;
|
||||
private MediaPeriodHolder playingPeriodHolder;
|
||||
|
||||
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
|
||||
LoadControl loadControl, boolean playWhenReady, @Player.RepeatMode int repeatMode,
|
||||
boolean shuffleModeEnabled, Handler eventHandler, ExoPlayer player) {
|
||||
public ExoPlayerImplInternal(
|
||||
Renderer[] renderers,
|
||||
TrackSelector trackSelector,
|
||||
TrackSelectorResult emptyTrackSelectorResult,
|
||||
LoadControl loadControl,
|
||||
boolean playWhenReady,
|
||||
@Player.RepeatMode int repeatMode,
|
||||
boolean shuffleModeEnabled,
|
||||
Handler eventHandler,
|
||||
ExoPlayer player) {
|
||||
this.renderers = renderers;
|
||||
this.trackSelector = trackSelector;
|
||||
this.emptyTrackSelectorResult = emptyTrackSelectorResult;
|
||||
this.loadControl = loadControl;
|
||||
this.playWhenReady = playWhenReady;
|
||||
this.repeatMode = repeatMode;
|
||||
this.shuffleModeEnabled = shuffleModeEnabled;
|
||||
this.eventHandler = eventHandler;
|
||||
this.state = Player.STATE_IDLE;
|
||||
this.player = player;
|
||||
|
||||
backBufferDurationUs = loadControl.getBackBufferDurationUs();
|
||||
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe();
|
||||
|
||||
seekParameters = SeekParameters.DEFAULT;
|
||||
playbackInfo = new PlaybackInfo(null, null, 0, C.TIME_UNSET);
|
||||
playbackInfo =
|
||||
new PlaybackInfo(
|
||||
/* timeline= */ null, /* startPositionUs= */ C.TIME_UNSET, emptyTrackSelectorResult);
|
||||
playbackInfoUpdate = new PlaybackInfoUpdate();
|
||||
rendererCapabilities = new RendererCapabilities[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
renderers[i].setIndex(i);
|
||||
|
|
@ -305,84 +311,99 @@ import java.io.IOException;
|
|||
switch (msg.what) {
|
||||
case MSG_PREPARE:
|
||||
prepareInternal((MediaSource) msg.obj, msg.arg1 != 0);
|
||||
return true;
|
||||
break;
|
||||
case MSG_SET_PLAY_WHEN_READY:
|
||||
setPlayWhenReadyInternal(msg.arg1 != 0);
|
||||
return true;
|
||||
break;
|
||||
case MSG_SET_REPEAT_MODE:
|
||||
setRepeatModeInternal(msg.arg1);
|
||||
return true;
|
||||
break;
|
||||
case MSG_SET_SHUFFLE_ENABLED:
|
||||
setShuffleModeEnabledInternal(msg.arg1 != 0);
|
||||
return true;
|
||||
break;
|
||||
case MSG_DO_SOME_WORK:
|
||||
doSomeWork();
|
||||
return true;
|
||||
break;
|
||||
case MSG_SEEK_TO:
|
||||
seekToInternal((SeekPosition) msg.obj);
|
||||
return true;
|
||||
break;
|
||||
case MSG_SET_PLAYBACK_PARAMETERS:
|
||||
setPlaybackParametersInternal((PlaybackParameters) msg.obj);
|
||||
return true;
|
||||
break;
|
||||
case MSG_SET_SEEK_PARAMETERS:
|
||||
setSeekParametersInternal((SeekParameters) msg.obj);
|
||||
return true;
|
||||
break;
|
||||
case MSG_STOP:
|
||||
stopInternal(/* reset= */ msg.arg1 != 0, /* acknowledgeStop= */ true);
|
||||
return true;
|
||||
break;
|
||||
case MSG_RELEASE:
|
||||
releaseInternal();
|
||||
return true;
|
||||
break;
|
||||
case MSG_PERIOD_PREPARED:
|
||||
handlePeriodPrepared((MediaPeriod) msg.obj);
|
||||
return true;
|
||||
break;
|
||||
case MSG_REFRESH_SOURCE_INFO:
|
||||
handleSourceInfoRefreshed((MediaSourceRefreshInfo) msg.obj);
|
||||
return true;
|
||||
break;
|
||||
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED:
|
||||
handleContinueLoadingRequested((MediaPeriod) msg.obj);
|
||||
return true;
|
||||
break;
|
||||
case MSG_TRACK_SELECTION_INVALIDATED:
|
||||
reselectTracksInternal();
|
||||
return true;
|
||||
break;
|
||||
case MSG_CUSTOM:
|
||||
sendMessagesInternal((ExoPlayerMessage[]) msg.obj);
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
maybeNotifyPlaybackInfoChanged();
|
||||
} catch (ExoPlaybackException e) {
|
||||
Log.e(TAG, "Renderer error.", e);
|
||||
stopInternal(/* reset= */ false, /* acknowledgeStop= */ false);
|
||||
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
|
||||
return true;
|
||||
maybeNotifyPlaybackInfoChanged();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Source error.", e);
|
||||
stopInternal(/* reset= */ false, /* acknowledgeStop= */ false);
|
||||
eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget();
|
||||
return true;
|
||||
maybeNotifyPlaybackInfoChanged();
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Internal runtime error.", e);
|
||||
stopInternal(/* reset= */ false, /* acknowledgeStop= */ false);
|
||||
eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e))
|
||||
.sendToTarget();
|
||||
return true;
|
||||
maybeNotifyPlaybackInfoChanged();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
|
||||
private void setState(int state) {
|
||||
if (this.state != state) {
|
||||
this.state = state;
|
||||
eventHandler.obtainMessage(MSG_STATE_CHANGED, state, 0).sendToTarget();
|
||||
if (playbackInfo.playbackState != state) {
|
||||
playbackInfo = playbackInfo.copyWithPlaybackState(state);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIsLoading(boolean isLoading) {
|
||||
if (this.isLoading != isLoading) {
|
||||
this.isLoading = isLoading;
|
||||
eventHandler.obtainMessage(MSG_LOADING_CHANGED, isLoading ? 1 : 0, 0).sendToTarget();
|
||||
if (playbackInfo.isLoading != isLoading) {
|
||||
playbackInfo = playbackInfo.copyWithIsLoading(isLoading);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeNotifyPlaybackInfoChanged() {
|
||||
if (playbackInfoUpdate.hasPendingUpdate(playbackInfo)) {
|
||||
eventHandler
|
||||
.obtainMessage(
|
||||
MSG_PLAYBACK_INFO_CHANGED,
|
||||
playbackInfoUpdate.operationAcks,
|
||||
playbackInfoUpdate.positionDiscontinuity
|
||||
? playbackInfoUpdate.discontinuityReason
|
||||
: C.INDEX_UNSET,
|
||||
playbackInfo)
|
||||
.sendToTarget();
|
||||
playbackInfoUpdate.reset(playbackInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -403,10 +424,10 @@ import java.io.IOException;
|
|||
stopRenderers();
|
||||
updatePlaybackPositions();
|
||||
} else {
|
||||
if (state == Player.STATE_READY) {
|
||||
if (playbackInfo.playbackState == Player.STATE_READY) {
|
||||
startRenderers();
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
} else if (state == Player.STATE_BUFFERING) {
|
||||
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
}
|
||||
|
|
@ -474,10 +495,9 @@ import java.io.IOException;
|
|||
MediaPeriodId periodId = playingPeriodHolder.info.id;
|
||||
long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs);
|
||||
if (newPositionUs != playbackInfo.positionUs) {
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs,
|
||||
playbackInfo.contentPositionUs);
|
||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, Player.DISCONTINUITY_REASON_INTERNAL,
|
||||
0, playbackInfo).sendToTarget();
|
||||
playbackInfo =
|
||||
playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs);
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -511,8 +531,7 @@ import java.io.IOException;
|
|||
if (periodPositionUs != playbackInfo.positionUs) {
|
||||
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
|
||||
playbackInfo.contentPositionUs);
|
||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, Player.DISCONTINUITY_REASON_INTERNAL,
|
||||
0, playbackInfo).sendToTarget();
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||
}
|
||||
} else {
|
||||
rendererPositionUs = mediaClock.syncAndGetPositionUs();
|
||||
|
|
@ -575,7 +594,7 @@ import java.io.IOException;
|
|||
&& playingPeriodHolder.info.isFinal) {
|
||||
setState(Player.STATE_ENDED);
|
||||
stopRenderers();
|
||||
} else if (state == Player.STATE_BUFFERING) {
|
||||
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
float playbackSpeed = mediaClock.getPlaybackParameters().speed;
|
||||
boolean isNewlyReady = enabledRenderers.length > 0
|
||||
? (allRenderersReadyOrEnded && loadingPeriodHolder.haveSufficientBuffer(
|
||||
|
|
@ -587,7 +606,7 @@ import java.io.IOException;
|
|||
startRenderers();
|
||||
}
|
||||
}
|
||||
} else if (state == Player.STATE_READY) {
|
||||
} else if (playbackInfo.playbackState == Player.STATE_READY) {
|
||||
boolean isStillReady = enabledRenderers.length > 0 ? allRenderersReadyOrEnded
|
||||
: isTimelineReady(playingPeriodDurationUs);
|
||||
if (!isStillReady) {
|
||||
|
|
@ -597,15 +616,16 @@ import java.io.IOException;
|
|||
}
|
||||
}
|
||||
|
||||
if (state == Player.STATE_BUFFERING) {
|
||||
if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
for (Renderer renderer : enabledRenderers) {
|
||||
renderer.maybeThrowStreamError();
|
||||
}
|
||||
}
|
||||
|
||||
if ((playWhenReady && state == Player.STATE_READY) || state == Player.STATE_BUFFERING) {
|
||||
if ((playWhenReady && playbackInfo.playbackState == Player.STATE_READY)
|
||||
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
scheduleNextWork(operationStartTimeMs, RENDERING_INTERVAL_MS);
|
||||
} else if (enabledRenderers.length != 0 && state != Player.STATE_ENDED) {
|
||||
} else if (enabledRenderers.length != 0 && playbackInfo.playbackState != Player.STATE_ENDED) {
|
||||
scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
|
||||
} else {
|
||||
handler.removeMessages(MSG_DO_SOME_WORK);
|
||||
|
|
@ -626,12 +646,10 @@ import java.io.IOException;
|
|||
}
|
||||
|
||||
private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {
|
||||
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
|
||||
Timeline timeline = playbackInfo.timeline;
|
||||
if (mediaSource == null || timeline == null) {
|
||||
pendingInitialSeekPosition = seekPosition;
|
||||
eventHandler
|
||||
.obtainMessage(MSG_SEEK_ACK, /* seekAdjusted */ 0, 0, playbackInfo)
|
||||
.sendToTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -679,8 +697,9 @@ import java.io.IOException;
|
|||
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
|
||||
}
|
||||
} finally {
|
||||
eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
|
||||
.sendToTarget();
|
||||
if (seekPositionAdjusted) {
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -779,7 +798,9 @@ import java.io.IOException;
|
|||
private void stopInternal(boolean reset, boolean acknowledgeStop) {
|
||||
resetInternal(
|
||||
/* releaseMediaSource= */ true, /* resetPosition= */ reset, /* resetState= */ reset);
|
||||
notifySourceInfoRefresh(acknowledgeStop);
|
||||
playbackInfoUpdate.incrementPendingOperationAcks(
|
||||
pendingPrepareCount + (acknowledgeStop ? 1 : 0));
|
||||
pendingPrepareCount = 0;
|
||||
loadControl.onStopped();
|
||||
setState(Player.STATE_IDLE);
|
||||
}
|
||||
|
|
@ -817,25 +838,29 @@ import java.io.IOException;
|
|||
readingPeriodHolder = null;
|
||||
playingPeriodHolder = null;
|
||||
setIsLoading(false);
|
||||
Timeline timeline = playbackInfo.timeline;
|
||||
int firstPeriodIndex =
|
||||
timeline == null || timeline.isEmpty()
|
||||
? 0
|
||||
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
|
||||
.firstPeriodIndex;
|
||||
if (resetPosition) {
|
||||
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
|
||||
// (firstPeriodIndex,0) isn't ignored.
|
||||
Timeline timeline = playbackInfo.timeline;
|
||||
int firstPeriodIndex = timeline == null || timeline.isEmpty()
|
||||
? 0
|
||||
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
|
||||
.firstPeriodIndex;
|
||||
pendingInitialSeekPosition = null;
|
||||
playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET);
|
||||
} else {
|
||||
// The new start position is the current playback position.
|
||||
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, playbackInfo.positionUs,
|
||||
playbackInfo.contentPositionUs);
|
||||
}
|
||||
if (resetState) {
|
||||
mediaPeriodInfoSequence.setTimeline(null);
|
||||
playbackInfo = playbackInfo.copyWithTimeline(null, null);
|
||||
}
|
||||
playbackInfo =
|
||||
new PlaybackInfo(
|
||||
resetState ? null : playbackInfo.timeline,
|
||||
resetState ? null : playbackInfo.manifest,
|
||||
resetPosition ? new MediaPeriodId(firstPeriodIndex) : playbackInfo.periodId,
|
||||
// Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored.
|
||||
resetPosition ? C.TIME_UNSET : playbackInfo.startPositionUs,
|
||||
resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs,
|
||||
playbackInfo.playbackState,
|
||||
/* isLoading= */ false,
|
||||
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult);
|
||||
if (releaseMediaSource) {
|
||||
if (mediaSource != null) {
|
||||
mediaSource.releaseSource();
|
||||
|
|
@ -849,7 +874,8 @@ import java.io.IOException;
|
|||
for (ExoPlayerMessage message : messages) {
|
||||
message.target.handleMessage(message.messageType, message.message);
|
||||
}
|
||||
if (state == Player.STATE_READY || state == Player.STATE_BUFFERING) {
|
||||
if (playbackInfo.playbackState == Player.STATE_READY
|
||||
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
// The message may have caused something to change that now requires us to do work.
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
|
|
@ -909,11 +935,11 @@ import java.io.IOException;
|
|||
boolean[] streamResetFlags = new boolean[renderers.length];
|
||||
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
|
||||
playbackInfo.positionUs, recreateStreams, streamResetFlags);
|
||||
if (state != Player.STATE_ENDED && periodPositionUs != playbackInfo.positionUs) {
|
||||
if (playbackInfo.playbackState != Player.STATE_ENDED
|
||||
&& periodPositionUs != playbackInfo.positionUs) {
|
||||
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
|
||||
playbackInfo.contentPositionUs);
|
||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, Player.DISCONTINUITY_REASON_INTERNAL,
|
||||
0, playbackInfo).sendToTarget();
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||
resetRendererPosition(periodPositionUs);
|
||||
}
|
||||
|
||||
|
|
@ -936,8 +962,7 @@ import java.io.IOException;
|
|||
}
|
||||
}
|
||||
}
|
||||
eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult)
|
||||
.sendToTarget();
|
||||
playbackInfo = playbackInfo.copyWithTrackSelectorResult(periodHolder.trackSelectorResult);
|
||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||
} else {
|
||||
// Release and re-prepare/buffer periods after the one whose selection changed.
|
||||
|
|
@ -954,7 +979,7 @@ import java.io.IOException;
|
|||
loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false);
|
||||
}
|
||||
}
|
||||
if (state != Player.STATE_ENDED) {
|
||||
if (playbackInfo.playbackState != Player.STATE_ENDED) {
|
||||
maybeContinueLoading();
|
||||
updatePlaybackPositions();
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
|
|
@ -1010,6 +1035,8 @@ import java.io.IOException;
|
|||
playbackInfo = playbackInfo.copyWithTimeline(timeline, manifest);
|
||||
|
||||
if (oldTimeline == null) {
|
||||
playbackInfoUpdate.incrementPendingOperationAcks(pendingPrepareCount);
|
||||
pendingPrepareCount = 0;
|
||||
if (pendingInitialSeekPosition != null) {
|
||||
Pair<Integer, Long> periodPosition = resolveSeekPosition(pendingInitialSeekPosition);
|
||||
pendingInitialSeekPosition = null;
|
||||
|
|
@ -1024,7 +1051,6 @@ import java.io.IOException;
|
|||
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId, periodId.isAd() ? 0 : positionUs,
|
||||
positionUs);
|
||||
notifySourceInfoRefresh();
|
||||
}
|
||||
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||
if (timeline.isEmpty()) {
|
||||
|
|
@ -1038,10 +1064,7 @@ import java.io.IOException;
|
|||
startPositionUs);
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId,
|
||||
periodId.isAd() ? 0 : startPositionUs, startPositionUs);
|
||||
notifySourceInfoRefresh();
|
||||
}
|
||||
} else {
|
||||
notifySourceInfoRefresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -1050,7 +1073,6 @@ import java.io.IOException;
|
|||
MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder
|
||||
: loadingPeriodHolder;
|
||||
if (periodHolder == null && playingPeriodIndex >= oldTimeline.getPeriodCount()) {
|
||||
notifySourceInfoRefresh();
|
||||
return;
|
||||
}
|
||||
Object playingPeriodUid = periodHolder == null
|
||||
|
|
@ -1090,7 +1112,6 @@ import java.io.IOException;
|
|||
MediaPeriodId periodId = new MediaPeriodId(newPeriodIndex);
|
||||
newPositionUs = seekToPeriodPosition(periodId, newPositionUs);
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs, C.TIME_UNSET);
|
||||
notifySourceInfoRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1107,14 +1128,12 @@ import java.io.IOException;
|
|||
long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.contentPositionUs);
|
||||
long contentPositionUs = periodId.isAd() ? playbackInfo.contentPositionUs : C.TIME_UNSET;
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs, contentPositionUs);
|
||||
notifySourceInfoRefresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (periodHolder == null) {
|
||||
// We don't have any period holders, so we're done.
|
||||
notifySourceInfoRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1152,8 +1171,6 @@ import java.io.IOException;
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
notifySourceInfoRefresh();
|
||||
}
|
||||
|
||||
private MediaPeriodHolder updatePeriodInfo(MediaPeriodHolder periodHolder, int periodIndex) {
|
||||
|
|
@ -1172,18 +1189,6 @@ import java.io.IOException;
|
|||
// Reset, but retain the source so that it can still be used should a seek occur.
|
||||
resetInternal(
|
||||
/* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false);
|
||||
notifySourceInfoRefresh();
|
||||
}
|
||||
|
||||
private void notifySourceInfoRefresh() {
|
||||
notifySourceInfoRefresh(/* acknowledgeStop= */ false);
|
||||
}
|
||||
|
||||
private void notifySourceInfoRefresh(boolean acknowledgeStop) {
|
||||
int prepareOrStopAcks = pendingPrepareCount + (acknowledgeStop ? 1 : 0);
|
||||
pendingPrepareCount = 0;
|
||||
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, prepareOrStopAcks, 0, playbackInfo)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1287,7 +1292,7 @@ import java.io.IOException;
|
|||
|
||||
if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
|
||||
setIsLoading(false);
|
||||
} else if (loadingPeriodHolder != null && !isLoading) {
|
||||
} else if (loadingPeriodHolder != null && !playbackInfo.isLoading) {
|
||||
maybeContinueLoading();
|
||||
}
|
||||
|
||||
|
|
@ -1305,9 +1310,8 @@ import java.io.IOException;
|
|||
setPlayingPeriodHolder(playingPeriodHolder.next);
|
||||
playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id,
|
||||
playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
||||
updatePlaybackPositions();
|
||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY,
|
||||
Player.DISCONTINUITY_REASON_PERIOD_TRANSITION, 0, playbackInfo).sendToTarget();
|
||||
}
|
||||
|
||||
if (readingPeriodHolder.info.isFinal) {
|
||||
|
|
@ -1488,7 +1492,7 @@ import java.io.IOException;
|
|||
}
|
||||
|
||||
playingPeriodHolder = periodHolder;
|
||||
eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult).sendToTarget();
|
||||
playbackInfo = playbackInfo.copyWithTrackSelectorResult(periodHolder.trackSelectorResult);
|
||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||
}
|
||||
|
||||
|
|
@ -1514,7 +1518,7 @@ import java.io.IOException;
|
|||
rendererIndex);
|
||||
Format[] formats = getFormats(newSelection);
|
||||
// The renderer needs enabling with its new track selection.
|
||||
boolean playing = playWhenReady && state == Player.STATE_READY;
|
||||
boolean playing = playWhenReady && playbackInfo.playbackState == Player.STATE_READY;
|
||||
// Consider as joining only if the renderer was previously disabled.
|
||||
boolean joining = !wasRendererEnabled && playing;
|
||||
// Enable the renderer.
|
||||
|
|
@ -1805,7 +1809,40 @@ import java.io.IOException;
|
|||
this.timeline = timeline;
|
||||
this.manifest = manifest;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PlaybackInfoUpdate {
|
||||
|
||||
private PlaybackInfo lastPlaybackInfo;
|
||||
private int operationAcks;
|
||||
private boolean positionDiscontinuity;
|
||||
private @DiscontinuityReason int discontinuityReason;
|
||||
|
||||
public boolean hasPendingUpdate(PlaybackInfo playbackInfo) {
|
||||
return playbackInfo != lastPlaybackInfo || operationAcks > 0 || positionDiscontinuity;
|
||||
}
|
||||
|
||||
public void reset(PlaybackInfo playbackInfo) {
|
||||
lastPlaybackInfo = playbackInfo;
|
||||
operationAcks = 0;
|
||||
positionDiscontinuity = false;
|
||||
}
|
||||
|
||||
public void incrementPendingOperationAcks(int operationAcks) {
|
||||
this.operationAcks += operationAcks;
|
||||
}
|
||||
|
||||
public void setPositionDiscontinuity(@DiscontinuityReason int discontinuityReason) {
|
||||
if (positionDiscontinuity
|
||||
&& this.discontinuityReason != Player.DISCONTINUITY_REASON_INTERNAL) {
|
||||
// We always prefer non-internal discontinuity reasons. We also assume that we won't report
|
||||
// more than one non-internal discontinuity per message iteration.
|
||||
Assertions.checkArgument(discontinuityReason == Player.DISCONTINUITY_REASON_INTERNAL);
|
||||
return;
|
||||
}
|
||||
positionDiscontinuity = true;
|
||||
this.discontinuityReason = discontinuityReason;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,35 +15,59 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
|
||||
/**
|
||||
* Information about an ongoing playback.
|
||||
*/
|
||||
/* package */ final class PlaybackInfo {
|
||||
|
||||
public final Timeline timeline;
|
||||
public final Object manifest;
|
||||
public final @Nullable Timeline timeline;
|
||||
public final @Nullable Object manifest;
|
||||
public final MediaPeriodId periodId;
|
||||
public final long startPositionUs;
|
||||
public final long contentPositionUs;
|
||||
public final int playbackState;
|
||||
public final boolean isLoading;
|
||||
public final TrackSelectorResult trackSelectorResult;
|
||||
|
||||
public volatile long positionUs;
|
||||
public volatile long bufferedPositionUs;
|
||||
|
||||
public PlaybackInfo(Timeline timeline, Object manifest, int periodIndex, long startPositionUs) {
|
||||
this(timeline, manifest, new MediaPeriodId(periodIndex), startPositionUs, C.TIME_UNSET);
|
||||
public PlaybackInfo(
|
||||
@Nullable Timeline timeline, long startPositionUs, TrackSelectorResult trackSelectorResult) {
|
||||
this(
|
||||
timeline,
|
||||
/* manifest= */ null,
|
||||
new MediaPeriodId(0),
|
||||
startPositionUs,
|
||||
/* contentPositionUs =*/ C.TIME_UNSET,
|
||||
Player.STATE_IDLE,
|
||||
/* isLoading= */ false,
|
||||
trackSelectorResult);
|
||||
}
|
||||
|
||||
public PlaybackInfo(Timeline timeline, Object manifest, MediaPeriodId periodId,
|
||||
long startPositionUs, long contentPositionUs) {
|
||||
public PlaybackInfo(
|
||||
@Nullable Timeline timeline,
|
||||
@Nullable Object manifest,
|
||||
MediaPeriodId periodId,
|
||||
long startPositionUs,
|
||||
long contentPositionUs,
|
||||
int playbackState,
|
||||
boolean isLoading,
|
||||
TrackSelectorResult trackSelectorResult) {
|
||||
this.timeline = timeline;
|
||||
this.manifest = manifest;
|
||||
this.periodId = periodId;
|
||||
this.startPositionUs = startPositionUs;
|
||||
this.contentPositionUs = contentPositionUs;
|
||||
positionUs = startPositionUs;
|
||||
bufferedPositionUs = startPositionUs;
|
||||
this.positionUs = startPositionUs;
|
||||
this.bufferedPositionUs = startPositionUs;
|
||||
this.playbackState = playbackState;
|
||||
this.isLoading = isLoading;
|
||||
this.trackSelectorResult = trackSelectorResult;
|
||||
}
|
||||
|
||||
public PlaybackInfo fromNewPosition(int periodIndex, long startPositionUs,
|
||||
|
|
@ -53,19 +77,88 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
|||
|
||||
public PlaybackInfo fromNewPosition(MediaPeriodId periodId, long startPositionUs,
|
||||
long contentPositionUs) {
|
||||
return new PlaybackInfo(timeline, manifest, periodId, startPositionUs, contentPositionUs);
|
||||
return new PlaybackInfo(
|
||||
timeline,
|
||||
manifest,
|
||||
periodId,
|
||||
startPositionUs,
|
||||
contentPositionUs,
|
||||
playbackState,
|
||||
isLoading,
|
||||
trackSelectorResult);
|
||||
}
|
||||
|
||||
public PlaybackInfo copyWithPeriodIndex(int periodIndex) {
|
||||
PlaybackInfo playbackInfo = new PlaybackInfo(timeline, manifest,
|
||||
periodId.copyWithPeriodIndex(periodIndex), startPositionUs, contentPositionUs);
|
||||
PlaybackInfo playbackInfo =
|
||||
new PlaybackInfo(
|
||||
timeline,
|
||||
manifest,
|
||||
periodId.copyWithPeriodIndex(periodIndex),
|
||||
startPositionUs,
|
||||
contentPositionUs,
|
||||
playbackState,
|
||||
isLoading,
|
||||
trackSelectorResult);
|
||||
copyMutablePositions(this, playbackInfo);
|
||||
return playbackInfo;
|
||||
}
|
||||
|
||||
public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) {
|
||||
PlaybackInfo playbackInfo = new PlaybackInfo(timeline, manifest, periodId, startPositionUs,
|
||||
contentPositionUs);
|
||||
PlaybackInfo playbackInfo =
|
||||
new PlaybackInfo(
|
||||
timeline,
|
||||
manifest,
|
||||
periodId,
|
||||
startPositionUs,
|
||||
contentPositionUs,
|
||||
playbackState,
|
||||
isLoading,
|
||||
trackSelectorResult);
|
||||
copyMutablePositions(this, playbackInfo);
|
||||
return playbackInfo;
|
||||
}
|
||||
|
||||
public PlaybackInfo copyWithPlaybackState(int playbackState) {
|
||||
PlaybackInfo playbackInfo =
|
||||
new PlaybackInfo(
|
||||
timeline,
|
||||
manifest,
|
||||
periodId,
|
||||
startPositionUs,
|
||||
contentPositionUs,
|
||||
playbackState,
|
||||
isLoading,
|
||||
trackSelectorResult);
|
||||
copyMutablePositions(this, playbackInfo);
|
||||
return playbackInfo;
|
||||
}
|
||||
|
||||
public PlaybackInfo copyWithIsLoading(boolean isLoading) {
|
||||
PlaybackInfo playbackInfo =
|
||||
new PlaybackInfo(
|
||||
timeline,
|
||||
manifest,
|
||||
periodId,
|
||||
startPositionUs,
|
||||
contentPositionUs,
|
||||
playbackState,
|
||||
isLoading,
|
||||
trackSelectorResult);
|
||||
copyMutablePositions(this, playbackInfo);
|
||||
return playbackInfo;
|
||||
}
|
||||
|
||||
public PlaybackInfo copyWithTrackSelectorResult(TrackSelectorResult trackSelectorResult) {
|
||||
PlaybackInfo playbackInfo =
|
||||
new PlaybackInfo(
|
||||
timeline,
|
||||
manifest,
|
||||
periodId,
|
||||
startPositionUs,
|
||||
contentPositionUs,
|
||||
playbackState,
|
||||
isLoading,
|
||||
trackSelectorResult);
|
||||
copyMutablePositions(this, playbackInfo);
|
||||
return playbackInfo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public final class TrackSelectorResult {
|
|||
* @return Whether this result is equivalent to {@code other} for all renderers.
|
||||
*/
|
||||
public boolean isEquivalent(TrackSelectorResult other) {
|
||||
if (other == null) {
|
||||
if (other == null || other.selections.length != selections.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue