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

Fail to play MPD Widevine with ClearKeys #952

Closed
fr3ns1s opened this issue Jan 8, 2024 · 4 comments
Closed

Fail to play MPD Widevine with ClearKeys #952

fr3ns1s opened this issue Jan 8, 2024 · 4 comments
Assignees
Labels

Comments

@fr3ns1s
Copy link

fr3ns1s commented Jan 8, 2024

info about MediaDrm widevine from Firestick 4K

2024-01-08 19:41:28.387 20424-20424 vendor                          Google
2024-01-08 19:41:28.415 20424-20424 version                         14.0.0
2024-01-08 19:41:28.416 20424-20424 description                     Widevine CDM
2024-01-08 19:41:28.417 20424-20424 algorithms                      AES/CBC/NoPadding,HmacSHA256
2024-01-08 19:41:28.443 20424-20424 securityLevel                   L1
2024-01-08 19:41:28.471 20424-20424 systemId                        17706
2024-01-08 19:41:28.494 20424-20424 hdcpLevel                       HDCP-2.2
2024-01-08 19:41:28.520 20424-20424 maxHdcpLevel                    HDCP-2.2
2024-01-08 19:41:28.548 20424-20424 usageReportingSupport           True
2024-01-08 19:41:28.576 20424-20424 maxNumberOfSessions             120
2024-01-08 19:41:28.601 20424-20424 numberOfOpenSessions            0

url
https://linear305-it-dash1-prd-ll.cdn13.skycdp.com/016a/31248/FHD/skysportmax/master.mpd

keys

key:4fbb022704c9abcbb709484a5667fd79
kid:0036a901e6f0381cfd1b6fcf10cc6038
drmCallbrack: {"type":"temporary","keys":[{"kid":"ADapAebwOBz9G2_PEMxgOA","k":"T7sCJwTJq8u3CUhKVmf9eQ","kty":"oct"}]}

No error on console , works on shaka ... others mpd works fine

@oceanjules
Copy link
Contributor

@fr3ns1s,

Thank you for your report. It seems to me like a duplicate of: google/ExoPlayer#9169, #563, #780, #777 (comment)

TLDR: it might have something to do with your ContentProtection node missing default_KID which is required as per: https://dashif-documents.azurewebsites.net/Guidelines-Security/master/Guidelines-Security.html#CPS-mpd-scheme

@fr3ns1s
Copy link
Author

fr3ns1s commented Jan 8, 2024

works!
ty

@marksdemellin
Copy link

I’m having the same issue. Is there a way to solve this problem? Adding manually the default_kid= string could be a workaround ?

@fr3ns1s
Copy link
Author

fr3ns1s commented Jan 9, 2024

yes ... this is my code (i'm not an android developer)

CustomDashManifestParser file:

import android.text.TextUtils;
import android.util.Base64;
import android.util.Pair;
import androidx.media3.common.C;
import androidx.media3.common.DrmInitData.SchemeData;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.XmlPullParserUtil;
import androidx.media3.exoplayer.dash.manifest.DashManifestParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;
import androidx.media3.extractor.mp4.PsshAtomUtil;
import com.google.common.base.Ascii;

@UnstableApi @SuppressWarnings("NullableOnContainingClass")
public final class CustomDashManifestParser extends DashManifestParser {

    private final DashManifestParser defaultParser = new DashManifestParser();
    private  String defaultKid_global = "";

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }
    public CustomDashManifestParser(String kid) {
        if (kid.length() == 32) {
            byte[] data = hexStringToByteArray(kid);
            defaultKid_global = new UUID(ByteBuffer.wrap(data, 0, 8).getLong(), ByteBuffer.wrap(data, 8, 8).getLong()).toString();
        }
    }

    @Override
    protected Pair<@NullableType String, @NullableType SchemeData> parseContentProtection(
            XmlPullParser xpp) throws XmlPullParserException, IOException {

        String schemeType = null;
        String licenseServerUrl = null;
        byte[] data = null;
        UUID uuid = null;

        String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
        if (schemeIdUri != null) {
            switch (Ascii.toLowerCase(schemeIdUri)) {
                case "urn:mpeg:dash:mp4protection:2011":
                    schemeType = xpp.getAttributeValue(null, "value");
                    String defaultKid = XmlPullParserUtil.getAttributeValueIgnorePrefix(xpp, "default_KID");
                    if (TextUtils.isEmpty(defaultKid) || "00000000-0000-0000-0000-000000000000".equals(defaultKid)  && !"".equals(defaultKid_global)){
                        defaultKid = defaultKid_global;
                    }
                    String[] defaultKidStrings = defaultKid.split("\\s+");
                    UUID[] defaultKids = new UUID[defaultKidStrings.length];
                    for (int i = 0; i < defaultKidStrings.length; i++) {
                        defaultKids[i] = UUID.fromString(defaultKidStrings[i]);
                    }
                    data = PsshAtomUtil.buildPsshAtom(C.COMMON_PSSH_UUID, defaultKids, null);
                    uuid = C.COMMON_PSSH_UUID;
                    break;
                case "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95":
                    uuid = C.PLAYREADY_UUID;
                    break;
                case "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":
                    uuid = C.WIDEVINE_UUID;
                    break;
                case "urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e":
                    uuid = C.CLEARKEY_UUID;
                    break;
                default:
                    break;
            }
        }

        do {
            xpp.next();
            if (XmlPullParserUtil.isStartTag(xpp, "clearkey:Laurl") && xpp.next() == XmlPullParser.TEXT) {
                licenseServerUrl = xpp.getText();
            } else if (XmlPullParserUtil.isStartTag(xpp, "ms:laurl")) {
                licenseServerUrl = xpp.getAttributeValue(null, "licenseUrl");
            } else if (data == null
                    && XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
                    && xpp.next() == XmlPullParser.TEXT) {
                // The cenc:pssh element is defined in 23001-7:2015.
                data = Base64.decode(xpp.getText(), Base64.DEFAULT);
                uuid = PsshAtomUtil.parseUuid(data);
                if (uuid == null) {

                    data = null;
                }
            } else if (data == null
                    && C.PLAYREADY_UUID.equals(uuid)
                    && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
                    && xpp.next() == XmlPullParser.TEXT) {
                // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
                data =
                        PsshAtomUtil.buildPsshAtom(
                                C.PLAYREADY_UUID, Base64.decode(xpp.getText(), Base64.DEFAULT));
            } else {
                //maybeSkipTag(xpp);
            }
        } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection"));
        SchemeData schemeData =
                uuid != null ? new SchemeData(uuid, licenseServerUrl, MimeTypes.VIDEO_MP4, data) : null;
        return Pair.create(schemeType, schemeData);
    }
}

and on playeractivity add setManifestParser(CustomDashManifestParser(channel.kid))

 player = ExoPlayer.Builder(this)
            .setTrackSelector(trackSelector!!)
            .setMediaSourceFactory(
                DashMediaSource.Factory(
                    DefaultDashChunkSource.Factory(dataSourceFactory),
                    dataSourceFactory
                ).setManifestParser(CustomDashManifestParser(channel.kid))
                    .setDrmSessionManagerProvider { clearkeyDrmSessionManager }
            )
            .build()

@androidx androidx locked and limited conversation to collaborators Mar 9, 2024
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

5 participants