// ==UserScript==
// @name EME Logger
// @namespace http://gf.qytechs.cn/
// @version 0.1
// @description Inject EME interface and log its function calls.
// @author cramer
// @match *://*/*
// @run-at document-start
// @grant none
// ==/UserScript==
(async () => {
const indent = (s,n=4) => s.split('\n').map(l=>Array(n).fill(' ').join('')+l).join('\n');
const b64 = {
decode: s => Uint8Array.from(atob(s), c => c.charCodeAt(0)),
encode: b => btoa(String.fromCharCode(...new Uint8Array(b)))
};
const fnproxy = (object, func) => new Proxy(object, { apply: func });
const proxy = (object, key, func) => Object.defineProperty(object, key, {
value: fnproxy(object[key], func)
});
proxy(Navigator.prototype, 'requestMediaKeySystemAccess', async (_target, _this, _args) => {
const [keySystem, supportedConfigurations] = _args;
console.log(
`[EME] Navigator::requestMediaKeySystemAccess\n` +
` Key System: ${keySystem}\n` +
` Supported Configurations:\n` +
indent(JSON.stringify(supportedConfigurations, null, ' '))
);
return _target.apply(_this, _args);
});
proxy(MediaKeySystemAccess.prototype, 'createMediaKeys', async (_target, _this, _args) => {
console.log(
`[EME] MediaKeySystemAccess::createMediaKeys\n` +
` Key System: ${_this.keySystem}\n` +
` Configurations:\n` +
indent(JSON.stringify(_this.getConfiguration(), null, ' '))
);
return _target.apply(_this, _args);
});
proxy(MediaKeys.prototype, 'setServerCertificate', async (_target, _this, _args) => {
const [serverCertificate] = _args;
console.log(
`[EME] MediaKeys::setServerCertificate\n` +
` Server Certificate: ${b64.encode(serverCertificate)}`
);
return _target.apply(_this, _args);
});
proxy(MediaKeys.prototype, 'createSession', (_target, _this, _args) => {
const [sessionType] = _args;
console.log(
`[EME] MediaKeys::createSession\n` +
` Session Type: ${sessionType || 'temporary (default)'}`
);
return _target.apply(_this, _args);
});
proxy(EventTarget.prototype, 'addEventListener', async (_target, _this, _args) => {
const [type, listener] = _args;
if (_this instanceof MediaKeySession) {
switch(type) {
case 'keystatuseschange': {
_args[1] = fnproxy(listener, (_target, _this, _args) => {
const [event] = _args;
const keySession = event.target;
const {sessionId} = keySession;
console.log(
`[EME] MediaKeySession::keystatuseschange\n` +
` Session ID: ${sessionId || '(not available)'}\n` +
Array.from(keySession.keyStatuses).map(([keyId, status]) =>
` [${status.toUpperCase()}] ${b64.encode(keyId)}`
).join('\n')
);
return _target.apply(_this, _args);;
});
break;
}
case 'message': {
_args[1] = fnproxy(listener, (_target, _this, _args) => {
const [event] = _args;
const keySession = event.target;
const {sessionId} = keySession;
const {message} = event;
console.log(
`[EME] MediaKeySession::message\n` +
` Session ID: ${sessionId || '(not available)'}\n` +
` Message: ${b64.encode(message)}`
);
return _target.apply(_this, _args);;
});
break;
}
}
}
return _target.apply(_this, _args);
});
proxy(MediaKeySession.prototype, 'generateRequest', async (_target, _this, _args) => {
const [initDataType, initData] = _args;
console.log(
`[EME] MediaKeySession::generateRequest\n` +
` Session ID: ${_this.sessionId || '(not available)'}\n` +
` Init Data Type: ${initDataType}\n` +
` Init Data: ${b64.encode(initData)}`
);
return _target.apply(_this, _args);
});
proxy(MediaKeySession.prototype, 'load', async (_target, _this, _args) => {
const [sessionId] = _args;
console.log(
`[EME] MediaKeySession::load\n` +
` Session ID: ${sessionId || '(not available)'}`
);
return _target.apply(_this, _args);
});
proxy(MediaKeySession.prototype, 'update', async (_target, _this, _args) => {
const [response] = _args;
console.log(
`[EME] MediaKeySession::update\n` +
` Session ID: ${_this.sessionId || '(not available)'}\n` +
` Response: ${b64.encode(response)}`
);
return _target.apply(_this, _args);
});
proxy(MediaKeySession.prototype, 'close', async (_target, _this, _args) => {
console.log(
`[EME] MediaKeySession::close\n` +
` Session ID: ${_this.sessionId || '(not available)'}`
);
return _target.apply(_this, _args);
});
proxy(MediaKeySession.prototype, 'remove', async (_target, _this, _args) => {
console.log(
`[EME] MediaKeySession::remove\n` +
` Session ID: ${_this.sessionId || '(not available)'}`
);
return _target.apply(_this, _args);
});
})();