summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DrmInitData.java
blob: 2a25d1deb4b59e1ede735585cdc9eedbcc0fe5ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mozilla.thirdparty.com.google.android.exoplayer2.drm;

import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;

/**
 * Initialization data for one or more DRM schemes.
 */
public final class DrmInitData implements Comparator<SchemeData>, Parcelable {

  /**
   * Merges {@link DrmInitData} obtained from a media manifest and a media stream.
   *
   * <p>The result is generated as follows.
   *
   * <ol>
   *   <li>Include all {@link SchemeData}s from {@code manifestData} where {@link
   *       SchemeData#hasData()} is true.
   *   <li>Include all {@link SchemeData}s in {@code mediaData} where {@link SchemeData#hasData()}
   *       is true and for which we did not include an entry from the manifest targeting the same
   *       UUID.
   *   <li>If available, the scheme type from the manifest is used. If not, the scheme type from the
   *       media is used.
   * </ol>
   *
   * @param manifestData DRM session acquisition data obtained from the manifest.
   * @param mediaData DRM session acquisition data obtained from the media.
   * @return A {@link DrmInitData} obtained from merging a media manifest and a media stream.
   */
  public static @Nullable DrmInitData createSessionCreationData(
      @Nullable DrmInitData manifestData, @Nullable DrmInitData mediaData) {
    ArrayList<SchemeData> result = new ArrayList<>();
    String schemeType = null;
    if (manifestData != null) {
      schemeType = manifestData.schemeType;
      for (SchemeData data : manifestData.schemeDatas) {
        if (data.hasData()) {
          result.add(data);
        }
      }
    }

    if (mediaData != null) {
      if (schemeType == null) {
        schemeType = mediaData.schemeType;
      }
      int manifestDatasCount = result.size();
      for (SchemeData data : mediaData.schemeDatas) {
        if (data.hasData() && !containsSchemeDataWithUuid(result, manifestDatasCount, data.uuid)) {
          result.add(data);
        }
      }
    }

    return result.isEmpty() ? null : new DrmInitData(schemeType, result);
  }

  private final SchemeData[] schemeDatas;

  // Lazily initialized hashcode.
  private int hashCode;

  /** The protection scheme type, or null if not applicable or unknown. */
  @Nullable public final String schemeType;

  /**
   * Number of {@link SchemeData}s.
   */
  public final int schemeDataCount;

  /**
   * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
   */
  public DrmInitData(List<SchemeData> schemeDatas) {
    this(null, false, schemeDatas.toArray(new SchemeData[0]));
  }

  /**
   * @param schemeType See {@link #schemeType}.
   * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
   */
  public DrmInitData(@Nullable String schemeType, List<SchemeData> schemeDatas) {
    this(schemeType, false, schemeDatas.toArray(new SchemeData[0]));
  }

  /**
   * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
   */
  public DrmInitData(SchemeData... schemeDatas) {
    this(null, schemeDatas);
  }

  /**
   * @param schemeType See {@link #schemeType}.
   * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
   */
  public DrmInitData(@Nullable String schemeType, SchemeData... schemeDatas) {
    this(schemeType, true, schemeDatas);
  }

  private DrmInitData(@Nullable String schemeType, boolean cloneSchemeDatas,
      SchemeData... schemeDatas) {
    this.schemeType = schemeType;
    if (cloneSchemeDatas) {
      schemeDatas = schemeDatas.clone();
    }
    this.schemeDatas = schemeDatas;
    schemeDataCount = schemeDatas.length;
    // Sorting ensures that universal scheme data (i.e. data that applies to all schemes) is matched
    // last. It's also required by the equals and hashcode implementations.
    Arrays.sort(this.schemeDatas, this);
  }

  /* package */
  DrmInitData(Parcel in) {
    schemeType = in.readString();
    schemeDatas = Util.castNonNull(in.createTypedArray(SchemeData.CREATOR));
    schemeDataCount = schemeDatas.length;
  }

  /**
   * Retrieves data for a given DRM scheme, specified by its UUID.
   *
   * @deprecated Use {@link #get(int)} and {@link SchemeData#matches(UUID)} instead.
   * @param uuid The DRM scheme's UUID.
   * @return The initialization data for the scheme, or null if the scheme is not supported.
   */
  @Deprecated
  @Nullable
  public SchemeData get(UUID uuid) {
    for (SchemeData schemeData : schemeDatas) {
      if (schemeData.matches(uuid)) {
        return schemeData;
      }
    }
    return null;
  }

  /**
   * Retrieves the {@link SchemeData} at a given index.
   *
   * @param index The index of the scheme to return. Must not exceed {@link #schemeDataCount}.
   * @return The {@link SchemeData} at the specified index.
   */
  public SchemeData get(int index) {
    return schemeDatas[index];
  }

  /**
   * Returns a copy with the specified protection scheme type.
   *
   * @param schemeType A protection scheme type. May be null.
   * @return A copy with the specified protection scheme type.
   */
  public DrmInitData copyWithSchemeType(@Nullable String schemeType) {
    if (Util.areEqual(this.schemeType, schemeType)) {
      return this;
    }
    return new DrmInitData(schemeType, false, schemeDatas);
  }

  /**
   * Returns an instance containing the {@link #schemeDatas} from both this and {@code other}. The
   * {@link #schemeType} of the instances being merged must either match, or at least one scheme
   * type must be {@code null}.
   *
   * @param drmInitData The instance to merge.
   * @return The merged result.
   */
  public DrmInitData merge(DrmInitData drmInitData) {
    Assertions.checkState(
        schemeType == null
            || drmInitData.schemeType == null
            || TextUtils.equals(schemeType, drmInitData.schemeType));
    String mergedSchemeType = schemeType != null ? this.schemeType : drmInitData.schemeType;
    SchemeData[] mergedSchemeDatas =
        Util.nullSafeArrayConcatenation(schemeDatas, drmInitData.schemeDatas);
    return new DrmInitData(mergedSchemeType, mergedSchemeDatas);
  }

  @Override
  public int hashCode() {
    if (hashCode == 0) {
      int result = (schemeType == null ? 0 : schemeType.hashCode());
      result = 31 * result + Arrays.hashCode(schemeDatas);
      hashCode = result;
    }
    return hashCode;
  }

  @Override
  public boolean equals(@Nullable Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
      return false;
    }
    DrmInitData other = (DrmInitData) obj;
    return Util.areEqual(schemeType, other.schemeType)
        && Arrays.equals(schemeDatas, other.schemeDatas);
  }

  @Override
  public int compare(SchemeData first, SchemeData second) {
    return C.UUID_NIL.equals(first.uuid) ? (C.UUID_NIL.equals(second.uuid) ? 0 : 1)
        : first.uuid.compareTo(second.uuid);
  }

  // Parcelable implementation.

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(schemeType);
    dest.writeTypedArray(schemeDatas, 0);
  }

  public static final Parcelable.Creator<DrmInitData> CREATOR =
      new Parcelable.Creator<DrmInitData>() {

    @Override
    public DrmInitData createFromParcel(Parcel in) {
      return new DrmInitData(in);
    }

    @Override
    public DrmInitData[] newArray(int size) {
      return new DrmInitData[size];
    }

  };

  // Internal methods.

  private static boolean containsSchemeDataWithUuid(
      ArrayList<SchemeData> datas, int limit, UUID uuid) {
    for (int i = 0; i < limit; i++) {
      if (datas.get(i).uuid.equals(uuid)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Scheme initialization data.
   */
  public static final class SchemeData implements Parcelable {

    // Lazily initialized hashcode.
    private int hashCode;

    /**
     * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e.
     * applies to all schemes).
     */
    private final UUID uuid;
    /** The URL of the server to which license requests should be made. May be null if unknown. */
    @Nullable public final String licenseServerUrl;
    /** The mimeType of {@link #data}. */
    public final String mimeType;
    /** The initialization data. May be null for scheme support checks only. */
    @Nullable public final byte[] data;

    /**
     * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is
     *     universal (i.e. applies to all schemes).
     * @param mimeType See {@link #mimeType}.
     * @param data See {@link #data}.
     */
    public SchemeData(UUID uuid, String mimeType, @Nullable byte[] data) {
      this(uuid, /* licenseServerUrl= */ null, mimeType, data);
    }

    /**
     * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is
     *     universal (i.e. applies to all schemes).
     * @param licenseServerUrl See {@link #licenseServerUrl}.
     * @param mimeType See {@link #mimeType}.
     * @param data See {@link #data}.
     */
    public SchemeData(
        UUID uuid, @Nullable String licenseServerUrl, String mimeType, @Nullable byte[] data) {
      this.uuid = Assertions.checkNotNull(uuid);
      this.licenseServerUrl = licenseServerUrl;
      this.mimeType = Assertions.checkNotNull(mimeType);
      this.data = data;
    }

    /* package */ SchemeData(Parcel in) {
      uuid = new UUID(in.readLong(), in.readLong());
      licenseServerUrl = in.readString();
      mimeType = Util.castNonNull(in.readString());
      data = in.createByteArray();
    }

    /**
     * Returns whether this initialization data applies to the specified scheme.
     *
     * @param schemeUuid The scheme {@link UUID}.
     * @return Whether this initialization data applies to the specified scheme.
     */
    public boolean matches(UUID schemeUuid) {
      return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid);
    }

    /**
     * Returns whether this {@link SchemeData} can be used to replace {@code other}.
     *
     * @param other A {@link SchemeData}.
     * @return Whether this {@link SchemeData} can be used to replace {@code other}.
     */
    public boolean canReplace(SchemeData other) {
      return hasData() && !other.hasData() && matches(other.uuid);
    }

    /**
     * Returns whether {@link #data} is non-null.
     */
    public boolean hasData() {
      return data != null;
    }

    /**
     * Returns a copy of this instance with the specified data.
     *
     * @param data The data to include in the copy.
     * @return The new instance.
     */
    public SchemeData copyWithData(@Nullable byte[] data) {
      return new SchemeData(uuid, licenseServerUrl, mimeType, data);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (!(obj instanceof SchemeData)) {
        return false;
      }
      if (obj == this) {
        return true;
      }
      SchemeData other = (SchemeData) obj;
      return Util.areEqual(licenseServerUrl, other.licenseServerUrl)
          && Util.areEqual(mimeType, other.mimeType)
          && Util.areEqual(uuid, other.uuid)
          && Arrays.equals(data, other.data);
    }

    @Override
    public int hashCode() {
      if (hashCode == 0) {
        int result = uuid.hashCode();
        result = 31 * result + (licenseServerUrl == null ? 0 : licenseServerUrl.hashCode());
        result = 31 * result + mimeType.hashCode();
        result = 31 * result + Arrays.hashCode(data);
        hashCode = result;
      }
      return hashCode;
    }

    // Parcelable implementation.

    @Override
    public int describeContents() {
      return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      dest.writeLong(uuid.getMostSignificantBits());
      dest.writeLong(uuid.getLeastSignificantBits());
      dest.writeString(licenseServerUrl);
      dest.writeString(mimeType);
      dest.writeByteArray(data);
    }

    public static final Parcelable.Creator<SchemeData> CREATOR =
        new Parcelable.Creator<SchemeData>() {

      @Override
      public SchemeData createFromParcel(Parcel in) {
        return new SchemeData(in);
      }

      @Override
      public SchemeData[] newArray(int size) {
        return new SchemeData[size];
      }

    };

  }

}