Netflix Plus

在 Netflix 上开启最佳音视频质量和更多功能

目前为 2024-05-25 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Netflix Plus
  3. // @name:zh-CN Netflix Plus
  4. // @name:zh-TW Netflix Plus
  5. // @namespace http://tampermonkey.net/
  6. // @version 1.15
  7. // @description Enable best audio and video and more features on Netflix
  8. // @description:zh-CN 在 Netflix 上开启最佳音视频质量和更多功能
  9. // @description:zh-TW 在 Netflix 上啓用最佳影音品質和更多功能
  10. // @author TGSAN
  11. // @match https://www.netflix.com/*
  12. // @icon https://www.google.com/s2/favicons?sz=64&domain=netflix.com
  13. // @run-at document-start
  14. // @grant unsafeWindow
  15. // @grant GM_setValue
  16. // @grant GM_getValue
  17. // @grant GM_registerMenuCommand
  18. // @grant GM_unregisterMenuCommand
  19. // ==/UserScript==
  20.  
  21. (async () => {
  22. "use strict";
  23.  
  24. let windowCtx = self.window;
  25. if (self.unsafeWindow) {
  26. console.log("[Netflix Plus] use unsafeWindow mode");
  27. windowCtx = self.unsafeWindow;
  28. } else {
  29. console.log("[Netflix Plus] use window mode (your userscript extensions not support unsafeWindow)");
  30. }
  31.  
  32. const Event = class {
  33. constructor(script, target) {
  34. this.script = script;
  35. this.target = target;
  36.  
  37. this._cancel = false;
  38. this._replace = null;
  39. this._stop = false;
  40. }
  41.  
  42. preventDefault() {
  43. this._cancel = true;
  44. }
  45. stopPropagation() {
  46. this._stop = true;
  47. }
  48. replacePayload(payload) {
  49. this._replace = payload;
  50. }
  51. };
  52.  
  53. let callbacks = [];
  54. windowCtx.addBeforeScriptExecuteListener = (f) => {
  55. if (typeof f !== "function") {
  56. throw new Error("Event handler must be a function.");
  57. }
  58. callbacks.push(f);
  59. };
  60. windowCtx.removeBeforeScriptExecuteListener = (f) => {
  61. let i = callbacks.length;
  62. while (i--) {
  63. if (callbacks[i] === f) {
  64. callbacks.splice(i, 1);
  65. }
  66. }
  67. };
  68.  
  69. const dispatch = (script, target) => {
  70. if (script.tagName !== "SCRIPT") {
  71. return;
  72. }
  73.  
  74. const e = new Event(script, target);
  75.  
  76. if (typeof windowCtx.onbeforescriptexecute === "function") {
  77. try {
  78. windowCtx.onbeforescriptexecute(e);
  79. } catch (err) {
  80. console.error(err);
  81. }
  82. }
  83.  
  84. for (const func of callbacks) {
  85. if (e._stop) {
  86. break;
  87. }
  88. try {
  89. func(e);
  90. } catch (err) {
  91. console.error(err);
  92. }
  93. }
  94.  
  95. if (e._cancel) {
  96. script.textContent = "";
  97. script.remove();
  98. } else if (typeof e._replace === "string") {
  99. script.textContent = e._replace;
  100. }
  101. };
  102. const observer = new MutationObserver((mutations) => {
  103. for (const m of mutations) {
  104. for (const n of m.addedNodes) {
  105. dispatch(n, m.target);
  106. }
  107. }
  108. });
  109. observer.observe(document, {
  110. childList: true,
  111. subtree: true,
  112. });
  113.  
  114. const menuItems = [
  115. ["setMaxBitrate", "Automatically select best bitrate available"],
  116. ["useallSub", "Show all audio-tracks and subs"],
  117. ["closeimsc", "Use SUP subtitle replace IMSC subtitle"],
  118. ["useDDPandHA", "Enable Dolby and HE-AAC 5.1 Audio"],
  119. ["useFHDAndAVCH", "Focus 1080P and High-AVC"],
  120. ];
  121. let menuCommandList = [];
  122.  
  123. windowCtx.globalOptions = {
  124. useFHDAndAVCH: !checkAdvancedDrm(),
  125. useDDPandHA: true,
  126. setMaxBitrate: true,
  127. useallSub: true,
  128. get ["useddplus"]() {
  129. return windowCtx.globalOptions.useDDPandHA;
  130. },
  131. useAVC: false,
  132. get ["useFHD"]() {
  133. return windowCtx.globalOptions.useFHDAndAVCH;
  134. },
  135. usedef: false,
  136. get ["useHA"]() {
  137. return windowCtx.globalOptions.useDDPandHA;
  138. },
  139. get ["useAVCH"]() {
  140. return windowCtx.globalOptions.useFHDAndAVCH;
  141. },
  142. usevp9: false,
  143. useav1: false,
  144. useprk: true,
  145. usehevc: false,
  146. usef4k: true,
  147. usef12k: false,
  148. closeimsc: true
  149. };
  150.  
  151. windowCtx.onbeforescriptexecute = function (e) {
  152. let scripts = document.getElementsByTagName("script");
  153. if (scripts.length === 0) return;
  154. for (let i = 0; scripts.length > i; i++) {
  155. let dom = scripts[i];
  156. if (dom.src.includes("cadmium-playercore")) {
  157. let playercore = document.createElement('script');
  158. playercore.src = "https://static-os.kumo.moe/js/netflix/cadmium-playercore.js";
  159. playercore.crossOrigin = dom.crossOrigin;
  160. playercore.async = dom.async;
  161. playercore.id = dom.id;
  162. document.head.appendChild(playercore);
  163. dom.remove();
  164. windowCtx.onbeforescriptexecute = null;
  165. break;
  166. }
  167. }
  168. };
  169.  
  170. async function checkAdvancedDrm() {
  171. let supported = false;
  172. if (windowCtx.MSMediaKeys) {
  173. supported = true;
  174. }
  175. if (windowCtx.WebKitMediaKeys) {
  176. supported = true;
  177. }
  178. // Check L1
  179. let options = [
  180. {
  181. "videoCapabilities": [
  182. {
  183. "contentType": "video/mp4;codecs=avc1.42E01E",
  184. "robustness": "HW_SECURE_ALL"
  185. }
  186. ]
  187. }
  188. ];
  189.  
  190. try {
  191. await navigator.requestMediaKeySystemAccess("com.widevine.alpha.experiment", options);
  192. supported = true;
  193. } catch {}
  194. console.log("Supported advanced DRM: " + supported);
  195. return supported;
  196. }
  197.  
  198. function checkSelected(type) {
  199. let selected = GM_getValue("NETFLIX_PLUS_" + type);
  200. if (typeof selected == "boolean") {
  201. return selected;
  202. } else {
  203. return windowCtx.globalOptions[type];
  204. }
  205. }
  206.  
  207. function registerSelectableVideoProcessingMenuCommand(name, type) {
  208. let selected = checkSelected(type);
  209. windowCtx.globalOptions[type] = selected;
  210. return GM_registerMenuCommand((checkSelected(type) ? "✅" : "🔲") + " " + name, function() {
  211. GM_setValue("NETFLIX_PLUS_" + type, !selected);
  212. windowCtx.globalOptions[type] = !selected;
  213. updateMenuCommand();
  214. });
  215. }
  216.  
  217. async function updateMenuCommand() {
  218. for (let command of menuCommandList) {
  219. await GM_unregisterMenuCommand(command);
  220. }
  221. menuCommandList = [];
  222. for (let menuItem of menuItems) {
  223. menuCommandList.push(await registerSelectableVideoProcessingMenuCommand(menuItem[1], menuItem[0]));
  224. }
  225. }
  226.  
  227. updateMenuCommand();
  228. })();