mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
This commit is contained in:
parent
09147ff548
commit
01376443b3
@ -22,6 +22,8 @@
|
||||
* Flac extension: Parse `VORBIS_COMMENT` metadata
|
||||
([#5527](https://github.com/google/ExoPlayer/issues/5527)).
|
||||
* 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 ###
|
||||
|
||||
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Base {@link MediaSource} implementation to handle parallel reuse and to keep a list of {@link
|
||||
@ -33,6 +34,7 @@ import java.util.ArrayList;
|
||||
public abstract class BaseMediaSource implements MediaSource {
|
||||
|
||||
private final ArrayList<MediaSourceCaller> mediaSourceCallers;
|
||||
private final HashSet<MediaSourceCaller> enabledMediaSourceCallers;
|
||||
private final MediaSourceEventListener.EventDispatcher eventDispatcher;
|
||||
|
||||
@Nullable private Looper looper;
|
||||
@ -40,11 +42,13 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||
|
||||
public BaseMediaSource() {
|
||||
mediaSourceCallers = new ArrayList<>(/* initialCapacity= */ 1);
|
||||
enabledMediaSourceCallers = new HashSet<>(/* initialCapacity= */ 1);
|
||||
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()}.
|
||||
*
|
||||
* @param mediaTransferListener The transfer listener which should be informed of any media data
|
||||
@ -54,9 +58,15 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||
*/
|
||||
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
|
||||
* #prepareSourceInternal(TransferListener)}.
|
||||
* Releases the source, see {@link #releaseSource(MediaSourceCaller)}. This method is called
|
||||
* exactly once after each call to {@link #prepareSourceInternal(TransferListener)}.
|
||||
*/
|
||||
protected abstract void releaseSourceInternal();
|
||||
|
||||
@ -115,6 +125,11 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||
return eventDispatcher.withParameters(windowIndex, mediaPeriodId, mediaTimeOffsetMs);
|
||||
}
|
||||
|
||||
/** Returns whether the source is enabled. */
|
||||
protected final boolean isEnabled() {
|
||||
return !enabledMediaSourceCallers.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void addEventListener(Handler handler, MediaSourceEventListener eventListener) {
|
||||
eventDispatcher.addEventListener(handler, eventListener);
|
||||
@ -130,22 +145,47 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||
MediaSourceCaller caller, @Nullable TransferListener mediaTransferListener) {
|
||||
Looper looper = Looper.myLooper();
|
||||
Assertions.checkArgument(this.looper == null || this.looper == looper);
|
||||
Timeline timeline = this.timeline;
|
||||
mediaSourceCallers.add(caller);
|
||||
if (this.looper == null) {
|
||||
this.looper = looper;
|
||||
enabledMediaSourceCallers.add(caller);
|
||||
prepareSourceInternal(mediaTransferListener);
|
||||
} else if (timeline != null) {
|
||||
enable(caller);
|
||||
caller.onSourceInfoRefreshed(/* source= */ this, timeline);
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
mediaSourceCallers.remove(caller);
|
||||
if (mediaSourceCallers.isEmpty()) {
|
||||
looper = null;
|
||||
timeline = null;
|
||||
enabledMediaSourceCallers.clear();
|
||||
releaseSourceInternal();
|
||||
} else {
|
||||
disable(caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
||||
@Nullable private Handler eventHandler;
|
||||
@Nullable private TransferListener mediaTransferListener;
|
||||
|
||||
/** Create composite media source without child sources. */
|
||||
/** Creates composite media source without child sources. */
|
||||
protected CompositeMediaSource() {
|
||||
childSources = new HashMap<>();
|
||||
}
|
||||
@ -57,6 +57,22 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@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() {
|
||||
@ -97,6 +113,29 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
||||
childSources.put(id, new MediaSourceAndListener(mediaSource, caller, eventListener));
|
||||
mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener);
|
||||
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;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -68,6 +69,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
||||
private final List<MediaSourceHolder> mediaSourceHolders;
|
||||
private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
|
||||
private final Map<Object, MediaSourceHolder> mediaSourceByUid;
|
||||
private final Set<MediaSourceHolder> enabledMediaSourceHolders;
|
||||
private final boolean isAtomic;
|
||||
private final boolean useLazyPreparation;
|
||||
|
||||
@ -131,6 +133,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
||||
this.mediaSourceHolders = new ArrayList<>();
|
||||
this.nextTimelineUpdateOnCompletionActions = new HashSet<>();
|
||||
this.pendingOnCompletionActions = new HashSet<>();
|
||||
this.enabledMediaSourceHolders = new HashSet<>();
|
||||
this.isAtomic = isAtomic;
|
||||
this.useLazyPreparation = useLazyPreparation;
|
||||
addMediaSources(Arrays.asList(mediaSources));
|
||||
@ -418,7 +421,8 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
protected synchronized void prepareSourceInternal(
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(mediaTransferListener);
|
||||
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
|
||||
if (mediaSourcesPublic.isEmpty()) {
|
||||
@ -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
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid);
|
||||
@ -441,10 +451,12 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
||||
holder.isRemoved = true;
|
||||
prepareChildSource(holder, holder.mediaSource);
|
||||
}
|
||||
enableMediaSource(holder);
|
||||
holder.activeMediaPeriodIds.add(childMediaPeriodId);
|
||||
MediaPeriod mediaPeriod =
|
||||
holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
|
||||
mediaSourceByMediaPeriod.put(mediaPeriod, holder);
|
||||
disableUnusedMediaSources();
|
||||
return mediaPeriod;
|
||||
}
|
||||
|
||||
@ -454,13 +466,23 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
||||
Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
|
||||
holder.mediaSource.releasePeriod(mediaPeriod);
|
||||
holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id);
|
||||
if (!mediaSourceByMediaPeriod.isEmpty()) {
|
||||
disableUnusedMediaSources();
|
||||
}
|
||||
maybeReleaseChildSource(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void releaseSourceInternal() {
|
||||
protected void disableInternal() {
|
||||
super.disableInternal();
|
||||
enabledMediaSourceHolders.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void releaseSourceInternal() {
|
||||
super.releaseSourceInternal();
|
||||
mediaSourceHolders.clear();
|
||||
enabledMediaSourceHolders.clear();
|
||||
mediaSourceByUid.clear();
|
||||
shuffleOrder = shuffleOrder.cloneAndClear();
|
||||
if (playbackThreadHandler != null) {
|
||||
@ -718,6 +740,11 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
||||
mediaSourceHolders.add(newIndex, newMediaSourceHolder);
|
||||
mediaSourceByUid.put(newMediaSourceHolder.uid, newMediaSourceHolder);
|
||||
prepareChildSource(newMediaSourceHolder, newMediaSourceHolder.mediaSource);
|
||||
if (isEnabled() && mediaSourceByMediaPeriod.isEmpty()) {
|
||||
enabledMediaSourceHolders.add(newMediaSourceHolder);
|
||||
} else {
|
||||
disableChildSource(newMediaSourceHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMediaSourceInternal(MediaSourceHolder mediaSourceHolder, Timeline timeline) {
|
||||
@ -772,10 +799,27 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
||||
private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) {
|
||||
// Release if the source has been removed from the playlist and no periods are still active.
|
||||
if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) {
|
||||
enabledMediaSourceHolders.remove(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. */
|
||||
private static Object getMediaSourceHolderUid(Object periodUid) {
|
||||
return ConcatenatedTimeline.getChildTimelineUidFromConcatenatedUid(periodUid);
|
||||
|
@ -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.
|
||||
*
|
||||
@ -255,17 +256,31 @@ public interface MediaSource {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Returns a new {@link MediaPeriod} identified by {@code periodId}. This method may be called
|
||||
* multiple times without an intervening call to {@link #releasePeriod(MediaPeriod)}.
|
||||
* Enables the source for the creation of {@link MediaPeriod MediaPeriods}.
|
||||
*
|
||||
* <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 allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||
* @param startPositionUs The expected start position, in microseconds.
|
||||
@ -275,18 +290,35 @@ public interface MediaSource {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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>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.
|
||||
*/
|
||||
void releaseSource(MediaSourceCaller caller);
|
||||
|
Loading…
x
Reference in New Issue
Block a user