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.annotation.SuppressLint; 19 import android.os.Handler; 20 import android.os.Looper; 21 import android.os.Message; 22 import androidx.annotation.IntDef; 23 import androidx.annotation.Nullable; 24 import androidx.annotation.RequiresApi; 25 import com.google.android.exoplayer2.C; 26 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; 27 import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; 28 import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener; 29 import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; 30 import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; 31 import com.google.android.exoplayer2.util.Assertions; 32 import com.google.android.exoplayer2.util.Log; 33 import com.google.android.exoplayer2.util.MediaSourceEventDispatcher; 34 import com.google.android.exoplayer2.util.Util; 35 import java.lang.annotation.Documented; 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.UUID; 44 45 /** A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */ 46 @RequiresApi(18) 47 public class DefaultDrmSessionManager implements DrmSessionManager { 48 49 /** 50 * Builder for {@link DefaultDrmSessionManager} instances. 51 * 52 * <p>See {@link #Builder} for the list of default values. 53 */ 54 public static final class Builder { 55 56 private final HashMap<String, String> keyRequestParameters; 57 private UUID uuid; 58 private ExoMediaDrm.Provider exoMediaDrmProvider; 59 private boolean multiSession; 60 private int[] useDrmSessionsForClearContentTrackTypes; 61 private boolean playClearSamplesWithoutKeys; 62 private LoadErrorHandlingPolicy loadErrorHandlingPolicy; 63 64 /** 65 * Creates a builder with default values. The default values are: 66 * 67 * <ul> 68 * <li>{@link #setKeyRequestParameters keyRequestParameters}: An empty map. 69 * <li>{@link #setUuidAndExoMediaDrmProvider UUID}: {@link C#WIDEVINE_UUID}. 70 * <li>{@link #setUuidAndExoMediaDrmProvider ExoMediaDrm.Provider}: {@link 71 * FrameworkMediaDrm#DEFAULT_PROVIDER}. 72 * <li>{@link #setMultiSession multiSession}: {@code false}. 73 * <li>{@link #setUseDrmSessionsForClearContent useDrmSessionsForClearContent}: No tracks. 74 * <li>{@link #setPlayClearSamplesWithoutKeys playClearSamplesWithoutKeys}: {@code false}. 75 * <li>{@link #setLoadErrorHandlingPolicy LoadErrorHandlingPolicy}: {@link 76 * DefaultLoadErrorHandlingPolicy}. 77 * </ul> 78 */ Builder()79 public Builder() { 80 keyRequestParameters = new HashMap<>(); 81 uuid = C.WIDEVINE_UUID; 82 exoMediaDrmProvider = FrameworkMediaDrm.DEFAULT_PROVIDER; 83 loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); 84 useDrmSessionsForClearContentTrackTypes = new int[0]; 85 } 86 87 /** 88 * Sets the key request parameters to pass as the last argument to {@link 89 * ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null if not parameters need to 90 * be passed. 91 * 92 * <p>Custom data for PlayReady should be set under {@link #PLAYREADY_CUSTOM_DATA_KEY}. 93 * 94 * @param keyRequestParameters A map with parameters. 95 * @return This builder. 96 */ setKeyRequestParameters(@ullable Map<String, String> keyRequestParameters)97 public Builder setKeyRequestParameters(@Nullable Map<String, String> keyRequestParameters) { 98 this.keyRequestParameters.clear(); 99 if (keyRequestParameters != null) { 100 this.keyRequestParameters.putAll(keyRequestParameters); 101 } 102 return this; 103 } 104 105 /** 106 * Sets the UUID of the DRM scheme and the {@link ExoMediaDrm.Provider} to use. 107 * 108 * @param uuid The UUID of the DRM scheme. 109 * @param exoMediaDrmProvider The {@link ExoMediaDrm.Provider}. 110 * @return This builder. 111 */ setUuidAndExoMediaDrmProvider( UUID uuid, ExoMediaDrm.Provider exoMediaDrmProvider)112 public Builder setUuidAndExoMediaDrmProvider( 113 UUID uuid, ExoMediaDrm.Provider exoMediaDrmProvider) { 114 this.uuid = Assertions.checkNotNull(uuid); 115 this.exoMediaDrmProvider = Assertions.checkNotNull(exoMediaDrmProvider); 116 return this; 117 } 118 119 /** 120 * Sets whether this session manager is allowed to acquire multiple simultaneous sessions. 121 * 122 * <p>Users should pass false when a single key request will obtain all keys required to decrypt 123 * the associated content. {@code multiSession} is required when content uses key rotation. 124 * 125 * @param multiSession Whether this session manager is allowed to acquire multiple simultaneous 126 * sessions. 127 * @return This builder. 128 */ setMultiSession(boolean multiSession)129 public Builder setMultiSession(boolean multiSession) { 130 this.multiSession = multiSession; 131 return this; 132 } 133 134 /** 135 * Sets whether this session manager should attach {@link DrmSession DrmSessions} to the clear 136 * sections of the media content. 137 * 138 * <p>Using {@link DrmSession DrmSessions} for clear content avoids the recreation of decoders 139 * when transitioning between clear and encrypted sections of content. 140 * 141 * @param useDrmSessionsForClearContentTrackTypes The track types ({@link C#TRACK_TYPE_AUDIO} 142 * and/or {@link C#TRACK_TYPE_VIDEO}) for which to use a {@link DrmSession} regardless of 143 * whether the content is clear or encrypted. 144 * @return This builder. 145 * @throws IllegalArgumentException If {@code useDrmSessionsForClearContentTrackTypes} contains 146 * track types other than {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_VIDEO}. 147 */ setUseDrmSessionsForClearContent( int... useDrmSessionsForClearContentTrackTypes)148 public Builder setUseDrmSessionsForClearContent( 149 int... useDrmSessionsForClearContentTrackTypes) { 150 for (int trackType : useDrmSessionsForClearContentTrackTypes) { 151 Assertions.checkArgument( 152 trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_AUDIO); 153 } 154 this.useDrmSessionsForClearContentTrackTypes = 155 useDrmSessionsForClearContentTrackTypes.clone(); 156 return this; 157 } 158 159 /** 160 * Sets whether clear samples within protected content should be played when keys for the 161 * encrypted part of the content have yet to be loaded. 162 * 163 * @param playClearSamplesWithoutKeys Whether clear samples within protected content should be 164 * played when keys for the encrypted part of the content have yet to be loaded. 165 * @return This builder. 166 */ setPlayClearSamplesWithoutKeys(boolean playClearSamplesWithoutKeys)167 public Builder setPlayClearSamplesWithoutKeys(boolean playClearSamplesWithoutKeys) { 168 this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; 169 return this; 170 } 171 172 /** 173 * Sets the {@link LoadErrorHandlingPolicy} for key and provisioning requests. 174 * 175 * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. 176 * @return This builder. 177 */ setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy)178 public Builder setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) { 179 this.loadErrorHandlingPolicy = Assertions.checkNotNull(loadErrorHandlingPolicy); 180 return this; 181 } 182 183 /** Builds a {@link DefaultDrmSessionManager} instance. */ build(MediaDrmCallback mediaDrmCallback)184 public DefaultDrmSessionManager build(MediaDrmCallback mediaDrmCallback) { 185 return new DefaultDrmSessionManager( 186 uuid, 187 exoMediaDrmProvider, 188 mediaDrmCallback, 189 keyRequestParameters, 190 multiSession, 191 useDrmSessionsForClearContentTrackTypes, 192 playClearSamplesWithoutKeys, 193 loadErrorHandlingPolicy); 194 } 195 } 196 197 /** 198 * Signals that the {@link DrmInitData} passed to {@link #acquireSession} does not contain does 199 * not contain scheme data for the required UUID. 200 */ 201 public static final class MissingSchemeDataException extends Exception { 202 MissingSchemeDataException(UUID uuid)203 private MissingSchemeDataException(UUID uuid) { 204 super("Media does not support uuid: " + uuid); 205 } 206 } 207 208 /** 209 * A key for specifying PlayReady custom data in the key request parameters passed to {@link 210 * Builder#setKeyRequestParameters(Map)}. 211 */ 212 public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData"; 213 214 /** 215 * Determines the action to be done after a session acquired. One of {@link #MODE_PLAYBACK}, 216 * {@link #MODE_QUERY}, {@link #MODE_DOWNLOAD} or {@link #MODE_RELEASE}. 217 */ 218 @Documented 219 @Retention(RetentionPolicy.SOURCE) 220 @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE}) 221 public @interface Mode {} 222 /** 223 * Loads and refreshes (if necessary) a license for playback. Supports streaming and offline 224 * licenses. 225 */ 226 public static final int MODE_PLAYBACK = 0; 227 /** Restores an offline license to allow its status to be queried. */ 228 public static final int MODE_QUERY = 1; 229 /** Downloads an offline license or renews an existing one. */ 230 public static final int MODE_DOWNLOAD = 2; 231 /** Releases an existing offline license. */ 232 public static final int MODE_RELEASE = 3; 233 /** Number of times to retry for initial provisioning and key request for reporting error. */ 234 public static final int INITIAL_DRM_REQUEST_RETRY_COUNT = 3; 235 236 private static final String TAG = "DefaultDrmSessionMgr"; 237 238 private final UUID uuid; 239 private final ExoMediaDrm.Provider exoMediaDrmProvider; 240 private final MediaDrmCallback callback; 241 private final HashMap<String, String> keyRequestParameters; 242 private final boolean multiSession; 243 private final int[] useDrmSessionsForClearContentTrackTypes; 244 private final boolean playClearSamplesWithoutKeys; 245 private final ProvisioningManagerImpl provisioningManagerImpl; 246 private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; 247 248 private final List<DefaultDrmSession> sessions; 249 private final List<DefaultDrmSession> provisioningSessions; 250 251 private int prepareCallsCount; 252 @Nullable private ExoMediaDrm exoMediaDrm; 253 @Nullable private DefaultDrmSession placeholderDrmSession; 254 @Nullable private DefaultDrmSession noMultiSessionDrmSession; 255 @Nullable private Looper playbackLooper; 256 private int mode; 257 @Nullable private byte[] offlineLicenseKeySetId; 258 259 /* package */ volatile @Nullable MediaDrmHandler mediaDrmHandler; 260 261 /** 262 * @param uuid The UUID of the drm scheme. 263 * @param exoMediaDrm An underlying {@link ExoMediaDrm} for use by the manager. 264 * @param callback Performs key and provisioning requests. 265 * @param keyRequestParameters An optional map of parameters to pass as the last argument to 266 * {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null. 267 * @deprecated Use {@link Builder} instead. 268 */ 269 @SuppressWarnings("deprecation") 270 @Deprecated DefaultDrmSessionManager( UUID uuid, ExoMediaDrm exoMediaDrm, MediaDrmCallback callback, @Nullable HashMap<String, String> keyRequestParameters)271 public DefaultDrmSessionManager( 272 UUID uuid, 273 ExoMediaDrm exoMediaDrm, 274 MediaDrmCallback callback, 275 @Nullable HashMap<String, String> keyRequestParameters) { 276 this( 277 uuid, 278 exoMediaDrm, 279 callback, 280 keyRequestParameters == null ? new HashMap<>() : keyRequestParameters, 281 /* multiSession= */ false, 282 INITIAL_DRM_REQUEST_RETRY_COUNT); 283 } 284 285 /** 286 * @param uuid The UUID of the drm scheme. 287 * @param exoMediaDrm An underlying {@link ExoMediaDrm} for use by the manager. 288 * @param callback Performs key and provisioning requests. 289 * @param keyRequestParameters An optional map of parameters to pass as the last argument to 290 * {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null. 291 * @param multiSession A boolean that specify whether multiple key session support is enabled. 292 * Default is false. 293 * @deprecated Use {@link Builder} instead. 294 */ 295 @Deprecated DefaultDrmSessionManager( UUID uuid, ExoMediaDrm exoMediaDrm, MediaDrmCallback callback, @Nullable HashMap<String, String> keyRequestParameters, boolean multiSession)296 public DefaultDrmSessionManager( 297 UUID uuid, 298 ExoMediaDrm exoMediaDrm, 299 MediaDrmCallback callback, 300 @Nullable HashMap<String, String> keyRequestParameters, 301 boolean multiSession) { 302 this( 303 uuid, 304 exoMediaDrm, 305 callback, 306 keyRequestParameters == null ? new HashMap<>() : keyRequestParameters, 307 multiSession, 308 INITIAL_DRM_REQUEST_RETRY_COUNT); 309 } 310 311 /** 312 * @param uuid The UUID of the drm scheme. 313 * @param exoMediaDrm An underlying {@link ExoMediaDrm} for use by the manager. 314 * @param callback Performs key and provisioning requests. 315 * @param keyRequestParameters An optional map of parameters to pass as the last argument to 316 * {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null. 317 * @param multiSession A boolean that specify whether multiple key session support is enabled. 318 * Default is false. 319 * @param initialDrmRequestRetryCount The number of times to retry for initial provisioning and 320 * key request before reporting error. 321 * @deprecated Use {@link Builder} instead. 322 */ 323 @Deprecated DefaultDrmSessionManager( UUID uuid, ExoMediaDrm exoMediaDrm, MediaDrmCallback callback, @Nullable HashMap<String, String> keyRequestParameters, boolean multiSession, int initialDrmRequestRetryCount)324 public DefaultDrmSessionManager( 325 UUID uuid, 326 ExoMediaDrm exoMediaDrm, 327 MediaDrmCallback callback, 328 @Nullable HashMap<String, String> keyRequestParameters, 329 boolean multiSession, 330 int initialDrmRequestRetryCount) { 331 this( 332 uuid, 333 new ExoMediaDrm.AppManagedProvider(exoMediaDrm), 334 callback, 335 keyRequestParameters == null ? new HashMap<>() : keyRequestParameters, 336 multiSession, 337 /* useDrmSessionsForClearContentTrackTypes= */ new int[0], 338 /* playClearSamplesWithoutKeys= */ false, 339 new DefaultLoadErrorHandlingPolicy(initialDrmRequestRetryCount)); 340 } 341 DefaultDrmSessionManager( UUID uuid, ExoMediaDrm.Provider exoMediaDrmProvider, MediaDrmCallback callback, HashMap<String, String> keyRequestParameters, boolean multiSession, int[] useDrmSessionsForClearContentTrackTypes, boolean playClearSamplesWithoutKeys, LoadErrorHandlingPolicy loadErrorHandlingPolicy)342 private DefaultDrmSessionManager( 343 UUID uuid, 344 ExoMediaDrm.Provider exoMediaDrmProvider, 345 MediaDrmCallback callback, 346 HashMap<String, String> keyRequestParameters, 347 boolean multiSession, 348 int[] useDrmSessionsForClearContentTrackTypes, 349 boolean playClearSamplesWithoutKeys, 350 LoadErrorHandlingPolicy loadErrorHandlingPolicy) { 351 Assertions.checkNotNull(uuid); 352 Assertions.checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead"); 353 this.uuid = uuid; 354 this.exoMediaDrmProvider = exoMediaDrmProvider; 355 this.callback = callback; 356 this.keyRequestParameters = keyRequestParameters; 357 this.multiSession = multiSession; 358 this.useDrmSessionsForClearContentTrackTypes = useDrmSessionsForClearContentTrackTypes; 359 this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; 360 this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; 361 provisioningManagerImpl = new ProvisioningManagerImpl(); 362 mode = MODE_PLAYBACK; 363 sessions = new ArrayList<>(); 364 provisioningSessions = new ArrayList<>(); 365 } 366 367 /** 368 * Sets the mode, which determines the role of sessions acquired from the instance. This must be 369 * called before {@link #acquireSession(Looper, MediaSourceEventDispatcher, DrmInitData)} or 370 * {@link #acquirePlaceholderSession} is called. 371 * 372 * <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when 373 * required. 374 * 375 * <p>{@code mode} must be one of these: 376 * 377 * <ul> 378 * <li>{@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is 379 * requested otherwise the offline license is restored. 380 * <li>{@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license 381 * is restored. 382 * <li>{@link #MODE_DOWNLOAD}: If {@code offlineLicenseKeySetId} is null, an offline license is 383 * requested otherwise the offline license is renewed. 384 * <li>{@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline 385 * license is released. 386 * </ul> 387 * 388 * @param mode The mode to be set. 389 * @param offlineLicenseKeySetId The key set id of the license to be used with the given mode. 390 */ setMode(@ode int mode, @Nullable byte[] offlineLicenseKeySetId)391 public void setMode(@Mode int mode, @Nullable byte[] offlineLicenseKeySetId) { 392 Assertions.checkState(sessions.isEmpty()); 393 if (mode == MODE_QUERY || mode == MODE_RELEASE) { 394 Assertions.checkNotNull(offlineLicenseKeySetId); 395 } 396 this.mode = mode; 397 this.offlineLicenseKeySetId = offlineLicenseKeySetId; 398 } 399 400 // DrmSessionManager implementation. 401 402 @Override prepare()403 public final void prepare() { 404 if (prepareCallsCount++ == 0) { 405 Assertions.checkState(exoMediaDrm == null); 406 exoMediaDrm = exoMediaDrmProvider.acquireExoMediaDrm(uuid); 407 exoMediaDrm.setOnEventListener(new MediaDrmEventListener()); 408 } 409 } 410 411 @Override release()412 public final void release() { 413 if (--prepareCallsCount == 0) { 414 Assertions.checkNotNull(exoMediaDrm).release(); 415 exoMediaDrm = null; 416 } 417 } 418 419 @Override canAcquireSession(DrmInitData drmInitData)420 public boolean canAcquireSession(DrmInitData drmInitData) { 421 if (offlineLicenseKeySetId != null) { 422 // An offline license can be restored so a session can always be acquired. 423 return true; 424 } 425 List<SchemeData> schemeDatas = getSchemeDatas(drmInitData, uuid, true); 426 if (schemeDatas.isEmpty()) { 427 if (drmInitData.schemeDataCount == 1 && drmInitData.get(0).matches(C.COMMON_PSSH_UUID)) { 428 // Assume scheme specific data will be added before the session is opened. 429 Log.w( 430 TAG, "DrmInitData only contains common PSSH SchemeData. Assuming support for: " + uuid); 431 } else { 432 // No data for this manager's scheme. 433 return false; 434 } 435 } 436 String schemeType = drmInitData.schemeType; 437 if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) { 438 // If there is no scheme information, assume patternless AES-CTR. 439 return true; 440 } else if (C.CENC_TYPE_cbc1.equals(schemeType) 441 || C.CENC_TYPE_cbcs.equals(schemeType) 442 || C.CENC_TYPE_cens.equals(schemeType)) { 443 // API support for AES-CBC and pattern encryption was added in API 24. However, the 444 // implementation was not stable until API 25. 445 return Util.SDK_INT >= 25; 446 } 447 // Unknown schemes, assume one of them is supported. 448 return true; 449 } 450 451 @Override 452 @Nullable acquirePlaceholderSession(Looper playbackLooper, int trackType)453 public DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) { 454 assertExpectedPlaybackLooper(playbackLooper); 455 ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm); 456 boolean avoidPlaceholderDrmSessions = 457 FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType()) 458 && FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC; 459 // Avoid attaching a session to sparse formats. 460 if (avoidPlaceholderDrmSessions 461 || Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET 462 || exoMediaDrm.getExoMediaCryptoType() == null) { 463 return null; 464 } 465 maybeCreateMediaDrmHandler(playbackLooper); 466 if (placeholderDrmSession == null) { 467 DefaultDrmSession placeholderDrmSession = 468 createNewDefaultSession( 469 /* schemeDatas= */ Collections.emptyList(), /* isPlaceholderSession= */ true); 470 sessions.add(placeholderDrmSession); 471 this.placeholderDrmSession = placeholderDrmSession; 472 } 473 placeholderDrmSession.acquire(/* eventDispatcher= */ null); 474 return placeholderDrmSession; 475 } 476 477 @Override acquireSession( Looper playbackLooper, @Nullable MediaSourceEventDispatcher eventDispatcher, DrmInitData drmInitData)478 public DrmSession acquireSession( 479 Looper playbackLooper, 480 @Nullable MediaSourceEventDispatcher eventDispatcher, 481 DrmInitData drmInitData) { 482 assertExpectedPlaybackLooper(playbackLooper); 483 maybeCreateMediaDrmHandler(playbackLooper); 484 485 @Nullable List<SchemeData> schemeDatas = null; 486 if (offlineLicenseKeySetId == null) { 487 schemeDatas = getSchemeDatas(drmInitData, uuid, false); 488 if (schemeDatas.isEmpty()) { 489 final MissingSchemeDataException error = new MissingSchemeDataException(uuid); 490 if (eventDispatcher != null) { 491 eventDispatcher.dispatch( 492 (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionManagerError(error), 493 DrmSessionEventListener.class); 494 } 495 return new ErrorStateDrmSession(new DrmSessionException(error)); 496 } 497 } 498 499 @Nullable DefaultDrmSession session; 500 if (!multiSession) { 501 session = noMultiSessionDrmSession; 502 } else { 503 // Only use an existing session if it has matching init data. 504 session = null; 505 for (DefaultDrmSession existingSession : sessions) { 506 if (Util.areEqual(existingSession.schemeDatas, schemeDatas)) { 507 session = existingSession; 508 break; 509 } 510 } 511 } 512 513 if (session == null) { 514 // Create a new session. 515 session = createNewDefaultSession(schemeDatas, /* isPlaceholderSession= */ false); 516 if (!multiSession) { 517 noMultiSessionDrmSession = session; 518 } 519 sessions.add(session); 520 } 521 session.acquire(eventDispatcher); 522 return session; 523 } 524 525 @Override 526 @Nullable getExoMediaCryptoType(DrmInitData drmInitData)527 public Class<? extends ExoMediaCrypto> getExoMediaCryptoType(DrmInitData drmInitData) { 528 return canAcquireSession(drmInitData) 529 ? Assertions.checkNotNull(exoMediaDrm).getExoMediaCryptoType() 530 : null; 531 } 532 533 // Internal methods. 534 assertExpectedPlaybackLooper(Looper playbackLooper)535 private void assertExpectedPlaybackLooper(Looper playbackLooper) { 536 Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper); 537 this.playbackLooper = playbackLooper; 538 } 539 maybeCreateMediaDrmHandler(Looper playbackLooper)540 private void maybeCreateMediaDrmHandler(Looper playbackLooper) { 541 if (mediaDrmHandler == null) { 542 mediaDrmHandler = new MediaDrmHandler(playbackLooper); 543 } 544 } 545 createNewDefaultSession( @ullable List<SchemeData> schemeDatas, boolean isPlaceholderSession)546 private DefaultDrmSession createNewDefaultSession( 547 @Nullable List<SchemeData> schemeDatas, boolean isPlaceholderSession) { 548 Assertions.checkNotNull(exoMediaDrm); 549 // Placeholder sessions should always play clear samples without keys. 550 boolean playClearSamplesWithoutKeys = this.playClearSamplesWithoutKeys | isPlaceholderSession; 551 return new DefaultDrmSession( 552 uuid, 553 exoMediaDrm, 554 /* provisioningManager= */ provisioningManagerImpl, 555 /* releaseCallback= */ this::onSessionReleased, 556 schemeDatas, 557 mode, 558 playClearSamplesWithoutKeys, 559 isPlaceholderSession, 560 offlineLicenseKeySetId, 561 keyRequestParameters, 562 callback, 563 Assertions.checkNotNull(playbackLooper), 564 loadErrorHandlingPolicy); 565 } 566 onSessionReleased(DefaultDrmSession drmSession)567 private void onSessionReleased(DefaultDrmSession drmSession) { 568 sessions.remove(drmSession); 569 if (placeholderDrmSession == drmSession) { 570 placeholderDrmSession = null; 571 } 572 if (noMultiSessionDrmSession == drmSession) { 573 noMultiSessionDrmSession = null; 574 } 575 if (provisioningSessions.size() > 1 && provisioningSessions.get(0) == drmSession) { 576 // Other sessions were waiting for the released session to complete a provision operation. 577 // We need to have one of those sessions perform the provision operation instead. 578 provisioningSessions.get(1).provision(); 579 } 580 provisioningSessions.remove(drmSession); 581 } 582 583 /** 584 * Extracts {@link SchemeData} instances suitable for the given DRM scheme {@link UUID}. 585 * 586 * @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}. 587 * @param uuid The UUID. 588 * @param allowMissingData Whether a {@link SchemeData} with null {@link SchemeData#data} may be 589 * returned. 590 * @return The extracted {@link SchemeData} instances, or an empty list if no suitable data is 591 * present. 592 */ getSchemeDatas( DrmInitData drmInitData, UUID uuid, boolean allowMissingData)593 private static List<SchemeData> getSchemeDatas( 594 DrmInitData drmInitData, UUID uuid, boolean allowMissingData) { 595 // Look for matching scheme data (matching the Common PSSH box for ClearKey). 596 List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount); 597 for (int i = 0; i < drmInitData.schemeDataCount; i++) { 598 SchemeData schemeData = drmInitData.get(i); 599 boolean uuidMatches = 600 schemeData.matches(uuid) 601 || (C.CLEARKEY_UUID.equals(uuid) && schemeData.matches(C.COMMON_PSSH_UUID)); 602 if (uuidMatches && (schemeData.data != null || allowMissingData)) { 603 matchingSchemeDatas.add(schemeData); 604 } 605 } 606 return matchingSchemeDatas; 607 } 608 609 @SuppressLint("HandlerLeak") 610 private class MediaDrmHandler extends Handler { 611 MediaDrmHandler(Looper looper)612 public MediaDrmHandler(Looper looper) { 613 super(looper); 614 } 615 616 @Override handleMessage(Message msg)617 public void handleMessage(Message msg) { 618 byte[] sessionId = (byte[]) msg.obj; 619 if (sessionId == null) { 620 // The event is not associated with any particular session. 621 return; 622 } 623 for (DefaultDrmSession session : sessions) { 624 if (session.hasSessionId(sessionId)) { 625 session.onMediaDrmEvent(msg.what); 626 return; 627 } 628 } 629 } 630 } 631 632 private class ProvisioningManagerImpl implements DefaultDrmSession.ProvisioningManager { 633 @Override provisionRequired(DefaultDrmSession session)634 public void provisionRequired(DefaultDrmSession session) { 635 if (provisioningSessions.contains(session)) { 636 // The session has already requested provisioning. 637 return; 638 } 639 provisioningSessions.add(session); 640 if (provisioningSessions.size() == 1) { 641 // This is the first session requesting provisioning, so have it perform the operation. 642 session.provision(); 643 } 644 } 645 646 @Override onProvisionCompleted()647 public void onProvisionCompleted() { 648 for (DefaultDrmSession session : provisioningSessions) { 649 session.onProvisionCompleted(); 650 } 651 provisioningSessions.clear(); 652 } 653 654 @Override onProvisionError(Exception error)655 public void onProvisionError(Exception error) { 656 for (DefaultDrmSession session : provisioningSessions) { 657 session.onProvisionError(error); 658 } 659 provisioningSessions.clear(); 660 } 661 } 662 663 private class MediaDrmEventListener implements OnEventListener { 664 665 @Override onEvent( ExoMediaDrm md, @Nullable byte[] sessionId, int event, int extra, @Nullable byte[] data)666 public void onEvent( 667 ExoMediaDrm md, @Nullable byte[] sessionId, int event, int extra, @Nullable byte[] data) { 668 Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget(); 669 } 670 } 671 } 672