/*
* 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 android.os.Handler;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.TransferListener;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import java.io.IOException;
/**
* Defines and provides media to be played by an {@link ExoPlayer}. A MediaSource has two main
* responsibilities:
*
* <ul>
* <li>To provide the player with a {@link Timeline} defining the structure of its media, and to
* provide a new timeline whenever the structure of the media changes. The MediaSource
* provides these timelines by calling {@link MediaSourceCaller#onSourceInfoRefreshed} on the
* {@link MediaSourceCaller}s passed to {@link #prepareSource(MediaSourceCaller,
* TransferListener, PlayerId)}.
* <li>To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are
* obtained by calling {@link #createPeriod(MediaPeriodId, Allocator, long)}, and provide a
* way for the player to load and read the media.
* </ul>
*
* <p>{@code MediaSource} methods should not be called from application code. Instead, the playback
* logic in {@link ExoPlayer} will trigger methods at the right time.
*
* <p>Instances can be re-used, but only for one {@link ExoPlayer} instance simultaneously.
*
* <p>MediaSource methods will be called on one of two threads, an application thread or a playback
* thread. Each method is documented with the thread it is called on.
*/
public interface MediaSource {
/** Factory for creating {@link MediaSource MediaSources} from {@link MediaItem MediaItems}. */
interface Factory {
/**
* An instance that throws {@link UnsupportedOperationException} from {@link #createMediaSource}
* and {@link #getSupportedTypes()}.
*/
@UnstableApi
@SuppressWarnings("deprecation")
Factory UNSUPPORTED = MediaSourceFactory.UNSUPPORTED;
/**
* Sets the {@link CmcdConfiguration.Factory} used to obtain a {@link CmcdConfiguration} for a
* {@link MediaItem}.
*
* @return This factory, for convenience.
*/
@UnstableApi
default Factory setCmcdConfigurationFactory(
CmcdConfiguration.Factory cmcdConfigurationFactory) {
// do nothing
return this;
}
/**
* Sets the {@link DrmSessionManagerProvider} used to obtain a {@link DrmSessionManager} for a
* {@link MediaItem}.
*
* @return This factory, for convenience.
*/
@UnstableApi
Factory setDrmSessionManagerProvider(DrmSessionManagerProvider drmSessionManagerProvider);
/**
* Sets an optional {@link LoadErrorHandlingPolicy}.
*
* @return This factory, for convenience.
*/
@UnstableApi
Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy);
/**
* Returns the {@link C.ContentType content types} supported by media sources created by this
* factory.
*/
@UnstableApi
@C.ContentType
int[] getSupportedTypes();
/**
* Creates a new {@link MediaSource} with the specified {@link MediaItem}.
*
* @param mediaItem The media item to play.
* @return The new {@link MediaSource media source}.
*/
@UnstableApi
MediaSource createMediaSource(MediaItem mediaItem);
}
/** A caller of media sources, which will be notified of source events. */
@UnstableApi
interface MediaSourceCaller {
/**
* Called when the {@link Timeline} has been refreshed.
*
* <p>Called on the playback thread.
*
* @param source The {@link MediaSource} whose info has been refreshed.
* @param timeline The source's timeline.
*/
void onSourceInfoRefreshed(MediaSource source, Timeline timeline);
}
/** Identifier for a {@link MediaPeriod}. */
@UnstableApi
final class MediaPeriodId {
/** The unique id of the timeline period. */
public final Object periodUid;
/**
* If the media period is in an ad group, the index of the ad group in the period. {@link
* C#INDEX_UNSET} otherwise.
*/
public final int adGroupIndex;
/**
* If the media period is in an ad group, the index of the ad in its ad group in the period.
* {@link C#INDEX_UNSET} otherwise.
*/
public final int adIndexInAdGroup;
/**
* The sequence number of the window in the buffered sequence of windows this media period is
* part of. {@link C#INDEX_UNSET} if the media period id is not part of a buffered sequence of
* windows.
*/
public final long windowSequenceNumber;
/**
* The index of the next ad group to which the media period's content is clipped, or {@link
* C#INDEX_UNSET} if there is no following ad group or if this media period is an ad.
*/
public final int nextAdGroupIndex;
/**
* Creates a media period identifier for a period which is not part of a buffered sequence of
* windows.
*
* @param periodUid The unique id of the timeline period.
*/
public MediaPeriodId(Object periodUid) {
this(periodUid, /* windowSequenceNumber= */ C.INDEX_UNSET);
}
/**
* Creates a media period identifier for the specified period in the timeline.
*
* @param periodUid The unique id of the timeline period.
* @param windowSequenceNumber The sequence number of the window in the buffered sequence of
* windows this media period is part of.
*/
public MediaPeriodId(Object periodUid, long windowSequenceNumber) {
this(
periodUid,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET,
windowSequenceNumber,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
}
/**
* Creates a media period identifier for the specified clipped period in the timeline.
*
* @param periodUid The unique id of the timeline period.
* @param windowSequenceNumber The sequence number of the window in the buffered sequence of
* windows this media period is part of.
* @param nextAdGroupIndex The index of the next ad group to which the media period's content is
* clipped.
*/
public MediaPeriodId(Object periodUid, long windowSequenceNumber, int nextAdGroupIndex) {
this(
periodUid,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET,
windowSequenceNumber,
nextAdGroupIndex);
}
/**
* Creates a media period identifier that identifies an ad within an ad group at the specified
* timeline period.
*
* @param periodUid The unique id of the timeline period that contains the ad group.
* @param adGroupIndex The index of the ad group.
* @param adIndexInAdGroup The index of the ad in the ad group.
* @param windowSequenceNumber The sequence number of the window in the buffered sequence of
* windows this media period is part of.
*/
public MediaPeriodId(
Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) {
this(
periodUid,
adGroupIndex,
adIndexInAdGroup,
windowSequenceNumber,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
}
private MediaPeriodId(
Object periodUid,
int adGroupIndex,
int adIndexInAdGroup,
long windowSequenceNumber,
int nextAdGroupIndex) {
this.periodUid = periodUid;
this.adGroupIndex = adGroupIndex;
this.adIndexInAdGroup = adIndexInAdGroup;
this.windowSequenceNumber = windowSequenceNumber;
this.nextAdGroupIndex = nextAdGroupIndex;
}
/** Returns a copy of this period identifier but with {@code newPeriodUid} as its period uid. */
public MediaPeriodId copyWithPeriodUid(Object newPeriodUid) {
return periodUid.equals(newPeriodUid)
? this
: new MediaPeriodId(
newPeriodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, nextAdGroupIndex);
}
/** Returns a copy of this period identifier with a new {@code windowSequenceNumber}. */
public MediaPeriodId copyWithWindowSequenceNumber(long windowSequenceNumber) {
return this.windowSequenceNumber == windowSequenceNumber
? this
: new MediaPeriodId(
periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, nextAdGroupIndex);
}
/** Returns whether this period identifier identifies an ad in an ad group in a period. */
public boolean isAd() {
return adGroupIndex != C.INDEX_UNSET;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MediaPeriodId)) {
return false;
}
MediaPeriodId periodId = (MediaPeriodId) obj;
return periodUid.equals(periodId.periodUid)
&& adGroupIndex == periodId.adGroupIndex
&& adIndexInAdGroup == periodId.adIndexInAdGroup
&& windowSequenceNumber == periodId.windowSequenceNumber
&& nextAdGroupIndex == periodId.nextAdGroupIndex;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + periodUid.hashCode();
result = 31 * result + adGroupIndex;
result = 31 * result + adIndexInAdGroup;
result = 31 * result + (int) windowSequenceNumber;
result = 31 * result + nextAdGroupIndex;
return result;
}
}
/**
* Adds a {@link MediaSourceEventListener} to the list of listeners which are notified of media
* source events.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread.
*
* @param handler A handler on the which listener events will be posted.
* @param eventListener The listener to be added.
*/
@UnstableApi
void addEventListener(Handler handler, MediaSourceEventListener eventListener);
/**
* Removes a {@link MediaSourceEventListener} from the list of listeners which are notified of
* media source events.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread.
*
* @param eventListener The listener to be removed.
*/
@UnstableApi
void removeEventListener(MediaSourceEventListener eventListener);
/**
* Adds a {@link DrmSessionEventListener} to the list of listeners which are notified of DRM
* events for this media source.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread.
*
* @param handler A handler on the which listener events will be posted.
* @param eventListener The listener to be added.
*/
@UnstableApi
void addDrmEventListener(Handler handler, DrmSessionEventListener eventListener);
/**
* Removes a {@link DrmSessionEventListener} from the list of listeners which are notified of DRM
* events for this media source.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread.
*
* @param eventListener The listener to be removed.
*/
@UnstableApi
void removeDrmEventListener(DrmSessionEventListener eventListener);
/**
* Returns the initial placeholder timeline that is returned immediately when the real timeline is
* not yet known, or null to let the player create an initial timeline.
*
* <p>Should not be called directly from application code.
*
* <p>The initial timeline must use the same uids for windows and periods that the real timeline
* will use. It also must provide windows which are marked as dynamic to indicate that the window
* is expected to change when the real timeline arrives.
*
* <p>Any media source which has multiple windows should typically provide such an initial
* timeline to make sure the player reports the correct number of windows immediately.
*
* <p>This method must be called on the application thread.
*/
@UnstableApi
@Nullable
default Timeline getInitialTimeline() {
return null;
}
/**
* Returns true if the media source is guaranteed to never have zero or more than one window.
*
* <p>Should not be called directly from application code.
*
* <p>The default implementation returns {@code true}.
*
* <p>This method must be called on the application thread.
*
* @return true if the source has exactly one window.
*/
@UnstableApi
default boolean isSingleWindow() {
return true;
}
/**
* Returns the {@link MediaItem} whose media is provided by the source.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the application thread.
*/
@UnstableApi
MediaItem getMediaItem();
/**
* Returns whether the {@link MediaItem} for this source can be updated with the provided item.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the application thread.
*
* @param mediaItem The new {@link MediaItem}.
* @return Whether the source can be updated using this item.
*/
@UnstableApi
default boolean canUpdateMediaItem(MediaItem mediaItem) {
return false;
}
/**
* Updates the {@link MediaItem} for this source.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread and only if {@link #canUpdateMediaItem}
* returns {@code true} for the new {@link MediaItem}.
*
* @param mediaItem The new {@link MediaItem}.
*/
@UnstableApi
default void updateMediaItem(MediaItem mediaItem) {}
/**
* @deprecated Implement {@link #prepareSource(MediaSourceCaller, TransferListener, PlayerId)}
* instead.
*/
@UnstableApi
@Deprecated
default void prepareSource(
MediaSourceCaller caller, @Nullable TransferListener mediaTransferListener) {
prepareSource(caller, mediaTransferListener, PlayerId.UNSET);
}
/**
* 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>{@link MediaSourceCaller#onSourceInfoRefreshed(MediaSource, Timeline)} will be called once
* the source has a {@link Timeline}.
*
* <p>For each call to this method, a call to {@link #releaseSource(MediaSourceCaller)} is needed
* to remove the caller and to release the source if no longer required.
*
* <p>This method must be called on the playback thread.
*
* @param caller The {@link MediaSourceCaller} to be registered.
* @param 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 be only
* informed of transfers related to the media loads and not of auxiliary loads for manifests
* and other data.
* @param playerId The {@link PlayerId} of the player using this media source.
*/
@UnstableApi
void prepareSource(
MediaSourceCaller caller,
@Nullable TransferListener mediaTransferListener,
PlayerId playerId);
/**
* Throws any pending error encountered while loading or refreshing source information.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread and only after {@link
* #prepareSource(MediaSourceCaller, TransferListener, PlayerId)}.
*/
@UnstableApi
void maybeThrowSourceInfoRefreshError() throws IOException;
/**
* Enables the source for the creation of {@link MediaPeriod MediaPeriods}.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread and only after {@link
* #prepareSource(MediaSourceCaller, TransferListener, PlayerId)}.
*
* @param caller The {@link MediaSourceCaller} enabling the source.
*/
@UnstableApi
void enable(MediaSourceCaller caller);
/**
* Returns a new {@link MediaPeriod} identified by {@code periodId}.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread and only 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.
* @return A new {@link MediaPeriod}.
*/
@UnstableApi
MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs);
/**
* Releases the period.
*
* <p>Should not be called directly from application code.
*
* <p>This method must be called on the playback thread.
*
* @param mediaPeriod The period to release.
*/
@UnstableApi
void releasePeriod(MediaPeriod mediaPeriod);
/**
* 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>This method must be called on the playback thread and only 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.
*/
@UnstableApi
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>This method must be called on the playback thread and only if all created {@link MediaPeriod
* MediaPeriods} have been released by {@link #releasePeriod(MediaPeriod)}.
*
* @param caller The {@link MediaSourceCaller} to be unregistered.
*/
@UnstableApi
void releaseSource(MediaSourceCaller caller);
}