Stop sharing a Handler between EPI and EPII

Sharing the Handler has led to it being accidentally used for purposes
beyond the original intention.

Instead for EPII -> EPI communication: Call methods directly on
ExoPlayerImpl that then post from the playback thread to the
application thread.

And for the MediaSourceList and Queue initialization, create a dedicated
Handler based on the same applicationLooper.

PiperOrigin-RevId: 320590527
This commit is contained in:
ibaker 2020-07-10 14:10:14 +01:00 committed by kim-vde
parent 09e97cf279
commit 9290b468d0
2 changed files with 119 additions and 92 deletions

View File

@ -66,7 +66,7 @@ import java.util.concurrent.TimeoutException;
private final Renderer[] renderers; private final Renderer[] renderers;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final Handler applicationHandler; private final PlaybackUpdateListenerImpl playbackUpdateListener;
private final ExoPlayerImplInternal internalPlayer; private final ExoPlayerImplInternal internalPlayer;
private final Handler internalPlayerHandler; private final Handler internalPlayerHandler;
private final CopyOnWriteArrayList<ListenerHolder> listeners; private final CopyOnWriteArrayList<ListenerHolder> listeners;
@ -76,6 +76,7 @@ import java.util.concurrent.TimeoutException;
private final boolean useLazyPreparation; private final boolean useLazyPreparation;
private final MediaSourceFactory mediaSourceFactory; private final MediaSourceFactory mediaSourceFactory;
@Nullable private final AnalyticsCollector analyticsCollector; @Nullable private final AnalyticsCollector analyticsCollector;
private final Looper applicationLooper;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
@RepeatMode private int repeatMode; @RepeatMode private int repeatMode;
@ -142,6 +143,7 @@ import java.util.concurrent.TimeoutException;
this.useLazyPreparation = useLazyPreparation; this.useLazyPreparation = useLazyPreparation;
this.seekParameters = seekParameters; this.seekParameters = seekParameters;
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
this.applicationLooper = applicationLooper;
repeatMode = Player.REPEAT_MODE_OFF; repeatMode = Player.REPEAT_MODE_OFF;
listeners = new CopyOnWriteArrayList<>(); listeners = new CopyOnWriteArrayList<>();
mediaSourceHolderSnapshots = new ArrayList<>(); mediaSourceHolderSnapshots = new ArrayList<>();
@ -154,19 +156,13 @@ import java.util.concurrent.TimeoutException;
period = new Timeline.Period(); period = new Timeline.Period();
playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED; playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED;
maskingWindowIndex = C.INDEX_UNSET; maskingWindowIndex = C.INDEX_UNSET;
applicationHandler = playbackUpdateListener = new PlaybackUpdateListenerImpl(applicationLooper);
new Handler(applicationLooper) {
@Override
public void handleMessage(Message msg) {
ExoPlayerImpl.this.handleEvent(msg);
}
};
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
pendingListenerNotifications = new ArrayDeque<>(); pendingListenerNotifications = new ArrayDeque<>();
if (analyticsCollector != null) { if (analyticsCollector != null) {
analyticsCollector.setPlayer(this); analyticsCollector.setPlayer(this);
addListener(analyticsCollector); addListener(analyticsCollector);
bandwidthMeter.addEventListener(applicationHandler, analyticsCollector); bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector);
} }
internalPlayer = internalPlayer =
new ExoPlayerImplInternal( new ExoPlayerImplInternal(
@ -180,8 +176,9 @@ import java.util.concurrent.TimeoutException;
analyticsCollector, analyticsCollector,
seekParameters, seekParameters,
pauseAtEndOfMediaItems, pauseAtEndOfMediaItems,
applicationHandler, applicationLooper,
clock); clock,
playbackUpdateListener);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper()); internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
} }
@ -251,7 +248,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public Looper getApplicationLooper() { public Looper getApplicationLooper() {
return applicationHandler.getLooper(); return applicationLooper;
} }
@Override @Override
@ -588,11 +585,8 @@ import java.util.concurrent.TimeoutException;
// 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");
applicationHandler playbackUpdateListener.onPlaybackInfoUpdate(
.obtainMessage( new ExoPlayerImplInternal.PlaybackInfoUpdate(playbackInfo));
ExoPlayerImplInternal.MSG_PLAYBACK_INFO_CHANGED,
new ExoPlayerImplInternal.PlaybackInfoUpdate(playbackInfo))
.sendToTarget();
return; return;
} }
maskWindowIndexAndPositionForSeek(timeline, windowIndex, positionMs); maskWindowIndexAndPositionForSeek(timeline, windowIndex, positionMs);
@ -715,7 +709,7 @@ import java.util.concurrent.TimeoutException;
ExoPlaybackException.createForUnexpected( ExoPlaybackException.createForUnexpected(
new RuntimeException(new TimeoutException("Player release timed out."))))); new RuntimeException(new TimeoutException("Player release timed out.")))));
} }
applicationHandler.removeCallbacksAndMessages(null); playbackUpdateListener.handler.removeCallbacksAndMessages(null);
if (analyticsCollector != null) { if (analyticsCollector != null) {
bandwidthMeter.removeEventListener(analyticsCollector); bandwidthMeter.removeEventListener(analyticsCollector);
} }
@ -869,20 +863,6 @@ import java.util.concurrent.TimeoutException;
return playbackInfo.timeline; return playbackInfo.timeline;
} }
// Not private so it can be called from an inner class without going through a thunk method.
/* package */ void handleEvent(Message msg) {
switch (msg.what) {
case ExoPlayerImplInternal.MSG_PLAYBACK_INFO_CHANGED:
handlePlaybackInfo((ExoPlayerImplInternal.PlaybackInfoUpdate) msg.obj);
break;
case ExoPlayerImplInternal.MSG_PLAYBACK_SPEED_CHANGED:
handlePlaybackSpeed((Float) msg.obj, /* operationAck= */ msg.arg1 != 0);
break;
default:
throw new IllegalStateException();
}
}
private int getCurrentWindowIndexInternal() { private int getCurrentWindowIndexInternal() {
if (shouldMaskPosition()) { if (shouldMaskPosition()) {
return maskingWindowIndex; return maskingWindowIndex;
@ -1282,6 +1262,49 @@ import java.util.concurrent.TimeoutException;
return playbackInfo.timeline.isEmpty() || pendingOperationAcks > 0; return playbackInfo.timeline.isEmpty() || pendingOperationAcks > 0;
} }
private final class PlaybackUpdateListenerImpl
implements ExoPlayerImplInternal.PlaybackUpdateListener, Handler.Callback {
private static final int MSG_PLAYBACK_INFO_CHANGED = 0;
private static final int MSG_PLAYBACK_SPEED_CHANGED = 1;
private final Handler handler;
private PlaybackUpdateListenerImpl(Looper applicationLooper) {
handler = Util.createHandler(applicationLooper, /* callback= */ this);
}
@Override
public void onPlaybackInfoUpdate(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfo) {
handler.obtainMessage(MSG_PLAYBACK_INFO_CHANGED, playbackInfo).sendToTarget();
}
@Override
public void onPlaybackSpeedChange(float playbackSpeed, boolean acknowledgeCommand) {
handler
.obtainMessage(
MSG_PLAYBACK_SPEED_CHANGED,
/* arg1= */ acknowledgeCommand ? 1 : 0,
/* arg2= */ 0,
/* obj= */ playbackSpeed)
.sendToTarget();
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_PLAYBACK_INFO_CHANGED:
handlePlaybackInfo((ExoPlayerImplInternal.PlaybackInfoUpdate) msg.obj);
break;
case MSG_PLAYBACK_SPEED_CHANGED:
handlePlaybackSpeed((Float) msg.obj, /* operationAck= */ msg.arg1 != 0);
break;
default:
throw new IllegalStateException();
}
return true;
}
}
private static final class PlaybackInfoUpdate implements Runnable { private static final class PlaybackInfoUpdate implements Runnable {
private final PlaybackInfo playbackInfo; private final PlaybackInfo playbackInfo;

View File

@ -63,9 +63,57 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final String TAG = "ExoPlayerImplInternal"; private static final String TAG = "ExoPlayerImplInternal";
// External messages public static final class PlaybackInfoUpdate {
public static final int MSG_PLAYBACK_INFO_CHANGED = 0;
public static final int MSG_PLAYBACK_SPEED_CHANGED = 1; private boolean hasPendingChange;
public PlaybackInfo playbackInfo;
public int operationAcks;
public boolean positionDiscontinuity;
@DiscontinuityReason public int discontinuityReason;
public boolean hasPlayWhenReadyChangeReason;
@PlayWhenReadyChangeReason public int playWhenReadyChangeReason;
public PlaybackInfoUpdate(PlaybackInfo playbackInfo) {
this.playbackInfo = playbackInfo;
}
public void incrementPendingOperationAcks(int operationAcks) {
hasPendingChange |= operationAcks > 0;
this.operationAcks += operationAcks;
}
public void setPlaybackInfo(PlaybackInfo playbackInfo) {
hasPendingChange |= this.playbackInfo != playbackInfo;
this.playbackInfo = playbackInfo;
}
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;
}
hasPendingChange = true;
positionDiscontinuity = true;
this.discontinuityReason = discontinuityReason;
}
public void setPlayWhenReadyChangeReason(
@PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
hasPendingChange = true;
this.hasPlayWhenReadyChangeReason = true;
this.playWhenReadyChangeReason = playWhenReadyChangeReason;
}
}
public interface PlaybackUpdateListener {
void onPlaybackInfoUpdate(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfo);
void onPlaybackSpeedChange(float playbackSpeed, boolean acknowledgeCommand);
}
// Internal messages // Internal messages
private static final int MSG_PREPARE = 0; private static final int MSG_PREPARE = 0;
@ -113,7 +161,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
private final HandlerWrapper handler; private final HandlerWrapper handler;
private final HandlerThread internalPlaybackThread; private final HandlerThread internalPlaybackThread;
private final Handler eventHandler;
private final Timeline.Window window; private final Timeline.Window window;
private final Timeline.Period period; private final Timeline.Period period;
private final long backBufferDurationUs; private final long backBufferDurationUs;
@ -121,6 +168,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final DefaultMediaClock mediaClock; private final DefaultMediaClock mediaClock;
private final ArrayList<PendingMessageInfo> pendingMessages; private final ArrayList<PendingMessageInfo> pendingMessages;
private final Clock clock; private final Clock clock;
private final PlaybackUpdateListener playbackUpdateListener;
private final MediaPeriodQueue queue; private final MediaPeriodQueue queue;
private final MediaSourceList mediaSourceList; private final MediaSourceList mediaSourceList;
@ -160,8 +208,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
@Nullable AnalyticsCollector analyticsCollector, @Nullable AnalyticsCollector analyticsCollector,
SeekParameters seekParameters, SeekParameters seekParameters,
boolean pauseAtEndOfWindow, boolean pauseAtEndOfWindow,
Handler eventHandler, Looper applicationLooper,
Clock clock) { Clock clock,
PlaybackUpdateListener playbackUpdateListener) {
this.playbackUpdateListener = playbackUpdateListener;
this.renderers = renderers; this.renderers = renderers;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
this.emptyTrackSelectorResult = emptyTrackSelectorResult; this.emptyTrackSelectorResult = emptyTrackSelectorResult;
@ -171,9 +221,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
this.shuffleModeEnabled = shuffleModeEnabled; this.shuffleModeEnabled = shuffleModeEnabled;
this.seekParameters = seekParameters; this.seekParameters = seekParameters;
this.pauseAtEndOfWindow = pauseAtEndOfWindow; this.pauseAtEndOfWindow = pauseAtEndOfWindow;
this.eventHandler = eventHandler;
this.clock = clock; this.clock = clock;
this.queue = new MediaPeriodQueue(analyticsCollector, eventHandler);
backBufferDurationUs = loadControl.getBackBufferDurationUs(); backBufferDurationUs = loadControl.getBackBufferDurationUs();
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe(); retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe();
@ -191,13 +239,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
period = new Timeline.Period(); period = new Timeline.Period();
trackSelector.init(/* listener= */ this, bandwidthMeter); trackSelector.init(/* listener= */ this, bandwidthMeter);
deliverPendingMessageAtStartPositionRequired = true;
Handler eventHandler = new Handler(applicationLooper);
queue = new MediaPeriodQueue(analyticsCollector, eventHandler);
mediaSourceList = new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler);
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect. // not normally change to this priority" is incorrect.
internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO); internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start(); internalPlaybackThread.start();
handler = clock.createHandler(internalPlaybackThread.getLooper(), this); handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
deliverPendingMessageAtStartPositionRequired = true;
mediaSourceList = new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler);
} }
public void experimental_setReleaseTimeoutMs(long releaseTimeoutMs) { public void experimental_setReleaseTimeoutMs(long releaseTimeoutMs) {
@ -571,7 +623,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void maybeNotifyPlaybackInfoChanged() { private void maybeNotifyPlaybackInfoChanged() {
playbackInfoUpdate.setPlaybackInfo(playbackInfo); playbackInfoUpdate.setPlaybackInfo(playbackInfo);
if (playbackInfoUpdate.hasPendingChange) { if (playbackInfoUpdate.hasPendingChange) {
eventHandler.obtainMessage(MSG_PLAYBACK_INFO_CHANGED, playbackInfoUpdate).sendToTarget(); playbackUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate);
playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo); playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo);
} }
} }
@ -1938,9 +1990,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void handlePlaybackSpeed(float playbackSpeed, boolean acknowledgeCommand) private void handlePlaybackSpeed(float playbackSpeed, boolean acknowledgeCommand)
throws ExoPlaybackException { throws ExoPlaybackException {
eventHandler playbackUpdateListener.onPlaybackSpeedChange(playbackSpeed, acknowledgeCommand);
.obtainMessage(MSG_PLAYBACK_SPEED_CHANGED, acknowledgeCommand ? 1 : 0, 0, playbackSpeed)
.sendToTarget();
updateTrackSelectionPlaybackSpeed(playbackSpeed); updateTrackSelectionPlaybackSpeed(playbackSpeed);
for (Renderer renderer : renderers) { for (Renderer renderer : renderers) {
if (renderer != null) { if (renderer != null) {
@ -2650,50 +2700,4 @@ import java.util.concurrent.atomic.AtomicBoolean;
this.shuffleOrder = shuffleOrder; this.shuffleOrder = shuffleOrder;
} }
} }
/* package */ static final class PlaybackInfoUpdate {
private boolean hasPendingChange;
public PlaybackInfo playbackInfo;
public int operationAcks;
public boolean positionDiscontinuity;
@DiscontinuityReason public int discontinuityReason;
public boolean hasPlayWhenReadyChangeReason;
@PlayWhenReadyChangeReason public int playWhenReadyChangeReason;
public PlaybackInfoUpdate(PlaybackInfo playbackInfo) {
this.playbackInfo = playbackInfo;
}
public void incrementPendingOperationAcks(int operationAcks) {
hasPendingChange |= operationAcks > 0;
this.operationAcks += operationAcks;
}
public void setPlaybackInfo(PlaybackInfo playbackInfo) {
hasPendingChange |= this.playbackInfo != playbackInfo;
this.playbackInfo = playbackInfo;
}
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;
}
hasPendingChange = true;
positionDiscontinuity = true;
this.discontinuityReason = discontinuityReason;
}
public void setPlayWhenReadyChangeReason(
@PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
hasPendingChange = true;
this.hasPlayWhenReadyChangeReason = true;
this.playWhenReadyChangeReason = playWhenReadyChangeReason;
}
}
} }