mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Prevent parallel timeline access in MaskingMediaSource
The list of MediaSourceHolder in ExoPlayerImpl is only maintained to be able to create a PlaylistTimeline for masking. By keeping only the id and a snapshot of the timeline of the MediaSourceHolder in ExoPlayerImpl, parallel access is prevented and we still have sufficient information to create the masking timeline. PiperOrigin-RevId: 319003837
This commit is contained in:
parent
a3bbcf3395
commit
20820800f3
7 changed files with 227 additions and 123 deletions
|
|
@ -64,6 +64,7 @@ import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -2186,6 +2187,24 @@ public final class Util {
|
||||||
: SystemClock.elapsedRealtime() + elapsedRealtimeEpochOffsetMs;
|
: SystemClock.elapsedRealtime() + elapsedRealtimeEpochOffsetMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the elements starting at {@code fromIndex} to {@code newFromIndex}.
|
||||||
|
*
|
||||||
|
* @param items The list of which to move elements.
|
||||||
|
* @param fromIndex The index at which the items to move start.
|
||||||
|
* @param toIndex The index up to which elements should be moved (exclusive).
|
||||||
|
* @param newFromIndex The new from index.
|
||||||
|
*/
|
||||||
|
public static <T extends Object> void moveItems(
|
||||||
|
List<T> items, int fromIndex, int toIndex, int newFromIndex) {
|
||||||
|
ArrayDeque<T> removedItems = new ArrayDeque<>();
|
||||||
|
int removedItemsLength = toIndex - fromIndex;
|
||||||
|
for (int i = removedItemsLength - 1; i >= 0; i--) {
|
||||||
|
removedItems.addFirst(items.remove(fromIndex + i));
|
||||||
|
}
|
||||||
|
items.addAll(Math.min(newFromIndex, items.size()), removedItems);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String getSystemProperty(String name) {
|
private static String getSystemProperty(String name) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
@ -71,7 +72,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
private final CopyOnWriteArrayList<ListenerHolder> listeners;
|
private final CopyOnWriteArrayList<ListenerHolder> listeners;
|
||||||
private final Timeline.Period period;
|
private final Timeline.Period period;
|
||||||
private final ArrayDeque<Runnable> pendingListenerNotifications;
|
private final ArrayDeque<Runnable> pendingListenerNotifications;
|
||||||
private final List<MediaSourceList.MediaSourceHolder> mediaSourceHolders;
|
private final List<MediaSourceHolderSnapshot> mediaSourceHolderSnapshots;
|
||||||
private final boolean useLazyPreparation;
|
private final boolean useLazyPreparation;
|
||||||
private final MediaSourceFactory mediaSourceFactory;
|
private final MediaSourceFactory mediaSourceFactory;
|
||||||
|
|
||||||
|
|
@ -130,7 +131,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
Looper applicationLooper) {
|
Looper applicationLooper) {
|
||||||
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
|
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
|
||||||
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
|
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
|
||||||
Assertions.checkState(renderers.length > 0);
|
checkState(renderers.length > 0);
|
||||||
this.renderers = checkNotNull(renderers);
|
this.renderers = checkNotNull(renderers);
|
||||||
this.trackSelector = checkNotNull(trackSelector);
|
this.trackSelector = checkNotNull(trackSelector);
|
||||||
this.mediaSourceFactory = mediaSourceFactory;
|
this.mediaSourceFactory = mediaSourceFactory;
|
||||||
|
|
@ -139,7 +140,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
|
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
|
||||||
repeatMode = Player.REPEAT_MODE_OFF;
|
repeatMode = Player.REPEAT_MODE_OFF;
|
||||||
listeners = new CopyOnWriteArrayList<>();
|
listeners = new CopyOnWriteArrayList<>();
|
||||||
mediaSourceHolders = new ArrayList<>();
|
mediaSourceHolderSnapshots = new ArrayList<>();
|
||||||
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
|
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
|
||||||
emptyTrackSelectorResult =
|
emptyTrackSelectorResult =
|
||||||
new TrackSelectorResult(
|
new TrackSelectorResult(
|
||||||
|
|
@ -387,7 +388,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMediaItems(List<MediaItem> mediaItems) {
|
public void addMediaItems(List<MediaItem> mediaItems) {
|
||||||
addMediaItems(/* index= */ mediaSourceHolders.size(), mediaItems);
|
addMediaItems(/* index= */ mediaSourceHolderSnapshots.size(), mediaItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -407,7 +408,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMediaSources(List<MediaSource> mediaSources) {
|
public void addMediaSources(List<MediaSource> mediaSources) {
|
||||||
addMediaSources(/* index= */ mediaSourceHolders.size(), mediaSources);
|
addMediaSources(/* index= */ mediaSourceHolderSnapshots.size(), mediaSources);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -442,14 +443,15 @@ import java.util.concurrent.TimeoutException;
|
||||||
Assertions.checkArgument(
|
Assertions.checkArgument(
|
||||||
fromIndex >= 0
|
fromIndex >= 0
|
||||||
&& fromIndex <= toIndex
|
&& fromIndex <= toIndex
|
||||||
&& toIndex <= mediaSourceHolders.size()
|
&& toIndex <= mediaSourceHolderSnapshots.size()
|
||||||
&& newFromIndex >= 0);
|
&& newFromIndex >= 0);
|
||||||
int currentWindowIndex = getCurrentWindowIndex();
|
int currentWindowIndex = getCurrentWindowIndex();
|
||||||
long currentPositionMs = getCurrentPosition();
|
long currentPositionMs = getCurrentPosition();
|
||||||
Timeline oldTimeline = getCurrentTimeline();
|
Timeline oldTimeline = getCurrentTimeline();
|
||||||
pendingOperationAcks++;
|
pendingOperationAcks++;
|
||||||
newFromIndex = Math.min(newFromIndex, mediaSourceHolders.size() - (toIndex - fromIndex));
|
newFromIndex =
|
||||||
MediaSourceList.moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
|
Math.min(newFromIndex, mediaSourceHolderSnapshots.size() - (toIndex - fromIndex));
|
||||||
|
Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex);
|
||||||
PlaybackInfo playbackInfo =
|
PlaybackInfo playbackInfo =
|
||||||
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
|
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
|
||||||
internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder);
|
internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder);
|
||||||
|
|
@ -464,10 +466,10 @@ import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearMediaItems() {
|
public void clearMediaItems() {
|
||||||
if (mediaSourceHolders.isEmpty()) {
|
if (mediaSourceHolderSnapshots.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
removeMediaItemsInternal(/* fromIndex= */ 0, /* toIndex= */ mediaSourceHolders.size());
|
removeMediaItemsInternal(/* fromIndex= */ 0, /* toIndex= */ mediaSourceHolderSnapshots.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -624,7 +626,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackSpeed(float playbackSpeed) {
|
||||||
Assertions.checkState(playbackSpeed > 0);
|
checkState(playbackSpeed > 0);
|
||||||
if (this.playbackSpeed == playbackSpeed) {
|
if (this.playbackSpeed == playbackSpeed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -918,11 +920,18 @@ import java.util.concurrent.TimeoutException;
|
||||||
pendingPlayWhenReadyChangeReason = playbackInfoUpdate.playWhenReadyChangeReason;
|
pendingPlayWhenReadyChangeReason = playbackInfoUpdate.playWhenReadyChangeReason;
|
||||||
}
|
}
|
||||||
if (pendingOperationAcks == 0) {
|
if (pendingOperationAcks == 0) {
|
||||||
if (!this.playbackInfo.timeline.isEmpty()
|
Timeline newTimeline = playbackInfoUpdate.playbackInfo.timeline;
|
||||||
&& playbackInfoUpdate.playbackInfo.timeline.isEmpty()) {
|
if (!this.playbackInfo.timeline.isEmpty() && newTimeline.isEmpty()) {
|
||||||
// Update the masking variables, which are used when the timeline becomes empty.
|
// Update the masking variables, which are used when the timeline becomes empty.
|
||||||
resetMaskingPosition();
|
resetMaskingPosition();
|
||||||
}
|
}
|
||||||
|
if (!newTimeline.isEmpty()) {
|
||||||
|
List<Timeline> timelines = ((PlaylistTimeline) newTimeline).getChildTimelines();
|
||||||
|
checkState(timelines.size() == mediaSourceHolderSnapshots.size());
|
||||||
|
for (int i = 0; i < timelines.size(); i++) {
|
||||||
|
mediaSourceHolderSnapshots.get(i).timeline = timelines.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
boolean positionDiscontinuity = hasPendingDiscontinuity;
|
boolean positionDiscontinuity = hasPendingDiscontinuity;
|
||||||
hasPendingDiscontinuity = false;
|
hasPendingDiscontinuity = false;
|
||||||
updatePlaybackInfo(
|
updatePlaybackInfo(
|
||||||
|
|
@ -940,7 +949,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
if (clearPlaylist) {
|
if (clearPlaylist) {
|
||||||
// Reset list of media source holders which are used for creating the masking timeline.
|
// Reset list of media source holders which are used for creating the masking timeline.
|
||||||
removeMediaSourceHolders(
|
removeMediaSourceHolders(
|
||||||
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolders.size());
|
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size());
|
||||||
resetMaskingPosition();
|
resetMaskingPosition();
|
||||||
} else {
|
} else {
|
||||||
maskWithCurrentPosition();
|
maskWithCurrentPosition();
|
||||||
|
|
@ -1004,9 +1013,9 @@ import java.util.concurrent.TimeoutException;
|
||||||
int currentWindowIndex = getCurrentWindowIndexInternal();
|
int currentWindowIndex = getCurrentWindowIndexInternal();
|
||||||
long currentPositionMs = getCurrentPosition();
|
long currentPositionMs = getCurrentPosition();
|
||||||
pendingOperationAcks++;
|
pendingOperationAcks++;
|
||||||
if (!mediaSourceHolders.isEmpty()) {
|
if (!mediaSourceHolderSnapshots.isEmpty()) {
|
||||||
removeMediaSourceHolders(
|
removeMediaSourceHolders(
|
||||||
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolders.size());
|
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size());
|
||||||
}
|
}
|
||||||
List<MediaSourceList.MediaSourceHolder> holders =
|
List<MediaSourceList.MediaSourceHolder> holders =
|
||||||
addMediaSourceHolders(/* index= */ 0, mediaSources);
|
addMediaSourceHolders(/* index= */ 0, mediaSources);
|
||||||
|
|
@ -1055,7 +1064,8 @@ import java.util.concurrent.TimeoutException;
|
||||||
MediaSourceList.MediaSourceHolder holder =
|
MediaSourceList.MediaSourceHolder holder =
|
||||||
new MediaSourceList.MediaSourceHolder(mediaSources.get(i), useLazyPreparation);
|
new MediaSourceList.MediaSourceHolder(mediaSources.get(i), useLazyPreparation);
|
||||||
holders.add(holder);
|
holders.add(holder);
|
||||||
mediaSourceHolders.add(i + index, holder);
|
mediaSourceHolderSnapshots.add(
|
||||||
|
i + index, new MediaSourceHolderSnapshot(holder.uid, holder.mediaSource.getTimeline()));
|
||||||
}
|
}
|
||||||
shuffleOrder =
|
shuffleOrder =
|
||||||
shuffleOrder.cloneAndInsert(
|
shuffleOrder.cloneAndInsert(
|
||||||
|
|
@ -1065,11 +1075,11 @@ import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
private void removeMediaItemsInternal(int fromIndex, int toIndex) {
|
private void removeMediaItemsInternal(int fromIndex, int toIndex) {
|
||||||
Assertions.checkArgument(
|
Assertions.checkArgument(
|
||||||
fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolders.size());
|
fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size());
|
||||||
int currentWindowIndex = getCurrentWindowIndex();
|
int currentWindowIndex = getCurrentWindowIndex();
|
||||||
long currentPositionMs = getCurrentPosition();
|
long currentPositionMs = getCurrentPosition();
|
||||||
Timeline oldTimeline = getCurrentTimeline();
|
Timeline oldTimeline = getCurrentTimeline();
|
||||||
int currentMediaSourceCount = mediaSourceHolders.size();
|
int currentMediaSourceCount = mediaSourceHolderSnapshots.size();
|
||||||
pendingOperationAcks++;
|
pendingOperationAcks++;
|
||||||
removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex);
|
removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex);
|
||||||
PlaybackInfo playbackInfo =
|
PlaybackInfo playbackInfo =
|
||||||
|
|
@ -1094,17 +1104,14 @@ import java.util.concurrent.TimeoutException;
|
||||||
/* seekProcessed= */ false);
|
/* seekProcessed= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MediaSourceList.MediaSourceHolder> removeMediaSourceHolders(
|
private void removeMediaSourceHolders(int fromIndex, int toIndexExclusive) {
|
||||||
int fromIndex, int toIndexExclusive) {
|
|
||||||
List<MediaSourceList.MediaSourceHolder> removed = new ArrayList<>();
|
|
||||||
for (int i = toIndexExclusive - 1; i >= fromIndex; i--) {
|
for (int i = toIndexExclusive - 1; i >= fromIndex; i--) {
|
||||||
removed.add(mediaSourceHolders.remove(i));
|
mediaSourceHolderSnapshots.remove(i);
|
||||||
}
|
}
|
||||||
shuffleOrder = shuffleOrder.cloneAndRemove(fromIndex, toIndexExclusive);
|
shuffleOrder = shuffleOrder.cloneAndRemove(fromIndex, toIndexExclusive);
|
||||||
if (mediaSourceHolders.isEmpty()) {
|
if (mediaSourceHolderSnapshots.isEmpty()) {
|
||||||
hasAdsMediaSource = false;
|
hasAdsMediaSource = false;
|
||||||
}
|
}
|
||||||
return removed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1123,7 +1130,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
int sizeAfterModification =
|
int sizeAfterModification =
|
||||||
mediaSources.size() + (mediaSourceReplacement ? 0 : mediaSourceHolders.size());
|
mediaSources.size() + (mediaSourceReplacement ? 0 : mediaSourceHolderSnapshots.size());
|
||||||
for (int i = 0; i < mediaSources.size(); i++) {
|
for (int i = 0; i < mediaSources.size(); i++) {
|
||||||
MediaSource mediaSource = checkNotNull(mediaSources.get(i));
|
MediaSource mediaSource = checkNotNull(mediaSources.get(i));
|
||||||
if (mediaSource instanceof AdsMediaSource) {
|
if (mediaSource instanceof AdsMediaSource) {
|
||||||
|
|
@ -1139,9 +1146,9 @@ import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
private PlaybackInfo maskTimeline() {
|
private PlaybackInfo maskTimeline() {
|
||||||
return playbackInfo.copyWithTimeline(
|
return playbackInfo.copyWithTimeline(
|
||||||
mediaSourceHolders.isEmpty()
|
mediaSourceHolderSnapshots.isEmpty()
|
||||||
? Timeline.EMPTY
|
? Timeline.EMPTY
|
||||||
: new MediaSourceList.PlaylistTimeline(mediaSourceHolders, shuffleOrder));
|
: new PlaylistTimeline(mediaSourceHolderSnapshots, shuffleOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlaybackInfo maskTimelineAndWindowIndex(
|
private PlaybackInfo maskTimelineAndWindowIndex(
|
||||||
|
|
@ -1395,4 +1402,26 @@ import java.util.concurrent.TimeoutException;
|
||||||
listenerHolder.invoke(listenerInvocation);
|
listenerHolder.invoke(listenerInvocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class MediaSourceHolderSnapshot implements MediaSourceInfoHolder {
|
||||||
|
|
||||||
|
private final Object uid;
|
||||||
|
|
||||||
|
private Timeline timeline;
|
||||||
|
|
||||||
|
public MediaSourceHolderSnapshot(Object uid, Timeline timeline) {
|
||||||
|
this.uid = uid;
|
||||||
|
this.timeline = timeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getUid() {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline getTimeline() {
|
||||||
|
return timeline;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -596,7 +596,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
if (mediaSourceListUpdateMessage.windowIndex != C.INDEX_UNSET) {
|
if (mediaSourceListUpdateMessage.windowIndex != C.INDEX_UNSET) {
|
||||||
pendingInitialSeekPosition =
|
pendingInitialSeekPosition =
|
||||||
new SeekPosition(
|
new SeekPosition(
|
||||||
new MediaSourceList.PlaylistTimeline(
|
new PlaylistTimeline(
|
||||||
mediaSourceListUpdateMessage.mediaSourceHolders,
|
mediaSourceListUpdateMessage.mediaSourceHolders,
|
||||||
mediaSourceListUpdateMessage.shuffleOrder),
|
mediaSourceListUpdateMessage.shuffleOrder),
|
||||||
mediaSourceListUpdateMessage.windowIndex,
|
mediaSourceListUpdateMessage.windowIndex,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
|
||||||
|
/** A holder of information about a {@link MediaSource}. */
|
||||||
|
/* package */ interface MediaSourceInfoHolder {
|
||||||
|
|
||||||
|
/** Returns the uid of the {@link MediaSourceList.MediaSourceHolder}. */
|
||||||
|
Object getUid();
|
||||||
|
|
||||||
|
/** Returns the timeline. */
|
||||||
|
Timeline getTimeline();
|
||||||
|
}
|
||||||
|
|
@ -35,8 +35,6 @@ import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
|
|
@ -234,7 +232,7 @@ import java.util.Set;
|
||||||
int newEndIndex = newFromIndex + (toIndex - fromIndex) - 1;
|
int newEndIndex = newFromIndex + (toIndex - fromIndex) - 1;
|
||||||
int endIndex = Math.max(newEndIndex, toIndex - 1);
|
int endIndex = Math.max(newEndIndex, toIndex - 1);
|
||||||
int windowOffset = mediaSourceHolders.get(startIndex).firstWindowIndexInChild;
|
int windowOffset = mediaSourceHolders.get(startIndex).firstWindowIndexInChild;
|
||||||
moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
|
Util.moveItems(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
|
||||||
for (int i = startIndex; i <= endIndex; i++) {
|
for (int i = startIndex; i <= endIndex; i++) {
|
||||||
MediaSourceHolder holder = mediaSourceHolders.get(i);
|
MediaSourceHolder holder = mediaSourceHolders.get(i);
|
||||||
holder.firstWindowIndexInChild = windowOffset;
|
holder.firstWindowIndexInChild = windowOffset;
|
||||||
|
|
@ -472,18 +470,8 @@ import java.util.Set;
|
||||||
return PlaylistTimeline.getConcatenatedUid(holder.uid, childPeriodUid);
|
return PlaylistTimeline.getConcatenatedUid(holder.uid, childPeriodUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ static void moveMediaSourceHolders(
|
|
||||||
List<MediaSourceHolder> mediaSourceHolders, int fromIndex, int toIndex, int newFromIndex) {
|
|
||||||
MediaSourceHolder[] removedItems = new MediaSourceHolder[toIndex - fromIndex];
|
|
||||||
for (int i = removedItems.length - 1; i >= 0; i--) {
|
|
||||||
removedItems[i] = mediaSourceHolders.remove(fromIndex + i);
|
|
||||||
}
|
|
||||||
mediaSourceHolders.addAll(
|
|
||||||
Math.min(newFromIndex, mediaSourceHolders.size()), Arrays.asList(removedItems));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Data class to hold playlist media sources together with meta data needed to process them. */
|
/** Data class to hold playlist media sources together with meta data needed to process them. */
|
||||||
/* package */ static final class MediaSourceHolder {
|
/* package */ static final class MediaSourceHolder implements MediaSourceInfoHolder {
|
||||||
|
|
||||||
public final MaskingMediaSource mediaSource;
|
public final MaskingMediaSource mediaSource;
|
||||||
public final Object uid;
|
public final Object uid;
|
||||||
|
|
@ -503,88 +491,15 @@ import java.util.Set;
|
||||||
this.isRemoved = false;
|
this.isRemoved = false;
|
||||||
this.activeMediaPeriodIds.clear();
|
this.activeMediaPeriodIds.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Timeline exposing concatenated timelines of playlist media sources. */
|
@Override
|
||||||
/* package */ static final class PlaylistTimeline extends AbstractConcatenatedTimeline {
|
public Object getUid() {
|
||||||
|
return uid;
|
||||||
private final int windowCount;
|
|
||||||
private final int periodCount;
|
|
||||||
private final int[] firstPeriodInChildIndices;
|
|
||||||
private final int[] firstWindowInChildIndices;
|
|
||||||
private final Timeline[] timelines;
|
|
||||||
private final Object[] uids;
|
|
||||||
private final HashMap<Object, Integer> childIndexByUid;
|
|
||||||
|
|
||||||
public PlaylistTimeline(
|
|
||||||
Collection<MediaSourceHolder> mediaSourceHolders, ShuffleOrder shuffleOrder) {
|
|
||||||
super(/* isAtomic= */ false, shuffleOrder);
|
|
||||||
int childCount = mediaSourceHolders.size();
|
|
||||||
firstPeriodInChildIndices = new int[childCount];
|
|
||||||
firstWindowInChildIndices = new int[childCount];
|
|
||||||
timelines = new Timeline[childCount];
|
|
||||||
uids = new Object[childCount];
|
|
||||||
childIndexByUid = new HashMap<>();
|
|
||||||
int index = 0;
|
|
||||||
int windowCount = 0;
|
|
||||||
int periodCount = 0;
|
|
||||||
for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
|
|
||||||
timelines[index] = mediaSourceHolder.mediaSource.getTimeline();
|
|
||||||
firstWindowInChildIndices[index] = windowCount;
|
|
||||||
firstPeriodInChildIndices[index] = periodCount;
|
|
||||||
windowCount += timelines[index].getWindowCount();
|
|
||||||
periodCount += timelines[index].getPeriodCount();
|
|
||||||
uids[index] = mediaSourceHolder.uid;
|
|
||||||
childIndexByUid.put(uids[index], index++);
|
|
||||||
}
|
|
||||||
this.windowCount = windowCount;
|
|
||||||
this.periodCount = periodCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getChildIndexByPeriodIndex(int periodIndex) {
|
public Timeline getTimeline() {
|
||||||
return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex + 1, false, false);
|
return mediaSource.getTimeline();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getChildIndexByWindowIndex(int windowIndex) {
|
|
||||||
return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex + 1, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getChildIndexByChildUid(Object childUid) {
|
|
||||||
Integer index = childIndexByUid.get(childUid);
|
|
||||||
return index == null ? C.INDEX_UNSET : index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Timeline getTimelineByChildIndex(int childIndex) {
|
|
||||||
return timelines[childIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getFirstPeriodIndexByChildIndex(int childIndex) {
|
|
||||||
return firstPeriodInChildIndices[childIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getFirstWindowIndexByChildIndex(int childIndex) {
|
|
||||||
return firstWindowInChildIndices[childIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object getChildUidByChildIndex(int childIndex) {
|
|
||||||
return uids[childIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowCount() {
|
|
||||||
return windowCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPeriodCount() {
|
|
||||||
return periodCount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.source.ShuffleOrder;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Timeline exposing concatenated timelines of playlist media sources. */
|
||||||
|
/* package */ final class PlaylistTimeline extends AbstractConcatenatedTimeline {
|
||||||
|
|
||||||
|
private final int windowCount;
|
||||||
|
private final int periodCount;
|
||||||
|
private final int[] firstPeriodInChildIndices;
|
||||||
|
private final int[] firstWindowInChildIndices;
|
||||||
|
private final Timeline[] timelines;
|
||||||
|
private final Object[] uids;
|
||||||
|
private final HashMap<Object, Integer> childIndexByUid;
|
||||||
|
|
||||||
|
/** Creates an instance. */
|
||||||
|
public PlaylistTimeline(
|
||||||
|
Collection<? extends MediaSourceInfoHolder> mediaSourceInfoHolders,
|
||||||
|
ShuffleOrder shuffleOrder) {
|
||||||
|
super(/* isAtomic= */ false, shuffleOrder);
|
||||||
|
int childCount = mediaSourceInfoHolders.size();
|
||||||
|
firstPeriodInChildIndices = new int[childCount];
|
||||||
|
firstWindowInChildIndices = new int[childCount];
|
||||||
|
timelines = new Timeline[childCount];
|
||||||
|
uids = new Object[childCount];
|
||||||
|
childIndexByUid = new HashMap<>();
|
||||||
|
int index = 0;
|
||||||
|
int windowCount = 0;
|
||||||
|
int periodCount = 0;
|
||||||
|
for (MediaSourceInfoHolder mediaSourceInfoHolder : mediaSourceInfoHolders) {
|
||||||
|
timelines[index] = mediaSourceInfoHolder.getTimeline();
|
||||||
|
firstWindowInChildIndices[index] = windowCount;
|
||||||
|
firstPeriodInChildIndices[index] = periodCount;
|
||||||
|
windowCount += timelines[index].getWindowCount();
|
||||||
|
periodCount += timelines[index].getPeriodCount();
|
||||||
|
uids[index] = mediaSourceInfoHolder.getUid();
|
||||||
|
childIndexByUid.put(uids[index], index++);
|
||||||
|
}
|
||||||
|
this.windowCount = windowCount;
|
||||||
|
this.periodCount = periodCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the child timelines. */
|
||||||
|
/* package */ List<Timeline> getChildTimelines() {
|
||||||
|
return Arrays.asList(timelines);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getChildIndexByPeriodIndex(int periodIndex) {
|
||||||
|
return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex + 1, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getChildIndexByWindowIndex(int windowIndex) {
|
||||||
|
return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex + 1, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getChildIndexByChildUid(Object childUid) {
|
||||||
|
Integer index = childIndexByUid.get(childUid);
|
||||||
|
return index == null ? C.INDEX_UNSET : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Timeline getTimelineByChildIndex(int childIndex) {
|
||||||
|
return timelines[childIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getFirstPeriodIndexByChildIndex(int childIndex) {
|
||||||
|
return firstPeriodInChildIndices[childIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getFirstWindowIndexByChildIndex(int childIndex) {
|
||||||
|
return firstWindowInChildIndices[childIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object getChildUidByChildIndex(int childIndex) {
|
||||||
|
return uids[childIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowCount() {
|
||||||
|
return windowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeriodCount() {
|
||||||
|
return periodCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the {@link Timeline}. */
|
/** Returns the {@link Timeline}. */
|
||||||
public synchronized Timeline getTimeline() {
|
public Timeline getTimeline() {
|
||||||
return timeline;
|
return timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +150,7 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void onChildSourceInfoRefreshed(
|
protected void onChildSourceInfoRefreshed(
|
||||||
Void id, MediaSource mediaSource, Timeline newTimeline) {
|
Void id, MediaSource mediaSource, Timeline newTimeline) {
|
||||||
@Nullable MediaPeriodId idForMaskingPeriodPreparation = null;
|
@Nullable MediaPeriodId idForMaskingPeriodPreparation = null;
|
||||||
if (isPrepared) {
|
if (isPrepared) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue