diff --git a/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java b/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java index 32a5d234f73..44bf08aa570 100644 --- a/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java +++ b/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java @@ -370,6 +370,18 @@ public int hashCode() { return hashCode; } + // MIREGO: added toString + @Override + public String toString() { + return "SchemeData{" + + "hashCode=" + hashCode + + ", uuid=" + uuid + + ", licenseServerUrl='" + licenseServerUrl + '\'' + + ", mimeType='" + mimeType + '\'' + + ", data=" + Arrays.toString(data) + + '}'; + } + // Parcelable implementation. @Override diff --git a/libraries/common/src/main/java/androidx/media3/common/PlaybackException.java b/libraries/common/src/main/java/androidx/media3/common/PlaybackException.java index 2cbe9ed94fb..e37dca25808 100644 --- a/libraries/common/src/main/java/androidx/media3/common/PlaybackException.java +++ b/libraries/common/src/main/java/androidx/media3/common/PlaybackException.java @@ -310,6 +310,9 @@ public class PlaybackException extends Exception { /** MIREGO: Caused by the video codec being stalled (issue under investigation) */ public static final int ERROR_CODE_VIDEO_CODEC_STALLED = 5906; + /** MIREGO: Caused by a Drm offline key hashcode not found in the hash codes array */ + public static final int ERROR_CODE_OFFLINE_DRM_HASH_CODE_NOT_FOUND = 5907; + // DRM errors (6xxx). /** Caused by an unspecified error related to DRM protection. */ @@ -483,6 +486,8 @@ public static String getErrorCodeName(@ErrorCode int errorCode) { return "ERROR_CODE_NTP"; case ERROR_CODE_VIDEO_CODEC_STALLED: return "ERROR_CODE_VIDEO_CODEC_STALLED"; + case ERROR_CODE_OFFLINE_DRM_HASH_CODE_NOT_FOUND: + return "ERROR_CODE_OFFLINE_DRM_HASH_CODE_NOT_FOUND"; default: if (errorCode >= CUSTOM_ERROR_CODE_BASE) { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java index 5f06b76f54c..a371886a04d 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java @@ -123,6 +123,9 @@ public interface ReferenceCountListener { /** The DRM scheme datas, or null if this session uses offline keys. */ @Nullable public final List schemeDatas; + // MIREGO: added to be able to reuse offline sessions with compatible data without messing with the field schemeDatas, that is used to determine if it's an offline key when getting the key request + @Nullable public final List schemeDatasEvenOffline; + private final ExoMediaDrm mediaDrm; private final ProvisioningManager provisioningManager; private final ReferenceCountListener referenceCountListener; @@ -202,6 +205,9 @@ public DefaultDrmSession( } else { this.schemeDatas = Collections.unmodifiableList(Assertions.checkNotNull(schemeDatas)); } + // MIREGO: added. + this.schemeDatasEvenOffline = Collections.unmodifiableList(Assertions.checkNotNull(schemeDatas)); + this.keyRequestParameters = keyRequestParameters; this.callback = callback; this.eventDispatchers = new CopyOnWriteMultiset<>(); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java index 12199a88b42..42cf497b877 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java @@ -52,6 +52,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -324,7 +326,11 @@ private MissingSchemeDataException(UUID uuid) { private @MonotonicNonNull Looper playbackLooper; private @MonotonicNonNull Handler playbackHandler; private int mode; - @Nullable private byte[] offlineLicenseKeySetId; + + // MIREGO: multiple offline DRM keys. + private List offlineLicenseKeySetIdList; + private List drmInitDataHashList; + private @MonotonicNonNull PlayerId playerId; /* package */ @Nullable volatile MediaDrmHandler mediaDrmHandler; @@ -388,7 +394,21 @@ public void setMode(@Mode int mode, @Nullable byte[] offlineLicenseKeySetId) { checkNotNull(offlineLicenseKeySetId); } this.mode = mode; - this.offlineLicenseKeySetId = offlineLicenseKeySetId; + // MIREGO: multiple offline DRM keys. + this.offlineLicenseKeySetIdList = (offlineLicenseKeySetId != null) ? ImmutableList.of(offlineLicenseKeySetId) : Collections.emptyList(); + this.drmInitDataHashList = Collections.emptyList(); + } + + // MIREGO: multiple offline DRM keys. Added function + public void setMode(@Mode int mode, List offlineLicenseKeySetIdList, List drmInitDataHashList) { + Log.d(TAG, "setMode %d offlineLicenseKeySetId size=%s", mode, offlineLicenseKeySetIdList.size()); + checkState(sessions.isEmpty()); + if (mode == MODE_QUERY || mode == MODE_RELEASE) { + checkArgument(!offlineLicenseKeySetIdList.isEmpty()); + } + this.mode = mode; + this.offlineLicenseKeySetIdList = offlineLicenseKeySetIdList; + this.drmInitDataHashList = drmInitDataHashList; } // DrmSessionManager implementation. @@ -479,7 +499,7 @@ private DrmSession acquireSession( } @Nullable List schemeDatas = null; - if (offlineLicenseKeySetId == null) { + if (offlineLicenseKeySetIdList.isEmpty()) { // MIREGO: multiple offline DRM keys schemeDatas = getSchemeDatas(checkNotNull(format.drmInitData), uuid, false); if (schemeDatas.isEmpty()) { final MissingSchemeDataException error = new MissingSchemeDataException(uuid); @@ -490,6 +510,10 @@ private DrmSession acquireSession( return new ErrorStateDrmSession( new DrmSessionException(error, PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR)); } + } else if (offlineLicenseKeySetIdList.size() > 1) { // MIREGO: multiple offline DRM keys. + // we have to select the right session or create a new one with the right drmInitData/key + // if we only have 1 key set, just fallback to legacy behavior (use a null schemeData so we always use the same session) + schemeDatas = getSchemeDatas(checkNotNull(format.drmInitData), uuid, false); } @Nullable DefaultDrmSession session; @@ -499,7 +523,7 @@ private DrmSession acquireSession( // Only use an existing session if it has matching init data. session = null; for (DefaultDrmSession existingSession : sessions) { - if (Util.areEqual(existingSession.schemeDatas, schemeDatas)) { + if (Util.areEqual(existingSession.schemeDatasEvenOffline, schemeDatas)) { // MIREGO: multiple offline DRM keys. session = existingSession; break; } @@ -511,6 +535,7 @@ private DrmSession acquireSession( session = createAndAcquireSessionWithRetry( schemeDatas, + format.drmInitData.hashCode(), // MIREGO: multiple offline DRM keys /* isPlaceholderSession= */ false, eventDispatcher, shouldReleasePreacquiredSessionsBeforeRetrying); @@ -558,6 +583,7 @@ private DrmSession maybeAcquirePlaceholderSession( DefaultDrmSession placeholderDrmSession = createAndAcquireSessionWithRetry( /* schemeDatas= */ ImmutableList.of(), + 0, // MIREGO: multiple offline DRM keys /* isPlaceholderSession= */ true, /* eventDispatcher= */ null, shouldReleasePreacquiredSessionsBeforeRetrying); @@ -570,7 +596,7 @@ private DrmSession maybeAcquirePlaceholderSession( } private boolean canAcquireSession(DrmInitData drmInitData) { - if (offlineLicenseKeySetId != null) { + if (!offlineLicenseKeySetIdList.isEmpty()) { // MIREGO: multiple offline DRM keys // An offline license can be restored so a session can always be acquired. return true; } @@ -623,17 +649,18 @@ private void maybeCreateMediaDrmHandler(Looper playbackLooper) { private DefaultDrmSession createAndAcquireSessionWithRetry( @Nullable List schemeDatas, + int drmInitDataHash, // MIREGO: multiple offline DRM keys boolean isPlaceholderSession, @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, boolean shouldReleasePreacquiredSessionsBeforeRetrying) { DefaultDrmSession session = - createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); + createAndAcquireSession(schemeDatas, drmInitDataHash, isPlaceholderSession, eventDispatcher); // MIREGO: multiple offline DRM keys // If we're short on DRM session resources, first try eagerly releasing all our keepalive // sessions and then retry the acquisition. if (acquisitionFailedIndicatingResourceShortage(session) && !keepaliveSessions.isEmpty()) { releaseAllKeepaliveSessions(); undoAcquisition(session, eventDispatcher); - session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); + session = createAndAcquireSession(schemeDatas, drmInitDataHash, isPlaceholderSession, eventDispatcher); // MIREGO: multiple offline DRM keys } // If the acquisition failed again due to continued resource shortage, and @@ -649,7 +676,7 @@ private DefaultDrmSession createAndAcquireSessionWithRetry( releaseAllKeepaliveSessions(); } undoAcquisition(session, eventDispatcher); - session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); + session = createAndAcquireSession(schemeDatas, drmInitDataHash, isPlaceholderSession, eventDispatcher); // MIREGO: multiple offline DRM keys } return session; } @@ -703,11 +730,29 @@ private void releaseAllPreacquiredSessions() { */ private DefaultDrmSession createAndAcquireSession( @Nullable List schemeDatas, + int drmInitDataHash, // MIREGO: multiple offline DRM keys boolean isPlaceholderSession, @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) { checkNotNull(exoMediaDrm); // Placeholder sessions should always play clear samples without keys. boolean playClearSamplesWithoutKeys = this.playClearSamplesWithoutKeys | isPlaceholderSession; + + // MIREGO: multiple offline DRM keys. Added block to get the right keySetId + byte[] offlineLicenseKeySetId = null; + if (offlineLicenseKeySetIdList.size() == 1) { // Keep legacy behavior with single offline key (just use it) + offlineLicenseKeySetId = offlineLicenseKeySetIdList.get(0); + } else if (!offlineLicenseKeySetIdList.isEmpty()){ //multiple offline DRM keys. Find the right keyId from the drmInitData hash + int index = drmInitDataHashList.indexOf(drmInitDataHash); + if (index >= 0) { + offlineLicenseKeySetId = offlineLicenseKeySetIdList.get(index); + } else { // oops, we haven't found the drmInitData hash. We might as well fallback to the first key. + Log.e(TAG, + new PlaybackException("Offline Drm hash code not found. Trying first available key", new RuntimeException(), + PlaybackException.ERROR_CODE_OFFLINE_DRM_HASH_CODE_NOT_FOUND)); + offlineLicenseKeySetId = offlineLicenseKeySetIdList.get(0); + } + } + DefaultDrmSession session = new DefaultDrmSession( uuid,