• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.exoplayer2;
17 
18 import android.net.Uri;
19 import androidx.annotation.Nullable;
20 import com.google.android.exoplayer2.offline.StreamKey;
21 import com.google.android.exoplayer2.util.Assertions;
22 import com.google.android.exoplayer2.util.Util;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.UUID;
30 
31 /** Representation of a media item. */
32 public final class MediaItem {
33 
34   /**
35    * Creates a {@link MediaItem} for the given source uri.
36    *
37    * @param sourceUri The source uri.
38    * @return An {@link MediaItem} for the given source uri.
39    */
fromUri(String sourceUri)40   public static MediaItem fromUri(String sourceUri) {
41     return new MediaItem.Builder().setSourceUri(sourceUri).build();
42   }
43 
44   /**
45    * Creates a {@link MediaItem} for the given {@link Uri source uri}.
46    *
47    * @param sourceUri The {@link Uri source uri}.
48    * @return An {@link MediaItem} for the given source uri.
49    */
fromUri(Uri sourceUri)50   public static MediaItem fromUri(Uri sourceUri) {
51     return new MediaItem.Builder().setSourceUri(sourceUri).build();
52   }
53 
54   /** A builder for {@link MediaItem} instances. */
55   public static final class Builder {
56 
57     @Nullable private String mediaId;
58     @Nullable private Uri sourceUri;
59     @Nullable private String mimeType;
60     private long clipStartPositionMs;
61     private long clipEndPositionMs;
62     private boolean clipRelativeToLiveWindow;
63     private boolean clipRelativeToDefaultPosition;
64     private boolean clipStartsAtKeyFrame;
65     @Nullable private Uri drmLicenseUri;
66     private Map<String, String> drmLicenseRequestHeaders;
67     @Nullable private UUID drmUuid;
68     private boolean drmMultiSession;
69     private boolean drmPlayClearContentWithoutKey;
70     private List<Integer> drmSessionForClearTypes;
71     @Nullable private byte[] drmKeySetId;
72     private List<StreamKey> streamKeys;
73     @Nullable private String customCacheKey;
74     private List<Subtitle> subtitles;
75     @Nullable private Uri adTagUri;
76     @Nullable private Object tag;
77     @Nullable private MediaMetadata mediaMetadata;
78 
79     /** Creates a builder. */
Builder()80     public Builder() {
81       clipEndPositionMs = C.TIME_END_OF_SOURCE;
82       drmSessionForClearTypes = Collections.emptyList();
83       drmLicenseRequestHeaders = Collections.emptyMap();
84       streamKeys = Collections.emptyList();
85       subtitles = Collections.emptyList();
86     }
87 
Builder(MediaItem mediaItem)88     private Builder(MediaItem mediaItem) {
89       this();
90       clipEndPositionMs = mediaItem.clippingProperties.endPositionMs;
91       clipRelativeToLiveWindow = mediaItem.clippingProperties.relativeToLiveWindow;
92       clipRelativeToDefaultPosition = mediaItem.clippingProperties.relativeToDefaultPosition;
93       clipStartPositionMs = mediaItem.clippingProperties.startPositionMs;
94       clipStartsAtKeyFrame = mediaItem.clippingProperties.startsAtKeyFrame;
95       mediaId = mediaItem.mediaId;
96       mediaMetadata = mediaItem.mediaMetadata;
97       @Nullable PlaybackProperties playbackProperties = mediaItem.playbackProperties;
98       if (playbackProperties != null) {
99         adTagUri = playbackProperties.adTagUri;
100         customCacheKey = playbackProperties.customCacheKey;
101         mimeType = playbackProperties.mimeType;
102         sourceUri = playbackProperties.sourceUri;
103         streamKeys = playbackProperties.streamKeys;
104         subtitles = playbackProperties.subtitles;
105         tag = playbackProperties.tag;
106         @Nullable DrmConfiguration drmConfiguration = playbackProperties.drmConfiguration;
107         if (drmConfiguration != null) {
108           drmLicenseUri = drmConfiguration.licenseUri;
109           drmLicenseRequestHeaders = drmConfiguration.requestHeaders;
110           drmMultiSession = drmConfiguration.multiSession;
111           drmPlayClearContentWithoutKey = drmConfiguration.playClearContentWithoutKey;
112           drmSessionForClearTypes = drmConfiguration.sessionForClearTypes;
113           drmUuid = drmConfiguration.uuid;
114           drmKeySetId = drmConfiguration.getKeySetId();
115         }
116       }
117     }
118 
119     /**
120      * Sets the optional media id which identifies the media item. If not specified, {@link
121      * #setSourceUri} must be called and the string representation of {@link
122      * PlaybackProperties#sourceUri} is used as the media id.
123      */
setMediaId(@ullable String mediaId)124     public Builder setMediaId(@Nullable String mediaId) {
125       this.mediaId = mediaId;
126       return this;
127     }
128 
129     /**
130      * Sets the optional source uri. If not specified, {@link #setMediaId(String)} must be called.
131      */
setSourceUri(@ullable String sourceUri)132     public Builder setSourceUri(@Nullable String sourceUri) {
133       return setSourceUri(sourceUri == null ? null : Uri.parse(sourceUri));
134     }
135 
136     /**
137      * Sets the optional source {@link Uri}. If not specified, {@link #setMediaId(String)} must be
138      * called.
139      */
setSourceUri(@ullable Uri sourceUri)140     public Builder setSourceUri(@Nullable Uri sourceUri) {
141       this.sourceUri = sourceUri;
142       return this;
143     }
144 
145     /**
146      * Sets the optional mime type.
147      *
148      * <p>The mime type may be used as a hint for inferring the type of the media item.
149      *
150      * <p>If a {@link PlaybackProperties#sourceUri} is set, the mime type is used to create a {@link
151      * PlaybackProperties} object. Otherwise it will be ignored.
152      *
153      * @param mimeType The mime type.
154      */
setMimeType(@ullable String mimeType)155     public Builder setMimeType(@Nullable String mimeType) {
156       this.mimeType = mimeType;
157       return this;
158     }
159 
160     /**
161      * Sets the optional start position in milliseconds which must be a value larger than or equal
162      * to zero (Default: 0).
163      */
setClipStartPositionMs(long startPositionMs)164     public Builder setClipStartPositionMs(long startPositionMs) {
165       Assertions.checkArgument(startPositionMs >= 0);
166       this.clipStartPositionMs = startPositionMs;
167       return this;
168     }
169 
170     /**
171      * Sets the optional end position in milliseconds which must be a value larger than or equal to
172      * zero, or {@link C#TIME_END_OF_SOURCE} to end when playback reaches the end of media (Default:
173      * {@link C#TIME_END_OF_SOURCE}).
174      */
setClipEndPositionMs(long endPositionMs)175     public Builder setClipEndPositionMs(long endPositionMs) {
176       Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0);
177       this.clipEndPositionMs = endPositionMs;
178       return this;
179     }
180 
181     /**
182      * Sets whether the start/end positions should move with the live window for live streams. If
183      * {@code false}, live streams end when playback reaches the end position in live window seen
184      * when the media is first loaded (Default: {@code false}).
185      */
setClipRelativeToLiveWindow(boolean relativeToLiveWindow)186     public Builder setClipRelativeToLiveWindow(boolean relativeToLiveWindow) {
187       this.clipRelativeToLiveWindow = relativeToLiveWindow;
188       return this;
189     }
190 
191     /**
192      * Sets whether the start position and the end position are relative to the default position in
193      * the window (Default: {@code false}).
194      */
setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition)195     public Builder setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition) {
196       this.clipRelativeToDefaultPosition = relativeToDefaultPosition;
197       return this;
198     }
199 
200     /**
201      * Sets whether the start point is guaranteed to be a key frame. If {@code false}, the playback
202      * transition into the clip may not be seamless (Default: {@code false}).
203      */
setClipStartsAtKeyFrame(boolean startsAtKeyFrame)204     public Builder setClipStartsAtKeyFrame(boolean startsAtKeyFrame) {
205       this.clipStartsAtKeyFrame = startsAtKeyFrame;
206       return this;
207     }
208 
209     /**
210      * Sets the optional license server {@link Uri}. If a license uri is set, the {@link
211      * DrmConfiguration#uuid} needs to be specified as well.
212      *
213      * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm license uri is used to create a
214      * {@link PlaybackProperties} object. Otherwise it will be ignored.
215      */
setDrmLicenseUri(@ullable Uri licenseUri)216     public Builder setDrmLicenseUri(@Nullable Uri licenseUri) {
217       drmLicenseUri = licenseUri;
218       return this;
219     }
220 
221     /**
222      * Sets the optional license server uri as a {@link String}. If a license uri is set, the {@link
223      * DrmConfiguration#uuid} needs to be specified as well.
224      *
225      * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm license uri is used to create a
226      * {@link PlaybackProperties} object. Otherwise it will be ignored.
227      */
setDrmLicenseUri(@ullable String licenseUri)228     public Builder setDrmLicenseUri(@Nullable String licenseUri) {
229       drmLicenseUri = licenseUri == null ? null : Uri.parse(licenseUri);
230       return this;
231     }
232 
233     /**
234      * Sets the optional request headers attached to the drm license request.
235      *
236      * <p>{@code null} or an empty {@link Map} can be used for a reset.
237      *
238      * <p>If no valid drm configuration is specified, the drm license request headers are ignored.
239      */
setDrmLicenseRequestHeaders( @ullable Map<String, String> licenseRequestHeaders)240     public Builder setDrmLicenseRequestHeaders(
241         @Nullable Map<String, String> licenseRequestHeaders) {
242       this.drmLicenseRequestHeaders =
243           licenseRequestHeaders != null && !licenseRequestHeaders.isEmpty()
244               ? Collections.unmodifiableMap(new HashMap<>(licenseRequestHeaders))
245               : Collections.emptyMap();
246       return this;
247     }
248 
249     /**
250      * Sets the {@link UUID} of the protection scheme. If a drm system uuid is set, the {@link
251      * DrmConfiguration#licenseUri} needs to be set as well.
252      *
253      * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm system uuid is used to create a
254      * {@link PlaybackProperties} object. Otherwise it will be ignored.
255      */
setDrmUuid(@ullable UUID uuid)256     public Builder setDrmUuid(@Nullable UUID uuid) {
257       drmUuid = uuid;
258       return this;
259     }
260 
261     /**
262      * Sets whether the drm configuration is multi session enabled.
263      *
264      * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm multi session flag is used to
265      * create a {@link PlaybackProperties} object. Otherwise it will be ignored.
266      */
setDrmMultiSession(boolean multiSession)267     public Builder setDrmMultiSession(boolean multiSession) {
268       drmMultiSession = multiSession;
269       return this;
270     }
271 
272     /**
273      * Sets whether clear samples within protected content should be played when keys for the
274      * encrypted part of the content have yet to be loaded.
275      */
setDrmPlayClearContentWithoutKey(boolean playClearContentWithoutKey)276     public Builder setDrmPlayClearContentWithoutKey(boolean playClearContentWithoutKey) {
277       this.drmPlayClearContentWithoutKey = playClearContentWithoutKey;
278       return this;
279     }
280 
281     /**
282      * Sets whether a drm session should be used for clear tracks of type {@link C#TRACK_TYPE_VIDEO}
283      * and {@link C#TRACK_TYPE_AUDIO}.
284      *
285      * <p>This method overrides what has been set by previously calling {@link
286      * #setDrmSessionForClearTypes(List)}.
287      */
setDrmSessionForClearPeriods(boolean sessionForClearPeriods)288     public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) {
289       this.setDrmSessionForClearTypes(
290           sessionForClearPeriods
291               ? Arrays.asList(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO)
292               : Collections.emptyList());
293       return this;
294     }
295 
296     /**
297      * Sets a list of {@link C}{@code .TRACK_TYPE_*} constants for which to use a drm session even
298      * when the tracks are in the clear.
299      *
300      * <p>For the common case of using a drm session for {@link C#TRACK_TYPE_VIDEO} and {@link
301      * C#TRACK_TYPE_AUDIO} the {@link #setDrmSessionForClearPeriods(boolean)} can be used.
302      *
303      * <p>This method overrides what has been set by previously calling {@link
304      * #setDrmSessionForClearPeriods(boolean)}.
305      *
306      * <p>{@code null} or an empty {@link List} can be used for a reset.
307      */
setDrmSessionForClearTypes(@ullable List<Integer> sessionForClearTypes)308     public Builder setDrmSessionForClearTypes(@Nullable List<Integer> sessionForClearTypes) {
309       this.drmSessionForClearTypes =
310           sessionForClearTypes != null && !sessionForClearTypes.isEmpty()
311               ? Collections.unmodifiableList(new ArrayList<>(sessionForClearTypes))
312               : Collections.emptyList();
313       return this;
314     }
315 
316     /**
317      * Sets the key set ID of the offline license.
318      *
319      * <p>The key set ID identifies an offline license. The ID is required to query, renew or
320      * release an existing offline license (see {@code DefaultDrmSessionManager#setMode(int
321      * mode,byte[] offlineLicenseKeySetId)}).
322      *
323      * <p>If no valid DRM configuration is specified, the key set ID is ignored.
324      */
setDrmKeySetId(@ullable byte[] keySetId)325     public Builder setDrmKeySetId(@Nullable byte[] keySetId) {
326       this.drmKeySetId = keySetId != null ? Arrays.copyOf(keySetId, keySetId.length) : null;
327       return this;
328     }
329 
330     /**
331      * Sets the optional stream keys by which the manifest is filtered (only used for adaptive
332      * streams).
333      *
334      * <p>{@code null} or an empty {@link List} can be used for a reset.
335      *
336      * <p>If a {@link PlaybackProperties#sourceUri} is set, the stream keys are used to create a
337      * {@link PlaybackProperties} object. Otherwise it will be ignored.
338      */
setStreamKeys(@ullable List<StreamKey> streamKeys)339     public Builder setStreamKeys(@Nullable List<StreamKey> streamKeys) {
340       this.streamKeys =
341           streamKeys != null && !streamKeys.isEmpty()
342               ? Collections.unmodifiableList(new ArrayList<>(streamKeys))
343               : Collections.emptyList();
344       return this;
345     }
346 
347     /**
348      * Sets the optional custom cache key (only used for progressive streams).
349      *
350      * <p>If a {@link PlaybackProperties#sourceUri} is set, the custom cache key is used to create a
351      * {@link PlaybackProperties} object. Otherwise it will be ignored.
352      */
setCustomCacheKey(@ullable String customCacheKey)353     public Builder setCustomCacheKey(@Nullable String customCacheKey) {
354       this.customCacheKey = customCacheKey;
355       return this;
356     }
357 
358     /**
359      * Sets the optional subtitles.
360      *
361      * <p>{@code null} or an empty {@link List} can be used for a reset.
362      *
363      * <p>If a {@link PlaybackProperties#sourceUri} is set, the subtitles are used to create a
364      * {@link PlaybackProperties} object. Otherwise it will be ignored.
365      */
setSubtitles(@ullable List<Subtitle> subtitles)366     public Builder setSubtitles(@Nullable List<Subtitle> subtitles) {
367       this.subtitles =
368           subtitles != null && !subtitles.isEmpty()
369               ? Collections.unmodifiableList(new ArrayList<>(subtitles))
370               : Collections.emptyList();
371       return this;
372     }
373 
374     /**
375      * Sets the optional ad tag URI.
376      *
377      * <p>If a {@link PlaybackProperties#sourceUri} is set, the ad tag URI is used to create a
378      * {@link PlaybackProperties} object. Otherwise it will be ignored.
379      */
setAdTagUri(@ullable String adTagUri)380     public Builder setAdTagUri(@Nullable String adTagUri) {
381       this.adTagUri = adTagUri != null ? Uri.parse(adTagUri) : null;
382       return this;
383     }
384 
385     /**
386      * Sets the optional ad tag {@link Uri}.
387      *
388      * <p>If a {@link PlaybackProperties#sourceUri} is set, the ad tag URI is used to create a
389      * {@link PlaybackProperties} object. Otherwise it will be ignored.
390      */
setAdTagUri(@ullable Uri adTagUri)391     public Builder setAdTagUri(@Nullable Uri adTagUri) {
392       this.adTagUri = adTagUri;
393       return this;
394     }
395 
396     /**
397      * Sets the optional tag for custom attributes. The tag for the media source which will be
398      * published in the {@code com.google.android.exoplayer2.Timeline} of the source as {@code
399      * com.google.android.exoplayer2.Timeline.Window#tag}.
400      *
401      * <p>If a {@link PlaybackProperties#sourceUri} is set, the tag is used to create a {@link
402      * PlaybackProperties} object. Otherwise it will be ignored.
403      */
setTag(@ullable Object tag)404     public Builder setTag(@Nullable Object tag) {
405       this.tag = tag;
406       return this;
407     }
408 
409     /** Sets the media metadata. */
setMediaMetadata(MediaMetadata mediaMetadata)410     public Builder setMediaMetadata(MediaMetadata mediaMetadata) {
411       this.mediaMetadata = mediaMetadata;
412       return this;
413     }
414 
415     /**
416      * Returns a new {@link MediaItem} instance with the current builder values.
417      */
build()418     public MediaItem build() {
419       Assertions.checkState(drmLicenseUri == null || drmUuid != null);
420       @Nullable PlaybackProperties playbackProperties = null;
421       if (sourceUri != null) {
422         playbackProperties =
423             new PlaybackProperties(
424                 sourceUri,
425                 mimeType,
426                 drmUuid != null
427                     ? new DrmConfiguration(
428                         drmUuid,
429                         drmLicenseUri,
430                         drmLicenseRequestHeaders,
431                         drmMultiSession,
432                         drmPlayClearContentWithoutKey,
433                         drmSessionForClearTypes,
434                         drmKeySetId)
435                     : null,
436                 streamKeys,
437                 customCacheKey,
438                 subtitles,
439                 adTagUri,
440                 tag);
441         mediaId = mediaId != null ? mediaId : sourceUri.toString();
442       }
443       return new MediaItem(
444           Assertions.checkNotNull(mediaId),
445           new ClippingProperties(
446               clipStartPositionMs,
447               clipEndPositionMs,
448               clipRelativeToLiveWindow,
449               clipRelativeToDefaultPosition,
450               clipStartsAtKeyFrame),
451           playbackProperties,
452           mediaMetadata != null ? mediaMetadata : new MediaMetadata.Builder().build());
453     }
454   }
455 
456   /** DRM configuration for a media item. */
457   public static final class DrmConfiguration {
458 
459     /** The UUID of the protection scheme. */
460     public final UUID uuid;
461 
462     /**
463      * Optional license server {@link Uri}. If {@code null} then the license server must be
464      * specified by the media.
465      */
466     @Nullable public final Uri licenseUri;
467 
468     /** The headers to attach to the request for the license uri. */
469     public final Map<String, String> requestHeaders;
470 
471     /** Whether the drm configuration is multi session enabled. */
472     public final boolean multiSession;
473 
474     /**
475      * Whether clear samples within protected content should be played when keys for the encrypted
476      * part of the content have yet to be loaded.
477      */
478     public final boolean playClearContentWithoutKey;
479 
480     /** The types of clear tracks for which to use a drm session. */
481     public final List<Integer> sessionForClearTypes;
482 
483     @Nullable private final byte[] keySetId;
484 
DrmConfiguration( UUID uuid, @Nullable Uri licenseUri, Map<String, String> requestHeaders, boolean multiSession, boolean playClearContentWithoutKey, List<Integer> drmSessionForClearTypes, @Nullable byte[] keySetId)485     private DrmConfiguration(
486         UUID uuid,
487         @Nullable Uri licenseUri,
488         Map<String, String> requestHeaders,
489         boolean multiSession,
490         boolean playClearContentWithoutKey,
491         List<Integer> drmSessionForClearTypes,
492         @Nullable byte[] keySetId) {
493       this.uuid = uuid;
494       this.licenseUri = licenseUri;
495       this.requestHeaders = requestHeaders;
496       this.multiSession = multiSession;
497       this.playClearContentWithoutKey = playClearContentWithoutKey;
498       this.sessionForClearTypes = drmSessionForClearTypes;
499       this.keySetId = keySetId != null ? Arrays.copyOf(keySetId, keySetId.length) : null;
500     }
501 
502     /** Returns the key set ID of the offline license. */
503     @Nullable
getKeySetId()504     public byte[] getKeySetId() {
505       return keySetId != null ? Arrays.copyOf(keySetId, keySetId.length) : null;
506     }
507 
508     @Override
equals(@ullable Object obj)509     public boolean equals(@Nullable Object obj) {
510       if (this == obj) {
511         return true;
512       }
513       if (!(obj instanceof DrmConfiguration)) {
514         return false;
515       }
516 
517       DrmConfiguration other = (DrmConfiguration) obj;
518       return uuid.equals(other.uuid)
519           && Util.areEqual(licenseUri, other.licenseUri)
520           && Util.areEqual(requestHeaders, other.requestHeaders)
521           && multiSession == other.multiSession
522           && playClearContentWithoutKey == other.playClearContentWithoutKey
523           && sessionForClearTypes.equals(other.sessionForClearTypes)
524           && Arrays.equals(keySetId, other.keySetId);
525     }
526 
527     @Override
hashCode()528     public int hashCode() {
529       int result = uuid.hashCode();
530       result = 31 * result + (licenseUri != null ? licenseUri.hashCode() : 0);
531       result = 31 * result + requestHeaders.hashCode();
532       result = 31 * result + (multiSession ? 1 : 0);
533       result = 31 * result + (playClearContentWithoutKey ? 1 : 0);
534       result = 31 * result + sessionForClearTypes.hashCode();
535       result = 31 * result + Arrays.hashCode(keySetId);
536       return result;
537     }
538   }
539 
540   /** Properties for local playback. */
541   public static final class PlaybackProperties {
542 
543     /** The source {@link Uri}. */
544     public final Uri sourceUri;
545 
546     /**
547      * The optional mime type of the item, or {@code null} if unspecified.
548      *
549      * <p>The mime type can be used to disambiguate media items that have a uri which does not allow
550      * to infer the actual media type.
551      */
552     @Nullable public final String mimeType;
553 
554     /** Optional {@link DrmConfiguration} for the media. */
555     @Nullable public final DrmConfiguration drmConfiguration;
556 
557     /** Optional stream keys by which the manifest is filtered. */
558     public final List<StreamKey> streamKeys;
559 
560     /** Optional custom cache key (only used for progressive streams). */
561     @Nullable public final String customCacheKey;
562 
563     /** Optional subtitles to be sideloaded. */
564     public final List<Subtitle> subtitles;
565 
566     /** Optional ad tag {@link Uri}. */
567     @Nullable public final Uri adTagUri;
568 
569     /**
570      * Optional tag for custom attributes. The tag for the media source which will be published in
571      * the {@code com.google.android.exoplayer2.Timeline} of the source as {@code
572      * com.google.android.exoplayer2.Timeline.Window#tag}.
573      */
574     @Nullable public final Object tag;
575 
PlaybackProperties( Uri sourceUri, @Nullable String mimeType, @Nullable DrmConfiguration drmConfiguration, List<StreamKey> streamKeys, @Nullable String customCacheKey, List<Subtitle> subtitles, @Nullable Uri adTagUri, @Nullable Object tag)576     private PlaybackProperties(
577         Uri sourceUri,
578         @Nullable String mimeType,
579         @Nullable DrmConfiguration drmConfiguration,
580         List<StreamKey> streamKeys,
581         @Nullable String customCacheKey,
582         List<Subtitle> subtitles,
583         @Nullable Uri adTagUri,
584         @Nullable Object tag) {
585       this.sourceUri = sourceUri;
586       this.mimeType = mimeType;
587       this.drmConfiguration = drmConfiguration;
588       this.streamKeys = streamKeys;
589       this.customCacheKey = customCacheKey;
590       this.subtitles = subtitles;
591       this.adTagUri = adTagUri;
592       this.tag = tag;
593     }
594 
595     @Override
equals(@ullable Object obj)596     public boolean equals(@Nullable Object obj) {
597       if (this == obj) {
598         return true;
599       }
600       if (!(obj instanceof PlaybackProperties)) {
601         return false;
602       }
603       PlaybackProperties other = (PlaybackProperties) obj;
604 
605       return sourceUri.equals(other.sourceUri)
606           && Util.areEqual(mimeType, other.mimeType)
607           && Util.areEqual(drmConfiguration, other.drmConfiguration)
608           && streamKeys.equals(other.streamKeys)
609           && Util.areEqual(customCacheKey, other.customCacheKey)
610           && subtitles.equals(other.subtitles)
611           && Util.areEqual(adTagUri, other.adTagUri)
612           && Util.areEqual(tag, other.tag);
613     }
614 
615     @Override
hashCode()616     public int hashCode() {
617       int result = sourceUri.hashCode();
618       result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
619       result = 31 * result + (drmConfiguration == null ? 0 : drmConfiguration.hashCode());
620       result = 31 * result + streamKeys.hashCode();
621       result = 31 * result + (customCacheKey == null ? 0 : customCacheKey.hashCode());
622       result = 31 * result + subtitles.hashCode();
623       result = 31 * result + (adTagUri == null ? 0 : adTagUri.hashCode());
624       result = 31 * result + (tag == null ? 0 : tag.hashCode());
625       return result;
626     }
627   }
628 
629   /** Properties for a text track. */
630   public static final class Subtitle {
631 
632     /** The {@link Uri} to the subtitle file. */
633     public final Uri uri;
634     /** The MIME type. */
635     public final String mimeType;
636     /** The language. */
637     @Nullable public final String language;
638     /** The selection flags. */
639     @C.SelectionFlags public final int selectionFlags;
640 
641     /**
642      * Creates an instance.
643      *
644      * @param uri The {@link Uri uri} to the subtitle file.
645      * @param mimeType The mime type.
646      * @param language The optional language.
647      */
Subtitle(Uri uri, String mimeType, @Nullable String language)648     public Subtitle(Uri uri, String mimeType, @Nullable String language) {
649       this(uri, mimeType, language, /* selectionFlags= */ 0);
650     }
651 
652     /**
653      * Creates an instance with the given selection flags.
654      *
655      * @param uri The {@link Uri uri} to the subtitle file.
656      * @param mimeType The mime type.
657      * @param language The optional language.
658      * @param selectionFlags The selection flags.
659      */
Subtitle( Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags)660     public Subtitle(
661         Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags) {
662       this.uri = uri;
663       this.mimeType = mimeType;
664       this.language = language;
665       this.selectionFlags = selectionFlags;
666     }
667 
668     @Override
equals(@ullable Object obj)669     public boolean equals(@Nullable Object obj) {
670       if (this == obj) {
671         return true;
672       }
673       if (!(obj instanceof Subtitle)) {
674         return false;
675       }
676 
677       Subtitle other = (Subtitle) obj;
678 
679       return uri.equals(other.uri)
680           && mimeType.equals(other.mimeType)
681           && Util.areEqual(language, other.language)
682           && selectionFlags == other.selectionFlags;
683     }
684 
685     @Override
hashCode()686     public int hashCode() {
687       int result = uri.hashCode();
688       result = 31 * result + mimeType.hashCode();
689       result = 31 * result + (language == null ? 0 : language.hashCode());
690       result = 31 * result + selectionFlags;
691       return result;
692     }
693   }
694 
695   /** Optionally clips the media item to a custom start and end position. */
696   public static final class ClippingProperties {
697 
698     /** The start position in milliseconds. This is a value larger than or equal to zero. */
699     public final long startPositionMs;
700 
701     /**
702      * The end position in milliseconds. This is a value larger than or equal to zero or {@link
703      * C#TIME_END_OF_SOURCE} to play to the end of the stream.
704      */
705     public final long endPositionMs;
706 
707     /**
708      * Whether the clipping of active media periods moves with a live window. If {@code false},
709      * playback ends when it reaches {@link #endPositionMs}.
710      */
711     public final boolean relativeToLiveWindow;
712 
713     /**
714      * Whether {@link #startPositionMs} and {@link #endPositionMs} are relative to the default
715      * position.
716      */
717     public final boolean relativeToDefaultPosition;
718 
719     /** Sets whether the start point is guaranteed to be a key frame. */
720     public final boolean startsAtKeyFrame;
721 
ClippingProperties( long startPositionMs, long endPositionMs, boolean relativeToLiveWindow, boolean relativeToDefaultPosition, boolean startsAtKeyFrame)722     private ClippingProperties(
723         long startPositionMs,
724         long endPositionMs,
725         boolean relativeToLiveWindow,
726         boolean relativeToDefaultPosition,
727         boolean startsAtKeyFrame) {
728       this.startPositionMs = startPositionMs;
729       this.endPositionMs = endPositionMs;
730       this.relativeToLiveWindow = relativeToLiveWindow;
731       this.relativeToDefaultPosition = relativeToDefaultPosition;
732       this.startsAtKeyFrame = startsAtKeyFrame;
733     }
734 
735     @Override
equals(@ullable Object obj)736     public boolean equals(@Nullable Object obj) {
737       if (this == obj) {
738         return true;
739       }
740       if (!(obj instanceof ClippingProperties)) {
741         return false;
742       }
743 
744       ClippingProperties other = (ClippingProperties) obj;
745 
746       return startPositionMs == other.startPositionMs
747           && endPositionMs == other.endPositionMs
748           && relativeToLiveWindow == other.relativeToLiveWindow
749           && relativeToDefaultPosition == other.relativeToDefaultPosition
750           && startsAtKeyFrame == other.startsAtKeyFrame;
751     }
752 
753     @Override
hashCode()754     public int hashCode() {
755       int result = Long.valueOf(startPositionMs).hashCode();
756       result = 31 * result + Long.valueOf(endPositionMs).hashCode();
757       result = 31 * result + (relativeToLiveWindow ? 1 : 0);
758       result = 31 * result + (relativeToDefaultPosition ? 1 : 0);
759       result = 31 * result + (startsAtKeyFrame ? 1 : 0);
760       return result;
761     }
762   }
763 
764   /** Identifies the media item. */
765   public final String mediaId;
766 
767   /** Optional playback properties. Maybe be {@code null} if shared over process boundaries. */
768   @Nullable public final PlaybackProperties playbackProperties;
769 
770   /** The media metadata. */
771   public final MediaMetadata mediaMetadata;
772 
773   /** The clipping properties. */
774   public final ClippingProperties clippingProperties;
775 
MediaItem( String mediaId, ClippingProperties clippingProperties, @Nullable PlaybackProperties playbackProperties, MediaMetadata mediaMetadata)776   private MediaItem(
777       String mediaId,
778       ClippingProperties clippingProperties,
779       @Nullable PlaybackProperties playbackProperties,
780       MediaMetadata mediaMetadata) {
781     this.mediaId = mediaId;
782     this.playbackProperties = playbackProperties;
783     this.mediaMetadata = mediaMetadata;
784     this.clippingProperties = clippingProperties;
785   }
786 
787   /** Returns a {@link Builder} initialized with the values of this instance. */
buildUpon()788   public Builder buildUpon() {
789     return new Builder(this);
790   }
791 
792   @Override
equals(@ullable Object obj)793   public boolean equals(@Nullable Object obj) {
794     if (this == obj) {
795       return true;
796     }
797     if (!(obj instanceof MediaItem)) {
798       return false;
799     }
800 
801     MediaItem other = (MediaItem) obj;
802 
803     return Util.areEqual(mediaId, other.mediaId)
804         && clippingProperties.equals(other.clippingProperties)
805         && Util.areEqual(playbackProperties, other.playbackProperties)
806         && Util.areEqual(mediaMetadata, other.mediaMetadata);
807   }
808 
809   @Override
hashCode()810   public int hashCode() {
811     int result = mediaId.hashCode();
812     result = 31 * result + (playbackProperties != null ? playbackProperties.hashCode() : 0);
813     result = 31 * result + clippingProperties.hashCode();
814     result = 31 * result + mediaMetadata.hashCode();
815     return result;
816   }
817 }
818