Commit 01376443 by tonihei Committed by Oliver Woodman

Add MediaSource.enable/disable.

These methods helps to indicate that a media source isn't used to create new
periods in the immediate term and thus limited resources can be released.

PiperOrigin-RevId: 258373069
parent 09147ff5
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
* Flac extension: Parse `VORBIS_COMMENT` metadata * Flac extension: Parse `VORBIS_COMMENT` metadata
([#5527](https://github.com/google/ExoPlayer/issues/5527)). ([#5527](https://github.com/google/ExoPlayer/issues/5527)).
* Set `compileSdkVersion` to 29 to use Android Q APIs. * Set `compileSdkVersion` to 29 to use Android Q APIs.
* Add `enable` and `disable` methods to `MediaSource` to improve resource
management in playlists.
### 2.10.3 ### ### 2.10.3 ###
......
...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Timeline; ...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
/** /**
* Base {@link MediaSource} implementation to handle parallel reuse and to keep a list of {@link * Base {@link MediaSource} implementation to handle parallel reuse and to keep a list of {@link
...@@ -33,6 +34,7 @@ import java.util.ArrayList; ...@@ -33,6 +34,7 @@ import java.util.ArrayList;
public abstract class BaseMediaSource implements MediaSource { public abstract class BaseMediaSource implements MediaSource {
private final ArrayList<MediaSourceCaller> mediaSourceCallers; private final ArrayList<MediaSourceCaller> mediaSourceCallers;
private final HashSet<MediaSourceCaller> enabledMediaSourceCallers;
private final MediaSourceEventListener.EventDispatcher eventDispatcher; private final MediaSourceEventListener.EventDispatcher eventDispatcher;
@Nullable private Looper looper; @Nullable private Looper looper;
...@@ -40,11 +42,13 @@ public abstract class BaseMediaSource implements MediaSource { ...@@ -40,11 +42,13 @@ public abstract class BaseMediaSource implements MediaSource {
public BaseMediaSource() { public BaseMediaSource() {
mediaSourceCallers = new ArrayList<>(/* initialCapacity= */ 1); mediaSourceCallers = new ArrayList<>(/* initialCapacity= */ 1);
enabledMediaSourceCallers = new HashSet<>(/* initialCapacity= */ 1);
eventDispatcher = new MediaSourceEventListener.EventDispatcher(); eventDispatcher = new MediaSourceEventListener.EventDispatcher();
} }
/** /**
* Starts source preparation. This method is called at most once until the next call to {@link * Starts source preparation and enables the source, see {@link #prepareSource(MediaSourceCaller,
* TransferListener)}. This method is called at most once until the next call to {@link
* #releaseSourceInternal()}. * #releaseSourceInternal()}.
* *
* @param mediaTransferListener The transfer listener which should be informed of any media data * @param mediaTransferListener The transfer listener which should be informed of any media data
...@@ -54,9 +58,15 @@ public abstract class BaseMediaSource implements MediaSource { ...@@ -54,9 +58,15 @@ public abstract class BaseMediaSource implements MediaSource {
*/ */
protected abstract void prepareSourceInternal(@Nullable TransferListener mediaTransferListener); protected abstract void prepareSourceInternal(@Nullable TransferListener mediaTransferListener);
/** Enables the source, see {@link #enable(MediaSourceCaller)}. */
protected void enableInternal() {}
/** Disables the source, see {@link #disable(MediaSourceCaller)}. */
protected void disableInternal() {}
/** /**
* Releases the source. This method is called exactly once after each call to {@link * Releases the source, see {@link #releaseSource(MediaSourceCaller)}. This method is called
* #prepareSourceInternal(TransferListener)}. * exactly once after each call to {@link #prepareSourceInternal(TransferListener)}.
*/ */
protected abstract void releaseSourceInternal(); protected abstract void releaseSourceInternal();
...@@ -115,6 +125,11 @@ public abstract class BaseMediaSource implements MediaSource { ...@@ -115,6 +125,11 @@ public abstract class BaseMediaSource implements MediaSource {
return eventDispatcher.withParameters(windowIndex, mediaPeriodId, mediaTimeOffsetMs); return eventDispatcher.withParameters(windowIndex, mediaPeriodId, mediaTimeOffsetMs);
} }
/** Returns whether the source is enabled. */
protected final boolean isEnabled() {
return !enabledMediaSourceCallers.isEmpty();
}
@Override @Override
public final void addEventListener(Handler handler, MediaSourceEventListener eventListener) { public final void addEventListener(Handler handler, MediaSourceEventListener eventListener) {
eventDispatcher.addEventListener(handler, eventListener); eventDispatcher.addEventListener(handler, eventListener);
...@@ -130,22 +145,47 @@ public abstract class BaseMediaSource implements MediaSource { ...@@ -130,22 +145,47 @@ public abstract class BaseMediaSource implements MediaSource {
MediaSourceCaller caller, @Nullable TransferListener mediaTransferListener) { MediaSourceCaller caller, @Nullable TransferListener mediaTransferListener) {
Looper looper = Looper.myLooper(); Looper looper = Looper.myLooper();
Assertions.checkArgument(this.looper == null || this.looper == looper); Assertions.checkArgument(this.looper == null || this.looper == looper);
Timeline timeline = this.timeline;
mediaSourceCallers.add(caller); mediaSourceCallers.add(caller);
if (this.looper == null) { if (this.looper == null) {
this.looper = looper; this.looper = looper;
enabledMediaSourceCallers.add(caller);
prepareSourceInternal(mediaTransferListener); prepareSourceInternal(mediaTransferListener);
} else if (timeline != null) { } else if (timeline != null) {
enable(caller);
caller.onSourceInfoRefreshed(/* source= */ this, timeline); caller.onSourceInfoRefreshed(/* source= */ this, timeline);
} }
} }
@Override @Override
public final void enable(MediaSourceCaller caller) {
Assertions.checkNotNull(looper);
boolean wasDisabled = enabledMediaSourceCallers.isEmpty();
enabledMediaSourceCallers.add(caller);
if (wasDisabled) {
enableInternal();
}
}
@Override
public final void disable(MediaSourceCaller caller) {
boolean wasEnabled = !enabledMediaSourceCallers.isEmpty();
enabledMediaSourceCallers.remove(caller);
if (wasEnabled && enabledMediaSourceCallers.isEmpty()) {
disableInternal();
}
}
@Override
public final void releaseSource(MediaSourceCaller caller) { public final void releaseSource(MediaSourceCaller caller) {
mediaSourceCallers.remove(caller); mediaSourceCallers.remove(caller);
if (mediaSourceCallers.isEmpty()) { if (mediaSourceCallers.isEmpty()) {
looper = null; looper = null;
timeline = null; timeline = null;
enabledMediaSourceCallers.clear();
releaseSourceInternal(); releaseSourceInternal();
} else {
disable(caller);
} }
} }
} }
...@@ -37,7 +37,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource { ...@@ -37,7 +37,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
@Nullable private Handler eventHandler; @Nullable private Handler eventHandler;
@Nullable private TransferListener mediaTransferListener; @Nullable private TransferListener mediaTransferListener;
/** Create composite media source without child sources. */ /** Creates composite media source without child sources. */
protected CompositeMediaSource() { protected CompositeMediaSource() {
childSources = new HashMap<>(); childSources = new HashMap<>();
} }
...@@ -59,6 +59,22 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource { ...@@ -59,6 +59,22 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
@Override @Override
@CallSuper @CallSuper
protected void enableInternal() {
for (MediaSourceAndListener childSource : childSources.values()) {
childSource.mediaSource.enable(childSource.caller);
}
}
@Override
@CallSuper
protected void disableInternal() {
for (MediaSourceAndListener childSource : childSources.values()) {
childSource.mediaSource.disable(childSource.caller);
}
}
@Override
@CallSuper
protected void releaseSourceInternal() { protected void releaseSourceInternal() {
for (MediaSourceAndListener childSource : childSources.values()) { for (MediaSourceAndListener childSource : childSources.values()) {
childSource.mediaSource.releaseSource(childSource.caller); childSource.mediaSource.releaseSource(childSource.caller);
...@@ -97,6 +113,29 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource { ...@@ -97,6 +113,29 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
childSources.put(id, new MediaSourceAndListener(mediaSource, caller, eventListener)); childSources.put(id, new MediaSourceAndListener(mediaSource, caller, eventListener));
mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener); mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener);
mediaSource.prepareSource(caller, mediaTransferListener); mediaSource.prepareSource(caller, mediaTransferListener);
if (!isEnabled()) {
mediaSource.disable(caller);
}
}
/**
* Enables a child source.
*
* @param id The unique id used to prepare the child source.
*/
protected final void enableChildSource(final T id) {
MediaSourceAndListener enabledChild = Assertions.checkNotNull(childSources.get(id));
enabledChild.mediaSource.enable(enabledChild.caller);
}
/**
* Disables a child source.
*
* @param id The unique id used to prepare the child source.
*/
protected final void disableChildSource(final T id) {
MediaSourceAndListener disabledChild = Assertions.checkNotNull(childSources.get(id));
disabledChild.mediaSource.disable(disabledChild.caller);
} }
/** /**
......
...@@ -35,6 +35,7 @@ import java.util.Collections; ...@@ -35,6 +35,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -68,6 +69,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -68,6 +69,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
private final List<MediaSourceHolder> mediaSourceHolders; private final List<MediaSourceHolder> mediaSourceHolders;
private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod; private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
private final Map<Object, MediaSourceHolder> mediaSourceByUid; private final Map<Object, MediaSourceHolder> mediaSourceByUid;
private final Set<MediaSourceHolder> enabledMediaSourceHolders;
private final boolean isAtomic; private final boolean isAtomic;
private final boolean useLazyPreparation; private final boolean useLazyPreparation;
...@@ -131,6 +133,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -131,6 +133,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
this.mediaSourceHolders = new ArrayList<>(); this.mediaSourceHolders = new ArrayList<>();
this.nextTimelineUpdateOnCompletionActions = new HashSet<>(); this.nextTimelineUpdateOnCompletionActions = new HashSet<>();
this.pendingOnCompletionActions = new HashSet<>(); this.pendingOnCompletionActions = new HashSet<>();
this.enabledMediaSourceHolders = new HashSet<>();
this.isAtomic = isAtomic; this.isAtomic = isAtomic;
this.useLazyPreparation = useLazyPreparation; this.useLazyPreparation = useLazyPreparation;
addMediaSources(Arrays.asList(mediaSources)); addMediaSources(Arrays.asList(mediaSources));
...@@ -418,7 +421,8 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -418,7 +421,8 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
} }
@Override @Override
public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { protected synchronized void prepareSourceInternal(
@Nullable TransferListener mediaTransferListener) {
super.prepareSourceInternal(mediaTransferListener); super.prepareSourceInternal(mediaTransferListener);
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage); playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
if (mediaSourcesPublic.isEmpty()) { if (mediaSourcesPublic.isEmpty()) {
...@@ -430,6 +434,12 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -430,6 +434,12 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
} }
} }
@SuppressWarnings("MissingSuperCall")
@Override
protected void enableInternal() {
// Suppress enabling all child sources here as they can be lazily enabled when creating periods.
}
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid); Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid);
...@@ -441,10 +451,12 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -441,10 +451,12 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
holder.isRemoved = true; holder.isRemoved = true;
prepareChildSource(holder, holder.mediaSource); prepareChildSource(holder, holder.mediaSource);
} }
enableMediaSource(holder);
holder.activeMediaPeriodIds.add(childMediaPeriodId); holder.activeMediaPeriodIds.add(childMediaPeriodId);
MediaPeriod mediaPeriod = MediaPeriod mediaPeriod =
holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs); holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
mediaSourceByMediaPeriod.put(mediaPeriod, holder); mediaSourceByMediaPeriod.put(mediaPeriod, holder);
disableUnusedMediaSources();
return mediaPeriod; return mediaPeriod;
} }
...@@ -454,13 +466,23 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -454,13 +466,23 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod)); Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
holder.mediaSource.releasePeriod(mediaPeriod); holder.mediaSource.releasePeriod(mediaPeriod);
holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id); holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id);
if (!mediaSourceByMediaPeriod.isEmpty()) {
disableUnusedMediaSources();
}
maybeReleaseChildSource(holder); maybeReleaseChildSource(holder);
} }
@Override @Override
public synchronized void releaseSourceInternal() { protected void disableInternal() {
super.disableInternal();
enabledMediaSourceHolders.clear();
}
@Override
protected synchronized void releaseSourceInternal() {
super.releaseSourceInternal(); super.releaseSourceInternal();
mediaSourceHolders.clear(); mediaSourceHolders.clear();
enabledMediaSourceHolders.clear();
mediaSourceByUid.clear(); mediaSourceByUid.clear();
shuffleOrder = shuffleOrder.cloneAndClear(); shuffleOrder = shuffleOrder.cloneAndClear();
if (playbackThreadHandler != null) { if (playbackThreadHandler != null) {
...@@ -718,6 +740,11 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -718,6 +740,11 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
mediaSourceHolders.add(newIndex, newMediaSourceHolder); mediaSourceHolders.add(newIndex, newMediaSourceHolder);
mediaSourceByUid.put(newMediaSourceHolder.uid, newMediaSourceHolder); mediaSourceByUid.put(newMediaSourceHolder.uid, newMediaSourceHolder);
prepareChildSource(newMediaSourceHolder, newMediaSourceHolder.mediaSource); prepareChildSource(newMediaSourceHolder, newMediaSourceHolder.mediaSource);
if (isEnabled() && mediaSourceByMediaPeriod.isEmpty()) {
enabledMediaSourceHolders.add(newMediaSourceHolder);
} else {
disableChildSource(newMediaSourceHolder);
}
} }
private void updateMediaSourceInternal(MediaSourceHolder mediaSourceHolder, Timeline timeline) { private void updateMediaSourceInternal(MediaSourceHolder mediaSourceHolder, Timeline timeline) {
...@@ -772,10 +799,27 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo ...@@ -772,10 +799,27 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) { private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) {
// Release if the source has been removed from the playlist and no periods are still active. // Release if the source has been removed from the playlist and no periods are still active.
if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) { if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) {
enabledMediaSourceHolders.remove(mediaSourceHolder);
releaseChildSource(mediaSourceHolder); releaseChildSource(mediaSourceHolder);
} }
} }
private void enableMediaSource(MediaSourceHolder mediaSourceHolder) {
enabledMediaSourceHolders.add(mediaSourceHolder);
enableChildSource(mediaSourceHolder);
}
private void disableUnusedMediaSources() {
Iterator<MediaSourceHolder> iterator = enabledMediaSourceHolders.iterator();
while (iterator.hasNext()) {
MediaSourceHolder holder = iterator.next();
if (holder.activeMediaPeriodIds.isEmpty()) {
disableChildSource(holder);
iterator.remove();
}
}
}
/** Return uid of media source holder from period uid of concatenated source. */ /** Return uid of media source holder from period uid of concatenated source. */
private static Object getMediaSourceHolderUid(Object periodUid) { private static Object getMediaSourceHolderUid(Object periodUid) {
return ConcatenatedTimeline.getChildTimelineUidFromConcatenatedUid(periodUid); return ConcatenatedTimeline.getChildTimelineUidFromConcatenatedUid(periodUid);
......
...@@ -235,7 +235,8 @@ public interface MediaSource { ...@@ -235,7 +235,8 @@ public interface MediaSource {
} }
/** /**
* Registers a {@link MediaSourceCaller} and starts source preparation if needed. * Registers a {@link MediaSourceCaller}. Starts source preparation if needed and enables the
* source for the creation of {@link MediaPeriod MediaPerods}.
* *
* <p>Should not be called directly from application code. * <p>Should not be called directly from application code.
* *
...@@ -255,17 +256,31 @@ public interface MediaSource { ...@@ -255,17 +256,31 @@ public interface MediaSource {
/** /**
* Throws any pending error encountered while loading or refreshing source information. * Throws any pending error encountered while loading or refreshing source information.
* <p> *
* Should not be called directly from application code. * <p>Should not be called directly from application code.
*
* <p>Must only be called after {@link #prepareSource(MediaSourceCaller, TransferListener)}.
*/ */
void maybeThrowSourceInfoRefreshError() throws IOException; void maybeThrowSourceInfoRefreshError() throws IOException;
/** /**
* Returns a new {@link MediaPeriod} identified by {@code periodId}. This method may be called * Enables the source for the creation of {@link MediaPeriod MediaPeriods}.
* multiple times without an intervening call to {@link #releasePeriod(MediaPeriod)}.
* *
* <p>Should not be called directly from application code. * <p>Should not be called directly from application code.
* *
* <p>Must only be called after {@link #prepareSource(MediaSourceCaller, TransferListener)}.
*
* @param caller The {@link MediaSourceCaller} enabling the source.
*/
void enable(MediaSourceCaller caller);
/**
* Returns a new {@link MediaPeriod} identified by {@code periodId}.
*
* <p>Should not be called directly from application code.
*
* <p>Must only be called if the source is enabled.
*
* @param id The identifier of the period. * @param id The identifier of the period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param startPositionUs The expected start position, in microseconds. * @param startPositionUs The expected start position, in microseconds.
...@@ -275,18 +290,35 @@ public interface MediaSource { ...@@ -275,18 +290,35 @@ public interface MediaSource {
/** /**
* Releases the period. * Releases the period.
* <p> *
* Should not be called directly from application code. * <p>Should not be called directly from application code.
* *
* @param mediaPeriod The period to release. * @param mediaPeriod The period to release.
*/ */
void releasePeriod(MediaPeriod mediaPeriod); void releasePeriod(MediaPeriod mediaPeriod);
/** /**
* Unregisters a caller and releases the source if no longer required. * Disables the source for the creation of {@link MediaPeriod MediaPeriods}. The implementation
* should not hold onto limited resources used for the creation of media periods.
*
* <p>Should not be called directly from application code.
*
* <p>Must only be called after all {@link MediaPeriod MediaPeriods} previously created by {@link
* #createPeriod(MediaPeriodId, Allocator, long)} have been released by {@link
* #releasePeriod(MediaPeriod)}.
*
* @param caller The {@link MediaSourceCaller} disabling the source.
*/
void disable(MediaSourceCaller caller);
/**
* Unregisters a caller, and disables and releases the source if no longer required.
* *
* <p>Should not be called directly from application code. * <p>Should not be called directly from application code.
* *
* <p>Must only be called if all created {@link MediaPeriod MediaPeriods} have been released by
* {@link #releasePeriod(MediaPeriod)}.
*
* @param caller The {@link MediaSourceCaller} to be unregistered. * @param caller The {@link MediaSourceCaller} to be unregistered.
*/ */
void releaseSource(MediaSourceCaller caller); void releaseSource(MediaSourceCaller caller);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment