强制将 Prime Video 锁定到最高画质
// ==UserScript==
// @name Prime Video Force HD
// @name:ja Prime Video 高画質のみ
// @name:zh-CN Prime Video 强制高画质
// @name:zh-TW Prime Video 強制高畫質
// @namespace http://tampermonkey.net/
// @version 1.0.5
// @description Force lock Prime Video's video quality to the highest
// @description:ja Prime Videoで再生中の動画の画質を最高画質に設定する
// @description:zh-CN 强制将 Prime Video 锁定到最高画质
// @description:zh-TW 強制將 Prime Video 鎖定到最高畫質
// @author TGSAN
// @match *://*.primevideo.com/*
// @include /https?:\/\/.*.?amazon\..+\/(.+\/)?gp\/video\/.*?/
// @include /https?:\/\/.*.?amazon\..+\/(.+\/)?Amazon-Video\/.*?/
// @icon https://www.google.com/s2/favicons?sz=64&domain=www.primevideo.com
// @inject-into page
// @run-at document-start
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
let windowCtx = self.window;
if (self.unsafeWindow) {
console.log("[Prime Video Force HD] use unsafeWindow mode");
windowCtx = self.unsafeWindow;
} else {
console.log("[Prime Video Force HD] use window mode (your userscript extensions not support unsafeWindow)");
}
function HookMPD(mpd) {
const parser = new DOMParser();
const mpdDoc = parser.parseFromString(mpd, "application/xml");
const adaptationSets = mpdDoc.getElementsByTagName("AdaptationSet");
for (let i = 0; i < adaptationSets.length; i++) {
let adaptationSet = adaptationSets[i];
let contentType = adaptationSet.getAttribute("contentType") || "";
let representations = adaptationSet.getElementsByTagName("Representation");
let representationsCopped = [];
let maxBandwidth = -1;
for (let repi = 0; repi < representations.length; repi++) {
const curBandwidth = Number.parseFloat(representations[repi].getAttribute("bandwidth"));
// console.log(curBandwidth);
if (curBandwidth >= maxBandwidth) {
maxBandwidth = curBandwidth;
}
representationsCopped.push(representations[repi]);
}
for (let repi = 0; repi < representationsCopped.length; repi++) {
const curBandwidth = Number.parseFloat(representationsCopped[repi].getAttribute("bandwidth"));
if (curBandwidth != maxBandwidth) {
adaptationSet.removeChild(representationsCopped[repi]);
}
}
if (contentType.toUpperCase() == "VIDEO") {
console.log(contentType + " set max bitrate: " + maxBandwidth + " bps (" + adaptationSet.getAttribute("maxWidth") + "x" + adaptationSet.getAttribute("maxHeight") + ")");
} else {
console.log(contentType + " set max bitrate: " + maxBandwidth + " bps");
}
}
const serializer = new XMLSerializer();
const newBody = serializer.serializeToString(mpdDoc);
let body = newBody;
// console.log(body);
return body;
}
const originXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
const xhr = this;
let url = arguments[1];
// console.log("XHR: " + url);
if (url.indexOf('.mpd') > -1) {
const responseGetter = Object.getOwnPropertyDescriptor(
XMLHttpRequest.prototype,
"response"
).get;
Object.defineProperty(xhr, "response", {
get: () => {
let result = responseGetter.call(xhr);
return HookMPD(result);
},
});
const responseTextGetter = Object.getOwnPropertyDescriptor(
XMLHttpRequest.prototype,
"responseText"
).get;
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = responseTextGetter.call(xhr);
return HookMPD(result);
},
});
}
return originXHROpen.apply(xhr, arguments);
};
const originFetchPrimeVideoFetch = windowCtx.fetch;
windowCtx.fetch = (...arg) => {
let url = "";
let isRequest = false;
switch (typeof arg[0]) {
case "object":
url = arg[0].url;
isRequest = true;
break;
case "string":
url = arg[0];
break;
default:
break;
}
// console.log("FETCH: " + url);
if (url.indexOf('.mpd') > -1) {
return new Promise((resolve, reject) => {
originFetchPrimeVideoFetch(...arg).then(res => {
res.text().then(text => {
let body = HookMPD(text);
let newRes = new Response(body, {
status: res.status,
statusText: res.statusText,
headers: res.headers
})
resolve(newRes);
}).catch(err => {
reject(err);
});
}).catch(err => {
reject(err);
});
});
} else {
return originFetchPrimeVideoFetch(...arg);
}
}
})();