| /* |
| * Copyright 2020 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.util.Assertions.checkNotNull; |
| import static com.google.android.exoplayer2.util.Assertions.checkState; |
| |
| import android.net.Uri; |
| import android.os.Bundle; |
| import androidx.annotation.IntDef; |
| import androidx.annotation.IntRange; |
| import androidx.annotation.Nullable; |
| import com.google.android.exoplayer2.offline.StreamKey; |
| import com.google.android.exoplayer2.util.Assertions; |
| import com.google.android.exoplayer2.util.Util; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import java.lang.annotation.Documented; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.UUID; |
| |
| /** Representation of a media item. */ |
| public final class MediaItem implements Bundleable { |
| |
| /** |
| * Creates a {@link MediaItem} for the given URI. |
| * |
| * @param uri The URI. |
| * @return An {@link MediaItem} for the given URI. |
| */ |
| public static MediaItem fromUri(String uri) { |
| return new MediaItem.Builder().setUri(uri).build(); |
| } |
| |
| /** |
| * Creates a {@link MediaItem} for the given {@link Uri URI}. |
| * |
| * @param uri The {@link Uri uri}. |
| * @return An {@link MediaItem} for the given URI. |
| */ |
| public static MediaItem fromUri(Uri uri) { |
| return new MediaItem.Builder().setUri(uri).build(); |
| } |
| |
| /** A builder for {@link MediaItem} instances. */ |
| public static final class Builder { |
| |
| @Nullable private String mediaId; |
| @Nullable private Uri uri; |
| @Nullable private String mimeType; |
| // TODO: Change this to ClippingProperties once all the deprecated individual setters are |
| // removed. |
| private ClippingConfiguration.Builder clippingConfiguration; |
| // TODO: Change this to @Nullable DrmConfiguration once all the deprecated individual setters |
| // are removed. |
| private DrmConfiguration.Builder drmConfiguration; |
| private List<StreamKey> streamKeys; |
| @Nullable private String customCacheKey; |
| private ImmutableList<SubtitleConfiguration> subtitleConfigurations; |
| @Nullable private AdsConfiguration adsConfiguration; |
| @Nullable private Object tag; |
| @Nullable private MediaMetadata mediaMetadata; |
| // TODO: Change this to LiveConfiguration once all the deprecated individual setters |
| // are removed. |
| private LiveConfiguration.Builder liveConfiguration; |
| |
| /** Creates a builder. */ |
| @SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor. |
| public Builder() { |
| clippingConfiguration = new ClippingConfiguration.Builder(); |
| drmConfiguration = new DrmConfiguration.Builder(); |
| streamKeys = Collections.emptyList(); |
| subtitleConfigurations = ImmutableList.of(); |
| liveConfiguration = new LiveConfiguration.Builder(); |
| } |
| |
| private Builder(MediaItem mediaItem) { |
| this(); |
| clippingConfiguration = mediaItem.clippingConfiguration.buildUpon(); |
| mediaId = mediaItem.mediaId; |
| mediaMetadata = mediaItem.mediaMetadata; |
| liveConfiguration = mediaItem.liveConfiguration.buildUpon(); |
| @Nullable LocalConfiguration localConfiguration = mediaItem.localConfiguration; |
| if (localConfiguration != null) { |
| customCacheKey = localConfiguration.customCacheKey; |
| mimeType = localConfiguration.mimeType; |
| uri = localConfiguration.uri; |
| streamKeys = localConfiguration.streamKeys; |
| subtitleConfigurations = localConfiguration.subtitleConfigurations; |
| tag = localConfiguration.tag; |
| drmConfiguration = |
| localConfiguration.drmConfiguration != null |
| ? localConfiguration.drmConfiguration.buildUpon() |
| : new DrmConfiguration.Builder(); |
| adsConfiguration = localConfiguration.adsConfiguration; |
| } |
| } |
| |
| /** |
| * Sets the optional media ID which identifies the media item. |
| * |
| * <p>By default {@link #DEFAULT_MEDIA_ID} is used. |
| */ |
| public Builder setMediaId(String mediaId) { |
| this.mediaId = checkNotNull(mediaId); |
| return this; |
| } |
| |
| /** |
| * Sets the optional URI. |
| * |
| * <p>If {@code uri} is null or unset then no {@link LocalConfiguration} object is created |
| * during {@link #build()} and no other {@code Builder} methods that would populate {@link |
| * MediaItem#localConfiguration} should be called. |
| */ |
| public Builder setUri(@Nullable String uri) { |
| return setUri(uri == null ? null : Uri.parse(uri)); |
| } |
| |
| /** |
| * Sets the optional URI. |
| * |
| * <p>If {@code uri} is null or unset then no {@link LocalConfiguration} object is created |
| * during {@link #build()} and no other {@code Builder} methods that would populate {@link |
| * MediaItem#localConfiguration} should be called. |
| */ |
| public Builder setUri(@Nullable Uri uri) { |
| this.uri = uri; |
| return this; |
| } |
| |
| /** |
| * Sets the optional MIME type. |
| * |
| * <p>The MIME type may be used as a hint for inferring the type of the media item. |
| * |
| * <p>This method should only be called if {@link #setUri} is passed a non-null value. |
| * |
| * @param mimeType The MIME type. |
| */ |
| public Builder setMimeType(@Nullable String mimeType) { |
| this.mimeType = mimeType; |
| return this; |
| } |
| |
| /** Sets the {@link ClippingConfiguration}, defaults to {@link ClippingConfiguration#UNSET}. */ |
| public Builder setClippingConfiguration(ClippingConfiguration clippingConfiguration) { |
| this.clippingConfiguration = clippingConfiguration.buildUpon(); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setClippingConfiguration(ClippingConfiguration)} and {@link |
| * ClippingConfiguration.Builder#setStartPositionMs(long)} instead. |
| */ |
| @Deprecated |
| public Builder setClipStartPositionMs(@IntRange(from = 0) long startPositionMs) { |
| clippingConfiguration.setStartPositionMs(startPositionMs); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setClippingConfiguration(ClippingConfiguration)} and {@link |
| * ClippingConfiguration.Builder#setEndPositionMs(long)} instead. |
| */ |
| @Deprecated |
| public Builder setClipEndPositionMs(long endPositionMs) { |
| clippingConfiguration.setEndPositionMs(endPositionMs); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setClippingConfiguration(ClippingConfiguration)} and {@link |
| * ClippingConfiguration.Builder#setRelativeToLiveWindow(boolean)} instead. |
| */ |
| @Deprecated |
| public Builder setClipRelativeToLiveWindow(boolean relativeToLiveWindow) { |
| clippingConfiguration.setRelativeToLiveWindow(relativeToLiveWindow); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setClippingConfiguration(ClippingConfiguration)} and {@link |
| * ClippingConfiguration.Builder#setRelativeToDefaultPosition(boolean)} instead. |
| */ |
| @Deprecated |
| public Builder setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition) { |
| clippingConfiguration.setRelativeToDefaultPosition(relativeToDefaultPosition); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setClippingConfiguration(ClippingConfiguration)} and {@link |
| * ClippingConfiguration.Builder#setStartsAtKeyFrame(boolean)} instead. |
| */ |
| @Deprecated |
| public Builder setClipStartsAtKeyFrame(boolean startsAtKeyFrame) { |
| clippingConfiguration.setStartsAtKeyFrame(startsAtKeyFrame); |
| return this; |
| } |
| |
| /** Sets the optional DRM configuration. */ |
| public Builder setDrmConfiguration(@Nullable DrmConfiguration drmConfiguration) { |
| this.drmConfiguration = |
| drmConfiguration != null ? drmConfiguration.buildUpon() : new DrmConfiguration.Builder(); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setLicenseUri(Uri)} instead. |
| */ |
| @Deprecated |
| public Builder setDrmLicenseUri(@Nullable Uri licenseUri) { |
| drmConfiguration.setLicenseUri(licenseUri); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setLicenseUri(String)} instead. |
| */ |
| @Deprecated |
| public Builder setDrmLicenseUri(@Nullable String licenseUri) { |
| drmConfiguration.setLicenseUri(licenseUri); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setLicenseRequestHeaders(Map)} instead. Note that {@link |
| * DrmConfiguration.Builder#setLicenseRequestHeaders(Map)} doesn't accept null, use an empty |
| * map to clear the headers. |
| */ |
| @Deprecated |
| public Builder setDrmLicenseRequestHeaders( |
| @Nullable Map<String, String> licenseRequestHeaders) { |
| drmConfiguration.setLicenseRequestHeaders( |
| licenseRequestHeaders != null ? licenseRequestHeaders : ImmutableMap.of()); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and pass the {@code uuid} to |
| * {@link DrmConfiguration.Builder#Builder(UUID)} instead. |
| */ |
| @Deprecated |
| public Builder setDrmUuid(@Nullable UUID uuid) { |
| drmConfiguration.setNullableScheme(uuid); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setMultiSession(boolean)} instead. |
| */ |
| @Deprecated |
| public Builder setDrmMultiSession(boolean multiSession) { |
| drmConfiguration.setMultiSession(multiSession); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setForceDefaultLicenseUri(boolean)} instead. |
| */ |
| @Deprecated |
| public Builder setDrmForceDefaultLicenseUri(boolean forceDefaultLicenseUri) { |
| drmConfiguration.setForceDefaultLicenseUri(forceDefaultLicenseUri); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setPlayClearContentWithoutKey(boolean)} instead. |
| */ |
| @Deprecated |
| public Builder setDrmPlayClearContentWithoutKey(boolean playClearContentWithoutKey) { |
| drmConfiguration.setPlayClearContentWithoutKey(playClearContentWithoutKey); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#forceSessionsForAudioAndVideoTracks(boolean)} instead. |
| */ |
| @Deprecated |
| public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) { |
| drmConfiguration.forceSessionsForAudioAndVideoTracks(sessionForClearPeriods); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setForcedSessionTrackTypes(List)} instead. Note that {@link |
| * DrmConfiguration.Builder#setForcedSessionTrackTypes(List)} doesn't accept null, use an |
| * empty list to clear the contents. |
| */ |
| @Deprecated |
| public Builder setDrmSessionForClearTypes( |
| @Nullable List<@C.TrackType Integer> sessionForClearTypes) { |
| drmConfiguration.setForcedSessionTrackTypes( |
| sessionForClearTypes != null ? sessionForClearTypes : ImmutableList.of()); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link |
| * DrmConfiguration.Builder#setKeySetId(byte[])} instead. |
| */ |
| @Deprecated |
| public Builder setDrmKeySetId(@Nullable byte[] keySetId) { |
| drmConfiguration.setKeySetId(keySetId); |
| return this; |
| } |
| |
| /** |
| * Sets the optional stream keys by which the manifest is filtered (only used for adaptive |
| * streams). |
| * |
| * <p>{@code null} or an empty {@link List} can be used for a reset. |
| * |
| * <p>If {@link #setUri} is passed a non-null {@code uri}, the stream keys are used to create a |
| * {@link LocalConfiguration} object. Otherwise they will be ignored. |
| */ |
| public Builder setStreamKeys(@Nullable List<StreamKey> streamKeys) { |
| this.streamKeys = |
| streamKeys != null && !streamKeys.isEmpty() |
| ? Collections.unmodifiableList(new ArrayList<>(streamKeys)) |
| : Collections.emptyList(); |
| return this; |
| } |
| |
| /** |
| * Sets the optional custom cache key (only used for progressive streams). |
| * |
| * <p>This method should only be called if {@link #setUri} is passed a non-null value. |
| */ |
| public Builder setCustomCacheKey(@Nullable String customCacheKey) { |
| this.customCacheKey = customCacheKey; |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setSubtitleConfigurations(List)} instead. Note that {@link |
| * #setSubtitleConfigurations(List)} doesn't accept null, use an empty list to clear the |
| * contents. |
| */ |
| @Deprecated |
| public Builder setSubtitles(@Nullable List<Subtitle> subtitles) { |
| this.subtitleConfigurations = |
| subtitles != null ? ImmutableList.copyOf(subtitles) : ImmutableList.of(); |
| return this; |
| } |
| |
| /** |
| * Sets the optional subtitles. |
| * |
| * <p>This method should only be called if {@link #setUri} is passed a non-null value. |
| */ |
| public Builder setSubtitleConfigurations(List<SubtitleConfiguration> subtitleConfigurations) { |
| this.subtitleConfigurations = ImmutableList.copyOf(subtitleConfigurations); |
| return this; |
| } |
| |
| /** |
| * Sets the optional {@link AdsConfiguration}. |
| * |
| * <p>This method should only be called if {@link #setUri} is passed a non-null value. |
| */ |
| public Builder setAdsConfiguration(@Nullable AdsConfiguration adsConfiguration) { |
| this.adsConfiguration = adsConfiguration; |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setAdsConfiguration(AdsConfiguration)}, parse the {@code adTagUri} |
| * with {@link Uri#parse(String)} and pass the result to {@link |
| * AdsConfiguration.Builder#Builder(Uri)} instead. |
| */ |
| @Deprecated |
| public Builder setAdTagUri(@Nullable String adTagUri) { |
| return setAdTagUri(adTagUri != null ? Uri.parse(adTagUri) : null); |
| } |
| |
| /** |
| * @deprecated Use {@link #setAdsConfiguration(AdsConfiguration)} and pass the {@code adTagUri} |
| * to {@link AdsConfiguration.Builder#Builder(Uri)} instead. |
| */ |
| @Deprecated |
| public Builder setAdTagUri(@Nullable Uri adTagUri) { |
| return setAdTagUri(adTagUri, /* adsId= */ null); |
| } |
| |
| /** |
| * @deprecated Use {@link #setAdsConfiguration(AdsConfiguration)}, pass the {@code adTagUri} to |
| * {@link AdsConfiguration.Builder#Builder(Uri)} and the {@code adsId} to {@link |
| * AdsConfiguration.Builder#setAdsId(Object)} instead. |
| */ |
| @Deprecated |
| public Builder setAdTagUri(@Nullable Uri adTagUri, @Nullable Object adsId) { |
| this.adsConfiguration = |
| adTagUri != null ? new AdsConfiguration.Builder(adTagUri).setAdsId(adsId).build() : null; |
| return this; |
| } |
| |
| /** Sets the {@link LiveConfiguration}. Defaults to {@link LiveConfiguration#UNSET}. */ |
| public Builder setLiveConfiguration(LiveConfiguration liveConfiguration) { |
| this.liveConfiguration = liveConfiguration.buildUpon(); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setLiveConfiguration(LiveConfiguration)} and {@link |
| * LiveConfiguration.Builder#setTargetOffsetMs(long)}. |
| */ |
| @Deprecated |
| public Builder setLiveTargetOffsetMs(long liveTargetOffsetMs) { |
| liveConfiguration.setTargetOffsetMs(liveTargetOffsetMs); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setLiveConfiguration(LiveConfiguration)} and {@link |
| * LiveConfiguration.Builder#setMinOffsetMs(long)}. |
| */ |
| @Deprecated |
| public Builder setLiveMinOffsetMs(long liveMinOffsetMs) { |
| liveConfiguration.setMinOffsetMs(liveMinOffsetMs); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setLiveConfiguration(LiveConfiguration)} and {@link |
| * LiveConfiguration.Builder#setMaxOffsetMs(long)}. |
| */ |
| @Deprecated |
| public Builder setLiveMaxOffsetMs(long liveMaxOffsetMs) { |
| liveConfiguration.setMaxOffsetMs(liveMaxOffsetMs); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setLiveConfiguration(LiveConfiguration)} and {@link |
| * LiveConfiguration.Builder#setMinPlaybackSpeed(float)}. |
| */ |
| @Deprecated |
| public Builder setLiveMinPlaybackSpeed(float minPlaybackSpeed) { |
| liveConfiguration.setMinPlaybackSpeed(minPlaybackSpeed); |
| return this; |
| } |
| |
| /** |
| * @deprecated Use {@link #setLiveConfiguration(LiveConfiguration)} and {@link |
| * LiveConfiguration.Builder#setMaxPlaybackSpeed(float)}. |
| */ |
| @Deprecated |
| public Builder setLiveMaxPlaybackSpeed(float maxPlaybackSpeed) { |
| liveConfiguration.setMaxPlaybackSpeed(maxPlaybackSpeed); |
| return this; |
| } |
| |
| /** |
| * Sets the optional tag for custom attributes. The tag for the media source which will be |
| * published in the {@code com.google.android.exoplayer2.Timeline} of the source as {@code |
| * com.google.android.exoplayer2.Timeline.Window#tag}. |
| * |
| * <p>This method should only be called if {@link #setUri} is passed a non-null value. |
| */ |
| public Builder setTag(@Nullable Object tag) { |
| this.tag = tag; |
| return this; |
| } |
| |
| /** Sets the media metadata. */ |
| public Builder setMediaMetadata(MediaMetadata mediaMetadata) { |
| this.mediaMetadata = mediaMetadata; |
| return this; |
| } |
| |
| /** Returns a new {@link MediaItem} instance with the current builder values. */ |
| @SuppressWarnings("deprecation") // Using PlaybackProperties while it exists. |
| public MediaItem build() { |
| // TODO: remove this check once all the deprecated individual DRM setters are removed. |
| checkState(drmConfiguration.licenseUri == null || drmConfiguration.scheme != null); |
| @Nullable PlaybackProperties localConfiguration = null; |
| @Nullable Uri uri = this.uri; |
| if (uri != null) { |
| localConfiguration = |
| new PlaybackProperties( |
| uri, |
| mimeType, |
| drmConfiguration.scheme != null ? drmConfiguration.build() : null, |
| adsConfiguration, |
| streamKeys, |
| customCacheKey, |
| subtitleConfigurations, |
| tag); |
| } |
| return new MediaItem( |
| mediaId != null ? mediaId : DEFAULT_MEDIA_ID, |
| clippingConfiguration.buildClippingProperties(), |
| localConfiguration, |
| liveConfiguration.build(), |
| mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY); |
| } |
| } |
| |
| /** DRM configuration for a media item. */ |
| public static final class DrmConfiguration { |
| |
| /** Builder for {@link DrmConfiguration}. */ |
| public static final class Builder { |
| |
| // TODO remove @Nullable annotation when the deprecated zero-arg constructor is removed. |
| @Nullable private UUID scheme; |
| @Nullable private Uri licenseUri; |
| private ImmutableMap<String, String> licenseRequestHeaders; |
| private boolean multiSession; |
| private boolean playClearContentWithoutKey; |
| private boolean forceDefaultLicenseUri; |
| private ImmutableList<@C.TrackType Integer> forcedSessionTrackTypes; |
| @Nullable private byte[] keySetId; |
| |
| /** |
| * Constructs an instance. |
| * |
| * @param scheme The {@link UUID} of the protection scheme. |
| */ |
| public Builder(UUID scheme) { |
| this.scheme = scheme; |
| this.licenseRequestHeaders = ImmutableMap.of(); |
| this.forcedSessionTrackTypes = ImmutableList.of(); |
| } |
| |
| /** |
| * @deprecated This only exists to support the deprecated setters for individual DRM |
| * properties on {@link MediaItem.Builder}. |
| */ |
| @Deprecated |
| private Builder() { |
| this.licenseRequestHeaders = ImmutableMap.of(); |
| this.forcedSessionTrackTypes = ImmutableList.of(); |
| } |
| |
| private Builder(DrmConfiguration drmConfiguration) { |
| this.scheme = drmConfiguration.scheme; |
| this.licenseUri = drmConfiguration.licenseUri; |
| this.licenseRequestHeaders = drmConfiguration.licenseRequestHeaders; |
| this.multiSession = drmConfiguration.multiSession; |
| this.playClearContentWithoutKey = drmConfiguration.playClearContentWithoutKey; |
| this.forceDefaultLicenseUri = drmConfiguration.forceDefaultLicenseUri; |
| this.forcedSessionTrackTypes = drmConfiguration.forcedSessionTrackTypes; |
| this.keySetId = drmConfiguration.keySetId; |
| } |
| |
| /** Sets the {@link UUID} of the protection scheme. */ |
| public Builder setScheme(UUID scheme) { |
| this.scheme = scheme; |
| return this; |
| } |
| |
| /** |
| * @deprecated This only exists to support the deprecated {@link |
| * MediaItem.Builder#setDrmUuid(UUID)}. |
| */ |
| @Deprecated |
| private Builder setNullableScheme(@Nullable UUID scheme) { |
| this.scheme = scheme; |
| return this; |
| } |
| |
| /** Sets the optional default DRM license server URI. */ |
| public Builder setLicenseUri(@Nullable Uri licenseUri) { |
| this.licenseUri = licenseUri; |
| return this; |
| } |
| |
| /** Sets the optional default DRM license server URI. */ |
| public Builder setLicenseUri(@Nullable String licenseUri) { |
| this.licenseUri = licenseUri == null ? null : Uri.parse(licenseUri); |
| return this; |
| } |
| |
| /** Sets the optional request headers attached to DRM license requests. */ |
| public Builder setLicenseRequestHeaders(Map<String, String> licenseRequestHeaders) { |
| this.licenseRequestHeaders = ImmutableMap.copyOf(licenseRequestHeaders); |
| return this; |
| } |
| |
| /** Sets whether multi session is enabled. */ |
| public Builder setMultiSession(boolean multiSession) { |
| this.multiSession = multiSession; |
| return this; |
| } |
| |
| /** |
| * Sets whether to always use the default DRM license server URI even if the media specifies |
| * its own DRM license server URI. |
| */ |
| public Builder setForceDefaultLicenseUri(boolean forceDefaultLicenseUri) { |
| this.forceDefaultLicenseUri = forceDefaultLicenseUri; |
| return this; |
| } |
| |
| /** |
| * Sets whether clear samples within protected content should be played when keys for the |
| * encrypted part of the content have yet to be loaded. |
| */ |
| public Builder setPlayClearContentWithoutKey(boolean playClearContentWithoutKey) { |
| this.playClearContentWithoutKey = playClearContentWithoutKey; |
| return this; |
| } |
| |
| /** |
| * Sets whether a DRM session should be used for clear tracks of type {@link |
| * C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_AUDIO}. |
| * |
| * <p>This method overrides what has been set by previously calling {@link |
| * #setForcedSessionTrackTypes(List)}. |
| */ |
| public Builder forceSessionsForAudioAndVideoTracks( |
| boolean useClearSessionsForAudioAndVideoTracks) { |
| this.setForcedSessionTrackTypes( |
| useClearSessionsForAudioAndVideoTracks |
| ? ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO) |
| : ImmutableList.of()); |
| return this; |
| } |
| |
| /** |
| * Sets a list of {@link C.TrackType track type} constants for which to use a DRM session even |
| * when the tracks are in the clear. |
| * |
| * <p>For the common case of using a DRM session for {@link C#TRACK_TYPE_VIDEO} and {@link |
| * C#TRACK_TYPE_AUDIO}, {@link #forceSessionsForAudioAndVideoTracks(boolean)} can be used. |
| * |
| * <p>This method overrides what has been set by previously calling {@link |
| * #forceSessionsForAudioAndVideoTracks(boolean)}. |
| */ |
| public Builder setForcedSessionTrackTypes( |
| List<@C.TrackType Integer> forcedSessionTrackTypes) { |
| this.forcedSessionTrackTypes = ImmutableList.copyOf(forcedSessionTrackTypes); |
| return this; |
| } |
| |
| /** |
| * Sets the key set ID of the offline license. |
| * |
| * <p>The key set ID identifies an offline license. The ID is required to query, renew or |
| * release an existing offline license (see {@code DefaultDrmSessionManager#setMode(int |
| * mode,byte[] offlineLicenseKeySetId)}). |
| */ |
| public Builder setKeySetId(@Nullable byte[] keySetId) { |
| this.keySetId = keySetId != null ? Arrays.copyOf(keySetId, keySetId.length) : null; |
| return this; |
| } |
| |
| public DrmConfiguration build() { |
| |
| return new DrmConfiguration(this); |
| } |
| } |
| |
| /** The UUID of the protection scheme. */ |
| public final UUID scheme; |
| |
| /** @deprecated Use {@link #scheme} instead. */ |
| @Deprecated public final UUID uuid; |
| |
| /** |
| * Optional default DRM license server {@link Uri}. If {@code null} then the DRM license server |
| * must be specified by the media. |
| */ |
| @Nullable public final Uri licenseUri; |
| |
| /** @deprecated Use {@link #licenseRequestHeaders} instead. */ |
| @Deprecated public final ImmutableMap<String, String> requestHeaders; |
| |
| /** The headers to attach to requests sent to the DRM license server. */ |
| public final ImmutableMap<String, String> licenseRequestHeaders; |
| |
| /** Whether the DRM configuration is multi session enabled. */ |
| public final boolean multiSession; |
| |
| /** |
| * Whether clear samples within protected content should be played when keys for the encrypted |
| * part of the content have yet to be loaded. |
| */ |
| public final boolean playClearContentWithoutKey; |
| |
| /** |
| * Whether to force use of {@link #licenseUri} even if the media specifies its own DRM license |
| * server URI. |
| */ |
| public final boolean forceDefaultLicenseUri; |
| |
| /** @deprecated Use {@link #forcedSessionTrackTypes}. */ |
| @Deprecated public final ImmutableList<@C.TrackType Integer> sessionForClearTypes; |
| /** |
| * The types of tracks for which to always use a DRM session even if the content is unencrypted. |
| */ |
| public final ImmutableList<@C.TrackType Integer> forcedSessionTrackTypes; |
| |
| @Nullable private final byte[] keySetId; |
| |
| @SuppressWarnings("deprecation") // Setting deprecated field |
| private DrmConfiguration(Builder builder) { |
| checkState(!(builder.forceDefaultLicenseUri && builder.licenseUri == null)); |
| this.scheme = checkNotNull(builder.scheme); |
| this.uuid = scheme; |
| this.licenseUri = builder.licenseUri; |
| this.requestHeaders = builder.licenseRequestHeaders; |
| this.licenseRequestHeaders = builder.licenseRequestHeaders; |
| this.multiSession = builder.multiSession; |
| this.forceDefaultLicenseUri = builder.forceDefaultLicenseUri; |
| this.playClearContentWithoutKey = builder.playClearContentWithoutKey; |
| this.sessionForClearTypes = builder.forcedSessionTrackTypes; |
| this.forcedSessionTrackTypes = builder.forcedSessionTrackTypes; |
| this.keySetId = |
| builder.keySetId != null |
| ? Arrays.copyOf(builder.keySetId, builder.keySetId.length) |
| : null; |
| } |
| |
| /** Returns the key set ID of the offline license. */ |
| @Nullable |
| public byte[] getKeySetId() { |
| return keySetId != null ? Arrays.copyOf(keySetId, keySetId.length) : null; |
| } |
| |
| /** Returns a {@link Builder} initialized with the values of this instance. */ |
| public Builder buildUpon() { |
| return new Builder(this); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof DrmConfiguration)) { |
| return false; |
| } |
| |
| DrmConfiguration other = (DrmConfiguration) obj; |
| return scheme.equals(other.scheme) |
| && Util.areEqual(licenseUri, other.licenseUri) |
| && Util.areEqual(licenseRequestHeaders, other.licenseRequestHeaders) |
| && multiSession == other.multiSession |
| && forceDefaultLicenseUri == other.forceDefaultLicenseUri |
| && playClearContentWithoutKey == other.playClearContentWithoutKey |
| && forcedSessionTrackTypes.equals(other.forcedSessionTrackTypes) |
| && Arrays.equals(keySetId, other.keySetId); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = scheme.hashCode(); |
| result = 31 * result + (licenseUri != null ? licenseUri.hashCode() : 0); |
| result = 31 * result + licenseRequestHeaders.hashCode(); |
| result = 31 * result + (multiSession ? 1 : 0); |
| result = 31 * result + (forceDefaultLicenseUri ? 1 : 0); |
| result = 31 * result + (playClearContentWithoutKey ? 1 : 0); |
| result = 31 * result + forcedSessionTrackTypes.hashCode(); |
| result = 31 * result + Arrays.hashCode(keySetId); |
| return result; |
| } |
| } |
| |
| /** Configuration for playing back linear ads with a media item. */ |
| public static final class AdsConfiguration { |
| |
| /** Builder for {@link AdsConfiguration} instances. */ |
| public static final class Builder { |
| |
| private Uri adTagUri; |
| @Nullable private Object adsId; |
| |
| /** |
| * Constructs a new instance. |
| * |
| * @param adTagUri The ad tag URI to load. |
| */ |
| public Builder(Uri adTagUri) { |
| this.adTagUri = adTagUri; |
| } |
| |
| /** Sets the ad tag URI to load. */ |
| public Builder setAdTagUri(Uri adTagUri) { |
| this.adTagUri = adTagUri; |
| return this; |
| } |
| |
| /** |
| * Sets the ads identifier. |
| * |
| * <p>See details on {@link AdsConfiguration#adsId} for how the ads identifier is used and how |
| * it's calculated if not explicitly set. |
| */ |
| public Builder setAdsId(@Nullable Object adsId) { |
| this.adsId = adsId; |
| return this; |
| } |
| |
| public AdsConfiguration build() { |
| return new AdsConfiguration(this); |
| } |
| } |
| |
| /** The ad tag URI to load. */ |
| public final Uri adTagUri; |
| |
| /** |
| * An opaque identifier for ad playback state associated with this item, or {@code null} if the |
| * combination of the {@link MediaItem.Builder#setMediaId(String) media ID} and {@link #adTagUri |
| * ad tag URI} should be used as the ads identifier. |
| * |
| * <p>Media items in the playlist that have the same ads identifier and ads loader share the |
| * same ad playback state. To resume ad playback when recreating the playlist on returning from |
| * the background, pass the same ads identifiers to the player. |
| */ |
| @Nullable public final Object adsId; |
| |
| private AdsConfiguration(Builder builder) { |
| this.adTagUri = builder.adTagUri; |
| this.adsId = builder.adsId; |
| } |
| |
| /** Returns a {@link Builder} initialized with the values of this instance. */ |
| public Builder buildUpon() { |
| return new Builder(adTagUri).setAdsId(adsId); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof AdsConfiguration)) { |
| return false; |
| } |
| |
| AdsConfiguration other = (AdsConfiguration) obj; |
| return adTagUri.equals(other.adTagUri) && Util.areEqual(adsId, other.adsId); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = adTagUri.hashCode(); |
| result = 31 * result + (adsId != null ? adsId.hashCode() : 0); |
| return result; |
| } |
| } |
| |
| /** Properties for local playback. */ |
| // TODO: Mark this final when PlaybackProperties is deleted. |
| public static class LocalConfiguration { |
| |
| /** The {@link Uri}. */ |
| public final Uri uri; |
| |
| /** |
| * The optional MIME type of the item, or {@code null} if unspecified. |
| * |
| * <p>The MIME type can be used to disambiguate media items that have a URI which does not allow |
| * to infer the actual media type. |
| */ |
| @Nullable public final String mimeType; |
| |
| /** Optional {@link DrmConfiguration} for the media. */ |
| @Nullable public final DrmConfiguration drmConfiguration; |
| |
| /** Optional ads configuration. */ |
| @Nullable public final AdsConfiguration adsConfiguration; |
| |
| /** Optional stream keys by which the manifest is filtered. */ |
| public final List<StreamKey> streamKeys; |
| |
| /** Optional custom cache key (only used for progressive streams). */ |
| @Nullable public final String customCacheKey; |
| |
| /** Optional subtitles to be sideloaded. */ |
| public final ImmutableList<SubtitleConfiguration> subtitleConfigurations; |
| /** @deprecated Use {@link #subtitleConfigurations} instead. */ |
| @Deprecated public final List<Subtitle> subtitles; |
| |
| /** |
| * Optional tag for custom attributes. The tag for the media source which will be published in |
| * the {@code com.google.android.exoplayer2.Timeline} of the source as {@code |
| * com.google.android.exoplayer2.Timeline.Window#tag}. |
| */ |
| @Nullable public final Object tag; |
| |
| @SuppressWarnings("deprecation") // Setting deprecated subtitles field. |
| private LocalConfiguration( |
| Uri uri, |
| @Nullable String mimeType, |
| @Nullable DrmConfiguration drmConfiguration, |
| @Nullable AdsConfiguration adsConfiguration, |
| List<StreamKey> streamKeys, |
| @Nullable String customCacheKey, |
| ImmutableList<SubtitleConfiguration> subtitleConfigurations, |
| @Nullable Object tag) { |
| this.uri = uri; |
| this.mimeType = mimeType; |
| this.drmConfiguration = drmConfiguration; |
| this.adsConfiguration = adsConfiguration; |
| this.streamKeys = streamKeys; |
| this.customCacheKey = customCacheKey; |
| this.subtitleConfigurations = subtitleConfigurations; |
| ImmutableList.Builder<Subtitle> subtitles = ImmutableList.builder(); |
| for (int i = 0; i < subtitleConfigurations.size(); i++) { |
| subtitles.add(subtitleConfigurations.get(i).buildUpon().buildSubtitle()); |
| } |
| this.subtitles = subtitles.build(); |
| this.tag = tag; |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof LocalConfiguration)) { |
| return false; |
| } |
| LocalConfiguration other = (LocalConfiguration) obj; |
| |
| return uri.equals(other.uri) |
| && Util.areEqual(mimeType, other.mimeType) |
| && Util.areEqual(drmConfiguration, other.drmConfiguration) |
| && Util.areEqual(adsConfiguration, other.adsConfiguration) |
| && streamKeys.equals(other.streamKeys) |
| && Util.areEqual(customCacheKey, other.customCacheKey) |
| && subtitleConfigurations.equals(other.subtitleConfigurations) |
| && Util.areEqual(tag, other.tag); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = uri.hashCode(); |
| result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode()); |
| result = 31 * result + (drmConfiguration == null ? 0 : drmConfiguration.hashCode()); |
| result = 31 * result + (adsConfiguration == null ? 0 : adsConfiguration.hashCode()); |
| result = 31 * result + streamKeys.hashCode(); |
| result = 31 * result + (customCacheKey == null ? 0 : customCacheKey.hashCode()); |
| result = 31 * result + subtitleConfigurations.hashCode(); |
| result = 31 * result + (tag == null ? 0 : tag.hashCode()); |
| return result; |
| } |
| } |
| |
| /** @deprecated Use {@link LocalConfiguration}. */ |
| @Deprecated |
| public static final class PlaybackProperties extends LocalConfiguration { |
| |
| private PlaybackProperties( |
| Uri uri, |
| @Nullable String mimeType, |
| @Nullable DrmConfiguration drmConfiguration, |
| @Nullable AdsConfiguration adsConfiguration, |
| List<StreamKey> streamKeys, |
| @Nullable String customCacheKey, |
| ImmutableList<SubtitleConfiguration> subtitleConfigurations, |
| @Nullable Object tag) { |
| super( |
| uri, |
| mimeType, |
| drmConfiguration, |
| adsConfiguration, |
| streamKeys, |
| customCacheKey, |
| subtitleConfigurations, |
| tag); |
| } |
| } |
| |
| /** Live playback configuration. */ |
| public static final class LiveConfiguration implements Bundleable { |
| |
| /** Builder for {@link LiveConfiguration} instances. */ |
| public static final class Builder { |
| private long targetOffsetMs; |
| private long minOffsetMs; |
| private long maxOffsetMs; |
| private float minPlaybackSpeed; |
| private float maxPlaybackSpeed; |
| |
| /** Constructs an instance. */ |
| public Builder() { |
| this.targetOffsetMs = C.TIME_UNSET; |
| this.minOffsetMs = C.TIME_UNSET; |
| this.maxOffsetMs = C.TIME_UNSET; |
| this.minPlaybackSpeed = C.RATE_UNSET; |
| this.maxPlaybackSpeed = C.RATE_UNSET; |
| } |
| |
| private Builder(LiveConfiguration liveConfiguration) { |
| this.targetOffsetMs = liveConfiguration.targetOffsetMs; |
| this.minOffsetMs = liveConfiguration.minOffsetMs; |
| this.maxOffsetMs = liveConfiguration.maxOffsetMs; |
| this.minPlaybackSpeed = liveConfiguration.minPlaybackSpeed; |
| this.maxPlaybackSpeed = liveConfiguration.maxPlaybackSpeed; |
| } |
| |
| /** |
| * Sets the target live offset, in milliseconds. |
| * |
| * <p>See {@code Player#getCurrentLiveOffset()}. |
| * |
| * <p>Defaults to {@link C#TIME_UNSET}, indicating the media-defined default will be used. |
| */ |
| public Builder setTargetOffsetMs(long targetOffsetMs) { |
| this.targetOffsetMs = targetOffsetMs; |
| return this; |
| } |
| |
| /** |
| * Sets the minimum allowed live offset, in milliseconds. |
| * |
| * <p>See {@code Player#getCurrentLiveOffset()}. |
| * |
| * <p>Defaults to {@link C#TIME_UNSET}, indicating the media-defined default will be used. |
| */ |
| public Builder setMinOffsetMs(long minOffsetMs) { |
| this.minOffsetMs = minOffsetMs; |
| return this; |
| } |
| |
| /** |
| * Sets the maximum allowed live offset, in milliseconds. |
| * |
| * <p>See {@code Player#getCurrentLiveOffset()}. |
| * |
| * <p>Defaults to {@link C#TIME_UNSET}, indicating the media-defined default will be used. |
| */ |
| public Builder setMaxOffsetMs(long maxOffsetMs) { |
| this.maxOffsetMs = maxOffsetMs; |
| return this; |
| } |
| |
| /** |
| * Sets the minimum playback speed. |
| * |
| * <p>Defaults to {@link C#RATE_UNSET}, indicating the media-defined default will be used. |
| */ |
| public Builder setMinPlaybackSpeed(float minPlaybackSpeed) { |
| this.minPlaybackSpeed = minPlaybackSpeed; |
| return this; |
| } |
| |
| /** |
| * Sets the maximum playback speed. |
| * |
| * <p>Defaults to {@link C#RATE_UNSET}, indicating the media-defined default will be used. |
| */ |
| public Builder setMaxPlaybackSpeed(float maxPlaybackSpeed) { |
| this.maxPlaybackSpeed = maxPlaybackSpeed; |
| return this; |
| } |
| |
| /** Creates a {@link LiveConfiguration} with the values from this builder. */ |
| public LiveConfiguration build() { |
| return new LiveConfiguration(this); |
| } |
| } |
| |
| /** |
| * A live playback configuration with unset values, meaning media-defined default values will be |
| * used. |
| */ |
| public static final LiveConfiguration UNSET = new LiveConfiguration.Builder().build(); |
| |
| /** |
| * Target offset from the live edge, in milliseconds, or {@link C#TIME_UNSET} to use the |
| * media-defined default. |
| */ |
| public final long targetOffsetMs; |
| |
| /** |
| * The minimum allowed offset from the live edge, in milliseconds, or {@link C#TIME_UNSET} to |
| * use the media-defined default. |
| */ |
| public final long minOffsetMs; |
| |
| /** |
| * The maximum allowed offset from the live edge, in milliseconds, or {@link C#TIME_UNSET} to |
| * use the media-defined default. |
| */ |
| public final long maxOffsetMs; |
| |
| /** |
| * Minimum factor by which playback can be sped up, or {@link C#RATE_UNSET} to use the |
| * media-defined default. |
| */ |
| public final float minPlaybackSpeed; |
| |
| /** |
| * Maximum factor by which playback can be sped up, or {@link C#RATE_UNSET} to use the |
| * media-defined default. |
| */ |
| public final float maxPlaybackSpeed; |
| |
| @SuppressWarnings("deprecation") // Using the deprecated constructor while it exists. |
| private LiveConfiguration(Builder builder) { |
| this( |
| builder.targetOffsetMs, |
| builder.minOffsetMs, |
| builder.maxOffsetMs, |
| builder.minPlaybackSpeed, |
| builder.maxPlaybackSpeed); |
| } |
| |
| /** @deprecated Use {@link Builder} instead. */ |
| @Deprecated |
| public LiveConfiguration( |
| long targetOffsetMs, |
| long minOffsetMs, |
| long maxOffsetMs, |
| float minPlaybackSpeed, |
| float maxPlaybackSpeed) { |
| this.targetOffsetMs = targetOffsetMs; |
| this.minOffsetMs = minOffsetMs; |
| this.maxOffsetMs = maxOffsetMs; |
| this.minPlaybackSpeed = minPlaybackSpeed; |
| this.maxPlaybackSpeed = maxPlaybackSpeed; |
| } |
| |
| /** Returns a {@link Builder} initialized with the values of this instance. */ |
| public Builder buildUpon() { |
| return new Builder(this); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof LiveConfiguration)) { |
| return false; |
| } |
| LiveConfiguration other = (LiveConfiguration) obj; |
| |
| return targetOffsetMs == other.targetOffsetMs |
| && minOffsetMs == other.minOffsetMs |
| && maxOffsetMs == other.maxOffsetMs |
| && minPlaybackSpeed == other.minPlaybackSpeed |
| && maxPlaybackSpeed == other.maxPlaybackSpeed; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = (int) (targetOffsetMs ^ (targetOffsetMs >>> 32)); |
| result = 31 * result + (int) (minOffsetMs ^ (minOffsetMs >>> 32)); |
| result = 31 * result + (int) (maxOffsetMs ^ (maxOffsetMs >>> 32)); |
| result = 31 * result + (minPlaybackSpeed != 0 ? Float.floatToIntBits(minPlaybackSpeed) : 0); |
| result = 31 * result + (maxPlaybackSpeed != 0 ? Float.floatToIntBits(maxPlaybackSpeed) : 0); |
| return result; |
| } |
| |
| // Bundleable implementation. |
| |
| @Documented |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| FIELD_TARGET_OFFSET_MS, |
| FIELD_MIN_OFFSET_MS, |
| FIELD_MAX_OFFSET_MS, |
| FIELD_MIN_PLAYBACK_SPEED, |
| FIELD_MAX_PLAYBACK_SPEED |
| }) |
| private @interface FieldNumber {} |
| |
| private static final int FIELD_TARGET_OFFSET_MS = 0; |
| private static final int FIELD_MIN_OFFSET_MS = 1; |
| private static final int FIELD_MAX_OFFSET_MS = 2; |
| private static final int FIELD_MIN_PLAYBACK_SPEED = 3; |
| private static final int FIELD_MAX_PLAYBACK_SPEED = 4; |
| |
| @Override |
| public Bundle toBundle() { |
| Bundle bundle = new Bundle(); |
| bundle.putLong(keyForField(FIELD_TARGET_OFFSET_MS), targetOffsetMs); |
| bundle.putLong(keyForField(FIELD_MIN_OFFSET_MS), minOffsetMs); |
| bundle.putLong(keyForField(FIELD_MAX_OFFSET_MS), maxOffsetMs); |
| bundle.putFloat(keyForField(FIELD_MIN_PLAYBACK_SPEED), minPlaybackSpeed); |
| bundle.putFloat(keyForField(FIELD_MAX_PLAYBACK_SPEED), maxPlaybackSpeed); |
| return bundle; |
| } |
| |
| /** Object that can restore {@link LiveConfiguration} from a {@link Bundle}. */ |
| public static final Creator<LiveConfiguration> CREATOR = |
| bundle -> |
| new LiveConfiguration( |
| bundle.getLong( |
| keyForField(FIELD_TARGET_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), |
| bundle.getLong(keyForField(FIELD_MIN_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), |
| bundle.getLong(keyForField(FIELD_MAX_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), |
| bundle.getFloat( |
| keyForField(FIELD_MIN_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET), |
| bundle.getFloat( |
| keyForField(FIELD_MAX_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET)); |
| |
| private static String keyForField(@LiveConfiguration.FieldNumber int field) { |
| return Integer.toString(field, Character.MAX_RADIX); |
| } |
| } |
| |
| /** Properties for a text track. */ |
| // TODO: Mark this final when Subtitle is deleted. |
| public static class SubtitleConfiguration { |
| |
| /** Builder for {@link SubtitleConfiguration} instances. */ |
| public static final class Builder { |
| private Uri uri; |
| @Nullable private String mimeType; |
| @Nullable private String language; |
| private @C.SelectionFlags int selectionFlags; |
| private @C.RoleFlags int roleFlags; |
| @Nullable private String label; |
| @Nullable private String id; |
| |
| /** |
| * Constructs an instance. |
| * |
| * @param uri The {@link Uri} to the subtitle file. |
| */ |
| public Builder(Uri uri) { |
| this.uri = uri; |
| } |
| |
| private Builder(SubtitleConfiguration subtitleConfiguration) { |
| this.uri = subtitleConfiguration.uri; |
| this.mimeType = subtitleConfiguration.mimeType; |
| this.language = subtitleConfiguration.language; |
| this.selectionFlags = subtitleConfiguration.selectionFlags; |
| this.roleFlags = subtitleConfiguration.roleFlags; |
| this.label = subtitleConfiguration.label; |
| this.id = subtitleConfiguration.id; |
| } |
| |
| /** Sets the {@link Uri} to the subtitle file. */ |
| public Builder setUri(Uri uri) { |
| this.uri = uri; |
| return this; |
| } |
| |
| /** Sets the MIME type. */ |
| public Builder setMimeType(String mimeType) { |
| this.mimeType = mimeType; |
| return this; |
| } |
| |
| /** Sets the optional language of the subtitle file. */ |
| public Builder setLanguage(@Nullable String language) { |
| this.language = language; |
| return this; |
| } |
| |
| /** Sets the flags used for track selection. */ |
| public Builder setSelectionFlags(@C.SelectionFlags int selectionFlags) { |
| this.selectionFlags = selectionFlags; |
| return this; |
| } |
| |
| /** Sets the role flags. These are used for track selection. */ |
| public Builder setRoleFlags(@C.RoleFlags int roleFlags) { |
| this.roleFlags = roleFlags; |
| return this; |
| } |
| |
| /** Sets the optional label for this subtitle track. */ |
| public Builder setLabel(@Nullable String label) { |
| this.label = label; |
| return this; |
| } |
| |
| /** Sets the optional ID for this subtitle track. */ |
| public Builder setId(@Nullable String id) { |
| this.id = id; |
| return this; |
| } |
| |
| /** Creates a {@link SubtitleConfiguration} from the values of this builder. */ |
| public SubtitleConfiguration build() { |
| return new SubtitleConfiguration(this); |
| } |
| |
| private Subtitle buildSubtitle() { |
| return new Subtitle(this); |
| } |
| } |
| |
| /** The {@link Uri} to the subtitle file. */ |
| public final Uri uri; |
| /** The optional MIME type of the subtitle file, or {@code null} if unspecified. */ |
| @Nullable public final String mimeType; |
| /** The language. */ |
| @Nullable public final String language; |
| /** The selection flags. */ |
| public final @C.SelectionFlags int selectionFlags; |
| /** The role flags. */ |
| public final @C.RoleFlags int roleFlags; |
| /** The label. */ |
| @Nullable public final String label; |
| /** |
| * The ID of the subtitles. This will be propagated to the {@link Format#id} of the subtitle |
| * track created from this configuration. |
| */ |
| @Nullable public final String id; |
| |
| private SubtitleConfiguration( |
| Uri uri, |
| String mimeType, |
| @Nullable String language, |
| int selectionFlags, |
| int roleFlags, |
| @Nullable String label, |
| @Nullable String id) { |
| this.uri = uri; |
| this.mimeType = mimeType; |
| this.language = language; |
| this.selectionFlags = selectionFlags; |
| this.roleFlags = roleFlags; |
| this.label = label; |
| this.id = id; |
| } |
| |
| private SubtitleConfiguration(Builder builder) { |
| this.uri = builder.uri; |
| this.mimeType = builder.mimeType; |
| this.language = builder.language; |
| this.selectionFlags = builder.selectionFlags; |
| this.roleFlags = builder.roleFlags; |
| this.label = builder.label; |
| this.id = builder.id; |
| } |
| |
| /** Returns a {@link Builder} initialized with the values of this instance. */ |
| public Builder buildUpon() { |
| return new Builder(this); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof SubtitleConfiguration)) { |
| return false; |
| } |
| |
| SubtitleConfiguration other = (SubtitleConfiguration) obj; |
| |
| return uri.equals(other.uri) |
| && Util.areEqual(mimeType, other.mimeType) |
| && Util.areEqual(language, other.language) |
| && selectionFlags == other.selectionFlags |
| && roleFlags == other.roleFlags |
| && Util.areEqual(label, other.label) |
| && Util.areEqual(id, other.id); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = uri.hashCode(); |
| result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode()); |
| result = 31 * result + (language == null ? 0 : language.hashCode()); |
| result = 31 * result + selectionFlags; |
| result = 31 * result + roleFlags; |
| result = 31 * result + (label == null ? 0 : label.hashCode()); |
| result = 31 * result + (id == null ? 0 : id.hashCode()); |
| return result; |
| } |
| } |
| |
| /** @deprecated Use {@link MediaItem.SubtitleConfiguration} instead */ |
| @Deprecated |
| public static final class Subtitle extends SubtitleConfiguration { |
| |
| /** @deprecated Use {@link Builder} instead. */ |
| @Deprecated |
| public Subtitle(Uri uri, String mimeType, @Nullable String language) { |
| this(uri, mimeType, language, /* selectionFlags= */ 0); |
| } |
| |
| /** @deprecated Use {@link Builder} instead. */ |
| @Deprecated |
| public Subtitle( |
| Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags) { |
| this(uri, mimeType, language, selectionFlags, /* roleFlags= */ 0, /* label= */ null); |
| } |
| |
| /** @deprecated Use {@link Builder} instead. */ |
| @Deprecated |
| public Subtitle( |
| Uri uri, |
| String mimeType, |
| @Nullable String language, |
| @C.SelectionFlags int selectionFlags, |
| @C.RoleFlags int roleFlags, |
| @Nullable String label) { |
| super(uri, mimeType, language, selectionFlags, roleFlags, label, /* id= */ null); |
| } |
| |
| private Subtitle(Builder builder) { |
| super(builder); |
| } |
| } |
| |
| /** Optionally clips the media item to a custom start and end position. */ |
| // TODO: Mark this final when ClippingProperties is deleted. |
| public static class ClippingConfiguration implements Bundleable { |
| |
| /** A clipping configuration with default values. */ |
| public static final ClippingConfiguration UNSET = new ClippingConfiguration.Builder().build(); |
| |
| /** Builder for {@link ClippingConfiguration} instances. */ |
| public static final class Builder { |
| private long startPositionMs; |
| private long endPositionMs; |
| private boolean relativeToLiveWindow; |
| private boolean relativeToDefaultPosition; |
| private boolean startsAtKeyFrame; |
| |
| /** Constructs an instance. */ |
| public Builder() { |
| endPositionMs = C.TIME_END_OF_SOURCE; |
| } |
| |
| private Builder(ClippingConfiguration clippingConfiguration) { |
| startPositionMs = clippingConfiguration.startPositionMs; |
| endPositionMs = clippingConfiguration.endPositionMs; |
| relativeToLiveWindow = clippingConfiguration.relativeToLiveWindow; |
| relativeToDefaultPosition = clippingConfiguration.relativeToDefaultPosition; |
| startsAtKeyFrame = clippingConfiguration.startsAtKeyFrame; |
| } |
| |
| /** |
| * Sets the optional start position in milliseconds which must be a value larger than or equal |
| * to zero (Default: 0). |
| */ |
| public Builder setStartPositionMs(@IntRange(from = 0) long startPositionMs) { |
| Assertions.checkArgument(startPositionMs >= 0); |
| this.startPositionMs = startPositionMs; |
| return this; |
| } |
| |
| /** |
| * Sets the optional end position in milliseconds which must be a value larger than or equal |
| * to zero, or {@link C#TIME_END_OF_SOURCE} to end when playback reaches the end of media |
| * (Default: {@link C#TIME_END_OF_SOURCE}). |
| */ |
| public Builder setEndPositionMs(long endPositionMs) { |
| Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0); |
| this.endPositionMs = endPositionMs; |
| return this; |
| } |
| |
| /** |
| * Sets whether the start/end positions should move with the live window for live streams. If |
| * {@code false}, live streams end when playback reaches the end position in live window seen |
| * when the media is first loaded (Default: {@code false}). |
| */ |
| public Builder setRelativeToLiveWindow(boolean relativeToLiveWindow) { |
| this.relativeToLiveWindow = relativeToLiveWindow; |
| return this; |
| } |
| |
| /** |
| * Sets whether the start position and the end position are relative to the default position |
| * in the window (Default: {@code false}). |
| */ |
| public Builder setRelativeToDefaultPosition(boolean relativeToDefaultPosition) { |
| this.relativeToDefaultPosition = relativeToDefaultPosition; |
| return this; |
| } |
| |
| /** |
| * Sets whether the start point is guaranteed to be a key frame. If {@code false}, the |
| * playback transition into the clip may not be seamless (Default: {@code false}). |
| */ |
| public Builder setStartsAtKeyFrame(boolean startsAtKeyFrame) { |
| this.startsAtKeyFrame = startsAtKeyFrame; |
| return this; |
| } |
| |
| /** |
| * Returns a {@link ClippingConfiguration} instance initialized with the values of this |
| * builder. |
| */ |
| public ClippingConfiguration build() { |
| return buildClippingProperties(); |
| } |
| |
| /** @deprecated Use {@link #build()} instead. */ |
| @Deprecated |
| public ClippingProperties buildClippingProperties() { |
| return new ClippingProperties(this); |
| } |
| } |
| |
| /** The start position in milliseconds. This is a value larger than or equal to zero. */ |
| @IntRange(from = 0) |
| public final long startPositionMs; |
| |
| /** |
| * The end position in milliseconds. This is a value larger than or equal to zero or {@link |
| * C#TIME_END_OF_SOURCE} to play to the end of the stream. |
| */ |
| public final long endPositionMs; |
| |
| /** |
| * Whether the clipping of active media periods moves with a live window. If {@code false}, |
| * playback ends when it reaches {@link #endPositionMs}. |
| */ |
| public final boolean relativeToLiveWindow; |
| |
| /** |
| * Whether {@link #startPositionMs} and {@link #endPositionMs} are relative to the default |
| * position. |
| */ |
| public final boolean relativeToDefaultPosition; |
| |
| /** Sets whether the start point is guaranteed to be a key frame. */ |
| public final boolean startsAtKeyFrame; |
| |
| private ClippingConfiguration(Builder builder) { |
| this.startPositionMs = builder.startPositionMs; |
| this.endPositionMs = builder.endPositionMs; |
| this.relativeToLiveWindow = builder.relativeToLiveWindow; |
| this.relativeToDefaultPosition = builder.relativeToDefaultPosition; |
| this.startsAtKeyFrame = builder.startsAtKeyFrame; |
| } |
| |
| /** Returns a {@link Builder} initialized with the values of this instance. */ |
| public Builder buildUpon() { |
| return new Builder(this); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof ClippingConfiguration)) { |
| return false; |
| } |
| |
| ClippingConfiguration other = (ClippingConfiguration) obj; |
| |
| return startPositionMs == other.startPositionMs |
| && endPositionMs == other.endPositionMs |
| && relativeToLiveWindow == other.relativeToLiveWindow |
| && relativeToDefaultPosition == other.relativeToDefaultPosition |
| && startsAtKeyFrame == other.startsAtKeyFrame; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = (int) (startPositionMs ^ (startPositionMs >>> 32)); |
| result = 31 * result + (int) (endPositionMs ^ (endPositionMs >>> 32)); |
| result = 31 * result + (relativeToLiveWindow ? 1 : 0); |
| result = 31 * result + (relativeToDefaultPosition ? 1 : 0); |
| result = 31 * result + (startsAtKeyFrame ? 1 : 0); |
| return result; |
| } |
| |
| // Bundleable implementation. |
| |
| @Documented |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| FIELD_START_POSITION_MS, |
| FIELD_END_POSITION_MS, |
| FIELD_RELATIVE_TO_LIVE_WINDOW, |
| FIELD_RELATIVE_TO_DEFAULT_POSITION, |
| FIELD_STARTS_AT_KEY_FRAME |
| }) |
| private @interface FieldNumber {} |
| |
| private static final int FIELD_START_POSITION_MS = 0; |
| private static final int FIELD_END_POSITION_MS = 1; |
| private static final int FIELD_RELATIVE_TO_LIVE_WINDOW = 2; |
| private static final int FIELD_RELATIVE_TO_DEFAULT_POSITION = 3; |
| private static final int FIELD_STARTS_AT_KEY_FRAME = 4; |
| |
| @Override |
| public Bundle toBundle() { |
| Bundle bundle = new Bundle(); |
| bundle.putLong(keyForField(FIELD_START_POSITION_MS), startPositionMs); |
| bundle.putLong(keyForField(FIELD_END_POSITION_MS), endPositionMs); |
| bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), relativeToLiveWindow); |
| bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), relativeToDefaultPosition); |
| bundle.putBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), startsAtKeyFrame); |
| return bundle; |
| } |
| |
| /** Object that can restore {@link ClippingConfiguration} from a {@link Bundle}. */ |
| public static final Creator<ClippingProperties> CREATOR = |
| bundle -> |
| new ClippingConfiguration.Builder() |
| .setStartPositionMs( |
| bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0)) |
| .setEndPositionMs( |
| bundle.getLong( |
| keyForField(FIELD_END_POSITION_MS), |
| /* defaultValue= */ C.TIME_END_OF_SOURCE)) |
| .setRelativeToLiveWindow( |
| bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), false)) |
| .setRelativeToDefaultPosition( |
| bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), false)) |
| .setStartsAtKeyFrame( |
| bundle.getBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), false)) |
| .buildClippingProperties(); |
| |
| private static String keyForField(@ClippingConfiguration.FieldNumber int field) { |
| return Integer.toString(field, Character.MAX_RADIX); |
| } |
| } |
| |
| /** @deprecated Use {@link ClippingConfiguration} instead. */ |
| @Deprecated |
| public static final class ClippingProperties extends ClippingConfiguration { |
| public static final ClippingProperties UNSET = |
| new ClippingConfiguration.Builder().buildClippingProperties(); |
| |
| private ClippingProperties(Builder builder) { |
| super(builder); |
| } |
| } |
| |
| /** |
| * The default media ID that is used if the media ID is not explicitly set by {@link |
| * Builder#setMediaId(String)}. |
| */ |
| public static final String DEFAULT_MEDIA_ID = ""; |
| |
| /** Empty {@link MediaItem}. */ |
| public static final MediaItem EMPTY = new MediaItem.Builder().build(); |
| |
| /** Identifies the media item. */ |
| public final String mediaId; |
| |
| /** |
| * Optional configuration for local playback. May be {@code null} if shared over process |
| * boundaries. |
| */ |
| @Nullable public final LocalConfiguration localConfiguration; |
| /** @deprecated Use {@link #localConfiguration} instead. */ |
| @Deprecated @Nullable public final PlaybackProperties playbackProperties; |
| |
| /** The live playback configuration. */ |
| public final LiveConfiguration liveConfiguration; |
| |
| /** The media metadata. */ |
| public final MediaMetadata mediaMetadata; |
| |
| /** The clipping properties. */ |
| public final ClippingConfiguration clippingConfiguration; |
| /** @deprecated Use {@link #clippingConfiguration} instead. */ |
| @Deprecated public final ClippingProperties clippingProperties; |
| |
| // Using PlaybackProperties and ClippingProperties until they're deleted. |
| @SuppressWarnings("deprecation") |
| private MediaItem( |
| String mediaId, |
| ClippingProperties clippingConfiguration, |
| @Nullable PlaybackProperties localConfiguration, |
| LiveConfiguration liveConfiguration, |
| MediaMetadata mediaMetadata) { |
| this.mediaId = mediaId; |
| this.localConfiguration = localConfiguration; |
| this.playbackProperties = localConfiguration; |
| this.liveConfiguration = liveConfiguration; |
| this.mediaMetadata = mediaMetadata; |
| this.clippingConfiguration = clippingConfiguration; |
| this.clippingProperties = clippingConfiguration; |
| } |
| |
| /** Returns a {@link Builder} initialized with the values of this instance. */ |
| public Builder buildUpon() { |
| return new Builder(this); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof MediaItem)) { |
| return false; |
| } |
| |
| MediaItem other = (MediaItem) obj; |
| |
| return Util.areEqual(mediaId, other.mediaId) |
| && clippingConfiguration.equals(other.clippingConfiguration) |
| && Util.areEqual(localConfiguration, other.localConfiguration) |
| && Util.areEqual(liveConfiguration, other.liveConfiguration) |
| && Util.areEqual(mediaMetadata, other.mediaMetadata); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = mediaId.hashCode(); |
| result = 31 * result + (localConfiguration != null ? localConfiguration.hashCode() : 0); |
| result = 31 * result + liveConfiguration.hashCode(); |
| result = 31 * result + clippingConfiguration.hashCode(); |
| result = 31 * result + mediaMetadata.hashCode(); |
| return result; |
| } |
| |
| // Bundleable implementation. |
| |
| @Documented |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| FIELD_MEDIA_ID, |
| FIELD_LIVE_CONFIGURATION, |
| FIELD_MEDIA_METADATA, |
| FIELD_CLIPPING_PROPERTIES |
| }) |
| private @interface FieldNumber {} |
| |
| private static final int FIELD_MEDIA_ID = 0; |
| private static final int FIELD_LIVE_CONFIGURATION = 1; |
| private static final int FIELD_MEDIA_METADATA = 2; |
| private static final int FIELD_CLIPPING_PROPERTIES = 3; |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p>It omits the {@link #localConfiguration} field. The {@link #localConfiguration} of an |
| * instance restored by {@link #CREATOR} will always be {@code null}. |
| */ |
| @Override |
| public Bundle toBundle() { |
| Bundle bundle = new Bundle(); |
| bundle.putString(keyForField(FIELD_MEDIA_ID), mediaId); |
| bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); |
| bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle()); |
| bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle()); |
| return bundle; |
| } |
| |
| /** |
| * Object that can restore {@link MediaItem} from a {@link Bundle}. |
| * |
| * <p>The {@link #localConfiguration} of a restored instance will always be {@code null}. |
| */ |
| public static final Creator<MediaItem> CREATOR = MediaItem::fromBundle; |
| |
| @SuppressWarnings("deprecation") // Unbundling to ClippingProperties while it still exists. |
| private static MediaItem fromBundle(Bundle bundle) { |
| String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID), DEFAULT_MEDIA_ID)); |
| @Nullable |
| Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION)); |
| LiveConfiguration liveConfiguration; |
| if (liveConfigurationBundle == null) { |
| liveConfiguration = LiveConfiguration.UNSET; |
| } else { |
| liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle); |
| } |
| @Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA)); |
| MediaMetadata mediaMetadata; |
| if (mediaMetadataBundle == null) { |
| mediaMetadata = MediaMetadata.EMPTY; |
| } else { |
| mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle); |
| } |
| @Nullable |
| Bundle clippingConfigurationBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES)); |
| ClippingProperties clippingConfiguration; |
| if (clippingConfigurationBundle == null) { |
| clippingConfiguration = ClippingProperties.UNSET; |
| } else { |
| clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle); |
| } |
| return new MediaItem( |
| mediaId, |
| clippingConfiguration, |
| /* playbackProperties= */ null, |
| liveConfiguration, |
| mediaMetadata); |
| } |
| |
| private static String keyForField(@FieldNumber int field) { |
| return Integer.toString(field, Character.MAX_RADIX); |
| } |
| } |