如何使用本机 WebOS 播放器播放 DRM 内容

How to play DRM content using native WebOS player

我们正在尝试使用本机 WebOS 播放器和 com.webos.service.drm 服务播放视频内容。目标是至少播放一些 DRM 内容(dash+widevine 或 hls+widevine)。清除内容工作正常,但 DRM 内容完全卡住,并且没有出现诊断消息。

同样的结果发生在 WebOS 3.4、4.4 和 6.0 上。是否有任何 DRM 内容播放的工作示例?

我们的代码如下。

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()

最后思路是[见here]

  • MPEG-DASH webOS TV 正式不支持流式传输。
  • HLS 仅在 public 版本
  • 中播放 AES-128 DRM
  • HTML5 EME/MSE 可以同时使用 widevine 和 playready

参见“流媒体协议和 DRM 组合”here