Add onSeekProcessed callback to Player interface.
This is useful to determine when a seek request was processed by the player and all playback state changes (mostly to BUFFERING) have been performed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=170826793
This commit is contained in:
parent
1495b9a473
commit
5baddfb56a
@ -209,6 +209,11 @@ import java.util.Locale;
|
||||
Log.d(TAG, "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekProcessed() {
|
||||
Log.d(TAG, "seekProcessed");
|
||||
}
|
||||
|
||||
// MetadataOutput
|
||||
|
||||
@Override
|
||||
|
@ -41,7 +41,6 @@ import com.google.android.gms.cast.framework.SessionManager;
|
||||
import com.google.android.gms.cast.framework.SessionManagerListener;
|
||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
import com.google.android.gms.common.api.PendingResult;
|
||||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import java.util.List;
|
||||
@ -115,6 +114,7 @@ public final class CastPlayer implements Player {
|
||||
private int currentWindowIndex;
|
||||
private boolean playWhenReady;
|
||||
private long lastReportedPositionMs;
|
||||
private int pendingSeekCount;
|
||||
private int pendingSeekWindowIndex;
|
||||
private long pendingSeekPositionMs;
|
||||
|
||||
@ -333,11 +333,16 @@ public final class CastPlayer implements Player {
|
||||
} else {
|
||||
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
|
||||
}
|
||||
pendingSeekCount++;
|
||||
pendingSeekWindowIndex = windowIndex;
|
||||
pendingSeekPositionMs = positionMs;
|
||||
for (EventListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
||||
}
|
||||
} else if (pendingSeekCount == 0) {
|
||||
for (EventListener listener : listeners) {
|
||||
listener.onSeekProcessed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,7 +541,7 @@ public final class CastPlayer implements Player {
|
||||
}
|
||||
}
|
||||
int currentWindowIndex = fetchCurrentWindowIndex(getMediaStatus());
|
||||
if (this.currentWindowIndex != currentWindowIndex) {
|
||||
if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) {
|
||||
this.currentWindowIndex = currentWindowIndex;
|
||||
for (EventListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
||||
@ -831,18 +836,18 @@ public final class CastPlayer implements Player {
|
||||
@Override
|
||||
public void onResult(@NonNull MediaChannelResult result) {
|
||||
int statusCode = result.getStatus().getStatusCode();
|
||||
if (statusCode == CastStatusCodes.REPLACED) {
|
||||
// A seek was executed before this one completed. Do nothing.
|
||||
} else {
|
||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||
pendingSeekPositionMs = C.TIME_UNSET;
|
||||
if (statusCode != CommonStatusCodes.SUCCESS) {
|
||||
if (statusCode != CastStatusCodes.SUCCESS && statusCode != CastStatusCodes.REPLACED) {
|
||||
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
||||
+ CastUtils.getLogString(statusCode));
|
||||
}
|
||||
if (--pendingSeekCount == 0) {
|
||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||
pendingSeekPositionMs = C.TIME_UNSET;
|
||||
for (EventListener listener : listeners) {
|
||||
listener.onSeekProcessed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ import com.google.android.exoplayer2.testutil.FakeRenderer;
|
||||
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@ -259,4 +261,38 @@ public final class ExoPlayerTest extends TestCase {
|
||||
assertTrue(renderer.isEnded);
|
||||
}
|
||||
|
||||
public void testSeekProcessedCallback() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(
|
||||
new TimelineWindowDefinition(true, false, 100000),
|
||||
new TimelineWindowDefinition(true, false, 100000));
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testSeekProcessedCallback")
|
||||
// Initial seek before timeline preparation finished.
|
||||
.pause().seek(10).waitForPlaybackState(Player.STATE_READY)
|
||||
// Re-seek to same position, start playback and wait until playback reaches second window.
|
||||
.seek(10).play().waitForPositionDiscontinuity()
|
||||
// Seek twice in concession, expecting the first seek to be replaced.
|
||||
.seek(5).seek(60).build();
|
||||
final List<Integer> playbackStatesWhenSeekProcessed = new ArrayList<>();
|
||||
Player.EventListener eventListener = new Player.DefaultEventListener() {
|
||||
private int currentPlaybackState = Player.STATE_IDLE;
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
currentPlaybackState = playbackState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekProcessed() {
|
||||
playbackStatesWhenSeekProcessed.add(currentPlaybackState);
|
||||
}
|
||||
};
|
||||
new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline).setEventListener(eventListener).setActionSchedule(actionSchedule)
|
||||
.build().start().blockUntilEnded(TIMEOUT_MS);
|
||||
assertEquals(3, playbackStatesWhenSeekProcessed.size());
|
||||
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(0));
|
||||
assertEquals(Player.STATE_READY, (int) playbackStatesWhenSeekProcessed.get(1));
|
||||
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(2));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -482,10 +482,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
maskingWindowIndex = 0;
|
||||
maskingWindowPositionMs = 0;
|
||||
}
|
||||
if (msg.arg1 != 0) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK);
|
||||
if (msg.arg1 != 0) {
|
||||
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_INTERNAL);
|
||||
}
|
||||
listener.onSeekProcessed();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -516,6 +517,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
listener.onTimelineChanged(timeline, manifest);
|
||||
}
|
||||
}
|
||||
if (pendingSeekAcks == 0 && sourceInfo.seekAcks > 0) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onSeekProcessed();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: {
|
||||
|
@ -701,12 +701,12 @@ import java.io.IOException;
|
||||
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
|
||||
// The seek position was valid for the timeline that it was performed into, but the
|
||||
// timeline has changed and a suitable seek position could not be resolved in the new one.
|
||||
playbackInfo = new PlaybackInfo(firstPeriodIndex, 0);
|
||||
eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, playbackInfo).sendToTarget();
|
||||
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
|
||||
// (firstPeriodIndex,0) isn't ignored.
|
||||
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
||||
setState(Player.STATE_ENDED);
|
||||
eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, new PlaybackInfo(firstPeriodIndex, 0))
|
||||
.sendToTarget();
|
||||
// Reset, but retain the source so that it can still be used should a seek occur.
|
||||
resetInternal(false);
|
||||
return;
|
||||
@ -1031,7 +1031,7 @@ import java.io.IOException;
|
||||
MediaPeriodId periodId =
|
||||
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
|
||||
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs, positionUs);
|
||||
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
|
||||
notifySourceInfoRefresh(manifest, playbackInfo, processedInitialSeekCount);
|
||||
}
|
||||
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||
if (timeline.isEmpty()) {
|
||||
@ -1182,22 +1182,23 @@ import java.io.IOException;
|
||||
int processedInitialSeekCount) {
|
||||
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
|
||||
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
|
||||
// Set the playback position to (firstPeriodIndex,0) for notifying the eventHandler.
|
||||
playbackInfo = new PlaybackInfo(firstPeriodIndex, 0);
|
||||
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
|
||||
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
|
||||
// (firstPeriodIndex,0) isn't ignored.
|
||||
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
||||
setState(Player.STATE_ENDED);
|
||||
// Set the playback position to (firstPeriodIndex,0) for notifying the eventHandler.
|
||||
notifySourceInfoRefresh(manifest, new PlaybackInfo(firstPeriodIndex, 0),
|
||||
processedInitialSeekCount);
|
||||
// Reset, but retain the source so that it can still be used should a seek occur.
|
||||
resetInternal(false);
|
||||
}
|
||||
|
||||
private void notifySourceInfoRefresh(Object manifest) {
|
||||
notifySourceInfoRefresh(manifest, 0);
|
||||
notifySourceInfoRefresh(manifest, playbackInfo, 0);
|
||||
}
|
||||
|
||||
private void notifySourceInfoRefresh(Object manifest, int processedInitialSeekCount) {
|
||||
private void notifySourceInfoRefresh(Object manifest, PlaybackInfo playbackInfo,
|
||||
int processedInitialSeekCount) {
|
||||
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED,
|
||||
new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget();
|
||||
}
|
||||
|
@ -134,6 +134,13 @@ public interface Player {
|
||||
*/
|
||||
void onPlaybackParametersChanged(PlaybackParameters playbackParameters);
|
||||
|
||||
/**
|
||||
* Called when all pending seek requests have been processed by the player. This is guaranteed
|
||||
* to happen after any necessary changes to the player state were reported to
|
||||
* {@link #onPlayerStateChanged(boolean, int)}.
|
||||
*/
|
||||
void onSeekProcessed();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,6 +193,11 @@ public interface Player {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekProcessed() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user