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, "]");
|
Log.d(TAG, "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSeekProcessed() {
|
||||||
|
Log.d(TAG, "seekProcessed");
|
||||||
|
}
|
||||||
|
|
||||||
// MetadataOutput
|
// MetadataOutput
|
||||||
|
|
||||||
@Override
|
@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.SessionManagerListener;
|
||||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
|
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.PendingResult;
|
||||||
import com.google.android.gms.common.api.ResultCallback;
|
import com.google.android.gms.common.api.ResultCallback;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -115,6 +114,7 @@ public final class CastPlayer implements Player {
|
|||||||
private int currentWindowIndex;
|
private int currentWindowIndex;
|
||||||
private boolean playWhenReady;
|
private boolean playWhenReady;
|
||||||
private long lastReportedPositionMs;
|
private long lastReportedPositionMs;
|
||||||
|
private int pendingSeekCount;
|
||||||
private int pendingSeekWindowIndex;
|
private int pendingSeekWindowIndex;
|
||||||
private long pendingSeekPositionMs;
|
private long pendingSeekPositionMs;
|
||||||
|
|
||||||
@ -333,11 +333,16 @@ public final class CastPlayer implements Player {
|
|||||||
} else {
|
} else {
|
||||||
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
|
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
|
||||||
}
|
}
|
||||||
|
pendingSeekCount++;
|
||||||
pendingSeekWindowIndex = windowIndex;
|
pendingSeekWindowIndex = windowIndex;
|
||||||
pendingSeekPositionMs = positionMs;
|
pendingSeekPositionMs = positionMs;
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
listener.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
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());
|
int currentWindowIndex = fetchCurrentWindowIndex(getMediaStatus());
|
||||||
if (this.currentWindowIndex != currentWindowIndex) {
|
if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) {
|
||||||
this.currentWindowIndex = currentWindowIndex;
|
this.currentWindowIndex = currentWindowIndex;
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
||||||
@ -831,18 +836,18 @@ public final class CastPlayer implements Player {
|
|||||||
@Override
|
@Override
|
||||||
public void onResult(@NonNull MediaChannelResult result) {
|
public void onResult(@NonNull MediaChannelResult result) {
|
||||||
int statusCode = result.getStatus().getStatusCode();
|
int statusCode = result.getStatus().getStatusCode();
|
||||||
if (statusCode == CastStatusCodes.REPLACED) {
|
if (statusCode != CastStatusCodes.SUCCESS && 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) {
|
|
||||||
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
||||||
+ CastUtils.getLogString(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.FakeShuffleOrder;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
@ -259,4 +261,38 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
assertTrue(renderer.isEnded);
|
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;
|
maskingWindowIndex = 0;
|
||||||
maskingWindowPositionMs = 0;
|
maskingWindowPositionMs = 0;
|
||||||
}
|
}
|
||||||
if (msg.arg1 != 0) {
|
|
||||||
for (Player.EventListener listener : listeners) {
|
for (Player.EventListener listener : listeners) {
|
||||||
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK);
|
if (msg.arg1 != 0) {
|
||||||
|
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_INTERNAL);
|
||||||
}
|
}
|
||||||
|
listener.onSeekProcessed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -516,6 +517,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
listener.onTimelineChanged(timeline, manifest);
|
listener.onTimelineChanged(timeline, manifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (pendingSeekAcks == 0 && sourceInfo.seekAcks > 0) {
|
||||||
|
for (Player.EventListener listener : listeners) {
|
||||||
|
listener.onSeekProcessed();
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: {
|
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: {
|
||||||
|
@ -701,12 +701,12 @@ import java.io.IOException;
|
|||||||
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
|
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
|
||||||
// The seek position was valid for the timeline that it was performed into, but the
|
// 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.
|
// 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
|
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
|
||||||
// (firstPeriodIndex,0) isn't ignored.
|
// (firstPeriodIndex,0) isn't ignored.
|
||||||
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
||||||
setState(Player.STATE_ENDED);
|
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.
|
// Reset, but retain the source so that it can still be used should a seek occur.
|
||||||
resetInternal(false);
|
resetInternal(false);
|
||||||
return;
|
return;
|
||||||
@ -1031,7 +1031,7 @@ import java.io.IOException;
|
|||||||
MediaPeriodId periodId =
|
MediaPeriodId periodId =
|
||||||
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
|
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
|
||||||
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs, positionUs);
|
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs, positionUs);
|
||||||
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
|
notifySourceInfoRefresh(manifest, playbackInfo, processedInitialSeekCount);
|
||||||
}
|
}
|
||||||
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||||
if (timeline.isEmpty()) {
|
if (timeline.isEmpty()) {
|
||||||
@ -1182,22 +1182,23 @@ import java.io.IOException;
|
|||||||
int processedInitialSeekCount) {
|
int processedInitialSeekCount) {
|
||||||
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
|
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
|
||||||
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
|
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
|
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
|
||||||
// (firstPeriodIndex,0) isn't ignored.
|
// (firstPeriodIndex,0) isn't ignored.
|
||||||
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
||||||
setState(Player.STATE_ENDED);
|
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.
|
// Reset, but retain the source so that it can still be used should a seek occur.
|
||||||
resetInternal(false);
|
resetInternal(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifySourceInfoRefresh(Object manifest) {
|
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,
|
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED,
|
||||||
new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget();
|
new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget();
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,13 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
void onPlaybackParametersChanged(PlaybackParameters playbackParameters);
|
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.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSeekProcessed() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user