/* * 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 com.google.android.exoplayer2; import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_UNAVAILABLE; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkState; import static java.lang.Math.max; import static java.lang.Math.min; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.SystemClock; import android.util.Pair; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.BundleUtil; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.InlineMe; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; /** * A flexible representation of the structure of media. A timeline is able to represent the * structure of a wide variety of media, from simple cases like a single media file through to * complex compositions of media such as playlists and streams with inserted ads. Instances are * immutable. For cases where media is changing dynamically (e.g. live streams), a timeline provides * a snapshot of the current state. * *
A timeline consists of {@link Window Windows} and {@link Period Periods}. * *
The following examples illustrate timelines for various use cases. * *
*
*
A timeline for a single media file or on-demand stream consists of a single period and window. * The window spans the whole period, indicating that all parts of the media are available for * playback. The window's default position is typically at the start of the period (indicated by the * black dot in the figure above). * *
*
*
A timeline for a playlist of media files or on-demand streams consists of multiple periods, * each with its own window. Each window spans the whole of the corresponding period, and typically * has a default position at the start of the period. The properties of the periods and windows * (e.g. their durations and whether the window is seekable) will often only become known when the * player starts buffering the corresponding file or stream. * *
*
*
A timeline for a live stream consists of a period whose duration is unknown, since it's * continually extending as more content is broadcast. If content only remains available for a * limited period of time then the window may start at a non-zero position, defining the region of * content that can still be played. The window will return true from {@link Window#isLive()} to * indicate it's a live stream and {@link Window#isDynamic} will be set to true as long as we expect * changes to the live window. Its default position is typically near to the live edge (indicated by * the black dot in the figure above). * *
*
*
A timeline for a live stream with indefinite availability is similar to the Live stream with limited availability case, except that the window * starts at the beginning of the period to indicate that all of the previously broadcast content * can still be played. * *
*
*
This case arises when a live stream is explicitly divided into separate periods, for example * at content boundaries. This case is similar to the Live stream with * limited availability case, except that the window may span more than one period. Multiple * periods are also possible in the indefinite availability case. * *
*
*
This case is the concatenation of the Single media file or on-demand * stream and Live stream with multiple periods cases. When playback * of the on-demand stream ends, playback of the live stream will start from its default position * near the live edge. * *
*
*
This case includes mid-roll ad groups, which are defined as part of the timeline's single * period. The period can be queried for information about the ad groups and the ads they contain. */ public abstract class Timeline implements Bundleable { /** * Holds information about a window in a {@link Timeline}. A window usually corresponds to one * playlist item and defines a region of media currently available for playback along with * additional information such as whether seeking is supported within the window. The figure below * shows some of the information defined by a window, as well as how this information relates to * corresponding {@link Period Periods} in the timeline. * *
*/
public static final class Window implements Bundleable {
/**
* A {@link #uid} for a window that must be used for single-window {@link Timeline Timelines}.
*/
public static final Object SINGLE_WINDOW_UID = new Object();
private static final Object FAKE_WINDOW_UID = new Object();
private static final MediaItem EMPTY_MEDIA_ITEM =
new MediaItem.Builder()
.setMediaId("com.google.android.exoplayer2.Timeline")
.setUri(Uri.EMPTY)
.build();
/**
* A unique identifier for the window. Single-window {@link Timeline Timelines} must use {@link
* #SINGLE_WINDOW_UID}.
*/
public Object uid;
/** @deprecated Use {@link #mediaItem} instead. */
@Deprecated @Nullable public Object tag;
/** The {@link MediaItem} associated to the window. Not necessarily unique. */
public MediaItem mediaItem;
/** The manifest of the window. May be {@code null}. */
@Nullable public Object manifest;
/**
* The start time of the presentation to which this window belongs in milliseconds since the
* Unix epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes
* only.
*/
public long presentationStartTimeMs;
/**
* The window's start time in milliseconds since the Unix epoch, or {@link C#TIME_UNSET} if
* unknown or not applicable.
*/
public long windowStartTimeMs;
/**
* The offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix epoch
* according to the clock of the media origin server, or {@link C#TIME_UNSET} if unknown or not
* applicable.
*
*
Note that the current Unix time can be retrieved using {@link #getCurrentUnixTimeMs()} and * is calculated as {@code SystemClock.elapsedRealtime() + elapsedRealtimeEpochOffsetMs}. */ public long elapsedRealtimeEpochOffsetMs; /** Whether it's possible to seek within this window. */ public boolean isSeekable; // TODO: Split this to better describe which parts of the window might change. For example it // should be possible to individually determine whether the start and end positions of the // window may change relative to the underlying periods. For an example of where it's useful to // know that the end position is fixed whilst the start position may still change, see: // https://github.com/google/ExoPlayer/issues/4780. /** Whether this window may change when the timeline is updated. */ public boolean isDynamic; /** @deprecated Use {@link #isLive()} instead. */ @Deprecated public boolean isLive; /** * The {@link MediaItem.LiveConfiguration} that is used or null if {@link #isLive()} returns * false. */ @Nullable public MediaItem.LiveConfiguration liveConfiguration; /** * Whether this window contains placeholder information because the real information has yet to * be loaded. */ public boolean isPlaceholder; /** * The default position relative to the start of the window at which to begin playback, in * microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a * non-zero default position projection, and if the specified projection cannot be performed * whilst remaining within the bounds of the window. */ public long defaultPositionUs; /** The duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown. */ public long durationUs; /** The index of the first period that belongs to this window. */ public int firstPeriodIndex; /** The index of the last period that belongs to this window. */ public int lastPeriodIndex; /** * The position of the start of this window relative to the start of the first period belonging * to it, in microseconds. */ public long positionInFirstPeriodUs; /** Creates window. */ public Window() { uid = SINGLE_WINDOW_UID; mediaItem = EMPTY_MEDIA_ITEM; } /** Sets the data held by this window. */ @SuppressWarnings("deprecation") public Window set( Object uid, @Nullable MediaItem mediaItem, @Nullable Object manifest, long presentationStartTimeMs, long windowStartTimeMs, long elapsedRealtimeEpochOffsetMs, boolean isSeekable, boolean isDynamic, @Nullable MediaItem.LiveConfiguration liveConfiguration, long defaultPositionUs, long durationUs, int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs) { this.uid = uid; this.mediaItem = mediaItem != null ? mediaItem : EMPTY_MEDIA_ITEM; this.tag = mediaItem != null && mediaItem.localConfiguration != null ? mediaItem.localConfiguration.tag : null; this.manifest = manifest; this.presentationStartTimeMs = presentationStartTimeMs; this.windowStartTimeMs = windowStartTimeMs; this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs; this.isSeekable = isSeekable; this.isDynamic = isDynamic; this.isLive = liveConfiguration != null; this.liveConfiguration = liveConfiguration; this.defaultPositionUs = defaultPositionUs; this.durationUs = durationUs; this.firstPeriodIndex = firstPeriodIndex; this.lastPeriodIndex = lastPeriodIndex; this.positionInFirstPeriodUs = positionInFirstPeriodUs; this.isPlaceholder = false; return this; } /** * Returns the default position relative to the start of the window at which to begin playback, * in milliseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a * non-zero default position projection, and if the specified projection cannot be performed * whilst remaining within the bounds of the window. */ public long getDefaultPositionMs() { return Util.usToMs(defaultPositionUs); } /** * Returns the default position relative to the start of the window at which to begin playback, * in microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a * non-zero default position projection, and if the specified projection cannot be performed * whilst remaining within the bounds of the window. */ public long getDefaultPositionUs() { return defaultPositionUs; } /** Returns the duration of the window in milliseconds, or {@link C#TIME_UNSET} if unknown. */ public long getDurationMs() { return Util.usToMs(durationUs); } /** Returns the duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown. */ public long getDurationUs() { return durationUs; } /** * Returns the position of the start of this window relative to the start of the first period * belonging to it, in milliseconds. */ public long getPositionInFirstPeriodMs() { return Util.usToMs(positionInFirstPeriodUs); } /** * Returns the position of the start of this window relative to the start of the first period * belonging to it, in microseconds. */ public long getPositionInFirstPeriodUs() { return positionInFirstPeriodUs; } /** * Returns the current time in milliseconds since the Unix epoch. * *
This method applies {@link #elapsedRealtimeEpochOffsetMs known corrections} made available * by the media such that this time corresponds to the clock of the media origin server. */ public long getCurrentUnixTimeMs() { return Util.getNowUnixTimeMs(elapsedRealtimeEpochOffsetMs); } /** Returns whether this is a live stream. */ // Verifies whether the deprecated isLive member field is in a correct state. @SuppressWarnings("deprecation") public boolean isLive() { checkState(isLive == (liveConfiguration != null)); return liveConfiguration != null; } // Provide backward compatibility for tag. @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || !getClass().equals(obj.getClass())) { return false; } Window that = (Window) obj; return Util.areEqual(uid, that.uid) && Util.areEqual(mediaItem, that.mediaItem) && Util.areEqual(manifest, that.manifest) && Util.areEqual(liveConfiguration, that.liveConfiguration) && presentationStartTimeMs == that.presentationStartTimeMs && windowStartTimeMs == that.windowStartTimeMs && elapsedRealtimeEpochOffsetMs == that.elapsedRealtimeEpochOffsetMs && isSeekable == that.isSeekable && isDynamic == that.isDynamic && isPlaceholder == that.isPlaceholder && defaultPositionUs == that.defaultPositionUs && durationUs == that.durationUs && firstPeriodIndex == that.firstPeriodIndex && lastPeriodIndex == that.lastPeriodIndex && positionInFirstPeriodUs == that.positionInFirstPeriodUs; } // Provide backward compatibility for tag. @Override public int hashCode() { int result = 7; result = 31 * result + uid.hashCode(); result = 31 * result + mediaItem.hashCode(); result = 31 * result + (manifest == null ? 0 : manifest.hashCode()); result = 31 * result + (liveConfiguration == null ? 0 : liveConfiguration.hashCode()); result = 31 * result + (int) (presentationStartTimeMs ^ (presentationStartTimeMs >>> 32)); result = 31 * result + (int) (windowStartTimeMs ^ (windowStartTimeMs >>> 32)); result = 31 * result + (int) (elapsedRealtimeEpochOffsetMs ^ (elapsedRealtimeEpochOffsetMs >>> 32)); result = 31 * result + (isSeekable ? 1 : 0); result = 31 * result + (isDynamic ? 1 : 0); result = 31 * result + (isPlaceholder ? 1 : 0); result = 31 * result + (int) (defaultPositionUs ^ (defaultPositionUs >>> 32)); result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); result = 31 * result + firstPeriodIndex; result = 31 * result + lastPeriodIndex; result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32)); return result; } // Bundleable implementation. @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ FIELD_MEDIA_ITEM, FIELD_PRESENTATION_START_TIME_MS, FIELD_WINDOW_START_TIME_MS, FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS, FIELD_IS_SEEKABLE, FIELD_IS_DYNAMIC, FIELD_LIVE_CONFIGURATION, FIELD_IS_PLACEHOLDER, FIELD_DEFAULT_POSITION_US, FIELD_DURATION_US, FIELD_FIRST_PERIOD_INDEX, FIELD_LAST_PERIOD_INDEX, FIELD_POSITION_IN_FIRST_PERIOD_US, }) private @interface FieldNumber {} private static final int FIELD_MEDIA_ITEM = 1; private static final int FIELD_PRESENTATION_START_TIME_MS = 2; private static final int FIELD_WINDOW_START_TIME_MS = 3; private static final int FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS = 4; private static final int FIELD_IS_SEEKABLE = 5; private static final int FIELD_IS_DYNAMIC = 6; private static final int FIELD_LIVE_CONFIGURATION = 7; private static final int FIELD_IS_PLACEHOLDER = 8; private static final int FIELD_DEFAULT_POSITION_US = 9; private static final int FIELD_DURATION_US = 10; private static final int FIELD_FIRST_PERIOD_INDEX = 11; private static final int FIELD_LAST_PERIOD_INDEX = 12; private static final int FIELD_POSITION_IN_FIRST_PERIOD_US = 13; private final Bundle toBundle(boolean excludeMediaItem) { Bundle bundle = new Bundle(); bundle.putBundle( keyForField(FIELD_MEDIA_ITEM), excludeMediaItem ? MediaItem.EMPTY.toBundle() : mediaItem.toBundle()); bundle.putLong(keyForField(FIELD_PRESENTATION_START_TIME_MS), presentationStartTimeMs); bundle.putLong(keyForField(FIELD_WINDOW_START_TIME_MS), windowStartTimeMs); bundle.putLong( keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), elapsedRealtimeEpochOffsetMs); bundle.putBoolean(keyForField(FIELD_IS_SEEKABLE), isSeekable); bundle.putBoolean(keyForField(FIELD_IS_DYNAMIC), isDynamic); @Nullable MediaItem.LiveConfiguration liveConfiguration = this.liveConfiguration; if (liveConfiguration != null) { bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); } bundle.putBoolean(keyForField(FIELD_IS_PLACEHOLDER), isPlaceholder); bundle.putLong(keyForField(FIELD_DEFAULT_POSITION_US), defaultPositionUs); bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); bundle.putInt(keyForField(FIELD_FIRST_PERIOD_INDEX), firstPeriodIndex); bundle.putInt(keyForField(FIELD_LAST_PERIOD_INDEX), lastPeriodIndex); bundle.putLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), positionInFirstPeriodUs); return bundle; } /** * {@inheritDoc} * *
It omits the {@link #uid} and {@link #manifest} fields. The {@link #uid} of an instance * restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the * instance will be {@code null}. */ // TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise. @Override public Bundle toBundle() { return toBundle(/* excludeMediaItem= */ false); } /** * Object that can restore {@link Period} from a {@link Bundle}. * *
The {@link #uid} of a restored instance will be a fake {@link Object} and the {@link
* #manifest} of the instance will be {@code null}.
*/
public static final Creator The figure below shows some of the information defined by a period, as well as how this
* information relates to a corresponding {@link Window} in the timeline.
*
* It omits the {@link #id} and {@link #uid} fields so these fields of an instance restored
* by {@link #CREATOR} will always be {@code null}.
*/
// TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise.
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex);
bundle.putLong(keyForField(FIELD_DURATION_US), durationUs);
bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs);
bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder);
bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle());
return bundle;
}
/**
* Object that can restore {@link Period} from a {@link Bundle}.
*
* The {@link #id} and {@link #uid} of restored instances will always be {@code null}.
*/
public static final Creator
*/
public static final class Period implements Bundleable {
/**
* An identifier for the period. Not necessarily unique. May be null if the ids of the period
* are not required.
*/
@Nullable public Object id;
/**
* A unique identifier for the period. May be null if the ids of the period are not required.
*/
@Nullable public Object uid;
/** The index of the window to which this period belongs. */
public int windowIndex;
/** The duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown. */
public long durationUs;
/**
* The position of the start of this period relative to the start of the window to which it
* belongs, in microseconds. May be negative if the start of the period is not within the
* window.
*/
public long positionInWindowUs;
/**
* Whether this period contains placeholder information because the real information has yet to
* be loaded.
*/
public boolean isPlaceholder;
private AdPlaybackState adPlaybackState;
/** Creates a new instance with no ad playback state. */
public Period() {
adPlaybackState = AdPlaybackState.NONE;
}
/**
* Sets the data held by this period.
*
* @param id An identifier for the period. Not necessarily unique. May be null if the ids of the
* period are not required.
* @param uid A unique identifier for the period. May be null if the ids of the period are not
* required.
* @param windowIndex The index of the window to which this period belongs.
* @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if
* unknown.
* @param positionInWindowUs The position of the start of this period relative to the start of
* the window to which it belongs, in milliseconds. May be negative if the start of the
* period is not within the window.
* @return This period, for convenience.
*/
public Period set(
@Nullable Object id,
@Nullable Object uid,
int windowIndex,
long durationUs,
long positionInWindowUs) {
return set(
id,
uid,
windowIndex,
durationUs,
positionInWindowUs,
AdPlaybackState.NONE,
/* isPlaceholder= */ false);
}
/**
* Sets the data held by this period.
*
* @param id An identifier for the period. Not necessarily unique. May be null if the ids of the
* period are not required.
* @param uid A unique identifier for the period. May be null if the ids of the period are not
* required.
* @param windowIndex The index of the window to which this period belongs.
* @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if
* unknown.
* @param positionInWindowUs The position of the start of this period relative to the start of
* the window to which it belongs, in milliseconds. May be negative if the start of the
* period is not within the window.
* @param adPlaybackState The state of the period's ads, or {@link AdPlaybackState#NONE} if
* there are no ads.
* @param isPlaceholder Whether this period contains placeholder information because the real
* information has yet to be loaded.
* @return This period, for convenience.
*/
public Period set(
@Nullable Object id,
@Nullable Object uid,
int windowIndex,
long durationUs,
long positionInWindowUs,
AdPlaybackState adPlaybackState,
boolean isPlaceholder) {
this.id = id;
this.uid = uid;
this.windowIndex = windowIndex;
this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs;
this.adPlaybackState = adPlaybackState;
this.isPlaceholder = isPlaceholder;
return this;
}
/** Returns the duration of the period in milliseconds, or {@link C#TIME_UNSET} if unknown. */
public long getDurationMs() {
return Util.usToMs(durationUs);
}
/** Returns the duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown. */
public long getDurationUs() {
return durationUs;
}
/**
* Returns the position of the start of this period relative to the start of the window to which
* it belongs, in milliseconds. May be negative if the start of the period is not within the
* window.
*/
public long getPositionInWindowMs() {
return Util.usToMs(positionInWindowUs);
}
/**
* Returns the position of the start of this period relative to the start of the window to which
* it belongs, in microseconds. May be negative if the start of the period is not within the
* window.
*/
public long getPositionInWindowUs() {
return positionInWindowUs;
}
/** Returns the opaque identifier for ads played with this period, or {@code null} if unset. */
@Nullable
public Object getAdsId() {
return adPlaybackState.adsId;
}
/** Returns the number of ad groups in the period. */
public int getAdGroupCount() {
return adPlaybackState.adGroupCount;
}
/**
* Returns the number of removed ad groups in the period. Ad groups with indices between {@code
* 0} (inclusive) and {@code removedAdGroupCount} (exclusive) will be empty.
*/
public int getRemovedAdGroupCount() {
return adPlaybackState.removedAdGroupCount;
}
/**
* Returns the time of the ad group at index {@code adGroupIndex} in the period, in
* microseconds.
*
* @param adGroupIndex The ad group index.
* @return The time of the ad group at the index relative to the start of the enclosing {@link
* Period}, in microseconds, or {@link C#TIME_END_OF_SOURCE} for a post-roll ad group.
*/
public long getAdGroupTimeUs(int adGroupIndex) {
return adPlaybackState.getAdGroup(adGroupIndex).timeUs;
}
/**
* Returns the index of the first ad in the specified ad group that should be played, or the
* number of ads in the ad group if no ads should be played.
*
* @param adGroupIndex The ad group index.
* @return The index of the first ad that should be played, or the number of ads in the ad group
* if no ads should be played.
*/
public int getFirstAdIndexToPlay(int adGroupIndex) {
return adPlaybackState.getAdGroup(adGroupIndex).getFirstAdIndexToPlay();
}
/**
* Returns the index of the next ad in the specified ad group that should be played after
* playing {@code adIndexInAdGroup}, or the number of ads in the ad group if no later ads should
* be played.
*
* @param adGroupIndex The ad group index.
* @param lastPlayedAdIndex The last played ad index in the ad group.
* @return The index of the next ad that should be played, or the number of ads in the ad group
* if the ad group does not have any ads remaining to play.
*/
public int getNextAdIndexToPlay(int adGroupIndex, int lastPlayedAdIndex) {
return adPlaybackState.getAdGroup(adGroupIndex).getNextAdIndexToPlay(lastPlayedAdIndex);
}
/**
* Returns whether all ads in the ad group at index {@code adGroupIndex} have been played,
* skipped or failed.
*
* @param adGroupIndex The ad group index.
* @return Whether all ads in the ad group at index {@code adGroupIndex} have been played,
* skipped or failed.
*/
public boolean hasPlayedAdGroup(int adGroupIndex) {
return !adPlaybackState.getAdGroup(adGroupIndex).hasUnplayedAds();
}
/**
* Returns the index of the ad group at or before {@code positionUs} in the period that should
* be played before the content at {@code positionUs}. Returns {@link C#INDEX_UNSET} if the ad
* group at or before {@code positionUs} has no ads remaining to be played, or if there is no
* such ad group.
*
* @param positionUs The period position at or before which to find an ad group, in
* microseconds.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexForPositionUs(long positionUs) {
return adPlaybackState.getAdGroupIndexForPositionUs(positionUs, durationUs);
}
/**
* Returns the index of the next ad group after {@code positionUs} in the period that has ads
* that should be played. Returns {@link C#INDEX_UNSET} if there is no such ad group.
*
* @param positionUs The period position after which to find an ad group, in microseconds.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexAfterPositionUs(long positionUs) {
return adPlaybackState.getAdGroupIndexAfterPositionUs(positionUs, durationUs);
}
/**
* Returns the number of ads in the ad group at index {@code adGroupIndex}, or {@link
* C#LENGTH_UNSET} if not yet known.
*
* @param adGroupIndex The ad group index.
* @return The number of ads in the ad group, or {@link C#LENGTH_UNSET} if not yet known.
*/
public int getAdCountInAdGroup(int adGroupIndex) {
return adPlaybackState.getAdGroup(adGroupIndex).count;
}
/**
* Returns the duration of the ad at index {@code adIndexInAdGroup} in the ad group at {@code
* adGroupIndex}, in microseconds, or {@link C#TIME_UNSET} if not yet known.
*
* @param adGroupIndex The ad group index.
* @param adIndexInAdGroup The ad index in the ad group.
* @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known.
*/
public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) {
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
return adGroup.count != C.LENGTH_UNSET ? adGroup.durationsUs[adIndexInAdGroup] : C.TIME_UNSET;
}
/**
* Returns the state of the ad at index {@code adIndexInAdGroup} in the ad group at {@code
* adGroupIndex}, or {@link AdPlaybackState#AD_STATE_UNAVAILABLE} if not yet known.
*
* @param adGroupIndex The ad group index.
* @return The state of the ad, or {@link AdPlaybackState#AD_STATE_UNAVAILABLE} if not yet
* known.
*/
public int getAdState(int adGroupIndex, int adIndexInAdGroup) {
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
return adGroup.count != C.LENGTH_UNSET
? adGroup.states[adIndexInAdGroup]
: AD_STATE_UNAVAILABLE;
}
/**
* Returns the position offset in the first unplayed ad at which to begin playback, in
* microseconds.
*/
public long getAdResumePositionUs() {
return adPlaybackState.adResumePositionUs;
}
/**
* Returns whether the ad group at index {@code adGroupIndex} is server-side inserted and part
* of the content stream.
*
* @param adGroupIndex The ad group index.
* @return Whether this ad group is server-side inserted and part of the content stream.
*/
public boolean isServerSideInsertedAdGroup(int adGroupIndex) {
return adPlaybackState.getAdGroup(adGroupIndex).isServerSideInserted;
}
/**
* Returns the offset in microseconds which should be added to the content stream when resuming
* playback after the specified ad group.
*
* @param adGroupIndex The ad group index.
* @return The offset that should be added to the content stream, in microseconds.
*/
public long getContentResumeOffsetUs(int adGroupIndex) {
return adPlaybackState.getAdGroup(adGroupIndex).contentResumeOffsetUs;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
Period that = (Period) obj;
return Util.areEqual(id, that.id)
&& Util.areEqual(uid, that.uid)
&& windowIndex == that.windowIndex
&& durationUs == that.durationUs
&& positionInWindowUs == that.positionInWindowUs
&& isPlaceholder == that.isPlaceholder
&& Util.areEqual(adPlaybackState, that.adPlaybackState);
}
@Override
public int hashCode() {
int result = 7;
result = 31 * result + (id == null ? 0 : id.hashCode());
result = 31 * result + (uid == null ? 0 : uid.hashCode());
result = 31 * result + windowIndex;
result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
result = 31 * result + (int) (positionInWindowUs ^ (positionInWindowUs >>> 32));
result = 31 * result + (isPlaceholder ? 1 : 0);
result = 31 * result + adPlaybackState.hashCode();
return result;
}
// Bundleable implementation.
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
FIELD_WINDOW_INDEX,
FIELD_DURATION_US,
FIELD_POSITION_IN_WINDOW_US,
FIELD_PLACEHOLDER,
FIELD_AD_PLAYBACK_STATE
})
private @interface FieldNumber {}
private static final int FIELD_WINDOW_INDEX = 0;
private static final int FIELD_DURATION_US = 1;
private static final int FIELD_POSITION_IN_WINDOW_US = 2;
private static final int FIELD_PLACEHOLDER = 3;
private static final int FIELD_AD_PLAYBACK_STATE = 4;
/**
* {@inheritDoc}
*
*