Commit 2cbf0ef0 by tonihei Committed by Oliver Woodman

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