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 license drm + ABR for streaming content "Crypto key not available" #8350

Closed
tiaragizka opened this issue Dec 14, 2020 · 12 comments
Closed
Assignees
Labels

Comments

@tiaragizka
Copy link

  1. How to use offline license drm + abr for streaming content ?

I've tried to use offline license for drm without abr and it worked, but when i try offline license to play drm source with abr, exo always tells me error "Crypto key not available", meanwhile when i try online license to play drm source with abr or without abr Player runs normally. is there anything else you need to do to play drm + abr source on an offline license?

I hope to help my problem.

Thank you

@sana69
Copy link

sana69 commented Dec 16, 2020

I also have the same problem when using abr + offline license doesn't work properly, exoplayer always tell me error "crypto key not available"

@tiaragizka
Copy link
Author

@ojw28 i try sample offline license from exoplayer for play drm source + abr but same problem exo tell me error "crypto key not available"

@tonihei
Copy link
Collaborator

tonihei commented Dec 18, 2020

Please provide full reproduction steps that allow us to reproduce and understand the problem. From the current description it's a bit unclear what kind of media you use, what kind of license, how you store and use the offline licenses and what "using abr" means in this context.

@tonihei tonihei self-assigned this Dec 18, 2020
@tiaragizka
Copy link
Author

tiaragizka commented Dec 21, 2020

@tonihei thanks your feedback
I built a player for Android TV, the sources I use are protected using DRM and I want to use an offline license. I currently store the key (offline license) in the local database and use it like the script below. From several existing sources, only sources that use adaptive bitrate (ABR) technology which is set from the server cannot be played and generate "Crypto key not available". but if the adaptive bitrate source is played using an online license that's fine.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_player);
    playerView = (PlayerView)findViewById(R.id.exoplayerView);

    String streamingLink = getStreamingLink(streamingIndex);
    Uri mpdUri = Uri.parse("https://10.10.1.x/channel1150_abr.mp4/manifest.mpd");
    initialResourcePlayer(mpdUri);
}

private void initialResourcePlayer(Uri mpdUri){
    if(buildResourceAsync != null){
        buildResourceAsync.cancel(true);
        buildResourceAsync = null;
    }

    buildResourceAsync = new BuildResourceAsync(this,mpdUri);
    buildResourceAsync.execute();
}

public OfflineLicenseHelper buildOfflineLicenseHelper(HttpDataSource.Factory httpDataSourceFactory){
    DrmSessionEventListener.EventDispatcher eventDispatcher = new DrmSessionEventListener.EventDispatcher();

    OfflineLicenseHelper offlineLicenseHelper = OfflineLicenseHelper.newWidevineInstance(
            licenseUrl,
            true,
            httpDataSourceFactory,
            eventDispatcher);

    return offlineLicenseHelper;
}

public DrmSessionManager buildDRMSessionManager(HttpDataSource.Factory httpDataSourceFactory, Uri streamUri) throws IOException{
    this.httpDataSourceFactory = httpDataSourceFactory;
    MediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory);

    drmSessionManager = new DefaultDrmSessionManager.Builder()
            .setUuidAndExoMediaDrmProvider(C.WIDEVINE_UUID,FrameworkMediaDrm.DEFAULT_PROVIDER)
            .setMultiSession(true)
            .build(drmCallback);

    loadDrmLicense(streamUri);
    return drmSessionManager;
}

public void loadDrmLicense(Uri streamUri){
    try {
        Log.d("GASSKEUN","loadDrmLicense");
        String streamUrl = streamUri.toString();
        offlineKeySetId = getLicenseKey(streamUrl);

        DataSource dataSource = httpDataSourceFactory.createDataSource();
        DashManifest dashManifest = DashUtil.loadManifest(dataSource, streamUri);
        Format format = DashUtil.loadFormatWithDrmInitData(dataSource,dashManifest.getPeriod(0));

        if(offlineKeySetId == null || !isLicenseValid(offlineKeySetId)){
            Log.d(TAG,"DOWNLOADING LICENSE...");
            offlineKeySetId = offlineLicenseHelper.downloadLicense(format);

            saveLicenseKey(streamUrl,offlineKeySetId);
        }

        if(isLicenseValid(offlineKeySetId)){
            Log.d(TAG",offlineKeySetId.toString());
            drmSessionManager.setMode(DefaultDrmSessionManager.MODE_PLAYBACK,offlineKeySetId);
        } else {
            Log.d(TAG,offlineKeySetId.toString());
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

class BuildResourceAsync extends AsyncTask<Void, Void, Void> {
    HttpDataSource.Factory httpDataSourceFactory;
    MediaSourceFactory mediaSourceFactory;
    DrmSessionManager drmSessionManager;
    RenderersFactory renderersFactory;
    DashMediaSource mediaSource;
    MediaItem mediaItem;
    Context context;
    Uri uri;

    public BuildResourceAsync(Context context, Uri uri){
        this.context = context;
        this.uri = uri;
    }

    @Override
    protected Void doInBackground(Void... voids) {

        try {
            String userAgent = Util.getUserAgent(context, "AptavisApp");
            httpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);

            mediaSourceFactory = new DefaultMediaSourceFactory(httpDataSourceFactory);
            renderersFactory = new DefaultRenderersFactory(context);
            offlineLicenseHelper = buildOfflineLicenseHelper(httpDataSourceFactory);
            mediaItem = new MediaItem.Builder()
                    .setUri(uri)
                    .setDrmMultiSession(true)
                    .setDrmUuid(C.WIDEVINE_UUID)
                    .build();

            drmSessionManager = buildDRMSessionManager(httpDataSourceFactory,uri);
        }catch (Exception e){
            e.printStackTrace();
        }

        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        try {
            mediaSource = new DashMediaSource.Factory(httpDataSourceFactory)
                    .setDrmSessionManager(drmSessionManager)
                    .createMediaSource(mediaItem);
        }catch (Exception e){
            e.printStackTrace();
        }
        initializePlayerDRM(renderersFactory,mediaSourceFactory,mediaSource);
        super.onPostExecute(aVoid);
        cancel(true);
    }
}

public void initializePlayerDRM(RenderersFactory renderersFactory, MediaSourceFactory mediaSourceFactory, MediaSource mediaSource){
    DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
    builder.setBufferDurationsMs(
            DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
            DefaultLoadControl.DEFAULT_MAX_BUFFER_MS,
            /* To reduce the startup time, also change the line below */
            DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
            DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);

    DefaultLoadControl loadControl = builder.build();

    BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(this).build();
    Handler eventHandler = new Handler();
            BandwidthMeter.EventListener eventListener = new BandwidthMeter.EventListener() {
        @Override
        public void onBandwidthSample(int elapsedMs, long bytesTransferred, long bitrateEstimate) {
        }
    };
    bandwidthMeter.addEventListener(eventHandler, eventListener);
    TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
    DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(this,videoTrackSelectionFactory);

    simpleExoPlayer = new SimpleExoPlayer.Builder(this, renderersFactory)
            .setLoadControl(loadControl)
            .setBandwidthMeter(bandwidthMeter)
            .setTrackSelector(defaultTrackSelector)
            .setMediaSourceFactory(mediaSourceFactory)
            .build();

    simpleExoPlayer.addMediaSource(mediaSource);
    simpleExoPlayer.prepare();
    simpleExoPlayer.setPlayWhenReady(true);
    playerView.setKeepScreenOn(true);
    playerView.setUseController(false);
    playerView.setPlayer(simpleExoPlayer);

    simpleExoPlayer.addListener(new ExoPlayer.EventListener() {
        @Override
        public void onPlayerError(ExoPlaybackException error) {
            switch (error.type) {
                case ExoPlaybackException.TYPE_SOURCE:
                    Log.d(TAG, "TYPE_SOURCE: " + error.getSourceException().getMessage());
                    break;
                case ExoPlaybackException.TYPE_RENDERER:
                    Log.d(TAG, "TYPE_RENDERER: " + error.getRendererException().getMessage());
                    break;
                case ExoPlaybackException.TYPE_UNEXPECTED:
                    Log.d(TAG, "TYPE_UNEXPECTED: " + error.getUnexpectedException().getMessage());
                    break;
                default:
                    Log.d(TAG, "TYPE_UNEXPECTED: Unable to connect");
                    break;
            }
        }
    });
}

@tonihei
Copy link
Collaborator

tonihei commented Dec 22, 2020

@ojw28 Could you take a look? I'm not sure how offline licenses work exactly and what needs to be done to make them work correctly for adaptive playback.

@tonihei tonihei assigned ojw28 and unassigned tonihei Dec 22, 2020
@ojw28
Copy link
Contributor

ojw28 commented Dec 23, 2020

Please read our DRM documentation. In particular note:

Known issue #3872 - Only one offline key set can be specified per playback. As a result, offline playback of multi-key content is currently supported only when the license server is configured as described in Case 1 above.

This means that your license server must be configured to behave as in case 1, where all of the keys needed for playback are returned in the license request, rather than just the key being requested.

Note that if your license server is configured as required, you will not need to call setMultiSession(true) for when using streaming licenses, either.

@tiaragizka
Copy link
Author

@ojw28 Even if we use 1 license for all playback, we still have to supply multi-key?

@ojw28
Copy link
Contributor

ojw28 commented Jan 5, 2021

If you're asking whether you need to call setMultiSession(true) in the case that all keys are contained within one license response, then the answer is no as per my response above.

If you're asking something else, then I'm afraid I don't understand the question.

@tiaragizka
Copy link
Author

@ojw28 Thank you for your response,
my stream have adaptive bitrate https://serverurl/main.mpd (720p,480p,320p), we encrypt the stream, using only one key (same key) for each stream

we have successfuly download this one key from license server, store it and use it to play each stream individually using direct stream url
https://serverurl/main_720p.mpd (can play)
https://serverurl/main_480p.mpd (can play)
https://serverurl/main_320p.mpd (can play)

but when we try to play the main url, each time the stream change quality exoplayer seem not supply the key we store previously and give "Crypto key not available"

is it required for exoplayer that our license server should return multiple key for abr with stored key, even if it same key for each quality?

or is there anyway we can manually create the multiple key structure from one key that we get before we store it for offline use?

@ojw28
Copy link
Contributor

ojw28 commented Jan 6, 2021

is it required for exoplayer that our license server should return multiple key for abr with stored key, even if it same key for each quality?

No, this is not required. It sounds like there's something wrong with the way the content is prepared, but it's unclear what that is. Without working test content (including an accessible license server), I don't think there's much we can do to assist. Please provide some if you'd like us to investigate further. Thanks!

@ojw28
Copy link
Contributor

ojw28 commented Jan 18, 2021

It doesn't look like you're using the same key for all of the streams. Looking at the channel 1150 links provided, the 480p and 720p streams are not using the same key. To see which keys are used in the content for each buffer being queued to the decoder, you can add logging here like:

Log.e("Key-Debug", "Key = " + Arrays.toString(buffer.cryptoInfo.key));

The keys I see in your content are:

480p: Key = [95, -107, -122, -49, -56, -33, -13, -93, -126, -107, -65, -96, 45, 95, -37, -13]
720p: Key = [9, 47, -89, 5, 123, -54, -92, -71, 35, 28, 100, -44, -66, 101, 75, 49]

Hence when you put both of them into the manifest, you have multi-key content, and my previous response still applies.

I'm going to go ahead and close this, since I don't think there's an ExoPlayer issue here.

@tiaragizka
Copy link
Author

@ojw28 Thank you for your support, we will retrace how we prepared our sources.

@google google locked and limited conversation to collaborators Mar 19, 2021
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