1 /* 2 * Copyright (C) 2016 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.drm; 17 18 import android.os.Parcel; 19 import android.os.Parcelable; 20 import android.text.TextUtils; 21 import androidx.annotation.Nullable; 22 import com.google.android.exoplayer2.C; 23 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; 24 import com.google.android.exoplayer2.util.Assertions; 25 import com.google.android.exoplayer2.util.Util; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Comparator; 29 import java.util.List; 30 import java.util.UUID; 31 32 /** 33 * Initialization data for one or more DRM schemes. 34 */ 35 public final class DrmInitData implements Comparator<SchemeData>, Parcelable { 36 37 /** 38 * Merges {@link DrmInitData} obtained from a media manifest and a media stream. 39 * 40 * <p>The result is generated as follows. 41 * 42 * <ol> 43 * <li>Include all {@link SchemeData}s from {@code manifestData} where {@link 44 * SchemeData#hasData()} is true. 45 * <li>Include all {@link SchemeData}s in {@code mediaData} where {@link SchemeData#hasData()} 46 * is true and for which we did not include an entry from the manifest targeting the same 47 * UUID. 48 * <li>If available, the scheme type from the manifest is used. If not, the scheme type from the 49 * media is used. 50 * </ol> 51 * 52 * @param manifestData DRM session acquisition data obtained from the manifest. 53 * @param mediaData DRM session acquisition data obtained from the media. 54 * @return A {@link DrmInitData} obtained from merging a media manifest and a media stream. 55 */ createSessionCreationData( @ullable DrmInitData manifestData, @Nullable DrmInitData mediaData)56 public static @Nullable DrmInitData createSessionCreationData( 57 @Nullable DrmInitData manifestData, @Nullable DrmInitData mediaData) { 58 ArrayList<SchemeData> result = new ArrayList<>(); 59 String schemeType = null; 60 if (manifestData != null) { 61 schemeType = manifestData.schemeType; 62 for (SchemeData data : manifestData.schemeDatas) { 63 if (data.hasData()) { 64 result.add(data); 65 } 66 } 67 } 68 69 if (mediaData != null) { 70 if (schemeType == null) { 71 schemeType = mediaData.schemeType; 72 } 73 int manifestDatasCount = result.size(); 74 for (SchemeData data : mediaData.schemeDatas) { 75 if (data.hasData() && !containsSchemeDataWithUuid(result, manifestDatasCount, data.uuid)) { 76 result.add(data); 77 } 78 } 79 } 80 81 return result.isEmpty() ? null : new DrmInitData(schemeType, result); 82 } 83 84 private final SchemeData[] schemeDatas; 85 86 // Lazily initialized hashcode. 87 private int hashCode; 88 89 /** The protection scheme type, or null if not applicable or unknown. */ 90 @Nullable public final String schemeType; 91 92 /** 93 * Number of {@link SchemeData}s. 94 */ 95 public final int schemeDataCount; 96 97 /** 98 * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. 99 */ DrmInitData(List<SchemeData> schemeDatas)100 public DrmInitData(List<SchemeData> schemeDatas) { 101 this(null, false, schemeDatas.toArray(new SchemeData[0])); 102 } 103 104 /** 105 * @param schemeType See {@link #schemeType}. 106 * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. 107 */ DrmInitData(@ullable String schemeType, List<SchemeData> schemeDatas)108 public DrmInitData(@Nullable String schemeType, List<SchemeData> schemeDatas) { 109 this(schemeType, false, schemeDatas.toArray(new SchemeData[0])); 110 } 111 112 /** 113 * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. 114 */ DrmInitData(SchemeData... schemeDatas)115 public DrmInitData(SchemeData... schemeDatas) { 116 this(null, schemeDatas); 117 } 118 119 /** 120 * @param schemeType See {@link #schemeType}. 121 * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. 122 */ DrmInitData(@ullable String schemeType, SchemeData... schemeDatas)123 public DrmInitData(@Nullable String schemeType, SchemeData... schemeDatas) { 124 this(schemeType, true, schemeDatas); 125 } 126 DrmInitData(@ullable String schemeType, boolean cloneSchemeDatas, SchemeData... schemeDatas)127 private DrmInitData(@Nullable String schemeType, boolean cloneSchemeDatas, 128 SchemeData... schemeDatas) { 129 this.schemeType = schemeType; 130 if (cloneSchemeDatas) { 131 schemeDatas = schemeDatas.clone(); 132 } 133 this.schemeDatas = schemeDatas; 134 schemeDataCount = schemeDatas.length; 135 // Sorting ensures that universal scheme data (i.e. data that applies to all schemes) is matched 136 // last. It's also required by the equals and hashcode implementations. 137 Arrays.sort(this.schemeDatas, this); 138 } 139 140 /* package */ DrmInitData(Parcel in)141 DrmInitData(Parcel in) { 142 schemeType = in.readString(); 143 schemeDatas = Util.castNonNull(in.createTypedArray(SchemeData.CREATOR)); 144 schemeDataCount = schemeDatas.length; 145 } 146 147 /** 148 * Retrieves data for a given DRM scheme, specified by its UUID. 149 * 150 * @deprecated Use {@link #get(int)} and {@link SchemeData#matches(UUID)} instead. 151 * @param uuid The DRM scheme's UUID. 152 * @return The initialization data for the scheme, or null if the scheme is not supported. 153 */ 154 @Deprecated 155 @Nullable get(UUID uuid)156 public SchemeData get(UUID uuid) { 157 for (SchemeData schemeData : schemeDatas) { 158 if (schemeData.matches(uuid)) { 159 return schemeData; 160 } 161 } 162 return null; 163 } 164 165 /** 166 * Retrieves the {@link SchemeData} at a given index. 167 * 168 * @param index The index of the scheme to return. Must not exceed {@link #schemeDataCount}. 169 * @return The {@link SchemeData} at the specified index. 170 */ get(int index)171 public SchemeData get(int index) { 172 return schemeDatas[index]; 173 } 174 175 /** 176 * Returns a copy with the specified protection scheme type. 177 * 178 * @param schemeType A protection scheme type. May be null. 179 * @return A copy with the specified protection scheme type. 180 */ copyWithSchemeType(@ullable String schemeType)181 public DrmInitData copyWithSchemeType(@Nullable String schemeType) { 182 if (Util.areEqual(this.schemeType, schemeType)) { 183 return this; 184 } 185 return new DrmInitData(schemeType, false, schemeDatas); 186 } 187 188 /** 189 * Returns an instance containing the {@link #schemeDatas} from both this and {@code other}. The 190 * {@link #schemeType} of the instances being merged must either match, or at least one scheme 191 * type must be {@code null}. 192 * 193 * @param drmInitData The instance to merge. 194 * @return The merged result. 195 */ merge(DrmInitData drmInitData)196 public DrmInitData merge(DrmInitData drmInitData) { 197 Assertions.checkState( 198 schemeType == null 199 || drmInitData.schemeType == null 200 || TextUtils.equals(schemeType, drmInitData.schemeType)); 201 String mergedSchemeType = schemeType != null ? this.schemeType : drmInitData.schemeType; 202 SchemeData[] mergedSchemeDatas = 203 Util.nullSafeArrayConcatenation(schemeDatas, drmInitData.schemeDatas); 204 return new DrmInitData(mergedSchemeType, mergedSchemeDatas); 205 } 206 207 @Override hashCode()208 public int hashCode() { 209 if (hashCode == 0) { 210 int result = (schemeType == null ? 0 : schemeType.hashCode()); 211 result = 31 * result + Arrays.hashCode(schemeDatas); 212 hashCode = result; 213 } 214 return hashCode; 215 } 216 217 @Override equals(@ullable Object obj)218 public boolean equals(@Nullable Object obj) { 219 if (this == obj) { 220 return true; 221 } 222 if (obj == null || getClass() != obj.getClass()) { 223 return false; 224 } 225 DrmInitData other = (DrmInitData) obj; 226 return Util.areEqual(schemeType, other.schemeType) 227 && Arrays.equals(schemeDatas, other.schemeDatas); 228 } 229 230 @Override compare(SchemeData first, SchemeData second)231 public int compare(SchemeData first, SchemeData second) { 232 return C.UUID_NIL.equals(first.uuid) ? (C.UUID_NIL.equals(second.uuid) ? 0 : 1) 233 : first.uuid.compareTo(second.uuid); 234 } 235 236 // Parcelable implementation. 237 238 @Override describeContents()239 public int describeContents() { 240 return 0; 241 } 242 243 @Override writeToParcel(Parcel dest, int flags)244 public void writeToParcel(Parcel dest, int flags) { 245 dest.writeString(schemeType); 246 dest.writeTypedArray(schemeDatas, 0); 247 } 248 249 public static final Parcelable.Creator<DrmInitData> CREATOR = 250 new Parcelable.Creator<DrmInitData>() { 251 252 @Override 253 public DrmInitData createFromParcel(Parcel in) { 254 return new DrmInitData(in); 255 } 256 257 @Override 258 public DrmInitData[] newArray(int size) { 259 return new DrmInitData[size]; 260 } 261 262 }; 263 264 // Internal methods. 265 containsSchemeDataWithUuid( ArrayList<SchemeData> datas, int limit, UUID uuid)266 private static boolean containsSchemeDataWithUuid( 267 ArrayList<SchemeData> datas, int limit, UUID uuid) { 268 for (int i = 0; i < limit; i++) { 269 if (datas.get(i).uuid.equals(uuid)) { 270 return true; 271 } 272 } 273 return false; 274 } 275 276 /** 277 * Scheme initialization data. 278 */ 279 public static final class SchemeData implements Parcelable { 280 281 // Lazily initialized hashcode. 282 private int hashCode; 283 284 /** 285 * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. 286 * applies to all schemes). 287 */ 288 public final UUID uuid; 289 /** The URL of the server to which license requests should be made. May be null if unknown. */ 290 @Nullable public final String licenseServerUrl; 291 /** The mimeType of {@link #data}. */ 292 public final String mimeType; 293 /** The initialization data. May be null for scheme support checks only. */ 294 @Nullable public final byte[] data; 295 296 /** 297 * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is 298 * universal (i.e. applies to all schemes). 299 * @param mimeType See {@link #mimeType}. 300 * @param data See {@link #data}. 301 */ SchemeData(UUID uuid, String mimeType, @Nullable byte[] data)302 public SchemeData(UUID uuid, String mimeType, @Nullable byte[] data) { 303 this(uuid, /* licenseServerUrl= */ null, mimeType, data); 304 } 305 306 /** 307 * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is 308 * universal (i.e. applies to all schemes). 309 * @param licenseServerUrl See {@link #licenseServerUrl}. 310 * @param mimeType See {@link #mimeType}. 311 * @param data See {@link #data}. 312 */ SchemeData( UUID uuid, @Nullable String licenseServerUrl, String mimeType, @Nullable byte[] data)313 public SchemeData( 314 UUID uuid, @Nullable String licenseServerUrl, String mimeType, @Nullable byte[] data) { 315 this.uuid = Assertions.checkNotNull(uuid); 316 this.licenseServerUrl = licenseServerUrl; 317 this.mimeType = Assertions.checkNotNull(mimeType); 318 this.data = data; 319 } 320 SchemeData(Parcel in)321 /* package */ SchemeData(Parcel in) { 322 uuid = new UUID(in.readLong(), in.readLong()); 323 licenseServerUrl = in.readString(); 324 mimeType = Util.castNonNull(in.readString()); 325 data = in.createByteArray(); 326 } 327 328 /** 329 * Returns whether this initialization data applies to the specified scheme. 330 * 331 * @param schemeUuid The scheme {@link UUID}. 332 * @return Whether this initialization data applies to the specified scheme. 333 */ matches(UUID schemeUuid)334 public boolean matches(UUID schemeUuid) { 335 return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid); 336 } 337 338 /** 339 * Returns whether this {@link SchemeData} can be used to replace {@code other}. 340 * 341 * @param other A {@link SchemeData}. 342 * @return Whether this {@link SchemeData} can be used to replace {@code other}. 343 */ canReplace(SchemeData other)344 public boolean canReplace(SchemeData other) { 345 return hasData() && !other.hasData() && matches(other.uuid); 346 } 347 348 /** 349 * Returns whether {@link #data} is non-null. 350 */ hasData()351 public boolean hasData() { 352 return data != null; 353 } 354 355 /** 356 * Returns a copy of this instance with the specified data. 357 * 358 * @param data The data to include in the copy. 359 * @return The new instance. 360 */ copyWithData(@ullable byte[] data)361 public SchemeData copyWithData(@Nullable byte[] data) { 362 return new SchemeData(uuid, licenseServerUrl, mimeType, data); 363 } 364 365 @Override equals(@ullable Object obj)366 public boolean equals(@Nullable Object obj) { 367 if (!(obj instanceof SchemeData)) { 368 return false; 369 } 370 if (obj == this) { 371 return true; 372 } 373 SchemeData other = (SchemeData) obj; 374 return Util.areEqual(licenseServerUrl, other.licenseServerUrl) 375 && Util.areEqual(mimeType, other.mimeType) 376 && Util.areEqual(uuid, other.uuid) 377 && Arrays.equals(data, other.data); 378 } 379 380 @Override hashCode()381 public int hashCode() { 382 if (hashCode == 0) { 383 int result = uuid.hashCode(); 384 result = 31 * result + (licenseServerUrl == null ? 0 : licenseServerUrl.hashCode()); 385 result = 31 * result + mimeType.hashCode(); 386 result = 31 * result + Arrays.hashCode(data); 387 hashCode = result; 388 } 389 return hashCode; 390 } 391 392 // Parcelable implementation. 393 394 @Override describeContents()395 public int describeContents() { 396 return 0; 397 } 398 399 @Override writeToParcel(Parcel dest, int flags)400 public void writeToParcel(Parcel dest, int flags) { 401 dest.writeLong(uuid.getMostSignificantBits()); 402 dest.writeLong(uuid.getLeastSignificantBits()); 403 dest.writeString(licenseServerUrl); 404 dest.writeString(mimeType); 405 dest.writeByteArray(data); 406 } 407 408 public static final Parcelable.Creator<SchemeData> CREATOR = 409 new Parcelable.Creator<SchemeData>() { 410 411 @Override 412 public SchemeData createFromParcel(Parcel in) { 413 return new SchemeData(in); 414 } 415 416 @Override 417 public SchemeData[] newArray(int size) { 418 return new SchemeData[size]; 419 } 420 421 }; 422 423 } 424 425 } 426