webos

How to play DRM content using native WebOS player


We are trying to play video content using native WebOS player and com.webos.service.drm service. The goal is to play at least some DRM content (dash+widevine or hls+widevine). Clear content works fine, but DRM content stucks completely, and no diagnostic messages appear.

The same result happens on WebOS 3.4, 4.4, and 6.0. Is there any working example of DRM content playback?

Our code is given below.

var appId = "com.zodiac.app";

// Define DRM Type
var drmType = "widevine";

function webos_request(service, params)
{
    var caller = arguments.callee.caller.name;

    console.log("REQ: %s: %s %s", caller, service, JSON.stringify(params))
    return new Promise( function(resolve, reject) {
        webOS.service.request(service, Object.assign({}, params, {
            onSuccess: function (result) { console.log("REQ SUCCESS: %s: %s %s: %s", caller, service, params.method, JSON.stringify(result)); resolve(result) },
            // onFailure: function (result) {
            //     console.log("[" + result.errorCode + "] " + result.errorText);
            //     reject()
            // }

            onFailure: function (result) { console.error("REQ ERROR: %s: %s %s: %s",   caller, service, params.method, JSON.stringify(result)); reject(result) }
        }))
    });
}


function webos_subscribe(service, params, method)
{
    var caller = arguments.callee.caller.name;
    var completed = false
    return new Promise( function(resolve, reject) {
        params.parameters = params.parameters || {}
        params.parameters.subscribe = true;

        console.log("SUB: %s: %s %s", caller, service, JSON.stringify(params))

        webOS.service.request("luna://com.webos.service.drm", Object.assign({}, params, {
            onSuccess: function (result) { // Subscription Callback

                if (!completed) {
                    completed = true;

                    if (result.subscribed) {
                        console.log("SUB: %s: %s: SUCCESS", caller, service)
                        resolve(result)
                    }
                    else {
                        console.error("SUB: %s: %s: FAILED", caller, service)
                        reject();
                    }

                }
                else
                    method(result)
            },
            onFailure: function (result) {
                console.error("SUB: %s: %s: onFailure", caller, service, result)
                // console.log('Player.subscribeLicensingError onFailure: ' + '[' + result.errorCode + '] ' + result.errorText);
                completed = true;
                reject()
            }
        }));
    });
}

function unloadDrmClient(clientId)
{
    webos_request("luna://com.webos.service.drm",
    {
        method:"unload",
        parameters: {
            "clientId": result.clientId
        }
    });
}

function loadDrmClient()
{
    return webos_request( "luna://com.webos.service.drm", {
            method:"load",
            parameters: {
                "drmType": drmType,
                "appId": appId
            }})
            .then(function (result) {
                console.log("DRM Client is loaded successfully. %s", JSON.stringify(result));

                document.addEventListener('visibilitychange', function() {
                    if (document.visibilityState === 'hidden') {
                        unloadDrmClient(result.clientId)
                    }
                })

                return result.clientId
            })
}


function sendRightInformation(clientId, url, la_url) {
    var msgId;

    // Message format for widevine
    var msg = [
        '<?xml version="1.0" encoding="utf-8"?>',
        '<WidevineCredentialsInfo xmlns="http://www.smarttv-alliance.org/DRM/widevine/2012/protocols/">',
        '<ContentURL>' + url + '</ContentURL>',
        '<DeviceID></DeviceID>',
        '<StreamID></StreamID>',
        '<ClientIP></ClientIP>',
        '<DRMServerURL>' + la_url + '</DRMServerURL>',
        '<DRMAckServerURL></DRMAckServerURL>',
        '<DRMHeartBeatURL></DRMHeartBeatURL>',
        '<DRMHeartBeatPeriod>0</DRMHeartBeatPeriod>',
        '<UserData></UserData>',
        '<Portal></Portal>',
        '<StoreFront></StoreFront>',
        '<BandwidthCheckURL></BandwidthCheckURL>',
        '<BandwidthCheckInterval></BandwidthCheckInterval>',
        '</WidevineCredentialsInfo>',
    ].join("")

    // Message type for widevine
    var msgType = "application/widevine+xml";

    // Unique ID of DRM system
    var drmSystemId = "urn:dvb:casystemid:19156";

    return webos_request( "luna://com.webos.service.drm", {
            method:"sendDrmMessage",
            parameters: {
                "clientId": clientId,
                "msgType": msgType,
                "msg": msg,
                "drmSystemId": drmSystemId
            }})
            .then( function (result) {
                // DRM API does not return the msgId, resultCode, resultMsg for Widevine type.
                console.log("sendDrmMessage succeeded. %s", JSON.stringify(result));
                return
            });
}

function subscribeLicensingError(clientId, msgId)
{
    return webos_subscribe("luna://com.webos.service.drm", {
        method:"getRightsError",
        parameters: {
            "clientId": clientId
        }},
        function (result) { // Subscription Callback
            var contentId = result.contentId;
            if (contentId == msgId) {
                if ( 0 == result.errorState) {
                    console.log("No license");
                    // Do something for error handling
                }
                else if ( 1 == result.errorState) {
                    console.log("Invalid license");
                    // Do something for error handling
                }
                else {
                    console.log("Unknown errorState: %s", JSON.stringify(result));
                }
            }
            else {
                console.log("skip notification %s", JSON.stringify(result));
            }
        });
}

var video = document.getElementById('myVideo');

function playback()
{
    var config = {

        'dash+wv': {
            type: "application/dash+xml",
            mediaTransportType: "WIDEVINE",
            url: 'https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd',
            la_url: 'https://widevine-proxy.appspot.com/proxy'
        },

        'hls+wv': {
            type: "application/x-mpegURL",
            mediaTransportType: "HLS",
            url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine-hls/hls.m3u8',
            la_url: 'https://cwip-shaka-proxy.appspot.com/no_auth'
        },

        'mp4': {
            type: "video/mp4",
            url: "https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/Sample-MP4-Video-File-Download.mp4"
        },

        'hls': {
            type: "application/vnd.apple.mpegurl",
            url: "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8",
        },

        'dash': {
            type: "application/dash+xml",
            url: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd'
        }
    }

    var stream = config['hls+wv']

    var prepare

    if (stream.la_url) {
        prepare = loadDrmClient()
        .then( function(id) {
            subscribeLicensingError(id, undefined);

            document.body.addEventListener("unload", function() {
                webOS.service.request("luna://com.webos.service.drm", {
                    method:"unload",
                    parameters: { "clientId": id },
                    onSuccess: function (result) {
                        console.log("DRM Client is unloaded successfully.");
                    },
                    onFailure: function (result) {
                        console.log("[" + result.errorCode + "] " + result.errorText);
                        // Do something for error handling
                    }
                });
            })
            return sendRightInformation(id, stream.url, stream.la_url).then( function () { return id; })
        })
    }
    else {
        prepare = Promise.resolve()
    }

    return prepare.then( function (id) {
        var type = stream.type

        if (stream.la_url) {

            var options = {
                mediaTransportType: stream.mediaTransportType,
                option: {
                    drm: {
                        type: drmType,
                        clientId: id,
                    }
                }
            };

            console.log("Options: %s", JSON.stringify(options));
            var mediaOption = encodeURIComponent(JSON.stringify(options));
            type += ';mediaOption=' + mediaOption;
        }

        console.log("open url: %s", stream.url)
        console.log("type    : %s", type)
        var source = document.createElement("source");
        source.setAttribute('src', stream.url);
        source.setAttribute('type', type);

        video.addEventListener('loadedmetadata', function(event) {
            console.log("loadedmetadata %O", event)
        });

        video.addEventListener('error', function(e) {
            console.error('error', e);
        });

        video.addEventListener('stalled', function(e) {
            console.log('stalled', e);
        });

        video.addEventListener('loadeddata', function() {
            console.log('[Device_Webos_Player] loadeddata');
        });

        video.addEventListener('loadedmetadata', function() {
            console.log('[Device_Webos_Player] loadedmetadata');
        });

        video.addEventListener('canplay', function () {
            console.log('[Device_Webos_Player] canplay');
        });

        video.addEventListener('durationchange', function() {
            console.log('[Device_Webos_Player] durationchange: ' + video.duration);
        });

        video.addEventListener('timeupdate', function() {
            console.log('[Device_Webos_Player] timeupdate: ' + video.currentTime);
        }, { once: true});


        video.appendChild(source);
        // video.load()
        video.play()
    })
}

playback()

Solution

  • Finally the idea is [see here]

    See "Streaming Protocol & DRM Combination" here