mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Fix threading issues between ExoPlayerImpl/ExoPlayerImplInternal
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=138758739
This commit is contained in:
parent
7ac1cab2d5
commit
8cc7dfda7d
3 changed files with 112 additions and 67 deletions
|
|
@ -112,6 +112,19 @@ public interface ExoPlayer {
|
||||||
*/
|
*/
|
||||||
interface EventListener {
|
interface EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the timeline and/or manifest has been refreshed.
|
||||||
|
* <p>
|
||||||
|
* Note that if the timeline has changed then a position discontinuity may also have occurred.
|
||||||
|
* For example the current period index may have changed as a result of periods being added or
|
||||||
|
* removed from the timeline. The will <em>not</em> be reported via a separate call to
|
||||||
|
* {@link #onPositionDiscontinuity()}.
|
||||||
|
*
|
||||||
|
* @param timeline The latest timeline, or null if the timeline is being cleared.
|
||||||
|
* @param manifest The latest manifest, or null if the manifest is being cleared.
|
||||||
|
*/
|
||||||
|
void onTimelineChanged(Timeline timeline, Object manifest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the available or selected tracks change.
|
* Called when the available or selected tracks change.
|
||||||
*
|
*
|
||||||
|
|
@ -138,14 +151,6 @@ public interface ExoPlayer {
|
||||||
*/
|
*/
|
||||||
void onPlayerStateChanged(boolean playWhenReady, int playbackState);
|
void onPlayerStateChanged(boolean playWhenReady, int playbackState);
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when timeline and/or manifest has been refreshed.
|
|
||||||
*
|
|
||||||
* @param timeline The latest timeline, or null if the timeline is being cleared.
|
|
||||||
* @param manifest The latest manifest, or null if the manifest is being cleared.
|
|
||||||
*/
|
|
||||||
void onTimelineChanged(Timeline timeline, Object manifest);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an error occurs. The playback state will transition to {@link #STATE_IDLE}
|
* Called when an error occurs. The playback state will transition to {@link #STATE_IDLE}
|
||||||
* immediately after this method is called. The player instance can still be used, and
|
* immediately after this method is called. The player instance can still be used, and
|
||||||
|
|
@ -156,9 +161,14 @@ public interface ExoPlayer {
|
||||||
void onPlayerError(ExoPlaybackException error);
|
void onPlayerError(ExoPlaybackException error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a position discontinuity occurs. Position discontinuities occur when seeks are
|
* Called when a position discontinuity occurs without a change to the timeline. A position
|
||||||
* performed, when playbacks transition from one period in the timeline to the next, and when
|
* discontinuity occurs when the current window or period index changes (as a result of playback
|
||||||
* the player introduces discontinuities internally.
|
* transitioning from one period in the timeline to the next), or when the playback position
|
||||||
|
* jumps within the period currently being played (as a result of a seek being performed, or
|
||||||
|
* when the source introduces a discontinuity internally).
|
||||||
|
* <p>
|
||||||
|
* When a position discontinuity occurs as a result of a change to the timeline this method is
|
||||||
|
* <em>not</em> called. {@link #onTimelineChanged(Timeline, Object)} is called in this case.
|
||||||
*/
|
*/
|
||||||
void onPositionDiscontinuity();
|
void onPositionDiscontinuity();
|
||||||
|
|
||||||
|
|
@ -403,7 +413,7 @@ public interface ExoPlayer {
|
||||||
Timeline getCurrentTimeline();
|
Timeline getCurrentTimeline();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the index of the period currently being played, or {@link C#INDEX_UNSET} if unknown.
|
* Returns the index of the period currently being played.
|
||||||
*/
|
*/
|
||||||
int getCurrentPeriodIndex();
|
int getCurrentPeriodIndex();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
|
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayerImplInternal.SourceInfo;
|
||||||
import com.google.android.exoplayer2.ExoPlayerImplInternal.TrackInfo;
|
import com.google.android.exoplayer2.ExoPlayerImplInternal.TrackInfo;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
|
@ -329,8 +329,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
||||||
pendingSeekAcks -= msg.arg1;
|
if (--pendingSeekAcks == 0) {
|
||||||
if (pendingSeekAcks == 0) {
|
|
||||||
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
listener.onPositionDiscontinuity();
|
listener.onPositionDiscontinuity();
|
||||||
|
|
@ -348,10 +347,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
|
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
|
||||||
@SuppressWarnings("unchecked")
|
SourceInfo sourceInfo = (SourceInfo) msg.obj;
|
||||||
Pair<Timeline, Object> timelineAndManifest = (Pair<Timeline, Object>) msg.obj;
|
timeline = sourceInfo.timeline;
|
||||||
timeline = timelineAndManifest.first;
|
manifest = sourceInfo.manifest;
|
||||||
manifest = timelineAndManifest.second;
|
playbackInfo = sourceInfo.playbackInfo;
|
||||||
|
pendingSeekAcks -= sourceInfo.seekAcks;
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
listener.onTimelineChanged(timeline, manifest);
|
listener.onTimelineChanged(timeline, manifest);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,22 @@ import java.io.IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class SourceInfo {
|
||||||
|
|
||||||
|
public final Timeline timeline;
|
||||||
|
public final Object manifest;
|
||||||
|
public final PlaybackInfo playbackInfo;
|
||||||
|
public final int seekAcks;
|
||||||
|
|
||||||
|
public SourceInfo(Timeline timeline, Object manifest, PlaybackInfo playbackInfo, int seekAcks) {
|
||||||
|
this.timeline = timeline;
|
||||||
|
this.manifest = manifest;
|
||||||
|
this.playbackInfo = playbackInfo;
|
||||||
|
this.seekAcks = seekAcks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static final String TAG = "ExoPlayerImplInternal";
|
private static final String TAG = "ExoPlayerImplInternal";
|
||||||
|
|
||||||
// External messages
|
// External messages
|
||||||
|
|
@ -533,15 +549,14 @@ import java.io.IOException;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (periodIndex == playbackInfo.periodIndex
|
if (periodIndex == playbackInfo.periodIndex
|
||||||
&& ((periodPositionUs == C.TIME_UNSET && playbackInfo.positionUs == C.TIME_UNSET)
|
&& ((periodPositionUs / 1000) == (playbackInfo.positionUs / 1000))) {
|
||||||
|| ((periodPositionUs / 1000) == (playbackInfo.positionUs / 1000)))) {
|
|
||||||
// Seek position equals the current position. Do nothing.
|
// Seek position equals the current position. Do nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
periodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
|
periodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
|
||||||
} finally {
|
} finally {
|
||||||
playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
|
playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
|
||||||
eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_SEEK_ACK, playbackInfo).sendToTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,11 +566,10 @@ import java.io.IOException;
|
||||||
rebuffering = false;
|
rebuffering = false;
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
|
|
||||||
if (periodPositionUs == C.TIME_UNSET || (readingPeriodHolder != playingPeriodHolder
|
if (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index
|
||||||
&& (periodIndex == playingPeriodHolder.index
|
|| periodIndex == readingPeriodHolder.index)) {
|
||||||
|| periodIndex == readingPeriodHolder.index))) {
|
// Clear the timeline because a renderer is reading ahead to the next period and the seek is
|
||||||
// Clear the timeline because either the seek position is not known, or a renderer is reading
|
// to either the playing or reading period.
|
||||||
// ahead to the next period and the seek is to either the playing or reading period.
|
|
||||||
periodIndex = C.INDEX_UNSET;
|
periodIndex = C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,10 +619,8 @@ import java.io.IOException;
|
||||||
playingPeriodHolder = null;
|
playingPeriodHolder = null;
|
||||||
readingPeriodHolder = null;
|
readingPeriodHolder = null;
|
||||||
loadingPeriodHolder = null;
|
loadingPeriodHolder = null;
|
||||||
if (periodPositionUs != C.TIME_UNSET) {
|
|
||||||
resetRendererPosition(periodPositionUs);
|
resetRendererPosition(periodPositionUs);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||||
return periodPositionUs;
|
return periodPositionUs;
|
||||||
|
|
@ -821,30 +833,37 @@ import java.io.IOException;
|
||||||
|
|
||||||
private void handleSourceInfoRefreshed(Pair<Timeline, Object> timelineAndManifest)
|
private void handleSourceInfoRefreshed(Pair<Timeline, Object> timelineAndManifest)
|
||||||
throws ExoPlaybackException, IOException {
|
throws ExoPlaybackException, IOException {
|
||||||
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, timelineAndManifest).sendToTarget();
|
Timeline oldTimeline = timeline;
|
||||||
Timeline oldTimeline = this.timeline;
|
timeline = timelineAndManifest.first;
|
||||||
this.timeline = timelineAndManifest.first;
|
Object manifest = timelineAndManifest.second;
|
||||||
|
|
||||||
|
if (oldTimeline == null) {
|
||||||
if (pendingInitialSeekCount > 0) {
|
if (pendingInitialSeekCount > 0) {
|
||||||
Pair<Integer, Long> periodPosition = resolveSeekPosition(pendingSeekPosition);
|
Pair<Integer, Long> periodPosition = resolveSeekPosition(pendingSeekPosition);
|
||||||
if (periodPosition == null) {
|
if (periodPosition == null) {
|
||||||
// TODO: We should probably propagate an error here.
|
// TODO: We should probably propagate an error here.
|
||||||
// We failed to resolve the seek position. Stop the player.
|
// We failed to resolve the seek position. Stop the player.
|
||||||
|
finishSourceInfoRefresh(manifest, false);
|
||||||
stopInternal();
|
stopInternal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second);
|
playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second);
|
||||||
eventHandler.obtainMessage(MSG_SEEK_ACK, pendingInitialSeekCount, 0, playbackInfo)
|
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||||
.sendToTarget();
|
Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET);
|
||||||
pendingInitialSeekCount = 0;
|
playbackInfo = new PlaybackInfo(defaultPosition.first, defaultPosition.second);
|
||||||
pendingSeekPosition = null;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the loaded periods to take into account the new timeline.
|
// Update the loaded periods to take into account the new timeline.
|
||||||
if (playingPeriodHolder != null) {
|
if (playingPeriodHolder != null) {
|
||||||
int index = timeline.getIndexOfPeriod(playingPeriodHolder.uid);
|
int index = timeline.getIndexOfPeriod(playingPeriodHolder.uid);
|
||||||
if (index == C.INDEX_UNSET) {
|
if (index == C.INDEX_UNSET) {
|
||||||
attemptRestart(playingPeriodHolder.index, oldTimeline, timeline);
|
boolean restarted = attemptRestart(playingPeriodHolder.index, oldTimeline, timeline);
|
||||||
|
finishSourceInfoRefresh(manifest, true);
|
||||||
|
if (!restarted) {
|
||||||
|
// TODO: We should probably propagate an error here.
|
||||||
|
stopInternal();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -873,8 +892,8 @@ import java.io.IOException;
|
||||||
long newPositionUs = seekToPeriodPosition(index, playbackInfo.positionUs);
|
long newPositionUs = seekToPeriodPosition(index, playbackInfo.positionUs);
|
||||||
if (newPositionUs != playbackInfo.positionUs) {
|
if (newPositionUs != playbackInfo.positionUs) {
|
||||||
playbackInfo = new PlaybackInfo(index, newPositionUs);
|
playbackInfo = new PlaybackInfo(index, newPositionUs);
|
||||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
|
||||||
}
|
}
|
||||||
|
finishSourceInfoRefresh(manifest, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -899,7 +918,12 @@ import java.io.IOException;
|
||||||
Object uid = loadingPeriodHolder.uid;
|
Object uid = loadingPeriodHolder.uid;
|
||||||
int index = timeline.getIndexOfPeriod(uid);
|
int index = timeline.getIndexOfPeriod(uid);
|
||||||
if (index == C.INDEX_UNSET) {
|
if (index == C.INDEX_UNSET) {
|
||||||
attemptRestart(loadingPeriodHolder.index, oldTimeline, timeline);
|
boolean restarted = attemptRestart(playingPeriodHolder.index, oldTimeline, timeline);
|
||||||
|
finishSourceInfoRefresh(manifest, true);
|
||||||
|
if (!restarted) {
|
||||||
|
// TODO: We should probably propagate an error here.
|
||||||
|
stopInternal();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
int windowIndex = timeline.getPeriod(index, this.period).windowIndex;
|
int windowIndex = timeline.getPeriod(index, this.period).windowIndex;
|
||||||
|
|
@ -908,7 +932,6 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
|
|
||||||
if (oldTimeline != null) {
|
if (oldTimeline != null) {
|
||||||
int newPlayingIndex = playingPeriodHolder != null ? playingPeriodHolder.index
|
int newPlayingIndex = playingPeriodHolder != null ? playingPeriodHolder.index
|
||||||
: loadingPeriodHolder != null ? loadingPeriodHolder.index : C.INDEX_UNSET;
|
: loadingPeriodHolder != null ? loadingPeriodHolder.index : C.INDEX_UNSET;
|
||||||
|
|
@ -916,18 +939,16 @@ import java.io.IOException;
|
||||||
&& newPlayingIndex != playbackInfo.periodIndex) {
|
&& newPlayingIndex != playbackInfo.periodIndex) {
|
||||||
playbackInfo = new PlaybackInfo(newPlayingIndex, playbackInfo.positionUs);
|
playbackInfo = new PlaybackInfo(newPlayingIndex, playbackInfo.positionUs);
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finishSourceInfoRefresh(manifest, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attemptRestart(int oldPeriodIndex, Timeline oldTimeline, Timeline newTimeline) {
|
private boolean attemptRestart(int oldPeriodIndex, Timeline oldTimeline, Timeline newTimeline) {
|
||||||
int newPeriodIndex = resolveSubsequentPeriod(oldPeriodIndex, oldTimeline, newTimeline);
|
int newPeriodIndex = resolveSubsequentPeriod(oldPeriodIndex, oldTimeline, newTimeline);
|
||||||
if (newPeriodIndex == C.INDEX_UNSET) {
|
if (newPeriodIndex == C.INDEX_UNSET) {
|
||||||
// TODO: We should probably propagate an error here.
|
|
||||||
// We failed to find a replacement period. Stop the player.
|
// We failed to find a replacement period. Stop the player.
|
||||||
stopInternal();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release all loaded periods.
|
// Release all loaded periods.
|
||||||
|
|
@ -943,9 +964,18 @@ import java.io.IOException;
|
||||||
timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET);
|
timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET);
|
||||||
newPeriodIndex = defaultPosition.first;
|
newPeriodIndex = defaultPosition.first;
|
||||||
long newPlayingPositionUs = defaultPosition.second;
|
long newPlayingPositionUs = defaultPosition.second;
|
||||||
|
|
||||||
playbackInfo = new PlaybackInfo(newPeriodIndex, newPlayingPositionUs);
|
playbackInfo = new PlaybackInfo(newPeriodIndex, newPlayingPositionUs);
|
||||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishSourceInfoRefresh(Object manifest, boolean processedInitialSeeks) {
|
||||||
|
SourceInfo sourceInfo = new SourceInfo(timeline, manifest, playbackInfo,
|
||||||
|
processedInitialSeeks ? pendingInitialSeekCount : 0);
|
||||||
|
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, sourceInfo).sendToTarget();
|
||||||
|
if (processedInitialSeeks) {
|
||||||
|
pendingInitialSeekCount = 0;
|
||||||
|
pendingSeekPosition = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1076,17 +1106,22 @@ import java.io.IOException;
|
||||||
int windowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
|
int windowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
|
||||||
boolean isFirstPeriodInWindow = newLoadingPeriodIndex
|
boolean isFirstPeriodInWindow = newLoadingPeriodIndex
|
||||||
== timeline.getWindow(windowIndex, window).firstPeriodIndex;
|
== timeline.getWindow(windowIndex, window).firstPeriodIndex;
|
||||||
long periodStartPositionUs = loadingPeriodHolder == null ? playbackInfo.positionUs
|
long periodStartPositionUs;
|
||||||
: (isFirstPeriodInWindow ? C.TIME_UNSET : 0);
|
if (loadingPeriodHolder == null) {
|
||||||
if (periodStartPositionUs == C.TIME_UNSET) {
|
periodStartPositionUs = playbackInfo.startPositionUs;
|
||||||
// This is the first period of a new window or we don't have a start position, so seek to
|
} else if (!isFirstPeriodInWindow) {
|
||||||
// the default position for the window. If we're buffering ahead we also project the
|
// We're starting to buffer a new period in the current window. Always start from the
|
||||||
// default position so that it's correct for starting playing the buffered duration of
|
// beginning of the period.
|
||||||
// time in the future.
|
periodStartPositionUs = 0;
|
||||||
long defaultPositionProjectionUs = loadingPeriodHolder == null ? 0
|
} else {
|
||||||
: (loadingPeriodHolder.rendererPositionOffsetUs
|
// We're starting to buffer a new window. When playback transitions to this window we'll
|
||||||
|
// want it to be from its default start position. The expected delay until playback
|
||||||
|
// transitions is equal the duration of media that's currently buffered (assuming no
|
||||||
|
// interruptions). Hence we project the default start position forward by the duration of
|
||||||
|
// the buffer, and start buffering from this point.
|
||||||
|
long defaultPositionProjectionUs = loadingPeriodHolder.rendererPositionOffsetUs
|
||||||
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()
|
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()
|
||||||
- loadingPeriodHolder.startPositionUs - rendererPositionUs);
|
- loadingPeriodHolder.startPositionUs - rendererPositionUs;
|
||||||
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, windowIndex,
|
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, windowIndex,
|
||||||
C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
|
C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
|
||||||
if (defaultPosition == null) {
|
if (defaultPosition == null) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue