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

Unable to Play using ClearKey #780

Closed
AmarnathCJD opened this issue Oct 30, 2023 · 12 comments
Closed

Unable to Play using ClearKey #780

AmarnathCJD opened this issue Oct 30, 2023 · 12 comments
Assignees
Labels

Comments

@AmarnathCJD
Copy link

Hello,

I have a dash (mpd) DRM protected Video which Works well on JW test Stream site, But I was unable to get it working With ExoPlayer
All i get is A blank screen with time seeker at 4hours++ random time

FULL CODE: https://gist.github.com/AmarnathCJD/e575991991f0a2b6201398cc24221f7b

drmKey = "b8ed4930014a0ad8f4bc2310f378edd2"
drmKeyId = "89413479ba07590893320faf05a6b7fd"
mpdUrl = "https://bpprod3linear.akamaized.net/bpk-tv/irdeto_com_Channel_301/output/manifest.mpd"
header = ["host": "bpprod3linear.akamaized.net"]
No License server.

My Questions:

  1. Is the Implementation correct?
  2. How to pass custom header "host" during recieving the content

Current Error:
Playback error androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:668) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loopOnce(Looper.java:205) at android.os.Looper.loop(Looper.java:294) at android.os.HandlerThread.run(HandlerThread.java:67) Caused by: android.media.MediaCodec$CryptoException: Decryption Error: ERROR_DRM_NO_LICENSE

Failed on decrypt, error description:Status(-8, EX_SERVICE_SPECIFIC): '1: {"cdmError":1,"errorMessage":"Decryption Error"}

PS: sorry for such silly question, This was all i could makeup after hours of google search for documentation, its hard as most codes are of exoplayer2 and backwards compatablity of this lib is not that great:)

@icbaker
Copy link
Collaborator

icbaker commented Oct 31, 2023

This part is not correct. The JSON string you've contructed there is an online license key response, not an offline keyset ID (which you would have got from e.g. OfflineLicenseHelper.downloadLicense). I don't think most (any?) ClearKey DRM implementations support offline DRM licenses. You should remove this.

.setKeySetId("{\"keys\":[{\"kty\":\"oct\",\"k\":\"${encodedDrmKey}\",\"kid\":\"${encodedDrmKeyId}\"}],\"type\":\"temporary\"}".toByteArray())

Your .setKeyRequestParameters(...) call will have no effect because you're using LocalMediaDrmCallback (i.e. no key request will be sent to any DRM server, so there's no request to attach these parameters to).


Why do you create two separate MediaItem instances, one with C.CLEARKEY_UUID and the other with C.WIDEVINE_UUID? It looks like the C.WIDEVINE_UUID is the one actually being used for playback, so it's not surprising it doesn't work when combined with your DefaultDrmSessionManager that you've configured to work for C.CLEARKEY_UUID.

Similarly, it seems your defaultHttpDataSourceFactory, dashChunkSourceFactory, manifestDataSourceFactory, and dashMediaSource locals are never actually used for playback (since when calling player.prepare you construct a new MediaSource instance without using any of these locals).


Why do you need MergingMediaSource if you're not merging it with anything?


You may find the code looks a bit simpler if you use Player.addMediaItem APIs and avoid manually constructing MediaSource instances. You can instead provide a custom MediaSource.Factory to the player so it can create the MediaSource instances internally: https://developer.android.com/guide/topics/media/exoplayer/media-sources#customizing-media-source-creation

@AmarnathCJD
Copy link
Author

May I get a simple Player.addmediaitem example with clearkey and clearkeyiD please

(,just this part)

It's a bit confusing

@icbaker
Copy link
Collaborator

icbaker commented Oct 31, 2023

Something like this, untested, and in Java rather than Kotlin

String clearkeyJsonResponse = "TODO";
DrmSessionManager clearkeyDrmSessionManager =
    new DefaultDrmSessionManager.Builder()
        .setUuidAndExoMediaDrmProvider(C.CLEARKEY_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
        .build(
            new LocalMediaDrmCallback(clearkeyJsonResponse.getBytes(StandardCharsets.UTF_8)));
ExoPlayer player =
    new ExoPlayer.Builder(/* context= */ this)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(/* context= */ this)
                .setDrmSessionManagerProvider(mediaItem -> clearkeyDrmSessionManager))
        .build();

String mpdUrl = "TODO";
player.addMediaItem(
    new MediaItem.Builder()
        .setUri(mpdUrl)
        .setDrmConfiguration(new MediaItem.DrmConfiguration.Builder(C.CLEARKEY_UUID).build())
        .build());

@icbaker
Copy link
Collaborator

icbaker commented Nov 1, 2023

I can't access your MPD linked above (403 error), but you may want to check that your ClearKey <ContentProtection> nodes contain the required default_KID attribute. See also google/ExoPlayer#9169 (comment).

@AmarnathCJD
Copy link
Author

Ok I'll get back to you:) thanks for such quick replies

@AmarnathCJD
Copy link
Author

AmarnathCJD commented Nov 2, 2023

I can't access your MPD linked above (403 error), but you may want to check that your ClearKey <ContentProtection> nodes contain the required default_KID attribute. See also google/ExoPlayer#9169 (comment).

I have checked this and it existsit exists like

ContentProtection enic:default_KID: value

@icbaker
Copy link
Collaborator

icbaker commented Nov 2, 2023

I'm going to close this because without access to the media there's no further assistance we can really offer (we're just guessing at potential causes at this point, which isn't a great use of time).

@icbaker icbaker closed this as completed Nov 2, 2023
@AmarnathCJD
Copy link
Author

Failed to make drm plugin: 4


import android.os.Build
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.dash.DashMediaSource
import androidx.media3.exoplayer.dash.DefaultDashChunkSource
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager
import androidx.media3.exoplayer.drm.FrameworkMediaDrm
import androidx.media3.exoplayer.drm.LocalMediaDrmCallback
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
import androidx.media3.ui.PlayerView

@RequiresApi(Build.VERSION_CODES.Q)
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
@Composable
fun LiveTV() {
    val context = LocalContext.current
    val trackSelector = DefaultTrackSelector(context)

    val player: ExoPlayer = remember {
        ExoPlayer.Builder(context).setTrackSelector(trackSelector)
            .build()
            .apply {
                prepare()
            }
    }

    val dashMediaItem = MediaItem.Builder()
        .setUri("https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd")
        .setMimeType(MimeTypes.APPLICATION_MPD)
        .setMediaMetadata(MediaMetadata.Builder().setTitle("Hello").build())
        .build()

    val uA = "Mozilla/5.0 (Linux; Android 11; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Mobile Safari/537.36"
    val defaultHttpDataSourceFactory = DefaultHttpDataSource.Factory()
        .setUserAgent(uA)
        .setTransferListener(
            DefaultBandwidthMeter.Builder(context)
                .setResetOnNetworkTypeChange(false)
                .build()
        )
    val dataSourceFactory = DefaultDashChunkSource.Factory(
        defaultHttpDataSourceFactory
    )

    val manifestDataSourceFactory = DefaultHttpDataSource.Factory().setUserAgent(uA)

    val drmCallback = LocalMediaDrmCallback("{\"keys\":[{\"kty\":\"oct\",\"k\":\"nrQFDeRLSAKTLifXUIPiZg\",\"kid\":\"FmY0xnWCPCNaSpRG-tUuTQ\"}],\"type\":\"temporary\"}".toByteArray())
    val drmSessionManager = DefaultDrmSessionManager.Builder()
        .setPlayClearSamplesWithoutKeys(true)
        .setMultiSession(false)
        .setKeyRequestParameters(HashMap())
        .setUuidAndExoMediaDrmProvider(C.CLEARKEY_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
        .build(drmCallback)
    val mediaSource = DashMediaSource.Factory(dataSourceFactory, manifestDataSourceFactory)
        .setDrmSessionManagerProvider { drmSessionManager }
        .createMediaSource(dashMediaItem)

    player.setMediaSource(mediaSource, true)
    player.prepare()

    Column(
        modifier =
        Modifier
            .fillMaxWidth()
            .height(260.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        AndroidView(
            factory = {
                PlayerView(context).apply {
                    this.player = player
                    player.playWhenReady = true
                    layoutParams = FrameLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                    )
                }
            },
            modifier = Modifier
                .fillMaxSize()
                .padding(0.dp, 0.dp, 0.dp, 0.dp)
                .background(Color.Black)
        )
    }
}

@icbaker
Copy link
Collaborator

icbaker commented Nov 3, 2023

https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd

This manifest includes a license server URL as documented here: https://github.com/Dash-Industry-Forum/ClearKey-Content-Protection#workflow

This means it shouldn't need a LocalMediaDrmCallback implementation and can instead use the default HttpMediaDrmCallback implementation. However I tried this by playing it with the demo app and requesting the DRM license failed with a certificate error (i.e. this content isn't really completely playable/valid):

Exception in CronetUrlRequest: net::ERR_CERT_DATE_INVALID, ErrorCode=11, InternalErrorCode=-201, Retryable=false

I logged the JSON license request that would have been sent:

{"kids":["nrQFDeRLSAKTLifXUIPiZg"],"type":"temporary"}

It seems in your hard-coded JSON response you've mixed up the kid and k attributes:

{\"keys\":[{\"kty\":\"oct\",\"k\":\"nrQFDeRLSAKTLifXUIPiZg\",\"kid\":\"FmY0xnWCPCNaSpRG-tUuTQ\"}],\"type\":\"temporary\"}

Fixing this and using LocalMediaDrmCallback with this JSON string makes the content playable:

{\"keys\":[{\"kty\":\"oct\",\"k\":\"FmY0xnWCPCNaSpRG-tUuTQ\",\"kid\":\"nrQFDeRLSAKTLifXUIPiZg\"}],\"type\":\"temporary\"}

Closing as I believe this resolves the question.

@icbaker icbaker closed this as completed Nov 3, 2023
@AmarnathCJD
Copy link
Author

AmarnathCJD commented Nov 4, 2023

I was able to play that public source using this but the one i started this question intially withis still unplayable with error:

Playback error
                                                                                                      androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error
                                                                                                          at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:668)
                                                                                                          at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                                                          at android.os.Looper.loopOnce(Looper.java:205)
                                                                                                          at android.os.Looper.loop(Looper.java:294)
                                                                                                          at android.os.HandlerThread.run(HandlerThread.java:67)
                                                                                                      Caused by: android.media.MediaCodec$CryptoException: Decryption Error: ERROR_DRM_NO_LICENSE
                                                                                                      cdm err: 1, oem err: 0, ctx: 0
                                                                                                      ============================== Beginning of DRM Plugin Log ==============================
                                                                                                        11-04 17:10:55.222 I No hidl drm factories found
                                                                                                        11-04 17:10:55.223 E Failed to find passthrough drm factories
                                                                                                        11-04 17:10:55.304 E uuid=[e2719d58a985b3c9 781ab030af78d30e] Failed to make drm plugin: 4
                                                                                                        11-04 17:10:58.857 E Not implemented
                                                                                                      ============================== End of DRM Plugin Log ==============================
                                                                                                          at android.media.MediaCodec.native_queueSecureInputBuffer(Native Method)
                                                                                                          at android.media.MediaCodec.queueSecureInputBuffer(MediaCodec.java:3105)
                                                                                                          at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doQueueSecureInputBuffer(AsynchronousMediaCodecBufferEnqueuer.java:240)
                                                                                                          at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doHandleMessage(AsynchronousMediaCodecBufferEnqueuer.java:205)
                                                                                                          at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.access$000(AsynchronousMediaCodecBufferEnqueuer.java:47)
                                                                                                          at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer$1.handleMessage(AsynchronousMediaCodecBufferEnqueuer.java:96)
                                                                                                          at android.os.Handler.dispatchMessage(Handler.java:106)
                                                                                                          ... 3 more

https://bpprod6linear.akamaized.net/bpk-tv/irdeto_com_Channel_464/output/manifest.mpd
val clearkeyJSON =
"{"keys":[{"kty":"oct","k":"YmMwYWFhNTQyZTA2YjE4MTZmZWE4M2IzMGQyNmJlMmM","kid":"YWZhMTE2ZGRlMTFhNWIwODgyNDA0MjMwODU0NjE2YmU"}],"type":"temporary"}"

{
"mpd": "https://bpprod6linear.akamaized.net/bpk-tv/irdeto_com_Channel_464/output/manifest.mpd",
"key_id": "afa116dde11a5b0882404230854616be",
"key": "bc0aaa542e06b1816fea83b30d26be2c",
"host": "bpprod6linear.akamaized.net",
}

@AmarnathCJD
Copy link
Author

AmarnathCJD commented Nov 4, 2023

jwplayer("myElement").setup({
                'preload': "auto",
                'width': "100%",
                'height': "100%",
                file: "https://bpprod6linear.akamaized.net/bpk-tv/irdeto_com_Channel_464/output/manifest.mpd", type: "dash",
                drm: {
                    "clearkey": {
                        "keyId": "afa116dde11a5b0882404230854616be", "key": "bc0aaa542e06b1816fea83b30d26be2c"
                    }
                }
            });

plays this

@trinme
Copy link

trinme commented Nov 5, 2023

Try check back your code of hex to base64 conversion. Or try manually using this site.

@androidx androidx locked and limited conversation to collaborators Jan 3, 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

3 participants