Use Handler instead of ExoPlayer messages in ConcatenatingMediaSource

ExoPlayer methods must not be called from any thread besides the specified
app thread. Therefore we shouldn't use them here. Using a regular Handler
instead is fully equivalent.

Issue:#5240
PiperOrigin-RevId: 227650489
This commit is contained in:
tonihei 2019-01-03 09:36:35 +00:00 committed by Oliver Woodman
parent f11abbda97
commit fc16833903
2 changed files with 47 additions and 53 deletions

View File

@ -4,6 +4,9 @@
* IMA extension: Clear ads loader listeners on release * IMA extension: Clear ads loader listeners on release
([#4114](https://github.com/google/ExoPlayer/issues/4114)). ([#4114](https://github.com/google/ExoPlayer/issues/4114)).
* Fix issue where sending callbacks for playlist changes may cause problems
because of parallel player access
([#5240](https://github.com/google/ExoPlayer/issues/5240)).
### 2.9.3 ### ### 2.9.3 ###

View File

@ -16,13 +16,12 @@
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.os.Handler; import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.PlayerMessage;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder; import com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder;
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
@ -45,8 +44,7 @@ import java.util.Map;
* during playback. It is valid for the same {@link MediaSource} instance to be present more than * during playback. It is valid for the same {@link MediaSource} instance to be present more than
* once in the concatenation. Access to this class is thread-safe. * once in the concatenation. Access to this class is thread-safe.
*/ */
public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHolder> public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHolder> {
implements PlayerMessage.Target {
private static final int MSG_ADD = 0; private static final int MSG_ADD = 0;
private static final int MSG_REMOVE = 1; private static final int MSG_REMOVE = 1;
@ -68,8 +66,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private final Timeline.Window window; private final Timeline.Window window;
private final Timeline.Period period; private final Timeline.Period period;
private @Nullable ExoPlayer player; @Nullable private Handler playbackThreadHandler;
private @Nullable Handler playerApplicationHandler; @Nullable private Handler applicationThreadHandler;
private boolean listenerNotificationScheduled; private boolean listenerNotificationScheduled;
private ShuffleOrder shuffleOrder; private ShuffleOrder shuffleOrder;
private int windowCount; private int windowCount;
@ -239,12 +237,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
mediaSourceHolders.add(new MediaSourceHolder(mediaSource)); mediaSourceHolders.add(new MediaSourceHolder(mediaSource));
} }
mediaSourcesPublic.addAll(index, mediaSourceHolders); mediaSourcesPublic.addAll(index, mediaSourceHolders);
if (player != null && !mediaSources.isEmpty()) { if (playbackThreadHandler != null && !mediaSources.isEmpty()) {
player playbackThreadHandler
.createMessage(this) .obtainMessage(MSG_ADD, new MessageData<>(index, mediaSourceHolders, actionOnCompletion))
.setType(MSG_ADD) .sendToTarget();
.setPayload(new MessageData<>(index, mediaSourceHolders, actionOnCompletion))
.send();
} else if (actionOnCompletion != null) { } else if (actionOnCompletion != null) {
actionOnCompletion.run(); actionOnCompletion.run();
} }
@ -328,12 +324,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
} }
return; return;
} }
if (player != null) { if (playbackThreadHandler != null) {
player playbackThreadHandler
.createMessage(this) .obtainMessage(MSG_REMOVE, new MessageData<>(fromIndex, toIndex, actionOnCompletion))
.setType(MSG_REMOVE) .sendToTarget();
.setPayload(new MessageData<>(fromIndex, toIndex, actionOnCompletion))
.send();
} else if (actionOnCompletion != null) { } else if (actionOnCompletion != null) {
actionOnCompletion.run(); actionOnCompletion.run();
} }
@ -371,12 +365,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
return; return;
} }
mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex)); mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex));
if (player != null) { if (playbackThreadHandler != null) {
player playbackThreadHandler
.createMessage(this) .obtainMessage(MSG_MOVE, new MessageData<>(currentIndex, newIndex, actionOnCompletion))
.setType(MSG_MOVE) .sendToTarget();
.setPayload(new MessageData<>(currentIndex, newIndex, actionOnCompletion))
.send();
} else if (actionOnCompletion != null) { } else if (actionOnCompletion != null) {
actionOnCompletion.run(); actionOnCompletion.run();
} }
@ -430,8 +422,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
*/ */
public final synchronized void setShuffleOrder( public final synchronized void setShuffleOrder(
ShuffleOrder shuffleOrder, @Nullable Runnable actionOnCompletion) { ShuffleOrder shuffleOrder, @Nullable Runnable actionOnCompletion) {
ExoPlayer player = this.player; Handler playbackThreadHandler = this.playbackThreadHandler;
if (player != null) { if (playbackThreadHandler != null) {
int size = getSize(); int size = getSize();
if (shuffleOrder.getLength() != size) { if (shuffleOrder.getLength() != size) {
shuffleOrder = shuffleOrder =
@ -439,11 +431,11 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
.cloneAndClear() .cloneAndClear()
.cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size); .cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size);
} }
player playbackThreadHandler
.createMessage(this) .obtainMessage(
.setType(MSG_SET_SHUFFLE_ORDER) MSG_SET_SHUFFLE_ORDER,
.setPayload(new MessageData<>(/* index= */ 0, shuffleOrder, actionOnCompletion)) new MessageData<>(/* index= */ 0, shuffleOrder, actionOnCompletion))
.send(); .sendToTarget();
} else { } else {
this.shuffleOrder = this.shuffleOrder =
shuffleOrder.getLength() > 0 ? shuffleOrder.cloneAndClear() : shuffleOrder; shuffleOrder.getLength() > 0 ? shuffleOrder.cloneAndClear() : shuffleOrder;
@ -465,8 +457,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
boolean isTopLevelSource, boolean isTopLevelSource,
@Nullable TransferListener mediaTransferListener) { @Nullable TransferListener mediaTransferListener) {
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener); super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
this.player = player; playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
playerApplicationHandler = new Handler(player.getApplicationLooper()); applicationThreadHandler = new Handler(player.getApplicationLooper());
if (mediaSourcesPublic.isEmpty()) { if (mediaSourcesPublic.isEmpty()) {
notifyListener(); notifyListener();
} else { } else {
@ -519,8 +511,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
super.releaseSourceInternal(); super.releaseSourceInternal();
mediaSourceHolders.clear(); mediaSourceHolders.clear();
mediaSourceByUid.clear(); mediaSourceByUid.clear();
player = null; playbackThreadHandler = null;
playerApplicationHandler = null; applicationThreadHandler = null;
shuffleOrder = shuffleOrder.cloneAndClear(); shuffleOrder = shuffleOrder.cloneAndClear();
windowCount = 0; windowCount = 0;
periodCount = 0; periodCount = 0;
@ -556,24 +548,22 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
return windowIndex + mediaSourceHolder.firstWindowIndexInChild; return windowIndex + mediaSourceHolder.firstWindowIndexInChild;
} }
@Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final void handleMessage(int messageType, @Nullable Object message) private boolean handleMessage(Message msg) {
throws ExoPlaybackException { if (playbackThreadHandler == null) {
if (player == null) {
// Stale event. // Stale event.
return; return false;
} }
switch (messageType) { switch (msg.what) {
case MSG_ADD: case MSG_ADD:
MessageData<Collection<MediaSourceHolder>> addMessage = MessageData<Collection<MediaSourceHolder>> addMessage =
(MessageData<Collection<MediaSourceHolder>>) Util.castNonNull(message); (MessageData<Collection<MediaSourceHolder>>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrder.cloneAndInsert(addMessage.index, addMessage.customData.size()); shuffleOrder = shuffleOrder.cloneAndInsert(addMessage.index, addMessage.customData.size());
addMediaSourcesInternal(addMessage.index, addMessage.customData); addMediaSourcesInternal(addMessage.index, addMessage.customData);
scheduleListenerNotification(addMessage.actionOnCompletion); scheduleListenerNotification(addMessage.actionOnCompletion);
break; break;
case MSG_REMOVE: case MSG_REMOVE:
MessageData<Integer> removeMessage = (MessageData<Integer>) Util.castNonNull(message); MessageData<Integer> removeMessage = (MessageData<Integer>) Util.castNonNull(msg.obj);
int fromIndex = removeMessage.index; int fromIndex = removeMessage.index;
int toIndex = removeMessage.customData; int toIndex = removeMessage.customData;
if (fromIndex == 0 && toIndex == shuffleOrder.getLength()) { if (fromIndex == 0 && toIndex == shuffleOrder.getLength()) {
@ -587,7 +577,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
scheduleListenerNotification(removeMessage.actionOnCompletion); scheduleListenerNotification(removeMessage.actionOnCompletion);
break; break;
case MSG_MOVE: case MSG_MOVE:
MessageData<Integer> moveMessage = (MessageData<Integer>) Util.castNonNull(message); MessageData<Integer> moveMessage = (MessageData<Integer>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index, moveMessage.index + 1); shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index, moveMessage.index + 1);
shuffleOrder = shuffleOrder.cloneAndInsert(moveMessage.customData, 1); shuffleOrder = shuffleOrder.cloneAndInsert(moveMessage.customData, 1);
moveMediaSourceInternal(moveMessage.index, moveMessage.customData); moveMediaSourceInternal(moveMessage.index, moveMessage.customData);
@ -595,7 +585,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
break; break;
case MSG_SET_SHUFFLE_ORDER: case MSG_SET_SHUFFLE_ORDER:
MessageData<ShuffleOrder> shuffleOrderMessage = MessageData<ShuffleOrder> shuffleOrderMessage =
(MessageData<ShuffleOrder>) Util.castNonNull(message); (MessageData<ShuffleOrder>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrderMessage.customData; shuffleOrder = shuffleOrderMessage.customData;
scheduleListenerNotification(shuffleOrderMessage.actionOnCompletion); scheduleListenerNotification(shuffleOrderMessage.actionOnCompletion);
break; break;
@ -603,8 +593,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
notifyListener(); notifyListener();
break; break;
case MSG_ON_COMPLETION: case MSG_ON_COMPLETION:
List<Runnable> actionsOnCompletion = (List<Runnable>) Util.castNonNull(message); List<Runnable> actionsOnCompletion = (List<Runnable>) Util.castNonNull(msg.obj);
Handler handler = Assertions.checkNotNull(playerApplicationHandler); Handler handler = Assertions.checkNotNull(applicationThreadHandler);
for (int i = 0; i < actionsOnCompletion.size(); i++) { for (int i = 0; i < actionsOnCompletion.size(); i++) {
handler.post(actionsOnCompletion.get(i)); handler.post(actionsOnCompletion.get(i));
} }
@ -612,11 +602,14 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
default: default:
throw new IllegalStateException(); throw new IllegalStateException();
} }
return true;
} }
private void scheduleListenerNotification(@Nullable Runnable actionOnCompletion) { private void scheduleListenerNotification(@Nullable Runnable actionOnCompletion) {
if (!listenerNotificationScheduled) { if (!listenerNotificationScheduled) {
Assertions.checkNotNull(player).createMessage(this).setType(MSG_NOTIFY_LISTENER).send(); Assertions.checkNotNull(playbackThreadHandler)
.obtainMessage(MSG_NOTIFY_LISTENER)
.sendToTarget();
listenerNotificationScheduled = true; listenerNotificationScheduled = true;
} }
if (actionOnCompletion != null) { if (actionOnCompletion != null) {
@ -636,11 +629,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic), mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic),
/* manifest= */ null); /* manifest= */ null);
if (!actionsOnCompletion.isEmpty()) { if (!actionsOnCompletion.isEmpty()) {
Assertions.checkNotNull(player) Assertions.checkNotNull(playbackThreadHandler)
.createMessage(this) .obtainMessage(MSG_ON_COMPLETION, actionsOnCompletion)
.setType(MSG_ON_COMPLETION) .sendToTarget();
.setPayload(actionsOnCompletion)
.send();
} }
} }