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