mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Add stop with position reset to Player interface.
The ExoPlayerImpl implementation forwards the stop request with this optional parameter. To ensure correct masking (e.g. when timeline updates arrive after calling reset in ExoPlayerImpl but before resetInternal in ExoPlayerImplInternal), we use the existing prepareAck counter and extend it also count stop operations. For this to work, we also return the updated empty timeline after finishing the reset. The CastPlayer doesn't support the two reset options so far. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=177132107
This commit is contained in:
parent
d84398788a
commit
f46cb907b7
11 changed files with 349 additions and 66 deletions
|
|
@ -18,11 +18,12 @@
|
|||
use this with `FfmpegAudioRenderer`.
|
||||
* Support extraction and decoding of Dolby Atmos
|
||||
([#2465](https://github.com/google/ExoPlayer/issues/2465)).
|
||||
* Added a reason to `EventListener.onTimelineChanged` to distinguish between
|
||||
* Add a reason to `EventListener.onTimelineChanged` to distinguish between
|
||||
initial preparation, reset and dynamic updates.
|
||||
* DefaultTrackSelector: Support undefined language text track selection when the
|
||||
preferred language is not available
|
||||
([#2980](https://github.com/google/ExoPlayer/issues/2980)).
|
||||
* Add optional parameter to `Player.stop` to reset the player when stopping.
|
||||
|
||||
### 2.6.0 ###
|
||||
|
||||
|
|
|
|||
|
|
@ -359,7 +359,13 @@ public final class CastPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public void stop() {
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
if (remoteMediaClient != null) {
|
||||
// TODO(b/69792021): Support or emulate stop without position reset.
|
||||
remoteMediaClient.stop();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -621,4 +621,158 @@ public final class ExoPlayerTest extends TestCase {
|
|||
new ExoPlayerTestRunner.Builder().setMediaSource(mediaSource).setActionSchedule(actionSchedule)
|
||||
.build().start().blockUntilEnded(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
public void testStopDoesNotResetPosition() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopDoesNotResetPosition")
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.stop()
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertTimelinesEqual(timeline);
|
||||
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED);
|
||||
testRunner.assertNoPositionDiscontinuities();
|
||||
}
|
||||
|
||||
public void testStopWithoutResetDoesNotResetPosition() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopWithoutResetDoesNotReset")
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.stop(/* reset= */ false)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertTimelinesEqual(timeline);
|
||||
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED);
|
||||
testRunner.assertNoPositionDiscontinuities();
|
||||
}
|
||||
|
||||
public void testStopWithResetDoesResetPosition() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopWithResetDoesReset")
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.stop(/* reset= */ true)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY);
|
||||
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED,
|
||||
Player.TIMELINE_CHANGE_REASON_RESET);
|
||||
testRunner.assertNoPositionDiscontinuities();
|
||||
}
|
||||
|
||||
public void testStopWithoutResetReleasesMediaSource() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
final FakeMediaSource mediaSource =
|
||||
new FakeMediaSource(timeline, /* manifest= */ null, Builder.VIDEO_FORMAT);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopReleasesMediaSource")
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.stop(/* reset= */ false)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilActionScheduleFinished(TIMEOUT_MS);
|
||||
mediaSource.assertReleased();
|
||||
testRunner.blockUntilEnded(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
public void testStopWithResetReleasesMediaSource() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
final FakeMediaSource mediaSource =
|
||||
new FakeMediaSource(timeline, /* manifest= */ null, Builder.VIDEO_FORMAT);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopReleasesMediaSource")
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.stop(/* reset= */ true)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilActionScheduleFinished(TIMEOUT_MS);
|
||||
mediaSource.assertReleased();
|
||||
testRunner.blockUntilEnded(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
public void testRepreparationDoesNotResetAfterStopWithReset() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
MediaSource secondSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepreparationAfterStop")
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.stop(/* reset= */ true)
|
||||
.waitForPlaybackState(Player.STATE_IDLE)
|
||||
.prepareSource(secondSource)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.setExpectedPlayerEndedCount(2)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline);
|
||||
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED,
|
||||
Player.TIMELINE_CHANGE_REASON_RESET, Player.TIMELINE_CHANGE_REASON_PREPARED);
|
||||
testRunner.assertNoPositionDiscontinuities();
|
||||
}
|
||||
|
||||
public void testSeekBeforeRepreparationPossibleAfterStopWithReset() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 2);
|
||||
MediaSource secondSource = new FakeMediaSource(secondTimeline, null, Builder.VIDEO_FORMAT);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testSeekAfterStopWithReset")
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.stop(/* reset= */ true)
|
||||
.waitForPlaybackState(Player.STATE_IDLE)
|
||||
// If we were still using the first timeline, this would throw.
|
||||
.seek(/* windowIndex= */ 1, /* positionMs= */ 0)
|
||||
.prepareSource(secondSource)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.setExpectedPlayerEndedCount(2)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, secondTimeline);
|
||||
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED,
|
||||
Player.TIMELINE_CHANGE_REASON_RESET, Player.TIMELINE_CHANGE_REASON_PREPARED);
|
||||
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
|
||||
testRunner.assertPlayedPeriodIndices(0, 1);
|
||||
}
|
||||
|
||||
public void testStopDuringPreparationOverwritesPreparation() throws Exception {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopOverwritesPrepare")
|
||||
.waitForPlaybackState(Player.STATE_BUFFERING)
|
||||
.stop(true)
|
||||
.build();
|
||||
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
|
||||
.setTimeline(timeline)
|
||||
.setActionSchedule(actionSchedule)
|
||||
.build()
|
||||
.start()
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
testRunner.assertTimelinesEqual(Timeline.EMPTY);
|
||||
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED);
|
||||
testRunner.assertNoPositionDiscontinuities();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
private boolean shuffleModeEnabled;
|
||||
private int playbackState;
|
||||
private int pendingSeekAcks;
|
||||
private int pendingPrepareAcks;
|
||||
private int pendingPrepareOrStopAcks;
|
||||
private boolean waitingForInitialTimeline;
|
||||
private boolean isLoading;
|
||||
private TrackGroupArray trackGroups;
|
||||
|
|
@ -134,35 +134,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
|
||||
if (!resetPosition) {
|
||||
maskingWindowIndex = getCurrentWindowIndex();
|
||||
maskingPeriodIndex = getCurrentPeriodIndex();
|
||||
maskingWindowPositionMs = getCurrentPosition();
|
||||
} else {
|
||||
maskingWindowIndex = 0;
|
||||
maskingPeriodIndex = 0;
|
||||
maskingWindowPositionMs = 0;
|
||||
}
|
||||
if (resetState) {
|
||||
if (!playbackInfo.timeline.isEmpty() || playbackInfo.manifest != null) {
|
||||
playbackInfo = playbackInfo.copyWithTimeline(Timeline.EMPTY, null);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTimelineChanged(playbackInfo.timeline, playbackInfo.manifest,
|
||||
Player.TIMELINE_CHANGE_REASON_RESET);
|
||||
}
|
||||
}
|
||||
if (tracksSelected) {
|
||||
tracksSelected = false;
|
||||
trackGroups = TrackGroupArray.EMPTY;
|
||||
trackSelections = emptyTrackSelections;
|
||||
trackSelector.onSelectionActivated(null);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTracksChanged(trackGroups, trackSelections);
|
||||
}
|
||||
}
|
||||
}
|
||||
waitingForInitialTimeline = true;
|
||||
pendingPrepareAcks++;
|
||||
pendingPrepareOrStopAcks++;
|
||||
reset(resetPosition, resetState);
|
||||
internalPlayer.prepare(mediaSource, resetPosition);
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +260,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public void stop() {
|
||||
internalPlayer.stop();
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
pendingPrepareOrStopAcks++;
|
||||
reset(/* resetPosition= */ reset, /* resetState= */ reset);
|
||||
internalPlayer.stop(reset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -468,14 +449,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
|
||||
int prepareAcks = msg.arg1;
|
||||
int prepareOrStopAcks = msg.arg1;
|
||||
int seekAcks = msg.arg2;
|
||||
handlePlaybackInfo((PlaybackInfo) msg.obj, prepareAcks, seekAcks, false,
|
||||
handlePlaybackInfo((PlaybackInfo) msg.obj, prepareOrStopAcks, seekAcks, false,
|
||||
/* ignored */ DISCONTINUITY_REASON_INTERNAL);
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: {
|
||||
if (pendingPrepareAcks == 0) {
|
||||
if (pendingPrepareOrStopAcks == 0) {
|
||||
TrackSelectorResult trackSelectorResult = (TrackSelectorResult) msg.obj;
|
||||
tracksSelected = true;
|
||||
trackGroups = trackSelectorResult.groups;
|
||||
|
|
@ -520,12 +501,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
}
|
||||
}
|
||||
|
||||
private void handlePlaybackInfo(PlaybackInfo playbackInfo, int prepareAcks, int seekAcks,
|
||||
private void handlePlaybackInfo(PlaybackInfo playbackInfo, int prepareOrStopAcks, int seekAcks,
|
||||
boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason) {
|
||||
Assertions.checkNotNull(playbackInfo.timeline);
|
||||
pendingPrepareAcks -= prepareAcks;
|
||||
pendingPrepareOrStopAcks -= prepareOrStopAcks;
|
||||
pendingSeekAcks -= seekAcks;
|
||||
if (pendingPrepareAcks == 0 && pendingSeekAcks == 0) {
|
||||
if (pendingPrepareOrStopAcks == 0 && pendingSeekAcks == 0) {
|
||||
boolean timelineOrManifestChanged = this.playbackInfo.timeline != playbackInfo.timeline
|
||||
|| this.playbackInfo.manifest != playbackInfo.manifest;
|
||||
this.playbackInfo = playbackInfo;
|
||||
|
|
@ -556,6 +537,36 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
}
|
||||
}
|
||||
|
||||
private void reset(boolean resetPosition, boolean resetState) {
|
||||
if (resetPosition) {
|
||||
maskingWindowIndex = 0;
|
||||
maskingPeriodIndex = 0;
|
||||
maskingWindowPositionMs = 0;
|
||||
} else {
|
||||
maskingWindowIndex = getCurrentWindowIndex();
|
||||
maskingPeriodIndex = getCurrentPeriodIndex();
|
||||
maskingWindowPositionMs = getCurrentPosition();
|
||||
}
|
||||
if (resetState) {
|
||||
if (!playbackInfo.timeline.isEmpty() || playbackInfo.manifest != null) {
|
||||
playbackInfo = playbackInfo.copyWithTimeline(Timeline.EMPTY, null);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTimelineChanged(playbackInfo.timeline, playbackInfo.manifest,
|
||||
Player.TIMELINE_CHANGE_REASON_RESET);
|
||||
}
|
||||
}
|
||||
if (tracksSelected) {
|
||||
tracksSelected = false;
|
||||
trackGroups = TrackGroupArray.EMPTY;
|
||||
trackSelections = emptyTrackSelections;
|
||||
trackSelector.onSelectionActivated(null);
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onTracksChanged(trackGroups, trackSelections);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long playbackInfoPositionUsToWindowPositionMs(long positionUs) {
|
||||
long positionMs = C.usToMs(positionUs);
|
||||
if (!playbackInfo.periodId.isAd()) {
|
||||
|
|
@ -566,7 +577,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
}
|
||||
|
||||
private boolean shouldMaskPosition() {
|
||||
return playbackInfo.timeline.isEmpty() || pendingSeekAcks > 0 || pendingPrepareAcks > 0;
|
||||
return playbackInfo.timeline.isEmpty() || pendingSeekAcks > 0 || pendingPrepareOrStopAcks > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,8 +196,8 @@ import java.io.IOException;
|
|||
handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
handler.sendEmptyMessage(MSG_STOP);
|
||||
public void stop(boolean reset) {
|
||||
handler.obtainMessage(MSG_STOP, reset ? 1 : 0, 0).sendToTarget();
|
||||
}
|
||||
|
||||
public void sendMessages(ExoPlayerMessage... messages) {
|
||||
|
|
@ -324,7 +324,7 @@ import java.io.IOException;
|
|||
return true;
|
||||
}
|
||||
case MSG_STOP: {
|
||||
stopInternal();
|
||||
stopInternal(/* reset= */ msg.arg1 != 0);
|
||||
return true;
|
||||
}
|
||||
case MSG_RELEASE: {
|
||||
|
|
@ -357,18 +357,18 @@ import java.io.IOException;
|
|||
} catch (ExoPlaybackException e) {
|
||||
Log.e(TAG, "Renderer error.", e);
|
||||
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
|
||||
stopInternal();
|
||||
stopInternal(/* reset= */ false);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Source error.", e);
|
||||
eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget();
|
||||
stopInternal();
|
||||
stopInternal(/* reset= */ false);
|
||||
return true;
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Internal runtime error.", e);
|
||||
eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e))
|
||||
.sendToTarget();
|
||||
stopInternal();
|
||||
stopInternal(/* reset= */ false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -394,8 +394,8 @@ import java.io.IOException;
|
|||
resetInternal(/* releaseMediaSource= */ true, resetPosition);
|
||||
loadControl.onPrepared();
|
||||
this.mediaSource = mediaSource;
|
||||
mediaSource.prepareSource(player, /* isTopLevelSource= */ true, /* listener = */ this);
|
||||
setState(Player.STATE_BUFFERING);
|
||||
mediaSource.prepareSource(player, /* isTopLevelSource= */ true, /* listener = */ this);
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
|
||||
|
|
@ -765,8 +765,23 @@ import java.io.IOException;
|
|||
mediaClock.setPlaybackParameters(playbackParameters);
|
||||
}
|
||||
|
||||
private void stopInternal() {
|
||||
resetInternal(/* releaseMediaSource= */ true, /* resetPosition= */ false);
|
||||
private void stopInternal(boolean reset) {
|
||||
// Releasing the internal player sets the timeline to null. Use the current timeline or
|
||||
// Timeline.EMPTY for notifying the eventHandler.
|
||||
Timeline publicTimeline = reset || playbackInfo.timeline == null
|
||||
? Timeline.EMPTY : playbackInfo.timeline;
|
||||
Object publicManifest = reset ? null : playbackInfo.manifest;
|
||||
resetInternal(/* releaseMediaSource= */ true, reset);
|
||||
PlaybackInfo publicPlaybackInfo = playbackInfo.copyWithTimeline(publicTimeline, publicManifest);
|
||||
if (reset) {
|
||||
// When resetting the state, set the playback position to 0 (instead of C.TIME_UNSET) for
|
||||
// notifying the eventHandler.
|
||||
publicPlaybackInfo =
|
||||
publicPlaybackInfo.fromNewPosition(playbackInfo.periodId.periodIndex, 0, C.TIME_UNSET);
|
||||
}
|
||||
int prepareOrStopAcks = pendingPrepareCount + 1;
|
||||
pendingPrepareCount = 0;
|
||||
notifySourceInfoRefresh(prepareOrStopAcks, 0, publicPlaybackInfo);
|
||||
loadControl.onStopped();
|
||||
setState(Player.STATE_IDLE);
|
||||
}
|
||||
|
|
@ -1170,13 +1185,14 @@ import java.io.IOException;
|
|||
notifySourceInfoRefresh(0, 0);
|
||||
}
|
||||
|
||||
private void notifySourceInfoRefresh(int prepareAcks, int seekAcks) {
|
||||
notifySourceInfoRefresh(prepareAcks, seekAcks, playbackInfo);
|
||||
private void notifySourceInfoRefresh(int prepareOrStopAcks, int seekAcks) {
|
||||
notifySourceInfoRefresh(prepareOrStopAcks, seekAcks, playbackInfo);
|
||||
}
|
||||
|
||||
private void notifySourceInfoRefresh(int prepareAcks, int seekAcks, PlaybackInfo playbackInfo) {
|
||||
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, prepareAcks, seekAcks, playbackInfo)
|
||||
.sendToTarget();
|
||||
private void notifySourceInfoRefresh(int prepareOrStopAcks, int seekAcks,
|
||||
PlaybackInfo playbackInfo) {
|
||||
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, prepareOrStopAcks, seekAcks,
|
||||
playbackInfo).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -429,17 +429,29 @@ public interface Player {
|
|||
PlaybackParameters getPlaybackParameters();
|
||||
|
||||
/**
|
||||
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
|
||||
* is to pause playback.
|
||||
* <p>
|
||||
* Calling this method will cause the playback state to transition to {@link #STATE_IDLE}. The
|
||||
* Stops playback without resetting the player. Use {@code setPlayWhenReady(false)} rather than
|
||||
* this method if the intention is to pause playback.
|
||||
*
|
||||
* <p>Calling this method will cause the playback state to transition to {@link #STATE_IDLE}. The
|
||||
* player instance can still be used, and {@link #release()} must still be called on the player if
|
||||
* it's no longer required.
|
||||
* <p>
|
||||
* Calling this method does not reset the playback position.
|
||||
*
|
||||
* <p>Calling this method does not reset the playback position.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Stops playback and optionally resets the player. Use {@code setPlayWhenReady(false)} rather
|
||||
* than this method if the intention is to pause playback.
|
||||
*
|
||||
* <p>Calling this method will cause the playback state to transition to {@link #STATE_IDLE}. The
|
||||
* player instance can still be used, and {@link #release()} must still be called on the player if
|
||||
* it's no longer required.
|
||||
*
|
||||
* @param reset Whether the player should be reset.
|
||||
*/
|
||||
void stop(boolean reset);
|
||||
|
||||
/**
|
||||
* Releases the player. This method must be called when the player is no longer required. The
|
||||
* player must not be used after calling this method.
|
||||
|
|
|
|||
|
|
@ -133,6 +133,9 @@ public class SimpleExoPlayer implements ExoPlayer {
|
|||
case C.TRACK_TYPE_AUDIO:
|
||||
audioRendererCount++;
|
||||
break;
|
||||
default:
|
||||
// Don't count other track types.
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.videoRendererCount = videoRendererCount;
|
||||
|
|
@ -692,6 +695,11 @@ public class SimpleExoPlayer implements ExoPlayer {
|
|||
player.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
player.stop(reset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
player.release();
|
||||
|
|
|
|||
|
|
@ -89,45 +89,89 @@ public abstract class Action {
|
|||
Surface surface);
|
||||
|
||||
/**
|
||||
* Calls {@link Player#seekTo(long)}.
|
||||
* Calls {@link Player#seekTo(long)} or {@link Player#seekTo(int, long)}.
|
||||
*/
|
||||
public static final class Seek extends Action {
|
||||
|
||||
private final Integer windowIndex;
|
||||
private final long positionMs;
|
||||
|
||||
/**
|
||||
* Action calls {@link Player#seekTo(long)}.
|
||||
*
|
||||
* @param tag A tag to use for logging.
|
||||
* @param positionMs The seek position.
|
||||
*/
|
||||
public Seek(String tag, long positionMs) {
|
||||
super(tag, "Seek:" + positionMs);
|
||||
this.windowIndex = null;
|
||||
this.positionMs = positionMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action calls {@link Player#seekTo(int, long)}.
|
||||
*
|
||||
* @param tag A tag to use for logging.
|
||||
* @param windowIndex The window to seek to.
|
||||
* @param positionMs The seek position.
|
||||
*/
|
||||
public Seek(String tag, int windowIndex, long positionMs) {
|
||||
super(tag, "Seek:" + positionMs);
|
||||
this.windowIndex = windowIndex;
|
||||
this.positionMs = positionMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doActionImpl(SimpleExoPlayer player, MappingTrackSelector trackSelector,
|
||||
Surface surface) {
|
||||
player.seekTo(positionMs);
|
||||
if (windowIndex == null) {
|
||||
player.seekTo(positionMs);
|
||||
} else {
|
||||
player.seekTo(windowIndex, positionMs);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Player#stop()}.
|
||||
* Calls {@link Player#stop()} or {@link Player#stop(boolean)}.
|
||||
*/
|
||||
public static final class Stop extends Action {
|
||||
|
||||
private static final String STOP_ACTION_TAG = "Stop";
|
||||
|
||||
private final Boolean reset;
|
||||
|
||||
/**
|
||||
* Action will call {@link Player#stop()}.
|
||||
*
|
||||
* @param tag A tag to use for logging.
|
||||
*/
|
||||
public Stop(String tag) {
|
||||
super(tag, "Stop");
|
||||
super(tag, STOP_ACTION_TAG);
|
||||
this.reset = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action will call {@link Player#stop(boolean)}.
|
||||
*
|
||||
* @param tag A tag to use for logging.
|
||||
* @param reset The value to pass to {@link Player#stop(boolean)}.
|
||||
*/
|
||||
public Stop(String tag, boolean reset) {
|
||||
super(tag, STOP_ACTION_TAG);
|
||||
this.reset = reset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doActionImpl(SimpleExoPlayer player, MappingTrackSelector trackSelector,
|
||||
Surface surface) {
|
||||
player.stop();
|
||||
if (reset == null) {
|
||||
player.stop();
|
||||
} else {
|
||||
player.stop(reset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,6 +160,17 @@ public final class ActionSchedule {
|
|||
return apply(new Seek(tag, positionMs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a seek action to be executed.
|
||||
*
|
||||
* @param windowIndex The window to seek to.
|
||||
* @param positionMs The seek position.
|
||||
* @return The builder, for convenience.
|
||||
*/
|
||||
public Builder seek(int windowIndex, long positionMs) {
|
||||
return apply(new Seek(tag, windowIndex, positionMs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a seek action to be executed and waits until playback resumes after the seek.
|
||||
*
|
||||
|
|
@ -192,6 +203,16 @@ public final class ActionSchedule {
|
|||
return apply(new Stop(tag));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a stop action to be executed.
|
||||
*
|
||||
* @param reset Whether the player should be reset.
|
||||
* @return The builder, for convenience.
|
||||
*/
|
||||
public Builder stop(boolean reset) {
|
||||
return apply(new Stop(tag, reset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a play action to be executed.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -166,13 +166,18 @@ public class FakeSimpleExoPlayer extends SimpleExoPlayer {
|
|||
|
||||
@Override
|
||||
public void stop() {
|
||||
stop(/* quitPlaybackThread= */ false);
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
stopPlayback(/* quitPlaybackThread= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ThreadJoinLoop")
|
||||
public void release() {
|
||||
stop(/* quitPlaybackThread= */ true);
|
||||
stopPlayback(/* quitPlaybackThread= */ true);
|
||||
while (playbackThread.isAlive()) {
|
||||
try {
|
||||
playbackThread.join();
|
||||
|
|
@ -513,7 +518,7 @@ public class FakeSimpleExoPlayer extends SimpleExoPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
private void stop(final boolean quitPlaybackThread) {
|
||||
private void stopPlayback(final boolean quitPlaybackThread) {
|
||||
playbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run () {
|
||||
|
|
|
|||
|
|
@ -130,6 +130,11 @@ public abstract class StubExoPlayer implements ExoPlayer {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean resetStateAndPosition) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
|||
Loading…
Reference in a new issue