Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offline DASH Widevine issue with ContentProtection #512

Closed
1 task
AgataZiolkowska opened this issue Jul 10, 2023 · 2 comments
Closed
1 task

Offline DASH Widevine issue with ContentProtection #512

AgataZiolkowska opened this issue Jul 10, 2023 · 2 comments
Assignees
Labels

Comments

@AgataZiolkowska
Copy link

AgataZiolkowska commented Jul 10, 2023

Version

ExoPlayer 2.18.3

More version details

No response

Devices that reproduce the issue

Reproducible on all devices.

Devices that do not reproduce the issue

--

Reproducible in the demo app?

Yes

Reproduction steps

We have identified a problem with the download-to-go feature for DASH content protected with Widevine DRM but without encryption headers. The stream can be downloaded successfully after obtaining the manifest. However, when we try to retrieve the key set ID for the downloaded license, an exception is thrown.

The ContentProtection segment in the manifest appears as follows:

<!-- Widevine -->
<ContentProtection schemeIdUri="urn:uuid:EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED">
</ContentProtection>

The demo app is also encountering the same error. However, the same content can be played online without any issues.
Is there an issue within Exoplayer? Or it's something which should be done on the provider level?

The license is retrieved using OfflineLicenseHelper.

Expected result

License and offline content can be obtained.

Actual result

com.google.android.exoplayer2.drm.DefaultDrmSessionManager$MissingSchemeDataException: Media does not support uuid: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.acquireSession(DefaultDrmSessionManager.java:554)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.acquireSession(DefaultDrmSessionManager.java:527)
at com.google.android.exoplayer2.drm. Offline LicenseHelper.openBlockingKeyRequest(OfflineLicenseHelper.java:283)
at com.google.android.exoplayer2.drm.OfflineLicenseHelper.blockingKeyRequest(OfflineLicenseHelper.java:267)
at com.google.android.exoplayer2.drm.Offline LicenseHelper.downloadLicense (OfflineLicenseHelper.java:192)

Media

The manifest and licenseURL have been sent in the email for further investigation, but the content has geo blockage.

Bug Report

  • You will email the zip file produced by adb bugreport to [email protected] after filing this issue.
@tonihei
Copy link
Collaborator

tonihei commented Jul 14, 2023

@icbaker Have you seen this before? it looks the Format object obtained from the downloaded manifest/segments does not contain the right schema information and that's why we throw this exception.

@icbaker
Copy link
Collaborator

icbaker commented Jul 19, 2023

I tried reproducing with the provided media and license URLs, with a VPN set up through the suggested location, but I received an HTTP 400 error from the license server when trying streaming playback.

However I was able to generate the stack trace in the original comment when trying to download the content.

The problem is that Format.drmInitData contains the following 3 SchemeData objects (I've avoided logging the data in case it's sensitive, but what matters for this is whether it's 'present' or not (null)):

schemeData: { uuid=9a04f079-9840-4286-ab92-e65be0885f95, scheme=playready, licenseServerUrl=null, mimeType=video/mp4, data present=false }
schemeData: { uuid=edef8ba9-79d6-4ace-a3c8-27dcd51d21ed, scheme=widevine, licenseServerUrl=null, mimeType=video/mp4, data present=false } 
schemeData: { uuid=1077efec-c0b2-4d02-ace3-3c1e52e2fb4b, scheme=cenc, licenseServerUrl=null, mimeType=video/mp4, data present=true }      

You can see that the Widevine and PlayReady entries have data == null while the CENC one has data != null.

In the demo app we have a check to ensure at least one of the SchemeData objects has a non-null data field:

// TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest.
if (!hasSchemaData(format.drmInitData)) {
Toast.makeText(context, R.string.download_start_error_offline_license, Toast.LENGTH_LONG)
.show();
Log.e(
TAG,
"Downloading content where DRM scheme data is not located in the manifest is not"
+ " supported");
return;
}

/**
* Returns whether any the {@link DrmInitData.SchemeData} contained in {@code drmInitData} has
* non-null {@link DrmInitData.SchemeData#data}.
*/
private boolean hasSchemaData(DrmInitData drmInitData) {
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
if (drmInitData.get(i).hasData()) {
return true;
}
}
return false;
}

This check passes because it finds the CENC one, so no error is logged at this point and control flow continues.

Unfortunately later in OfflineLicenseHelper we are only looking at the Widevine one, and so we fail loudly because data == null in the Widevine SchemeData:

schemeDatas = getSchemeDatas(checkNotNull(format.drmInitData), uuid, false);
if (schemeDatas.isEmpty()) {
final MissingSchemeDataException error = new MissingSchemeDataException(uuid);
Log.e(TAG, "DRM error", error);
if (eventDispatcher != null) {
eventDispatcher.drmSessionManagerError(error);
}

/**
* Extracts {@link SchemeData} instances suitable for the given DRM scheme {@link UUID}.
*
* @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}.
* @param uuid The UUID.
* @param allowMissingData Whether a {@link SchemeData} with null {@link SchemeData#data} may be
* returned.
* @return The extracted {@link SchemeData} instances, or an empty list if no suitable data is
* present.
*/
private static List<SchemeData> getSchemeDatas(
DrmInitData drmInitData, UUID uuid, boolean allowMissingData) {
// Look for matching scheme data (matching the Common PSSH box for ClearKey).
List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount);
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
SchemeData schemeData = drmInitData.get(i);
boolean uuidMatches =
schemeData.matches(uuid)
|| (C.CLEARKEY_UUID.equals(uuid) && schemeData.matches(C.COMMON_PSSH_UUID));
if (uuidMatches && (schemeData.data != null || allowMissingData)) {
matchingSchemeDatas.add(schemeData);
}
}
return matchingSchemeDatas;
}


As a quick hack I tried copying the data from the CENC SchemeData to the Widevine one, but although it (of course) got past the MissingSchemeDataException check - it just exploded later because the Widevine CDM didn't accept the CENC data:

Failed to fetch offline DRM license
  androidx.media3.exoplayer.drm.DrmSession$DrmSessionException: android.media.MediaDrm$MediaDrmStateException: Failed to get key request: ERROR_DRM_INIT_DATA
  ============================== Beginning of DRM Plugin Log ==============================
    07-19 14:41:24.422 I [cdm_engine.cpp(994):IsSecurityLevelSupported] level = L1
    07-19 14:41:24.425 I [oemcrypto_adapter_dynamic.cpp(900):Initialize] Level 3 Build Info (v17): OEMCrypto Level3 Code May 20 2022 21:36:54
    07-19 14:41:24.426 I [(0):] Level3 Library 22594 May 20 2022 21:36:54
    07-19 14:41:24.439 I [oemcrypto_adapter_dynamic.cpp(914):Initialize] L3 Initialized. Trying L1.
    07-19 14:41:24.557 W [oemcrypto_adapter_dynamic.cpp(1116):LoadLevel1] Could not load L1 _oecc109.
    07-19 14:41:24.557 W [oemcrypto_adapter_dynamic.cpp(1121):LoadLevel1] Could not load L1 _oecc115.
    07-19 14:41:24.557 W [oemcrypto_adapter_dynamic.cpp(1117):LoadLevel1] Could not load L1 _oecc110.
    07-19 14:41:24.565 I [(0):] L3 Terminate.
    07-19 14:41:24.565 E [oemcrypto_adapter_dynamic.cpp(955):Level1Terminate] L1 Terminate
    07-19 14:41:28.117 I [oemcrypto_adapter_dynamic.cpp(900):Initialize] Level 3 Build Info (v17): OEMCrypto Level3 Code May 20 2022 21:36:54
    07-19 14:41:28.117 I [(0):] Level3 Library 22594 May 20 2022 21:36:54
    07-19 14:41:28.121 I [oemcrypto_adapter_dynamic.cpp(914):Initialize] L3 Initialized. Trying L1.
    07-19 14:41:28.205 W [oemcrypto_adapter_dynamic.cpp(1117):LoadLevel1] Could not load L1 _oecc110.
    07-19 14:41:28.205 W [oemcrypto_adapter_dynamic.cpp(1121):LoadLevel1] Could not load L1 _oecc115.
    07-19 14:41:28.205 W [oemcrypto_adapter_dynamic.cpp(1116):LoadLevel1] Could not load L1 _oecc109.
    07-19 14:41:28.211 I [usage_table_header.cpp(203):RestoreTable] Found usage table to restore: entry_count = 0
    07-19 14:41:28.258 I [cdm_engine.cpp(983):QueryOemCryptoSessionId] session_id = sid2
    07-19 14:41:28.258 I [cdm_engine.cpp(187):OpenSession] New session: session_id = sid2
    07-19 14:41:28.264 I [cdm_engine.cpp(2141):SetPlaybackId] session_id = sid2, playback_id = b5Dz4J9TmSrY_SDc
    07-19 14:41:28.278 I [cdm_engine.cpp(274):GenerateKeyRequest] session_id = sid2, key_set_id = <empty>, license_type = Streaming
    07-19 14:41:28.472 I [cdm_engine.cpp(994):IsSecurityLevelSupported] level = L1
    07-19 14:41:28.473 I [cdm_engine.cpp(888):QuerySessionStatus] session_id = sid2
    07-19 14:41:31.488 I [cdm_engine.cpp(994):IsSecurityLevelSupported] level = L1
    07-19 14:41:35.262 I [cdm_engine.cpp(232):CloseSession] session_id = sid2
    07-19 14:41:35.277 E [oemcrypto_adapter_dynamic.cpp(955):Level1Terminate] L1 Terminate
    07-19 14:41:35.277 I [(0):] L3 Terminate.
    07-19 17:35:55.315 I [oemcrypto_adapter_dynamic.cpp(900):Initialize] Level 3 Build Info (v17): OEMCrypto Level3 Code May 20 2022 21:36:54
    07-19 17:35:55.315 I [(0):] Level3 Library 22594 May 20 2022 21:36:54
    07-19 17:35:55.321 I [oemcrypto_adapter_dynamic.cpp(914):Initialize] L3 Initialized. Trying L1.
    07-19 17:35:55.399 W [oemcrypto_adapter_dynamic.cpp(1121):LoadLevel1] Could not load L1 _oecc115.
    07-19 17:35:55.399 W [oemcrypto_adapter_dynamic.cpp(1116):LoadLevel1] Could not load L1 _oecc109.
    07-19 17:35:55.399 W [oemcrypto_adapter_dynamic.cpp(1117):LoadLevel1] Could not load L1 _oecc110.
    07-19 17:35:55.403 I [usage_table_header.cpp(203):RestoreTable] Found usage table to restore: entry_count = 0
    07-19 17:35:55.440 I [cdm_engine.cpp(187):OpenSession] New session: session_id = sid3
    07-19 17:35:55.440 I [cdm_engine.cpp(983):QueryOemCryptoSessionId] session_id = sid3
    07-19 17:35:55.455 W [cdm_session.cpp(487):GenerateKeyRequestInternal] Init data absent
    07-19 17:35:55.455 E [cdm_engine.cpp(334):GenerateKeyRequest] CdmSession::GenerateKeyRequest failed: session_id = sid3, status = 58
    07-19 17:35:55.455 I [cdm_engine.cpp(274):GenerateKeyRequest] session_id = sid3, key_set_id = <empty>, license_type = Offline
    07-19 17:35:55.455 E [initialization_data.cpp(79):InitializationData] Unable to select a supported Widevine PSSH from the init data
    07-19 17:35:55.455 E [initialization_data.cpp(125):SelectWidevinePssh] Widevine PSSH was not found in concatenated PSSH boxes
    07-19 17:35:55.460 I [cdm_engine.cpp(232):CloseSession] session_id = sid3
    07-19 17:39:05.357 I [(0):] L3 Terminate.
    07-19 17:39:05.357 E [oemcrypto_adapter_dynamic.cpp(955):Level1Terminate] L1 Terminate
    07-19 17:43:05.517 I No hidl drm factories found
    07-19 17:43:05.518 E Failed to find passthrough drm factories
    07-19 17:43:08.173 E Failed to find passthrough drm factories
    07-19 17:43:08.173 I No hidl drm factories found
    07-19 17:43:08.178 I [oemcrypto_adapter_dynamic.cpp(900):Initialize] Level 3 Build Info (v17): OEMCrypto Level3 Code May 20 2022 21:36:54
    07-19 17:43:08.178 I [(0):] Level3 Library 22594 May 20 2022 21:36:54
    07-19 17:43:08.181 I [oemcrypto_adapter_dynamic.cpp(914):Initialize] L3 Initialized. Trying L1.
    07-19 17:43:08.284 W [oemcrypto_adapter_dynamic.cpp(1116):LoadLevel1] Could not load L1 _oecc109.
    07-19 17:43:08.284 W [oemcrypto_adapter_dynamic.cpp(1117):LoadLevel1] Could not load L1 _oecc110.
    07-19 17:43:08.284 W [oemcrypto_adapter_dynamic.cpp(1121):LoadLevel1] Could not load L1 _oecc115.
    07-19 17:43:08.287 I [usage_table_header.cpp(203):RestoreTable] Found usage table to restore: entry_count = 0
    07-19 17:43:08.329 I [cdm_engine.cpp(187):OpenSession] New session: session_id = sid4
    07-19 17:43:08.329 I [cdm_engine.cpp(983):QueryOemCryptoSessionId] session_id = sid4
    07-19 17:43:08.343 E [initialization_data.cpp(125):SelectWidevinePssh] Widevine PSSH was not found in concatenated PSSH boxes
    07-19 17:43:08.343 E [initialization_data.cpp(79):InitializationData] Unable to select a supported Widevine PSSH from the init data
    07-19 17:43:08.343 I [cdm_engine.cpp(274):GenerateKeyRequest] session_id = sid4, key_set_id = <empty>, license_type = Offline
    07-19 17:43:08.343 W [cdm_session.cpp(487):GenerateKeyRequestInternal] Init data absent
    07-19 17:43:08.343 E [cdm_engine.cpp(334):GenerateKeyRequest] CdmSession::GenerateKeyRequest failed: session_id = sid4, status = 58
  ============================== End of DRM Plugin Log ==============================
      at androidx.media3.exoplayer.drm.DefaultDrmSession.onError(DefaultDrmSession.java:557)
      at androidx.media3.exoplayer.drm.DefaultDrmSession.onKeysError(DefaultDrmSession.java:547)
      at androidx.media3.exoplayer.drm.DefaultDrmSession.postKeyRequest(DefaultDrmSession.java:498)
      at androidx.media3.exoplayer.drm.DefaultDrmSession.doLicense(DefaultDrmSession.java:459)
      at androidx.media3.exoplayer.drm.DefaultDrmSession.acquire(DefaultDrmSession.java:327)
      at androidx.media3.exoplayer.drm.DefaultDrmSessionManager.createAndAcquireSession(DefaultDrmSessionManager.java:724)
      at androidx.media3.exoplayer.drm.DefaultDrmSessionManager.createAndAcquireSessionWithRetry(DefaultDrmSessionManager.java:624)
      at androidx.media3.exoplayer.drm.DefaultDrmSessionManager.acquireSession(DefaultDrmSessionManager.java:506)
      at androidx.media3.exoplayer.drm.DefaultDrmSessionManager.acquireSession(DefaultDrmSessionManager.java:452)
      at androidx.media3.exoplayer.drm.OfflineLicenseHelper.lambda$acquireFirstSessionOnHandlerThread$2$androidx-media3-exoplayer-drm-OfflineLicenseHelper(OfflineLicenseHelper.java:337)
      at androidx.media3.exoplayer.drm.OfflineLicenseHelper$$ExternalSyntheticLambda0.run(Unknown Source:10)
      at android.os.Handler.handleCallback(Handler.java:942)
      at android.os.Handler.dispatchMessage(Handler.java:99)
      at android.os.Looper.loopOnce(Looper.java:201)
      at android.os.Looper.loop(Looper.java:288)
      at android.os.HandlerThread.run(HandlerThread.java:67)

So I think there's two things here:

  1. The demo app should tighten the check in DownloadTracker.hasSchemeData to check specifically for widevine scheme data (since the following code is Widevine-specific). This means that your media would result in a "not supported" toast instead of an exception.
  2. You need to update your media to include the Widevine scheme data in the manifest, in order for it to be supported by the way the demo app integrates with OfflineLicenseHelper. Alternatively you need to find a way to craft a Format to pass directly to OfflineLicenseHelper that has a Widevine SchemeData with data != null.

rohitjoins pushed a commit that referenced this issue Aug 2, 2023
This code is Widevine specific. `OfflineLicenseHelper.downloadLicense`
requires the passed `Format` to have a `DrmInitData.SchemeData` with
Widevine UUID and non-null `data` field. The demo app tries to check
this in advance (to avoid an exception later), but its checks are
looser than those made by `OfflineLicenseHelper`. This change tightens
the checks to match.

Issue: #512
PiperOrigin-RevId: 549587506
rohitjoins pushed a commit to google/ExoPlayer that referenced this issue Aug 2, 2023
This code is Widevine specific. `OfflineLicenseHelper.downloadLicense`
requires the passed `Format` to have a `DrmInitData.SchemeData` with
Widevine UUID and non-null `data` field. The demo app tries to check
this in advance (to avoid an exception later), but its checks are
looser than those made by `OfflineLicenseHelper`. This change tightens
the checks to match.

Issue: androidx/media#512
PiperOrigin-RevId: 549587506
tianyif pushed a commit to google/ExoPlayer that referenced this issue Aug 11, 2023
This code is Widevine specific. `OfflineLicenseHelper.downloadLicense`
requires the passed `Format` to have a `DrmInitData.SchemeData` with
Widevine UUID and non-null `data` field. The demo app tries to check
this in advance (to avoid an exception later), but its checks are
looser than those made by `OfflineLicenseHelper`. This change tightens
the checks to match.

Issue: androidx/media#512
PiperOrigin-RevId: 549587506
(cherry picked from commit b7988e2)
tianyif pushed a commit that referenced this issue Aug 14, 2023
This code is Widevine specific. `OfflineLicenseHelper.downloadLicense`
requires the passed `Format` to have a `DrmInitData.SchemeData` with
Widevine UUID and non-null `data` field. The demo app tries to check
this in advance (to avoid an exception later), but its checks are
looser than those made by `OfflineLicenseHelper`. This change tightens
the checks to match.

Issue: #512
PiperOrigin-RevId: 549587506
(cherry picked from commit 1ccedf8)
tianyif pushed a commit to google/ExoPlayer that referenced this issue Aug 14, 2023
This code is Widevine specific. `OfflineLicenseHelper.downloadLicense`
requires the passed `Format` to have a `DrmInitData.SchemeData` with
Widevine UUID and non-null `data` field. The demo app tries to check
this in advance (to avoid an exception later), but its checks are
looser than those made by `OfflineLicenseHelper`. This change tightens
the checks to match.

Issue: androidx/media#512
PiperOrigin-RevId: 549587506
(cherry picked from commit b7988e2)
@icbaker icbaker closed this as completed Oct 27, 2023
@androidx androidx locked and limited conversation to collaborators Dec 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants