哔哩哔哩自动画质

自动解锁并更改哔哩哔哩视频的画质和音质,实现自动选择最高画质、无损音频及杜比全景声。

目前為 2024-06-30 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 哔哩哔哩自动画质
  3. // @namespace https://github.com/AHCorn/Bilibili-Auto-Quality/
  4. // @version 2.6.1
  5. // @license GPL-3.0
  6. // @description 自动解锁并更改哔哩哔哩视频的画质和音质,实现自动选择最高画质、无损音频及杜比全景声。
  7. // @author 安和(AHCorn)
  8. // @icon https://www.bilibili.com/favicon.ico
  9. // @match *://www.bilibili.com/video/*
  10. // @match *://www.bilibili.com/list/*
  11. // @match *://www.bilibili.com/blackboard/*
  12. // @match *://www.bilibili.com/watchlater/*
  13. // @match *://www.bilibili.com/bangumi/*
  14. // @match *://www.bilibili.com/watchroom/*
  15. // @match *://www.bilibili.com/medialist/*
  16. // @match *://bangumi.bilibili.com/*
  17. // @match *://live.bilibili.com/*
  18. // @grant GM_addStyle
  19. // @grant GM_setValue
  20. // @grant GM_getValue
  21. // @grant GM_registerMenuCommand
  22. // ==/UserScript==
  23.  
  24. (function () {
  25. 'use strict';
  26.  
  27. Object.defineProperty(navigator, 'userAgent', {
  28. value: "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15"
  29. });
  30.  
  31. window.localStorage['bilibili_player_force_DolbyAtmos&8K&HDR'] = 1;
  32.  
  33. GM_addStyle(`
  34. #bilibili-quality-selector {
  35. position: fixed;
  36. top: 50%;
  37. left: 50%;
  38. transform: translate(-50%, -50%);
  39. background-color: #f8f8f8;
  40. padding: 20px;
  41. border-radius: 10px;
  42. box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
  43. display: none;
  44. z-index: 10000;
  45. width: 300px;
  46. text-align: center;
  47. border: 1px solid #ddd;
  48. }
  49. #bilibili-quality-selector button {
  50. display: block;
  51. width: 90%;
  52. margin: 5px auto;
  53. padding: 10px;
  54. border: 1px solid #007bff;
  55. border-radius: 5px;
  56. background-color: white;
  57. color: #007bff;
  58. cursor: pointer;
  59. font-size: 16px;
  60. transition: all 0.3s ease;
  61. }
  62. #bilibili-quality-selector button.active {
  63. background-color: #007bff;
  64. color: white;
  65. }
  66. #bilibili-quality-selector button:hover {
  67. background-color: #0056b3;
  68. color: white;
  69. }
  70. #bilibili-quality-selector button.active:hover {
  71. background-color: #003f7f;
  72. }
  73. `);
  74.  
  75. let hiResAudioEnabled = GM_getValue('hiResAudio', false);
  76. let dolbyAtmosEnabled = GM_getValue('dolbyAtmos', false);
  77. let userQualitySetting = GM_getValue('qualitySetting', ' 自动选择最高画质 ');
  78. let userHasChangedQuality = false;
  79. let takeOverQualityControl = GM_getValue('takeOverQualityControl', false);
  80.  
  81. function isVipUser() {
  82. const vipElement = document.querySelector('.bili-avatar-icon.bili-avatar-right-icon.bili-avatar-icon-big-vip');
  83. const currentQuality = document.querySelector('.bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text');
  84. return vipElement !== null || (currentQuality && currentQuality.textContent.includes('大会员'));
  85. }
  86.  
  87. function selectQualityBasedOnSetting() {
  88. if (userHasChangedQuality) return;
  89.  
  90. const isVip = isVipUser();
  91. console.log(`用户是否为大会员:${isVip ? '是' : '否'}`);
  92. let currentQuality = document.querySelector('.bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text').textContent;
  93. console.log(`当前画质:${currentQuality}`);
  94. console.log(`目标画质:${userQualitySetting}`);
  95.  
  96. const qualityItems = document.querySelectorAll('.bpx-player-ctrl-quality-menu .bpx-player-ctrl-quality-menu-item');
  97. const availableQualities = Array.from(qualityItems)
  98. .map(item => ({
  99. name: item.textContent.trim(),
  100. element: item,
  101. isVipOnly: !!item.querySelector('.bpx-player-ctrl-quality-badge-bigvip')
  102. }))
  103. .filter(quality => isVip || !quality.isVipOnly);
  104.  
  105. console.log(`当前视频可用画质:`, availableQualities.map(q => q.name));
  106.  
  107. const qualityPreferences = ['8K', '杜比视界', 'HDR', '4K', '1080P 高码率', '1080P 60 帧', '1080P', '720P 60 帧', '720P', '480P', '360P'];
  108.  
  109. availableQualities.sort((a, b) => {
  110. const getQualityIndex = (name) => {
  111. for (let i = 0; i < qualityPreferences.length; i++) {
  112. if (name.includes(qualityPreferences[i])) {
  113. return i;
  114. }
  115. }
  116. return qualityPreferences.length;
  117. };
  118. return getQualityIndex(a.name) - getQualityIndex(b.name);
  119. });
  120.  
  121. let targetQuality;
  122. if (userQualitySetting === ' 自动选择最高画质 ') {
  123. targetQuality = availableQualities[0];
  124. } else {
  125. targetQuality = availableQualities.find(quality => quality.name.includes(userQualitySetting));
  126. if (!targetQuality) {
  127. console.log(`未找到目标画质 ${userQualitySetting},将选择最高可用画质`);
  128. targetQuality = availableQualities[0];
  129. }
  130. }
  131.  
  132. console.log(`实际目标画质:${targetQuality.name}`);
  133. targetQuality.element.click();
  134.  
  135. setTimeout(() => {
  136. currentQuality = document.querySelector('.bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text').textContent;
  137.  
  138. const getCurrentQualityIndex = (name) => {
  139. for (let i = 0; i < qualityPreferences.length; i++) {
  140. if (name.includes(qualityPreferences[i])) {
  141. return i;
  142. }
  143. }
  144. return qualityPreferences.length;
  145. };
  146.  
  147. const currentQualityIndex = getCurrentQualityIndex(currentQuality);
  148. const targetQualityIndex = getCurrentQualityIndex(targetQuality.name);
  149.  
  150. console.log(`二次切换检查 - 当前画质索引:${currentQualityIndex}, 目标画质索引:${targetQualityIndex}`); // 索引画质,延后10秒比对,不相符就再切换一次
  151. if (currentQualityIndex === targetQualityIndex) {
  152. console.log("当前画质和目标画质相符,无需执行二次切换");
  153. } else {
  154. console.log("当前画质和目标画质不相符,执行二次切换");
  155. targetQuality.element.click();
  156. }
  157. }, 7000);
  158.  
  159. const hiResButton = document.querySelector('.bpx-player-ctrl-flac');
  160. if (hiResButton) {
  161. if (isVip) {
  162. if (hiResAudioEnabled && !hiResButton.classList.contains('bpx-state-active')) {
  163. hiResButton.click();
  164. } else if (!hiResAudioEnabled && hiResButton.classList.contains('bpx-state-active')) {
  165. hiResButton.click();
  166. }
  167. } else {
  168. if (hiResButton.classList.contains('bpx-state-active')) {
  169. hiResButton.click();
  170. }
  171. }
  172. }
  173.  
  174. const dolbyButton = document.querySelector('.bpx-player-ctrl-dolby');
  175. if (dolbyButton) {
  176. if (isVip) {
  177. if (dolbyAtmosEnabled && !dolbyButton.classList.contains('bpx-state-active')) {
  178. dolbyButton.click();
  179. } else if (!dolbyAtmosEnabled && dolbyButton.classList.contains('bpx-state-active')) {
  180. dolbyButton.click();
  181. }
  182. } else {
  183. if (dolbyButton.classList.contains('bpx-state-active')) {
  184. dolbyButton.click();
  185. }
  186. }
  187. }
  188.  
  189. if (takeOverQualityControl) {
  190. const qualityControlElement = document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-quality');
  191. if (qualityControlElement) {
  192. qualityControlElement.remove();
  193. }
  194. }
  195. }
  196.  
  197. function createSettingsPanel() {
  198. const panel = document.createElement('div');
  199. panel.id = 'bilibili-quality-selector';
  200.  
  201. const QUALITIES = [' 自动选择最高画质 ', '8K', '杜比视界','HDR', '4K', '1080P 高码率', '1080P 60 帧', '1080P', '720P', '480P', '360P']; // 本次更新:移除 720P 60帧选项,该画质似乎已被B站移除
  202. QUALITIES.forEach(quality => {
  203. const button = document.createElement('button');
  204. button.textContent = quality;
  205. button.onclick = () => {
  206. userQualitySetting = quality;
  207. GM_setValue('qualitySetting', quality);
  208. userHasChangedQuality = true;
  209. updateQualityButtons(panel);
  210. selectQualityBasedOnSetting();
  211. };
  212. panel.appendChild(button);
  213. });
  214.  
  215. const hiResButton = document.createElement('button');
  216. hiResButton.textContent = 'Hi-Res 音质';
  217. hiResButton.onclick = () => {
  218. hiResAudioEnabled = !hiResAudioEnabled;
  219. GM_setValue('hiResAudio', hiResAudioEnabled);
  220. updateQualityButtons(panel);
  221. selectQualityBasedOnSetting();
  222. };
  223. panel.appendChild(hiResButton);
  224.  
  225. const dolbyAtmosButton = document.createElement('button');
  226. dolbyAtmosButton.textContent = '杜比全景声';
  227. dolbyAtmosButton.onclick = () => {
  228. dolbyAtmosEnabled = !dolbyAtmosEnabled;
  229. GM_setValue('dolbyAtmos', dolbyAtmosEnabled);
  230. updateQualityButtons(panel);
  231. selectQualityBasedOnSetting();
  232. };
  233. panel.appendChild(dolbyAtmosButton);
  234.  
  235. const takeOverQualityControlButton = document.createElement('button');
  236. takeOverQualityControlButton.textContent = '移除清晰度按钮(Beta)';
  237. takeOverQualityControlButton.onclick = () => {
  238. takeOverQualityControl = !takeOverQualityControl;
  239. GM_setValue('takeOverQualityControl', takeOverQualityControl);
  240. updateQualityButtons(panel);
  241. selectQualityBasedOnSetting();
  242. };
  243. panel.appendChild(takeOverQualityControlButton);
  244.  
  245. updateQualityButtons(panel);
  246. document.body.appendChild(panel);
  247. }
  248.  
  249. function updateQualityButtons(panel) {
  250. panel.querySelectorAll('button').forEach(button => {
  251. button.classList.remove('active');
  252. if (button.textContent === userQualitySetting ||
  253. (button.textContent === 'Hi-Res 音质' && hiResAudioEnabled) ||
  254. (button.textContent === '杜比全景声' && dolbyAtmosEnabled) ||
  255. (button.textContent === '移除清晰度按钮(Beta)' && takeOverQualityControl)) {
  256. button.classList.add('active');
  257. }
  258. });
  259. }
  260.  
  261. function toggleSettingsPanel() {
  262. let panel = document.getElementById('bilibili-quality-selector');
  263. if (!panel) {
  264. createSettingsPanel();
  265. panel = document.getElementById('bilibili-quality-selector');
  266. }
  267. panel.style.display = panel.style.display === 'block' ? 'none' : 'block';
  268. }
  269.  
  270. document.addEventListener('mousedown', function (event) {
  271. const panel = document.getElementById('bilibili-quality-selector');
  272. if (panel && !panel.contains(event.target)) {
  273. panel.style.display = 'none';
  274. }
  275. });
  276.  
  277. GM_registerMenuCommand("设置画质和音质", toggleSettingsPanel);
  278.  
  279. window.onload = function () {
  280. let hasElementAppeared = false;
  281. const observer = new MutationObserver(function (mutations, me) {
  282. const element = document.querySelector('.v-popover-wrap.header-avatar-wrap');
  283. if (element) {
  284. hasElementAppeared = true;
  285. setTimeout(selectQualityBasedOnSetting, 4000);
  286. console.log(`脚本开始运行,4秒后切换画质`);
  287. me.disconnect();
  288. }
  289. });
  290.  
  291. observer.observe(document.body, {
  292. childList: true,
  293. subtree: true
  294. });
  295.  
  296. setTimeout(function() {
  297. observer.disconnect();
  298. if (!hasElementAppeared) {
  299. console.error("等待超时,尝试执行中...");
  300. selectQualityBasedOnSetting();
  301. }
  302. }, 12000);
  303. };
  304.  
  305.  
  306. const parentElement = document.body;
  307.  
  308. parentElement.addEventListener('click', function(event) {
  309. const targetElement = event.target;
  310.  
  311. if (targetElement.tagName === 'DIV' || targetElement.tagName === 'P') {
  312. if (targetElement.hasAttribute('title') || targetElement.classList.contains('title')) {
  313. setTimeout(selectQualityBasedOnSetting, 6000);
  314. console.log('页面发生切换:', targetElement.textContent.trim());
  315. }
  316. }
  317. });
  318.  
  319. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址