public abstract class

BasePreloadManager<T>

extends java.lang.Object

 java.lang.Object

↳androidx.media3.exoplayer.source.preload.BasePreloadManager<T>

Subclasses:

DefaultPreloadManager

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-exoplayer:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

A base implementation of a preload manager, which maintains the lifecycle of media sources.

Methods should be called on the same thread.

Summary

Fields
protected final java.util.Comparator<java.lang.Object>rankingDataComparator

Constructors
protectedBasePreloadManager(java.util.Comparator<java.lang.Object> rankingDataComparator, TargetPreloadStatusControl<java.lang.Object> targetPreloadStatusControl, MediaSource.Factory mediaSourceFactory)

Methods
public final voidadd(MediaItem mediaItem, java.lang.Object rankingData)

Adds a MediaItem with its rankingData to the preload manager.

public final voidadd(MediaSource mediaSource, java.lang.Object rankingData)

Adds a MediaSource with its rankingData to the preload manager.

public voidaddListener(BasePreloadManager.Listener listener)

Adds a BasePreloadManager.Listener to listen to the preload events.

public voidclearListeners()

Clears all the listeners.

protected abstract voidclearSourceInternal(MediaSource mediaSource)

Clears the preloaded data of the given MediaSource, while not releasing the instance of it.

protected MediaSourcecreateMediaSourceForPreloading(MediaSource mediaSource)

Returns the MediaSource that the preload manager creates for preloading based on the given source.

public final MediaSourcegetMediaSource(MediaItem mediaItem)

Returns the MediaSource for the given MediaItem.

public final intgetSourceCount()

Gets the count of the media sources currently being managed by the preload manager.

protected final TargetPreloadStatusControl.PreloadStatusgetTargetPreloadStatus(MediaSource source)

Returns the target preload status of the given MediaSource.

public final voidinvalidate()

Invalidates the current preload progress, and triggers a new preload progress based on the new priorities of the managed media sources.

protected final voidonPreloadCompleted(MediaSource source)

Called when the given MediaSource completes preloading.

protected final voidonPreloadError(PreloadException error, MediaSource source)

Called when an error occurs.

protected final voidonPreloadSkipped(MediaSource source)

Called when the given MediaSource has been skipped before completing preloading.

protected abstract voidpreloadSourceInternal(MediaSource mediaSource, long startPositionsUs)

Preloads the given MediaSource.

public final voidrelease()

Releases the preload manager.

protected voidreleaseInternal()

Releases the preload manager, see BasePreloadManager.release().

protected abstract voidreleaseSourceInternal(MediaSource mediaSource)

Releases the given MediaSource.

public final booleanremove(MediaItem mediaItem)

Removes a MediaItem from the preload manager.

public final booleanremove(MediaSource mediaSource)

Removes a MediaSource from the preload manager.

public voidremoveListener(BasePreloadManager.Listener listener)

Removes a BasePreloadManager.Listener.

public final voidreset()

Resets the preload manager.

protected booleanshouldStartPreloadingNextSource()

Returns whether the next MediaSource should start preloading.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

protected final java.util.Comparator<java.lang.Object> rankingDataComparator

Constructors

protected BasePreloadManager(java.util.Comparator<java.lang.Object> rankingDataComparator, TargetPreloadStatusControl<java.lang.Object> targetPreloadStatusControl, MediaSource.Factory mediaSourceFactory)

Methods

public void addListener(BasePreloadManager.Listener listener)

Adds a BasePreloadManager.Listener to listen to the preload events.

This method can be called from any thread.

public void removeListener(BasePreloadManager.Listener listener)

Removes a BasePreloadManager.Listener.

public void clearListeners()

Clears all the listeners.

public final int getSourceCount()

Gets the count of the media sources currently being managed by the preload manager.

Returns:

The count of the media sources.

public final void add(MediaItem mediaItem, java.lang.Object rankingData)

Adds a MediaItem with its rankingData to the preload manager.

Parameters:

mediaItem: The MediaItem to add.
rankingData: The ranking data that is associated with the mediaItem.

public final void add(MediaSource mediaSource, java.lang.Object rankingData)

Adds a MediaSource with its rankingData to the preload manager.

Parameters:

mediaSource: The MediaSource to add.
rankingData: The ranking data that is associated with the mediaSource.

public final void invalidate()

Invalidates the current preload progress, and triggers a new preload progress based on the new priorities of the managed media sources.

public final MediaSource getMediaSource(MediaItem mediaItem)

Returns the MediaSource for the given MediaItem.

Parameters:

mediaItem: The media item.

Returns:

The source for the given mediaItem if it is managed by the preload manager, null otherwise.

public final boolean remove(MediaItem mediaItem)

Removes a MediaItem from the preload manager.

Parameters:

mediaItem: The MediaItem to remove.

Returns:

true if the preload manager is holding a MediaSource of the given MediaItem and it has been removed, otherwise false.

public final boolean remove(MediaSource mediaSource)

Removes a MediaSource from the preload manager.

Parameters:

mediaSource: The MediaSource to remove.

Returns:

true if the preload manager is holding the given MediaSource instance and it has been removed, otherwise false.

public final void reset()

Resets the preload manager. All sources that the preload manager is holding will be released.

public final void release()

Releases the preload manager.

The preload manager must not be used after calling this method.

protected final void onPreloadCompleted(MediaSource source)

Called when the given MediaSource completes preloading.

protected final void onPreloadError(PreloadException error, MediaSource source)

Called when an error occurs.

protected final void onPreloadSkipped(MediaSource source)

Called when the given MediaSource has been skipped before completing preloading.

protected final TargetPreloadStatusControl.PreloadStatus getTargetPreloadStatus(MediaSource source)

Returns the target preload status of the given MediaSource.

protected MediaSource createMediaSourceForPreloading(MediaSource mediaSource)

Returns the MediaSource that the preload manager creates for preloading based on the given source. The default implementation returns the same source.

Parameters:

mediaSource: The source based on which the preload manager creates for preloading.

Returns:

The source the preload manager creates for preloading.

protected boolean shouldStartPreloadingNextSource()

Returns whether the next MediaSource should start preloading.

protected abstract void preloadSourceInternal(MediaSource mediaSource, long startPositionsUs)

Preloads the given MediaSource.

Parameters:

mediaSource: The media source to preload.
startPositionsUs: The expected starting position in microseconds, or C.TIME_UNSET to indicate the default start position.

protected abstract void clearSourceInternal(MediaSource mediaSource)

Clears the preloaded data of the given MediaSource, while not releasing the instance of it.

Parameters:

mediaSource: The media source to clear.

protected abstract void releaseSourceInternal(MediaSource mediaSource)

Releases the given MediaSource.

Parameters:

mediaSource: The media source to release.

protected void releaseInternal()

Releases the preload manager, see BasePreloadManager.release().

Source

/*
 * Copyright 2023 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
 *
 *      https://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 androidx.media3.exoplayer.source.preload;

import static androidx.media3.common.util.Assertions.checkNotNull;

import android.os.Handler;
import android.os.Looper;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ListenerSet;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.MediaSource;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

/**
 * A base implementation of a preload manager, which maintains the lifecycle of {@linkplain
 * MediaSource media sources}.
 *
 * <p>Methods should be called on the same thread.
 */
@UnstableApi
public abstract class BasePreloadManager<T> {

  /** A base class of the builder of the concrete extension of {@link BasePreloadManager}. */
  protected abstract static class BuilderBase<T> {

    protected final Comparator<T> rankingDataComparator;
    protected final TargetPreloadStatusControl<T> targetPreloadStatusControl;
    protected final MediaSource.Factory mediaSourceFactory;

    public BuilderBase(
        Comparator<T> rankingDataComparator,
        TargetPreloadStatusControl<T> targetPreloadStatusControl,
        MediaSource.Factory mediaSourceFactory) {
      this.rankingDataComparator = rankingDataComparator;
      this.targetPreloadStatusControl = targetPreloadStatusControl;
      this.mediaSourceFactory = mediaSourceFactory;
    }

    public abstract BasePreloadManager<T> build();
  }

  /** Listener for events in a preload manager. */
  public interface Listener {

    /** Called when the given {@link MediaItem} has completed preloading. */
    void onCompleted(MediaItem mediaItem);

    /** Called when an {@linkplain PreloadException error} occurs. */
    void onError(PreloadException exception);
  }

  private final Object lock;
  protected final Comparator<T> rankingDataComparator;
  private final TargetPreloadStatusControl<T> targetPreloadStatusControl;
  private final MediaSource.Factory mediaSourceFactory;
  private final ListenerSet<Listener> listeners;
  private final Map<MediaItem, MediaSourceHolder> mediaItemMediaSourceHolderMap;
  private final Handler applicationHandler;

  @GuardedBy("lock")
  private final PriorityQueue<MediaSourceHolder> sourceHolderPriorityQueue;

  @GuardedBy("lock")
  @Nullable
  private TargetPreloadStatusControl.PreloadStatus targetPreloadStatusOfCurrentPreloadingSource;

  protected BasePreloadManager(
      Comparator<T> rankingDataComparator,
      TargetPreloadStatusControl<T> targetPreloadStatusControl,
      MediaSource.Factory mediaSourceFactory) {
    lock = new Object();
    applicationHandler = Util.createHandlerForCurrentOrMainLooper();
    this.rankingDataComparator = rankingDataComparator;
    this.targetPreloadStatusControl = targetPreloadStatusControl;
    this.mediaSourceFactory = mediaSourceFactory;
    listeners =
        new ListenerSet<>(applicationHandler.getLooper(), Clock.DEFAULT, (listener, flags) -> {});
    mediaItemMediaSourceHolderMap = new HashMap<>();
    sourceHolderPriorityQueue = new PriorityQueue<>();
  }

  /**
   * Adds a {@link Listener} to listen to the preload events.
   *
   * <p>This method can be called from any thread.
   */
  public void addListener(Listener listener) {
    listeners.add(listener);
  }

  /**
   * Removes a {@link Listener}.
   *
   * @throws IllegalStateException If this method is called from the wrong thread.
   */
  public void removeListener(Listener listener) {
    verifyApplicationThread();
    listeners.remove(listener);
  }

  /**
   * Clears all the {@linkplain Listener listeners}.
   *
   * @throws IllegalStateException If this method is called from the wrong thread.
   */
  public void clearListeners() {
    verifyApplicationThread();
    listeners.clear();
  }

  /**
   * Gets the count of the {@linkplain MediaSource media sources} currently being managed by the
   * preload manager.
   *
   * @return The count of the {@linkplain MediaSource media sources}.
   */
  public final int getSourceCount() {
    return mediaItemMediaSourceHolderMap.size();
  }

  /**
   * Adds a {@link MediaItem} with its {@code rankingData} to the preload manager.
   *
   * @param mediaItem The {@link MediaItem} to add.
   * @param rankingData The ranking data that is associated with the {@code mediaItem}.
   */
  public final void add(MediaItem mediaItem, T rankingData) {
    add(mediaSourceFactory.createMediaSource(mediaItem), rankingData);
  }

  /**
   * Adds a {@link MediaSource} with its {@code rankingData} to the preload manager.
   *
   * @param mediaSource The {@link MediaSource} to add.
   * @param rankingData The ranking data that is associated with the {@code mediaSource}.
   */
  public final void add(MediaSource mediaSource, T rankingData) {
    MediaSource mediaSourceForPreloading = createMediaSourceForPreloading(mediaSource);
    MediaSourceHolder mediaSourceHolder =
        new MediaSourceHolder(mediaSourceForPreloading, rankingData);
    mediaItemMediaSourceHolderMap.put(mediaSourceForPreloading.getMediaItem(), mediaSourceHolder);
  }

  /**
   * Invalidates the current preload progress, and triggers a new preload progress based on the new
   * priorities of the managed {@linkplain MediaSource media sources}.
   */
  public final void invalidate() {
    synchronized (lock) {
      sourceHolderPriorityQueue.clear();
      sourceHolderPriorityQueue.addAll(mediaItemMediaSourceHolderMap.values());
      while (!sourceHolderPriorityQueue.isEmpty() && !maybeStartPreloadNextSource()) {
        sourceHolderPriorityQueue.poll();
      }
    }
  }

  /**
   * Returns the {@link MediaSource} for the given {@link MediaItem}.
   *
   * @param mediaItem The media item.
   * @return The source for the given {@code mediaItem} if it is managed by the preload manager,
   *     null otherwise.
   */
  @Nullable
  public final MediaSource getMediaSource(MediaItem mediaItem) {
    if (!mediaItemMediaSourceHolderMap.containsKey(mediaItem)) {
      return null;
    }
    return mediaItemMediaSourceHolderMap.get(mediaItem).mediaSource;
  }

  /**
   * Removes a {@link MediaItem} from the preload manager.
   *
   * @param mediaItem The {@link MediaItem} to remove.
   * @return {@code true} if the preload manager is holding a {@link MediaSource} of the given
   *     {@link MediaItem} and it has been removed, otherwise {@code false}.
   */
  public final boolean remove(MediaItem mediaItem) {
    if (mediaItemMediaSourceHolderMap.containsKey(mediaItem)) {
      MediaSource mediaSource = mediaItemMediaSourceHolderMap.get(mediaItem).mediaSource;
      mediaItemMediaSourceHolderMap.remove(mediaItem);
      releaseSourceInternal(mediaSource);
      return true;
    }
    return false;
  }

  /**
   * Removes a {@link MediaSource} from the preload manager.
   *
   * @param mediaSource The {@link MediaSource} to remove.
   * @return {@code true} if the preload manager is holding the given {@link MediaSource} instance
   *     and it has been removed, otherwise {@code false}.
   */
  public final boolean remove(MediaSource mediaSource) {
    MediaItem mediaItem = mediaSource.getMediaItem();
    if (mediaItemMediaSourceHolderMap.containsKey(mediaItem)) {
      MediaSource heldMediaSource = mediaItemMediaSourceHolderMap.get(mediaItem).mediaSource;
      if (mediaSource == heldMediaSource) {
        mediaItemMediaSourceHolderMap.remove(mediaItem);
        releaseSourceInternal(mediaSource);
        return true;
      }
    }
    return false;
  }

  /**
   * Resets the preload manager. All sources that the preload manager is holding will be released.
   */
  public final void reset() {
    for (MediaSourceHolder sourceHolder : mediaItemMediaSourceHolderMap.values()) {
      releaseSourceInternal(sourceHolder.mediaSource);
    }
    mediaItemMediaSourceHolderMap.clear();
    synchronized (lock) {
      sourceHolderPriorityQueue.clear();
      targetPreloadStatusOfCurrentPreloadingSource = null;
    }
  }

  /**
   * Releases the preload manager.
   *
   * <p>The preload manager must not be used after calling this method.
   */
  public final void release() {
    reset();
    releaseInternal();
    clearListeners();
  }

  /** Called when the given {@link MediaSource} completes preloading. */
  protected final void onPreloadCompleted(MediaSource source) {
    applicationHandler.post(
        () -> {
          listeners.sendEvent(
              /* eventFlag= */ C.INDEX_UNSET,
              listener -> listener.onCompleted(source.getMediaItem()));
          maybeAdvanceToNextSource(source);
        });
  }

  /** Called when an error occurs. */
  protected final void onPreloadError(PreloadException error, MediaSource source) {
    applicationHandler.post(
        () -> {
          listeners.sendEvent(/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onError(error));
          maybeAdvanceToNextSource(source);
        });
  }

  /** Called when the given {@link MediaSource} has been skipped before completing preloading. */
  protected final void onPreloadSkipped(MediaSource source) {
    applicationHandler.post(() -> maybeAdvanceToNextSource(source));
  }

  private void maybeAdvanceToNextSource(MediaSource preloadingSource) {
    synchronized (lock) {
      if (sourceHolderPriorityQueue.isEmpty()
          || checkNotNull(sourceHolderPriorityQueue.peek()).mediaSource != preloadingSource) {
        return;
      }
      do {
        sourceHolderPriorityQueue.poll();
      } while (!sourceHolderPriorityQueue.isEmpty() && !maybeStartPreloadNextSource());
    }
  }

  /**
   * Returns the {@linkplain TargetPreloadStatusControl.PreloadStatus target preload status} of the
   * given {@link MediaSource}.
   */
  @Nullable
  protected final TargetPreloadStatusControl.PreloadStatus getTargetPreloadStatus(
      MediaSource source) {
    synchronized (lock) {
      if (sourceHolderPriorityQueue.isEmpty()
          || checkNotNull(sourceHolderPriorityQueue.peek()).mediaSource != source) {
        return null;
      }
      return targetPreloadStatusOfCurrentPreloadingSource;
    }
  }

  /**
   * Returns the {@link MediaSource} that the preload manager creates for preloading based on the
   * given {@link MediaSource source}. The default implementation returns the same source.
   *
   * @param mediaSource The source based on which the preload manager creates for preloading.
   * @return The source the preload manager creates for preloading.
   */
  protected MediaSource createMediaSourceForPreloading(MediaSource mediaSource) {
    return mediaSource;
  }

  /** Returns whether the next {@link MediaSource} should start preloading. */
  protected boolean shouldStartPreloadingNextSource() {
    return true;
  }

  /**
   * Preloads the given {@link MediaSource}.
   *
   * @param mediaSource The media source to preload.
   * @param startPositionsUs The expected starting position in microseconds, or {@link C#TIME_UNSET}
   *     to indicate the default start position.
   */
  protected abstract void preloadSourceInternal(MediaSource mediaSource, long startPositionsUs);

  /**
   * Clears the preloaded data of the given {@link MediaSource}, while not releasing the instance of
   * it.
   *
   * @param mediaSource The media source to clear.
   */
  protected abstract void clearSourceInternal(MediaSource mediaSource);

  /**
   * Releases the given {@link MediaSource}.
   *
   * @param mediaSource The media source to release.
   */
  protected abstract void releaseSourceInternal(MediaSource mediaSource);

  /** Releases the preload manager, see {@link #release()}. */
  protected void releaseInternal() {}

  /**
   * Starts to preload the {@link MediaSource} at the head of the priority queue, if the {@linkplain
   * TargetPreloadStatusControl.PreloadStatus target preload status} for that source is not null.
   *
   * @return {@code true} if the {@link MediaSource} at the head of the priority queue starts to
   *     preload, otherwise {@code false}.
   * @throws NullPointerException if the priority queue is empty.
   */
  @GuardedBy("lock")
  private boolean maybeStartPreloadNextSource() {
    if (shouldStartPreloadingNextSource()) {
      MediaSourceHolder preloadingHolder = checkNotNull(sourceHolderPriorityQueue.peek());
      this.targetPreloadStatusOfCurrentPreloadingSource =
          targetPreloadStatusControl.getTargetPreloadStatus(preloadingHolder.rankingData);
      if (targetPreloadStatusOfCurrentPreloadingSource != null) {
        preloadSourceInternal(preloadingHolder.mediaSource, preloadingHolder.startPositionUs);
        return true;
      } else {
        clearSourceInternal(preloadingHolder.mediaSource);
      }
    }
    return false;
  }

  private void verifyApplicationThread() {
    if (Looper.myLooper() != applicationHandler.getLooper()) {
      throw new IllegalStateException("Preload manager is accessed on the wrong thread.");
    }
  }

  /** A holder for information for preloading a single media source. */
  private final class MediaSourceHolder implements Comparable<MediaSourceHolder> {

    public final MediaSource mediaSource;
    public final T rankingData;
    public final long startPositionUs;

    public MediaSourceHolder(MediaSource mediaSource, T rankingData) {
      this(mediaSource, rankingData, C.TIME_UNSET);
    }

    public MediaSourceHolder(MediaSource mediaSource, T rankingData, long startPositionUs) {
      this.mediaSource = mediaSource;
      this.rankingData = rankingData;
      this.startPositionUs = startPositionUs;
    }

    @Override
    public int compareTo(BasePreloadManager<T>.MediaSourceHolder o) {
      return rankingDataComparator.compare(this.rankingData, o.rankingData);
    }
  }
}