videojs-contrib-eme
Version:
Supports Encrypted Media Extensions for playback of encrypted content in Video.js
3 lines (2 loc) • 18 kB
JavaScript
/*! @name videojs-contrib-eme @version 5.5.0 @license Apache-2.0 */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("video.js")):"function"==typeof define&&define.amd?define(["exports","video.js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).videojsContribEme={},e.videojs)}(this,(function(e,t){"use strict";function s(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=s(t);function i(){return i=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var s=arguments[t];for(var r in s)Object.prototype.hasOwnProperty.call(s,r)&&(e[r]=s[r])}return e},i.apply(this,arguments)}const n=(e,t)=>{if(e===t)return!0;if(e.byteLength!==t.byteLength)return!1;const s=new DataView(e),r=new DataView(t);for(let e=0;e<s.byteLength;e++)if(s.getUint8(e)!==r.getUint8(e))return!1;return!0},o=e=>e instanceof Uint8Array||e instanceof Uint16Array?e.buffer:e,a=(...e)=>{const t=r.default.obj||r.default;return(t.merge||t.mergeOptions).apply(t,e)},y=(...e)=>{const t=a(...e);return Object.keys(t).forEach((e=>{null===t[e]&&delete t[e]})),t},d=e=>{const t=[];return Object.keys(e).forEach((s=>{const r=E(s,e[s])[0];t.push(r)})),t};let c=r.default.xhr.httpHandler;c||(c=(e,t)=>(s,r,i)=>{if(s)e(s);else{if(r.statusCode>=400&&r.statusCode<=599){let s=i;return t&&(s=String.fromCharCode.apply(null,new Uint8Array(i))),void e({cause:s})}e(null,i)}});const m=(e,t,s,i,n)=>{const o=(e=>{const t=String.fromCharCode.apply(null,new Uint16Array(e)),s=(new window.DOMParser).parseFromString(t,"application/xml"),r=s.getElementsByTagName("HttpHeaders")[0];let i={};if(r){const e=r.getElementsByTagName("name"),t=r.getElementsByTagName("value");for(let s=0;s<e.length;s++)i[e[s].childNodes[0].nodeValue]=t[s].childNodes[0].nodeValue}const n=s.getElementsByTagName("Challenge")[0];let o;return n&&(o=window.atob(n.childNodes[0].nodeValue)),s.querySelector("parsererror")&&(i={"Content-Type":"text/xml; charset=utf-8",SOAPAction:'"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'},o=e),{headers:i,message:o}})(s),a=o.message,d=y(o.headers,i.emeHeaders,t.licenseHeaders);r.default.xhr({uri:t.url,method:"post",headers:d,body:a,responseType:"arraybuffer",requestType:"license",metadata:{keySystem:e}},c(n,!0))},l={EMEFailedToRequestMediaKeySystemAccess:"eme-failed-request-media-key-system-access",EMEFailedToCreateMediaKeys:"eme-failed-create-media-keys",EMEFailedToAttachMediaKeysToVideoElement:"eme-failed-attach-media-keys-to-video",EMEFailedToCreateMediaKeySession:"eme-failed-create-media-key-session",EMEFailedToSetServerCertificate:"eme-failed-set-server-certificate",EMEFailedToGenerateLicenseRequest:"eme-failed-generate-license-request",EMEFailedToUpdateSessionWithReceivedLicenseKeys:"eme-failed-update-session",EMEFailedToCloseSession:"eme-failed-close-session",EMEFailedToRemoveKeysFromSession:"eme-failed-remove-keys",EMEFailedToLoadSessionBySessionId:"eme-failed-load-session"},p="com.apple.fps.1_0",u=({initData:e,id:t,cert:s})=>{"string"==typeof t&&(t=(e=>{const t=new ArrayBuffer(2*e.length),s=new Uint16Array(t);for(let t=0;t<e.length;t++)s[t]=e.charCodeAt(t);return s})(t));let r=0;const i=new ArrayBuffer(e.byteLength+4+t.byteLength+4+s.byteLength),n=new DataView(i);new Uint8Array(i,r,e.byteLength).set(e),r+=e.byteLength,n.setUint32(r,t.byteLength,!0),r+=4;const o=new Uint16Array(i,r,t.length);o.set(t),r+=o.byteLength,n.setUint32(r,s.byteLength,!0),r+=4;return new Uint8Array(i,r,s.byteLength).set(s),new Uint8Array(i,0,i.byteLength)},f=(e,t)=>(s,i)=>{const n=y(s.emeHeaders,t.certificateHeaders);r.default.xhr({uri:t.certificateUri,responseType:"arraybuffer",requestType:"license",metadata:{keySystem:e},headers:n},c(((e,t)=>{e?i(e):i(null,new Uint8Array(t))})))},g=(e,t)=>(e=>{const t=document.createElement("a");return t.href=e,t.hostname})(t),k=(e,t)=>(s,i,n,o)=>{const a=y({"Content-type":"application/octet-stream"},s.emeHeaders,t.licenseHeaders);r.default.xhr({uri:t.licenseUri||t.url,method:"POST",responseType:"arraybuffer",requestType:"license",metadata:{keySystem:e,contentId:i},body:n,headers:a},c(o,!0))},h=({video:e,initData:t,options:s,eventBus:r,emeError:i})=>{const n=s.keySystems["com.apple.fps.1_0"],o=n.getCertificate||f(p,n),a=n.getContentId||g,y=n.getLicense||k(p,n);return new Promise(((e,t)=>{o(s,((s,r)=>{if(s){return i(s,{errorType:l.EMEFailedToSetServerCertificate,keySystem:p}),void t(s)}e(r)}))})).then((n=>{return(({video:e,contentId:t,initData:s,cert:r,options:i,getLicense:n,eventBus:o,emeError:a})=>new Promise(((y,d)=>{if(!e.webkitKeys)try{e.webkitSetMediaKeys(new window.WebKitMediaKeys(p))}catch(e){return a(e,{errorType:l.EMEFailedToCreateMediaKeys,keySystem:p}),void d("Could not create MediaKeys")}let c;try{c=e.webkitKeys.createSession("video/mp4",u({id:t,initData:s,cert:r}))}catch(e){return a(e,{errorType:l.EMEFailedToCreateMediaKeySession,keySystem:p}),void d("Could not create key session")}v(o,{type:"keysessioncreated",keySession:c}),c.contentId=t,c.addEventListener("webkitkeymessage",(e=>{v(o,{type:"keymessage",messageEvent:e}),n(i,t,e.message,((e,t)=>{if(o&&v(o,{type:"licenserequestattempted"}),e)return a(e,{errortype:l.EMEFailedToGenerateLicenseRequest,keySystem:p}),void d(e);c.update(new Uint8Array(t)),v(o,{type:"keysessionupdated",keySession:c})}))})),c.addEventListener("webkitkeyadded",(()=>{y()})),c.addEventListener("webkitkeyerror",(()=>{const e=c.error;a(e,{errorType:l.EMEFailedToUpdateSessionWithReceivedLicenseKeys,keySystem:p}),d(`KeySession error: code ${e.code}, systemCode ${e.systemCode}`)}))})))({video:e,cert:n,initData:t,getLicense:y,options:s,contentId:a(s,(o=t,String.fromCharCode.apply(null,new Uint16Array(o.buffer||o)))),eventBus:r,emeError:i});var o}))},S=e=>e.startsWith("com.apple.fps"),v=(e,t)=>{e.isDisposed()||e.trigger(i({},t))},E=(e,t)=>{if(t.supportedConfigurations)return t.supportedConfigurations;const s=S(e),r={},n=t.initDataTypes||(s?["sinf"]:null),o=t.audioContentType,a=t.audioRobustness,y=t.videoContentType||(s?"video/mp4":null),d=t.videoRobustness,c=t.persistentState;return(o||a)&&(r.audioCapabilities=[i({},o?{contentType:o}:{},a?{robustness:a}:{})]),(y||d)&&(r.videoCapabilities=[i({},y?{contentType:y}:{},d?{robustness:d}:{})]),c&&(r.persistentState=c),n&&(r.initDataTypes=n),[r]},w=(e,t)=>{const{mediaKeys:s,initDataType:i,initData:n,options:o,getLicense:a,removeSession:y,eventBus:d,contentId:c,emeError:m,keySystem:p}=t;try{const u=s.createSession();return v(d,{type:"keysessioncreated",keySession:u}),e.on("dispose",(()=>{u.close().then((()=>{v(d,{type:"keysessionclosed",keySession:u})})).catch((e=>{m(e,{errorType:l.EMEFailedToCloseSession,keySystem:p})}))})),new Promise(((s,f)=>{u.addEventListener("message",(e=>{v(d,{type:"keymessage",messageEvent:e}),"license-request"!==e.messageType&&"license-renewal"!==e.messageType||a(o,e.message,c).then((e=>{s(u.update(e).then((()=>{v(d,{type:"keysessionupdated",keySession:u})})).catch((e=>{m(e,{errorType:l.EMEFailedToUpdateSessionWithReceivedLicenseKeys,keySystem:p})})))})).catch((e=>{f(e)}))}),!1);const g="keystatuseschange";u.addEventListener(g,(s=>{let i=!1;d.isDisposed()||(v(d,{type:g,keyStatuses:u.keyStatuses}),u.keyStatuses.forEach(((e,t)=>{switch(v(d,{keyId:t,status:e,target:u,type:"keystatuschange"}),e){case"expired":i=!0;break;case"internal-error":const e='Key status reported as "internal-error." Leaving the session open since we don\'t have enough details to know if this error is fatal.';r.default.log.warn(e,s)}})),i&&(r.default.log.debug("Session expired, closing the session."),u.close().then((()=>{d.isDisposed()||(v(d,{type:"keysessionclosed",keySession:u}),y(n),w(e,t))})).catch((e=>{m(e,{errorType:l.EMEFailedToCloseSession,keySystem:p})}))))}),!1),u.generateRequest(i,n).catch((e=>{m(e,{errorType:l.EMEFailedToGenerateLicenseRequest,keySystem:p}),f("Unable to create or initialize key session")}))}))}catch(e){m(e,{errorType:l.EMEFailedToCreateMediaKeySession,keySystem:p})}},b=(e,t)=>{if("string"==typeof t&&(t={url:t}),!t.url&&t.licenseUri&&(t.url=t.licenseUri),!t.url&&!t.getLicense)throw new Error(`Missing url/licenseUri or getLicense in ${e} keySystem configuration.`);const s=S(e);if(s&&t.certificateUri&&!t.getCertificate&&(t.getCertificate=f(e,t)),s&&!t.getCertificate)throw new Error(`Missing getCertificate or certificateUri in ${e} keySystem configuration.`);return s&&!t.getContentId&&(t.getContentId=g),t.url&&!t.getLicense&&(t.getLicense="com.microsoft.playready"===e?((e,t)=>(s,r,i)=>{m(e,t,r,s,i)})(e,t):s?k(e,t):((e,t)=>(s,i,n)=>{const o=y({"Content-type":"application/octet-stream"},s.emeHeaders,t.licenseHeaders);r.default.xhr({uri:t.url,method:"POST",responseType:"arraybuffer",requestType:"license",metadata:{keySystem:e},body:i,headers:o},c(n,!0))})(e,t)),t},T=({player:e,video:t,initDataType:s,initData:r,keySystemAccess:i,options:n,removeSession:o,eventBus:a,emeError:y})=>{let d=Promise.resolve();const c=i.keySystem;let m;try{m=b(c,n.keySystems[c])}catch(e){return Promise.reject(e)}const p=m.getContentId?m.getContentId(n,(u=r,String.fromCharCode.apply(null,new Uint8Array(u.buffer||u)))):null;var u;if(void 0===t.mediaKeysObject){let s;t.mediaKeysObject=null,t.pendingSessionData=[],d=new Promise(((e,r)=>{t.keySystem=c,m.getCertificate?m.getCertificate(n,((t,i)=>{t?r(t):(s=i,e())})):e(i)})).then((()=>i.createMediaKeys())).then((r=>(v(a,{type:"keysystemaccesscomplete",mediaKeys:r}),(({player:e,video:t,certificate:s,createdMediaKeys:r,emeError:i})=>{t.mediaKeysObject=r;const n=[];s&&n.push(r.setServerCertificate(s).catch((e=>{const s={errorType:l.EMEFailedToSetServerCertificate,keySystem:t.keySystem};i(e,s)})));for(let s=0;s<t.pendingSessionData.length;s++){const r=t.pendingSessionData[s];n.push(w(e,{mediaKeys:t.mediaKeysObject,initDataType:r.initDataType,initData:r.initData,options:r.options,getLicense:r.getLicense,removeSession:r.removeSession,eventBus:r.eventBus,contentId:r.contentId,emeError:r.emeError,keySystem:t.keySystem}))}return t.pendingSessionData=[],n.push(t.setMediaKeys(r).catch((e=>{const s={errorType:l.EMEFailedToAttachMediaKeysToVideoElement,keySystem:t.keySystem};i(e,s)}))),Promise.all(n)})({player:e,video:t,certificate:s,createdMediaKeys:r,emeError:y})))).catch((e=>(y(e,{errorType:l.EMEFailedToCreateMediaKeys,keySystem:c}),e?Promise.reject(e):Promise.reject("Failed to create and initialize a MediaKeys object"))))}return d.then((()=>{const i=t.keySystem?((e,t,s)=>(r,i,n)=>new Promise(((o,a)=>{const y=function(e,t){s&&v(s,{type:"licenserequestattempted"}),e?a(e):o(t)};S(e)?t(r,n,new Uint8Array(i),y):t(r,i,y)})))(c,m.getLicense,a):null;return(({player:e,video:t,initDataType:s,initData:r,options:i,getLicense:n,contentId:o,removeSession:a,eventBus:y,emeError:d})=>{const c={initDataType:s,initData:r,options:i,getLicense:n,removeSession:a,eventBus:y,contentId:o,emeError:d,keySystem:t.keySystem};return t.mediaKeysObject?(c.mediaKeys=t.mediaKeysObject,w(e,c)):(t.pendingSessionData.push(c),Promise.resolve())})({player:e,video:t,initDataType:s,initData:r,options:n,getLicense:i,contentId:p,removeSession:o,eventBus:a,emeError:y})}))},M="com.microsoft.playready",C=(e,t,s,r,i)=>{const n=e.msKeys.createSession("video/mp4",t);if(!n){const e=new Error("Could not create key session.");throw i(e,{errorType:l.EMEFailedToCreateMediaKeySession,keySystem:M}),e}v(r,{type:"keysessioncreated",keySession:n}),n.addEventListener("mskeymessage",(e=>{v(r,{type:"keymessage",messageEvent:e}),((e,t,s,r,i)=>{let n=e.keySystems[M];if("function"==typeof n.getKey)return void n.getKey(e,s.destinationURL,s.message.buffer,((s,n)=>{if(s){const n={errorType:l.EMEFailedToRequestMediaKeySystemAccess,config:d(e.keySystems)};return i(s,n),void v(r,{message:"Unable to get key: "+s,target:t,type:"mskeyerror"})}t.update(n),v(r,{type:"keysessionupdated",keySession:t})}));"string"==typeof n?n={url:n}:"boolean"==typeof n&&(n={}),n.url||(n.url=s.destinationURL);const o=(e,s)=>{if(r&&v(r,{type:"licenserequestattempted"}),e)return i(e,{errorType:l.EMEFailedToGenerateLicenseRequest,keySystem:M}),void v(r,{message:"Unable to request key from url: "+n.url,target:t,type:"mskeyerror"});t.update(new Uint8Array(s))};n.getLicense?n.getLicense(e,s.message.buffer,o):m(M,n,s.message.buffer,e,o)})(s,n,e,r,i)})),n.addEventListener("mskeyerror",(e=>{const t={errorType:l.EMEFailedToCreateMediaKeySession,keySystem:M};i(n.error,t),v(r,{message:`Unexpected key error from key session with code: ${n.error.code} and systemCode: ${n.error.systemCode}`,target:n,type:"mskeyerror"})})),n.addEventListener("mskeyadded",(()=>{v(r,{target:n,type:"mskeyadded"})}))};const K=[{initDataTypes:["cenc"],audioCapabilities:[{contentType:'audio/mp4;codecs="mp4a.40.2"'}],videoCapabilities:[{contentType:'video/mp4;codecs="avc1.42E01E"'}]}],L=[{keySystem:"com.apple.fps",supportedConfig:[{initDataTypes:["sinf"],videoCapabilities:[{contentType:"video/mp4"}]}]},{keySystem:"com.microsoft.playready.recommendation",supportedConfig:K},{keySystem:"com.widevine.alpha",supportedConfig:K},{keySystem:"org.w3.clearkey",supportedConfig:K}],D=()=>{const e=window.Promise,t={fairplay:Boolean(window.WebKitMediaKeys),playready:!1,widevine:!1,clearkey:!1};return window.MediaKeys&&window.navigator.requestMediaKeySystemAccess?e.all(L.map((({keySystem:e,supportedConfig:t})=>window.navigator.requestMediaKeySystemAccess(e,t).catch((()=>{}))))).then((([e,s,r,i])=>(t.fairplay=Boolean(e),t.playready=Boolean(s),t.widevine=Boolean(r),t.clearkey=Boolean(i),t))):e.resolve(t)};const F=(e,t)=>{for(let s=0;s<e.length;s++){if(!e[s].initData)continue;const r=o(e[s].initData),i=o(t);if(n(r,i))return!0}return!1},_=(e,t)=>{for(let s=0;s<e.length;s++)if(e[s].initData===t)return void e.splice(s,1)},U=(e,t,s,i,n,o)=>{if(!s||!s.keySystems)return Promise.resolve();if(s.keySystems["com.apple.fps.1_0"])return r.default.log.debug("eme","Ignoring 'encrypted' event, using legacy fairplay keySystem com.apple.fps.1_0"),Promise.resolve();let a=t.initData;return(e=>{let t;return Object.keys(e).forEach((s=>{const r=E(s,e[s]);t=t?t.catch((e=>window.navigator.requestMediaKeySystemAccess(s,r))):window.navigator.requestMediaKeySystemAccess(s,r)})),t})(s.keySystems).then((r=>{const y=r.keySystem;return s.keySystems[y]&&s.keySystems[y].pssh&&(a=s.keySystems[y].pssh),F(i,a)||!a?Promise.resolve():(i.push({initData:a}),T({player:e,video:t.target,initDataType:t.initDataType,initData:a,keySystemAccess:r,options:s,removeSession:_.bind(null,i),eventBus:n,emeError:o}))})).catch((e=>{const t={errorType:l.EMEFailedToRequestMediaKeySystemAccess,config:d(s.keySystems)};o(e,t)}))},A=(e,t,s,r)=>t.keySystems&&t.keySystems["com.apple.fps.1_0"]&&e.initData?h({video:e.target,initData:e.initData,options:t,eventBus:s,emeError:r}):Promise.resolve(),q=(e,t,s,r,i)=>{if(!t.keySystems||!t.keySystems[M])return;if(s.reduce(((e,t)=>e||t.playready),!1))return;let n=e.initData;t.keySystems[M]&&t.keySystems[M].pssh&&(n=t.keySystems[M].pssh),n&&(s.push({playready:!0,initData:n}),(({video:e,initData:t,options:s,eventBus:r,emeError:i})=>{e.msKeys&&delete e.msKeys;try{e.msSetMediaKeys(new window.MSMediaKeys(M))}catch(e){throw i(e,{errorType:l.EMEFailedToCreateMediaKeys,keySystem:M}),new Error("Unable to create media keys for PlayReady key system. Error: "+e.message)}C(e,t,s,r,i)})({video:e.target,initData:n,options:t,eventBus:r,emeError:i}))},B=e=>a(e.currentSource(),e.eme.options),j=e=>{const t=e.src();t!==e.eme.activeSrc&&(e.eme.activeSrc=t,e.eme.sessions=[])},P=e=>(t,s)=>{const r={code:5};"string"==typeof t?r.message=t:t&&(t.message&&(r.message=t.message),t.cause&&(t.cause.length||t.cause.byteLength)&&(r.cause=t.cause),t.keySystem&&(r.keySystem=t.keySystem),r.originalError=t),s&&(r.metadata=s),e.error(r)},R=(e,t)=>{if("video"===e.$(".vjs-tech").tagName.toLowerCase())if(j(e),window.MediaKeys)e.tech_.el_.addEventListener("encrypted",(s=>{r.default.log.debug("eme","Received an 'encrypted' event"),j(e),U(e,s,B(e),e.eme.sessions,e.tech_,t)}));else if(window.WebKitMediaKeys)e.eme.initLegacyFairplay();else if(window.MSMediaKeys){e.tech_.el_.addEventListener("msneedkey",(s=>{r.default.log.debug("eme","Received an 'msneedkey' event"),j(e);try{q(s,B(e),e.eme.sessions,e.tech_,t)}catch(e){t(e)}}));const s=e=>{t(e)};e.tech_.on("mskeyerror",s),e.on("dispose",(()=>{e.tech_.off("mskeyerror",s)}))}},O=function(e={}){const t=this,s=P(t);t.ready((()=>R(t,s))),t.eme={setupEmeListeners(){R(t,s)},initializeMediaKeys(r={},i=function(){},n=!1){const o=a(t.currentSource(),e,r),y={initDataType:"cenc",initData:null,target:t.tech_.el_};if(j(t),window.MediaKeys)U(t,y,o,t.eme.sessions,t.tech_,s).then((()=>i())).catch((e=>{i(e),n||s(e)}));else if(window.MSMediaKeys){const e=r=>{t.tech_.off("mskeyadded",e),t.tech_.off("mskeyerror",e),"mskeyerror"===r.type?(i(r.target.error),n||s(r.message)):i()};t.tech_.one("mskeyadded",e),t.tech_.one("mskeyerror",e);try{q(y,o,t.eme.sessions,t.tech_,s)}catch(r){t.tech_.off("mskeyadded",e),t.tech_.off("mskeyerror",e),i(r),n||s(r)}}},initLegacyFairplay(){const e=e=>{r.default.log.debug("eme","Received a 'webkitneedkey' event"),j(t),A(e,B(t),t.tech_,s).catch((e=>{s(e)}))},i=s=>{const r=B(t).firstWebkitneedkeyTimeout||1e3,i=t.src();t.eme.webkitneedkey_=t.eme.webkitneedkey_||{},t.eme.webkitneedkey_.src!==i&&(t.eme.webkitneedkey_={handledFirstEvent:!1,src:i}),t.eme.webkitneedkey_.handledFirstEvent?e(s):(t.clearTimeout(t.eme.webkitneedkey_.timeout),t.eme.webkitneedkey_.timeout=t.setTimeout((()=>{t.eme.webkitneedkey_.handledFirstEvent=!0,t.eme.webkitneedkey_.timeout=null,e(s)}),r))};let n=t.tech_.el_;n.addEventListener("webkitneedkey",i);const o=()=>{t.off("dispose",o),null!==n&&n.removeEventListener("webkitneedkey",i),n=null};return t.on("dispose",o),o},detectSupportedCDMs:D,options:e}};r.default.registerPlugin("eme",O),O.Error=l,O.VERSION="5.5.0",e.default=O,e.emeErrorHandler=P,e.getOptions=B,e.handleEncryptedEvent=U,e.handleMsNeedKeyEvent=q,e.handleWebKitNeedKeyEvent=A,e.hasSession=F,e.removeSession=_,e.setupSessions=j,Object.defineProperty(e,"__esModule",{value:!0})}));