1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-01-11 09:50:32 +00:00

-Fixed media source update index check.

-Fixed media source manager excessive loading.
-Remove unneeded fields in loaded media source.
This commit is contained in:
John Zhen Mo 2018-02-23 19:00:08 -08:00
parent 8803b60b28
commit 19cbcd0c1d
2 changed files with 135 additions and 101 deletions

View File

@ -14,35 +14,16 @@ import java.io.IOException;
public class LoadedMediaSource implements ManagedMediaSource { public class LoadedMediaSource implements ManagedMediaSource {
private final PlayQueueItem playQueueItem;
private final StreamInfo streamInfo;
private final MediaSource source; private final MediaSource source;
private final long expireTimestamp; private final long expireTimestamp;
public LoadedMediaSource(@NonNull final PlayQueueItem playQueueItem, public LoadedMediaSource(@NonNull final MediaSource source, final long expireTimestamp) {
@NonNull final StreamInfo streamInfo,
@NonNull final MediaSource source,
final long expireTimestamp) {
this.playQueueItem = playQueueItem;
this.streamInfo = streamInfo;
this.source = source; this.source = source;
this.expireTimestamp = expireTimestamp; this.expireTimestamp = expireTimestamp;
} }
public PlayQueueItem getPlayQueueItem() {
return playQueueItem;
}
public StreamInfo getStreamInfo() {
return streamInfo;
}
public boolean isExpired() {
return System.currentTimeMillis() >= expireTimestamp;
}
@Override @Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
source.prepareSource(player, isTopLevelSource, listener); source.prepareSource(player, isTopLevelSource, listener);
@ -70,6 +51,6 @@ public class LoadedMediaSource implements ManagedMediaSource {
@Override @Override
public boolean canReplace() { public boolean canReplace() {
return isExpired(); return System.currentTimeMillis() >= expireTimestamp;
} }
} }

View File

@ -19,7 +19,10 @@ import org.schabi.newpipe.playlist.events.PlayQueueEvent;
import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.RemoveEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import io.reactivex.Single; import io.reactivex.Single;
@ -27,6 +30,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull; import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.PublishSubject;
public class MediaSourceManagerAlt { public class MediaSourceManagerAlt {
@ -48,21 +53,24 @@ public class MediaSourceManagerAlt {
private Subscription playQueueReactor; private Subscription playQueueReactor;
private CompositeDisposable loaderReactor; private CompositeDisposable loaderReactor;
private PlayQueueItem syncedItem;
private boolean isBlocked; private boolean isBlocked;
private SerialDisposable syncReactor;
private PlayQueueItem syncedItem;
private Set<PlayQueueItem> loadingItems;
public MediaSourceManagerAlt(@NonNull final PlaybackListener listener, public MediaSourceManagerAlt(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue) { @NonNull final PlayQueue playQueue) {
this(listener, playQueue, 1, 400L); this(listener, playQueue, 0, 400L);
} }
private MediaSourceManagerAlt(@NonNull final PlaybackListener listener, private MediaSourceManagerAlt(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue, @NonNull final PlayQueue playQueue,
final int windowSize, final int windowSize,
final long loadDebounceMillis) { final long loadDebounceMillis) {
if (windowSize <= 0) { if (windowSize < 0) {
throw new UnsupportedOperationException("MediaSourceManager window size must be greater than 0"); throw new UnsupportedOperationException(
"MediaSourceManager window size must be greater than 0");
} }
this.playbackListener = listener; this.playbackListener = listener;
@ -76,6 +84,9 @@ public class MediaSourceManagerAlt {
this.sources = new DynamicConcatenatingMediaSource(); this.sources = new DynamicConcatenatingMediaSource();
this.syncReactor = new SerialDisposable();
this.loadingItems = Collections.synchronizedSet(new HashSet<>());
playQueue.getBroadcastReceiver() playQueue.getBroadcastReceiver()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(getReactor()); .subscribe(getReactor());
@ -92,10 +103,12 @@ public class MediaSourceManagerAlt {
if (debouncedLoader != null) debouncedLoader.dispose(); if (debouncedLoader != null) debouncedLoader.dispose();
if (playQueueReactor != null) playQueueReactor.cancel(); if (playQueueReactor != null) playQueueReactor.cancel();
if (loaderReactor != null) loaderReactor.dispose(); if (loaderReactor != null) loaderReactor.dispose();
if (syncReactor != null) syncReactor.dispose();
if (sources != null) sources.releaseSource(); if (sources != null) sources.releaseSource();
playQueueReactor = null; playQueueReactor = null;
loaderReactor = null; loaderReactor = null;
syncReactor = null;
syncedItem = null; syncedItem = null;
sources = null; sources = null;
} }
@ -112,7 +125,8 @@ public class MediaSourceManagerAlt {
/** /**
* Blocks the player and repopulate the sources. * Blocks the player and repopulate the sources.
* *
* Does not ensure the player is unblocked and should be done explicitly through {@link #load() load}. * Does not ensure the player is unblocked and should be done explicitly
* through {@link #load() load}.
* */ * */
public void reset() { public void reset() {
tryBlock(); tryBlock();
@ -201,7 +215,7 @@ public class MediaSourceManagerAlt {
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Internal Helpers // Playback Locking
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private boolean isPlayQueueReady() { private boolean isPlayQueueReady() {
@ -209,33 +223,74 @@ public class MediaSourceManagerAlt {
return playQueue.isComplete() || isWindowLoaded; return playQueue.isComplete() || isWindowLoaded;
} }
private boolean tryBlock() { private boolean isPlaybackReady() {
if (!isBlocked) { return sources.getSize() > 0 &&
playbackListener.block(); sources.getMediaSource(playQueue.getIndex()) instanceof LoadedMediaSource;
resetSources();
isBlocked = true;
return true;
}
return false;
} }
private boolean tryUnblock() { private void tryBlock() {
if (isPlayQueueReady() && isBlocked && sources != null) { if (isBlocked) return;
playbackListener.block();
if (this.sources != null) this.sources.releaseSource();
this.sources = new DynamicConcatenatingMediaSource();
isBlocked = true;
}
private void tryUnblock() {
if (isPlayQueueReady() && isPlaybackReady() && isBlocked && sources != null) {
isBlocked = false; isBlocked = false;
playbackListener.unblock(sources); playbackListener.unblock(sources);
return true;
} }
return false;
} }
private void sync(final PlayQueueItem item, final StreamInfo info) { /*//////////////////////////////////////////////////////////////////////////
final PlayQueueItem currentItem = playQueue.getItem(); // Metadata Synchronization TODO: maybe this should be a separate manager
if (currentItem != item || syncedItem == item || playbackListener == null) return; //////////////////////////////////////////////////////////////////////////*/
syncedItem = currentItem; private void sync() {
final PlayQueueItem currentItem = playQueue.getItem();
if (isBlocked || currentItem == null) return;
final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info);
final Consumer<Throwable> onError = throwable -> syncInternal(currentItem, null);
if (syncedItem != currentItem) {
syncedItem = currentItem;
final Disposable sync = currentItem.getStream()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, onError);
syncReactor.set(sync);
}
}
private void syncInternal(@android.support.annotation.NonNull final PlayQueueItem item,
@Nullable final StreamInfo info) {
if (playQueue == null || playbackListener == null) return;
// Ensure the current item is up to date with the play queue // Ensure the current item is up to date with the play queue
if (playQueue.getItem() == currentItem && playQueue.getItem() == syncedItem) { if (playQueue.getItem() == item && playQueue.getItem() == syncedItem) {
playbackListener.sync(syncedItem, info); playbackListener.sync(syncedItem,info);
}
}
/*//////////////////////////////////////////////////////////////////////////
// MediaSource Loading
//////////////////////////////////////////////////////////////////////////*/
private Disposable getDebouncedLoader() {
return debouncedLoadSignal
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(timestamp -> loadImmediate());
}
private void populateSources() {
if (sources == null || sources.getSize() >= playQueue.size()) return;
for (int index = sources.getSize() - 1; index < playQueue.size(); index++) {
emplace(index, new PlaceholderMediaSource());
} }
} }
@ -254,11 +309,14 @@ public class MediaSourceManagerAlt {
final int leftBound = Math.max(0, currentIndex - windowSize); final int leftBound = Math.max(0, currentIndex - windowSize);
final int rightLimit = currentIndex + windowSize + 1; final int rightLimit = currentIndex + windowSize + 1;
final int rightBound = Math.min(playQueue.size(), rightLimit); final int rightBound = Math.min(playQueue.size(), rightLimit);
final List<PlayQueueItem> items = new ArrayList<>(playQueue.getStreams().subList(leftBound, rightBound)); final List<PlayQueueItem> items = new ArrayList<>(playQueue.getStreams().subList(leftBound,
rightBound));
// Do a round robin // Do a round robin
final int excess = rightLimit - playQueue.size(); final int excess = rightLimit - playQueue.size();
if (excess >= 0) items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess))); if (excess >= 0) {
items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess)));
}
for (final PlayQueueItem item: items) loadItem(item); for (final PlayQueueItem item: items) loadItem(item);
} }
@ -269,43 +327,28 @@ public class MediaSourceManagerAlt {
final int index = playQueue.indexOf(item); final int index = playQueue.indexOf(item);
if (index > sources.getSize() - 1) return; if (index > sources.getSize() - 1) return;
if (((ManagedMediaSource) sources.getMediaSource(index)).canReplace()) { final Consumer<ManagedMediaSource> onDone = mediaSource -> {
final Disposable loader = getMediaSource(item) update(playQueue.indexOf(item), mediaSource);
loadingItems.remove(item);
tryUnblock();
sync();
};
if (!loadingItems.contains(item) &&
((ManagedMediaSource) sources.getMediaSource(index)).canReplace()) {
loadingItems.add(item);
final Disposable loader = getLoadedMediaSource(item)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(mediaSource -> update(playQueue.indexOf(item), mediaSource)); .subscribe(onDone);
loaderReactor.add(loader); loaderReactor.add(loader);
} }
tryUnblock(); tryUnblock();
if (!isBlocked) { sync();
final MediaSource mediaSource = sources.getMediaSource(playQueue.indexOf(item));
final StreamInfo info = mediaSource instanceof LoadedMediaSource ?
((LoadedMediaSource) mediaSource).getStreamInfo() : null;
sync(item, info);
}
} }
private void resetSources() { private Single<ManagedMediaSource> getLoadedMediaSource(@NonNull final PlayQueueItem stream) {
if (this.sources != null) this.sources.releaseSource();
this.sources = new DynamicConcatenatingMediaSource();
}
private void populateSources() {
if (sources == null) return;
for (final PlayQueueItem item : playQueue.getStreams()) {
insert(playQueue.indexOf(item), new PlaceholderMediaSource());
}
}
private Disposable getDebouncedLoader() {
return debouncedLoadSignal
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(timestamp -> loadImmediate());
}
private Single<ManagedMediaSource> getMediaSource(@NonNull final PlayQueueItem stream) {
return stream.getStream().map(streamInfo -> { return stream.getStream().map(streamInfo -> {
if (playbackListener == null) { if (playbackListener == null) {
return new FailedMediaSource(stream, new IllegalStateException( return new FailedMediaSource(stream, new IllegalStateException(
@ -318,47 +361,44 @@ public class MediaSourceManagerAlt {
"MediaSource resolution is null")); "MediaSource resolution is null"));
} }
return new LoadedMediaSource(stream, streamInfo, source, final long expiration = System.currentTimeMillis() +
TimeUnit.MILLISECONDS.convert(2, TimeUnit.HOURS)); TimeUnit.MILLISECONDS.convert(2, TimeUnit.HOURS);
return new LoadedMediaSource(source, expiration);
}).onErrorReturn(throwable -> new FailedMediaSource(stream, throwable)); }).onErrorReturn(throwable -> new FailedMediaSource(stream, throwable));
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Media Source List Manipulation // Media Source List Manipulation
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void update(final int queueIndex, final MediaSource source) { /**
* Places a {@link MediaSource} into the {@link DynamicConcatenatingMediaSource}
* with position * in respect to the play queue only if no {@link MediaSource}
* already exists at the given index.
* */
private void emplace(final int index, final MediaSource source) {
if (sources == null) return; if (sources == null) return;
if (queueIndex < 0 || queueIndex < sources.getSize()) return; if (index < 0 || index < sources.getSize()) return;
sources.addMediaSource(queueIndex + 1, source); sources.addMediaSource(index, source);
sources.removeMediaSource(queueIndex);
} }
/** /**
* Inserts a source into {@link DynamicConcatenatingMediaSource} with position * Removes a {@link MediaSource} from {@link DynamicConcatenatingMediaSource}
* in respect to the play queue. * at the given index. If this index is out of bound, then the removal is ignored.
*
* If the play queue index already exists, then the insert is ignored.
* */ * */
private void insert(final int queueIndex, final PlaceholderMediaSource source) { private void remove(final int index) {
if (sources == null) return; if (sources == null) return;
if (queueIndex < 0 || queueIndex < sources.getSize()) return; if (index < 0 || index > sources.getSize()) return;
sources.addMediaSource(queueIndex, source); sources.removeMediaSource(index);
} }
/** /**
* Removes a source from {@link DynamicConcatenatingMediaSource} with the given play queue index. * Moves a {@link MediaSource} in {@link DynamicConcatenatingMediaSource}
* * from the given source index to the target index. If either index is out of bound,
* If the play queue index does not exist, the removal is ignored. * then the call is ignored.
* */ * */
private void remove(final int queueIndex) {
if (sources == null) return;
if (queueIndex < 0 || queueIndex > sources.getSize()) return;
sources.removeMediaSource(queueIndex);
}
private void move(final int source, final int target) { private void move(final int source, final int target) {
if (sources == null) return; if (sources == null) return;
if (source < 0 || target < 0) return; if (source < 0 || target < 0) return;
@ -366,4 +406,17 @@ public class MediaSourceManagerAlt {
sources.moveMediaSource(source, target); sources.moveMediaSource(source, target);
} }
/**
* Updates the {@link MediaSource} in {@link DynamicConcatenatingMediaSource}
* at the given index with a given {@link MediaSource}. If the index is out of bound,
* then the replacement is ignored.
* */
private void update(final int index, final MediaSource source) {
if (sources == null) return;
if (index < 0 || index >= sources.getSize()) return;
sources.addMediaSource(index + 1, source);
sources.removeMediaSource(index);
}
} }