public final class

LoopingMediaSource

extends CompositeMediaSource<java.lang.Void>

 java.lang.Object

androidx.media3.exoplayer.source.BaseMediaSource

androidx.media3.exoplayer.source.CompositeMediaSource<java.lang.Void>

↳androidx.media3.exoplayer.source.LoopingMediaSource

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.0.0-alpha03'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer
  • version: 1.0.0-alpha03

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

Overview

Loops a MediaSource a specified number of times.

Summary

Constructors
publicLoopingMediaSource(MediaSource childSource)

Loops the provided source indefinitely.

publicLoopingMediaSource(MediaSource childSource, int loopCount)

Loops the provided source a specified number of times.

Methods
public MediaPeriodcreatePeriod(MediaSource.MediaPeriodId id, Allocator allocator, long startPositionUs)

public TimelinegetInitialTimeline()

public MediaItemgetMediaItem()

protected MediaSource.MediaPeriodIdgetMediaPeriodIdForChildMediaPeriodId(java.lang.Object id, MediaSource.MediaPeriodId mediaPeriodId)

Returns the in the composite source corresponding to the specified in a child source.

public booleanisSingleWindow()

protected abstract voidonChildSourceInfoRefreshed(java.lang.Object id, MediaSource mediaSource, Timeline timeline)

Called when the source info of a child source has been refreshed.

protected abstract voidprepareSourceInternal(TransferListener mediaTransferListener)

Starts source preparation and enables the source, see MediaSource.prepareSource(MediaSource.MediaSourceCaller, TransferListener, PlayerId).

public voidreleasePeriod(MediaPeriod mediaPeriod)

from CompositeMediaSource<T>disableChildSource, disableInternal, enableChildSource, enableInternal, getMediaTimeForChildMediaTime, getWindowIndexForChildWindowIndex, maybeThrowSourceInfoRefreshError, prepareChildSource, releaseChildSource, releaseSourceInternal
from BaseMediaSourceaddDrmEventListener, addEventListener, createDrmEventDispatcher, createDrmEventDispatcher, createEventDispatcher, createEventDispatcher, createEventDispatcher, disable, enable, getPlayerId, isEnabled, prepareSource, refreshSourceInfo, releaseSource, removeDrmEventListener, removeEventListener
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public LoopingMediaSource(MediaSource childSource)

Loops the provided source indefinitely. Note that it is usually better to use Player.setRepeatMode(int).

Parameters:

childSource: The MediaSource to loop.

public LoopingMediaSource(MediaSource childSource, int loopCount)

Loops the provided source a specified number of times.

Parameters:

childSource: The MediaSource to loop.
loopCount: The desired number of loops. Must be strictly positive.

Methods

public MediaItem getMediaItem()

public Timeline getInitialTimeline()

public boolean isSingleWindow()

protected abstract void prepareSourceInternal(TransferListener mediaTransferListener)

Starts source preparation and enables the source, see MediaSource.prepareSource(MediaSource.MediaSourceCaller, TransferListener, PlayerId). This method is called at most once until the next call to BaseMediaSource.releaseSourceInternal().

Parameters:

mediaTransferListener: The transfer listener which should be informed of any media data transfers. May be null if no listener is available. Note that this listener should usually be only informed of transfers related to the media loads and not of auxiliary loads for manifests and other data.

public MediaPeriod createPeriod(MediaSource.MediaPeriodId id, Allocator allocator, long startPositionUs)

public void releasePeriod(MediaPeriod mediaPeriod)

protected abstract void onChildSourceInfoRefreshed(java.lang.Object id, MediaSource mediaSource, Timeline timeline)

Called when the source info of a child source has been refreshed.

Parameters:

id: The unique id used to prepare the child source.
mediaSource: The child source whose source info has been refreshed.
timeline: The timeline of the child source.

protected MediaSource.MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(java.lang.Object id, MediaSource.MediaPeriodId mediaPeriodId)

Returns the in the composite source corresponding to the specified in a child source. The default implementation does not change the media period id.

Parameters:

id: The unique id used to prepare the child source.
mediaPeriodId: A of the child source.

Returns:

The corresponding in the composite source. Null if no corresponding media period id can be determined.

Source

/*
 * Copyright (C) 2016 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 androidx.media3.exoplayer.source;

import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.TransferListener;
import androidx.media3.exoplayer.AbstractConcatenatedTimeline;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.ShuffleOrder.UnshuffledShuffleOrder;
import androidx.media3.exoplayer.upstream.Allocator;
import java.util.HashMap;
import java.util.Map;

/**
 * Loops a {@link MediaSource} a specified number of times.
 *
 * @deprecated To loop a {@link MediaSource} indefinitely, use {@link Player#setRepeatMode(int)}
 *     instead of this class. To add a {@link MediaSource} a specific number of times to the
 *     playlist, use {@link ExoPlayer#addMediaSource} in a loop with the same {@link MediaSource}.
 *     To combine repeated {@link MediaSource} instances into one {@link MediaSource}, for example
 *     to further wrap it in another {@link MediaSource}, use {@link ConcatenatingMediaSource} with
 *     the same {@link MediaSource} {@link ConcatenatingMediaSource#addMediaSource added} multiple
 *     times.
 */
@Deprecated
@UnstableApi
public final class LoopingMediaSource extends CompositeMediaSource<Void> {

  private final MaskingMediaSource maskingMediaSource;
  private final int loopCount;
  private final Map<MediaPeriodId, MediaPeriodId> childMediaPeriodIdToMediaPeriodId;
  private final Map<MediaPeriod, MediaPeriodId> mediaPeriodToChildMediaPeriodId;

  /**
   * Loops the provided source indefinitely. Note that it is usually better to use {@link
   * ExoPlayer#setRepeatMode(int)}.
   *
   * @param childSource The {@link MediaSource} to loop.
   */
  public LoopingMediaSource(MediaSource childSource) {
    this(childSource, Integer.MAX_VALUE);
  }

  /**
   * Loops the provided source a specified number of times.
   *
   * @param childSource The {@link MediaSource} to loop.
   * @param loopCount The desired number of loops. Must be strictly positive.
   */
  public LoopingMediaSource(MediaSource childSource, int loopCount) {
    Assertions.checkArgument(loopCount > 0);
    this.maskingMediaSource = new MaskingMediaSource(childSource, /* useLazyPreparation= */ false);
    this.loopCount = loopCount;
    childMediaPeriodIdToMediaPeriodId = new HashMap<>();
    mediaPeriodToChildMediaPeriodId = new HashMap<>();
  }

  @Override
  public MediaItem getMediaItem() {
    return maskingMediaSource.getMediaItem();
  }

  @Override
  @Nullable
  public Timeline getInitialTimeline() {
    return loopCount != Integer.MAX_VALUE
        ? new LoopingTimeline(maskingMediaSource.getTimeline(), loopCount)
        : new InfinitelyLoopingTimeline(maskingMediaSource.getTimeline());
  }

  @Override
  public boolean isSingleWindow() {
    return false;
  }

  @Override
  protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
    super.prepareSourceInternal(mediaTransferListener);
    prepareChildSource(/* id= */ null, maskingMediaSource);
  }

  @Override
  public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
    if (loopCount == Integer.MAX_VALUE) {
      return maskingMediaSource.createPeriod(id, allocator, startPositionUs);
    }
    Object childPeriodUid = LoopingTimeline.getChildPeriodUidFromConcatenatedUid(id.periodUid);
    MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(childPeriodUid);
    childMediaPeriodIdToMediaPeriodId.put(childMediaPeriodId, id);
    MediaPeriod mediaPeriod =
        maskingMediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
    mediaPeriodToChildMediaPeriodId.put(mediaPeriod, childMediaPeriodId);
    return mediaPeriod;
  }

  @Override
  public void releasePeriod(MediaPeriod mediaPeriod) {
    maskingMediaSource.releasePeriod(mediaPeriod);
    @Nullable
    MediaPeriodId childMediaPeriodId = mediaPeriodToChildMediaPeriodId.remove(mediaPeriod);
    if (childMediaPeriodId != null) {
      childMediaPeriodIdToMediaPeriodId.remove(childMediaPeriodId);
    }
  }

  @Override
  protected void onChildSourceInfoRefreshed(Void id, MediaSource mediaSource, Timeline timeline) {
    Timeline loopingTimeline =
        loopCount != Integer.MAX_VALUE
            ? new LoopingTimeline(timeline, loopCount)
            : new InfinitelyLoopingTimeline(timeline);
    refreshSourceInfo(loopingTimeline);
  }

  @Override
  @Nullable
  protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
      Void id, MediaPeriodId mediaPeriodId) {
    return loopCount != Integer.MAX_VALUE
        ? childMediaPeriodIdToMediaPeriodId.get(mediaPeriodId)
        : mediaPeriodId;
  }

  private static final class LoopingTimeline extends AbstractConcatenatedTimeline {

    private final Timeline childTimeline;
    private final int childPeriodCount;
    private final int childWindowCount;
    private final int loopCount;

    public LoopingTimeline(Timeline childTimeline, int loopCount) {
      super(/* isAtomic= */ false, new UnshuffledShuffleOrder(loopCount));
      this.childTimeline = childTimeline;
      childPeriodCount = childTimeline.getPeriodCount();
      childWindowCount = childTimeline.getWindowCount();
      this.loopCount = loopCount;
      if (childPeriodCount > 0) {
        Assertions.checkState(
            loopCount <= Integer.MAX_VALUE / childPeriodCount,
            "LoopingMediaSource contains too many periods");
      }
    }

    @Override
    public int getWindowCount() {
      return childWindowCount * loopCount;
    }

    @Override
    public int getPeriodCount() {
      return childPeriodCount * loopCount;
    }

    @Override
    protected int getChildIndexByPeriodIndex(int periodIndex) {
      return periodIndex / childPeriodCount;
    }

    @Override
    protected int getChildIndexByWindowIndex(int windowIndex) {
      return windowIndex / childWindowCount;
    }

    @Override
    protected int getChildIndexByChildUid(Object childUid) {
      if (!(childUid instanceof Integer)) {
        return C.INDEX_UNSET;
      }
      return (Integer) childUid;
    }

    @Override
    protected Timeline getTimelineByChildIndex(int childIndex) {
      return childTimeline;
    }

    @Override
    protected int getFirstPeriodIndexByChildIndex(int childIndex) {
      return childIndex * childPeriodCount;
    }

    @Override
    protected int getFirstWindowIndexByChildIndex(int childIndex) {
      return childIndex * childWindowCount;
    }

    @Override
    protected Object getChildUidByChildIndex(int childIndex) {
      return childIndex;
    }
  }

  private static final class InfinitelyLoopingTimeline extends ForwardingTimeline {

    public InfinitelyLoopingTimeline(Timeline timeline) {
      super(timeline);
    }

    @Override
    public int getNextWindowIndex(
        int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
      int childNextWindowIndex =
          timeline.getNextWindowIndex(windowIndex, repeatMode, shuffleModeEnabled);
      return childNextWindowIndex == C.INDEX_UNSET
          ? getFirstWindowIndex(shuffleModeEnabled)
          : childNextWindowIndex;
    }

    @Override
    public int getPreviousWindowIndex(
        int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
      int childPreviousWindowIndex =
          timeline.getPreviousWindowIndex(windowIndex, repeatMode, shuffleModeEnabled);
      return childPreviousWindowIndex == C.INDEX_UNSET
          ? getLastWindowIndex(shuffleModeEnabled)
          : childPreviousWindowIndex;
    }
  }
}