javascriptelectrondrmdash.js

How can I get protected playback of offline content with DOWNstream For Electron?


I'm trying to get protected playback of offline content within Electron.

I'm trying to use: https://github.com/castlabs/downstream_electron

along with their Widevine-compatible Electron build: https://github.com/castlabs/electron-releases/releases/tag/v1.8.7-hdcp-vmp1010

Which should, according to downstream_electron's README.md, "allow protected playback of offline content within Electron".

I have a proof-of-concept set up with dashjs where I can extract the session information and download a drm-protected dash manifest, along with the audio and video segments.

I'm using publicly available assets from ezdrm:
manifest: http://wvm.ezdrm.com/demo/stream.mpd
license server: http://widevine-dash.ezdrm.com/proxy?pX=BF9CEB

When I call DownstreamElectronFE.downloads.getOfflineLink() after downloading the asset, it gives me back this:

{ 
  offlineLink:"http://127.0.0.1:3010/movies/6441406178546155520/stream.mpd"
  persistent:"F75D9FC450010B582A7951ED228DAF85"
}

So I've got a local, drm-protected manifest being served from a local server. How do I get "protected playback of offline content within Electron"?

If I were to provide this offlineLink value as the source to a dash video player, it would still need to reach out to the internet to talk to the license server, so it wouldn't be truly offline.

A portion of my demo is below:

const dashjs = require('dashjs');

let session

function FakePersistentPlugin() {
    this.createPersistentSession = (persistentConfig) => {
        console.log('create - call of fake persistent plugin, persistentConfig', persistentConfig);
        return new Promise((resolve) => {
            setTimeout(function () {
                console.log('resolving session',session.sessionId)
                resolve(session.sessionId);
            }, 5000);
        });
    };
    this.removePersistentSession =  (sessionId) => {
        console.log('remove - call of fake persistent plugin, sessionId', sessionId);
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log('remove - call of fake persistent plugin resolved', sessionId);
                resolve();
            }, 5000);
        });
    };
}

const downstreamElectron = require("downstream-electron/dist").init(window,new FakePersistentPlugin());
console.log('downstreamElectron is ',downstreamElectron);


var player = dashjs.MediaPlayer().create();
player.setProtectionData({'com.widevine.alpha':{serverURL:'http://widevine-dash.ezdrm.com/proxy?pX=BF9CEB'}});
player.initialize(document.querySelector("#videoPlayer"))
player.attachSource('http://wvm.ezdrm.com/demo/stream.mpd')
player.play()

player.on( dashjs.MediaPlayer.events.KEY_SESSION_CREATED, (e) => {
    session = e.data.session;
    console.log('session is ',session)
});

const config = {
    licenseUrl:'http://widevine-dash.ezdrm.com/proxy?pX=BF9CEB',
    serverCertificate: new Uint8Array([0])
}

let manifestId
let persistId

function onProgress (err, stats) {
    if (err) {
        console.logs('on progress err', err, stats);
    }
    console.log('on progress ',stats);
};

function onFinish (err, info) {
    if (err) {
        console.log("onFinish error", err);
    } else {
        console.log("onFinish success", info);
        downstreamElectron.downloads.getOfflineLink(info.manifestInfo.id)
        .then(
            function onSuccess(result) {console.log("offlineLink success", result)},
            function onError(err) {console.log("error", err)
          })
    }
};



downstreamElectron.downloads.create('http://wvm.ezdrm.com/demo/stream.mpd')
.then(
    function onSuccess(result) {
        console.log("create success", result)
        manifestId = result.id
        downstreamElectron.downloads.createPersistent(manifestId,config)
        .then(
            (result)=>{
                console.log("createPersistent success",result)
                persistId = result

                downstreamElectron.downloads.savePersistent(manifestId,persistId)
                .then(
                    () => {
                        console.log('savePersistent success')

                        downstreamElectron.downloads.start(manifestId,{video:['video/avc1'],audio:['audio/und/mp4a']})
                        .then(
                            function onSuccess() {console.log("start success")},
                            function onError(err) {console.log("start error", err)
                          })

                        downstreamElectron.downloads.subscribe(manifestId, 1000, onProgress, onFinish)
                        .then(
                            function onSuccess() {console.log("subscribed successfully")},
                            function onError(err) {console.log("subscribe error", err)
                        })
                    },
                    (err) => {
                        console.log('savePersistent error',err)
                    }
                )
            },
            (err)=>{
                console.log("createPersistent error",err)
            }
        )
    },
    function onError(err) {console.log("create error", err)}
)

Solution

  • You need to request the license ahead of time, and specifically request a license that allows persistence (offline use). Basically, you ned to trigger the license request, before going offline. There might be a function call to do this somewhere, otherwise you could start playback to get the license, and terminate it afterwards.

    Note that the license issued has to allow offline usage, that is not the default. The EZDRM demo stuff, might allow you to request such a license, but it is not likely to give you one like that by default.