BiliBili 高级倍速功能

BiliBili倍速插件,支持自定义速度、记忆上一次速度、快捷键调速。

  1. // ==UserScript==
  2. // @name BiliBili 高级倍速功能
  3. // @namespace cec8225d12878f0fc33997eb79a69894
  4. // @version 1.8
  5. // @description BiliBili倍速插件,支持自定义速度、记忆上一次速度、快捷键调速。
  6. // @author TheBszk
  7. // @match https://www.bilibili.com/video/*
  8. // @match https://www.bilibili.com/list/*
  9. // @match https://www.bilibili.com/bangumi/play/*
  10. // @match https://www.bilibili.com/cheese/play/*
  11. // @match https://www.bilibili.com/festival/*
  12. // @icon https://www.bilibili.com/favicon.ico
  13. // @license AGPL
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. "use strict";
  18. const CUSTOM_RATE_ARRAY = "custom_rate_array";
  19. const CUSTOM_RATE = "custom_rate";
  20. const CUSTOM_ShowTimeState = "custom_showtimestate";
  21. const CUSTOM_ArrowRightSpeed = "custom_arrowrightspeed";
  22. const CUSTOM_SwitchCustomSpeed = "custom_switchcustomspeed";
  23. const CUSTOM_DefaultWideScreen = "custom_defaultwidescreen";
  24. const CUSTOM_Volume = "custom_volume";
  25. const CUSTOM_GlobalVolumeAdjustment = "custom_globalvolumeadjustment";
  26.  
  27. if (!localStorage.getItem(CUSTOM_ArrowRightSpeed)) {
  28. localStorage.setItem(CUSTOM_ArrowRightSpeed, "2x"); //设置默认值
  29. }
  30.  
  31. function getPageType() {
  32. const path = window.location.pathname;
  33.  
  34. if (path.startsWith("/video/")) {
  35. return "video";
  36. } else if (path.startsWith("/list/")) {
  37. return "list";
  38. } else if (path.startsWith("/bangumi/play/")) {
  39. return "bangumi";
  40. } else if (path.startsWith("/cheese/play/")) {
  41. return "cheese";
  42. } else if (path.startsWith("/festival/")) {
  43. return "festival";
  44. } else {
  45. return "unknown";
  46. }
  47. }
  48.  
  49. const pageType = getPageType();
  50.  
  51. if (pageType == "video" || pageType == "list" || pageType == "bangumi" || pageType == "cheese" || pageType == "festival") {
  52. var MENUCLASS = "bpx-player-ctrl-playbackrate-menu";
  53. var MENUCLASS_ITEM = "bpx-player-ctrl-playbackrate-menu-item";
  54. var MENUCLASS_ACTIVE = "bpx-state-active";
  55. } else {
  56. return;
  57. }
  58.  
  59. function getRate() {
  60. let rate = localStorage.getItem(CUSTOM_RATE);
  61. if (rate <= 0) {
  62. rate = 1;
  63. }
  64. return rate;
  65. }
  66.  
  67. function getRateArray() {
  68. let storageData = localStorage.getItem(CUSTOM_RATE_ARRAY);
  69. let rates;
  70. if (storageData == null) {
  71. rates = [];
  72. } else {
  73. rates = storageData.split(",");
  74. }
  75. if (rates.length === 0) {
  76. //如果没有,则初始化一个默认的
  77. rates = [0.5, 1.0, 1.5, 2, 2.5, 3.0, 4.0];
  78. localStorage.setItem(CUSTOM_RATE_ARRAY, rates.join(","));
  79. }
  80. return rates;
  81. }
  82.  
  83. // 创建显示元素
  84. function createTip() {
  85. var elem = document.createElement("div");
  86. elem.style.display = "none";
  87. elem.style.position = "absolute";
  88. elem.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
  89. elem.style.color = "white";
  90. elem.style.padding = "5px";
  91. elem.style.borderRadius = "5px";
  92. elem.style.zIndex = "1000";
  93. elem.style.fontSize = "22px";
  94. return elem;
  95. }
  96.  
  97. var timeDisplay = createTip();
  98. timeDisplay.style.top = "20px";
  99. timeDisplay.style.right = "20px";
  100.  
  101. let _showtime;
  102.  
  103. function setShowTimeState(state) {
  104. localStorage.setItem(CUSTOM_ShowTimeState, state);
  105. if (state == true) {
  106. timeDisplay.style.display = "block";
  107.  
  108. if (!_showtime) {
  109. _showtime = setInterval(FlashShowTime, 1000);
  110. }
  111. } else {
  112. timeDisplay.style.display = "none";
  113.  
  114. if (_showtime) {
  115. clearInterval(_showtime);
  116. _showtime = 0;
  117. }
  118. }
  119. }
  120.  
  121. var tipDisplay = createTip();
  122. tipDisplay.style.right = "20px";
  123.  
  124. let hideTimer;
  125. let bpx_player_control_entity = document.querySelector(".bpx-player-control-entity");
  126. let bpx_player_control_wrap = document.querySelector(".bpx-player-control-wrap");
  127. function showTip(title, time) {
  128. let h;
  129. if (bpx_player_control_entity.getAttribute("data-shadow-show") == "true") {
  130. h = 0;
  131. } else {
  132. h = bpx_player_control_wrap.clientHeight;
  133. }
  134.  
  135. tipDisplay.style.bottom = h + 20 + "px";
  136. tipDisplay.textContent = title;
  137. tipDisplay.style.display = "block";
  138.  
  139. if (!hideTimer) {
  140. clearTimeout(hideTimer);
  141. }
  142.  
  143. hideTimer = setTimeout(function () {
  144. tipDisplay.style.display = "none";
  145. }, time);
  146. }
  147.  
  148. function showPlayRate(rate) {
  149. showTip(`速度: ${rate}x`, 1200);
  150. }
  151.  
  152. function showVolume(volume) {
  153. showTip(`音量: ${volume}%`, 1200);
  154. }
  155.  
  156. class SettingPopup {
  157. popup_dragend_move(e) {
  158. this.popup.style.left = e.clientX - this.offsetX + this.startX + "px";
  159. this.popup.style.top = e.clientY - this.offsetY + this.startY + "px";
  160. }
  161. constructor() {
  162. this.speedlist = getRateArray().join(",");
  163. this.ArrowRightTime = localStorage.getItem(CUSTOM_ArrowRightSpeed);
  164. this.SwitchCustomSpeed = localStorage.getItem(CUSTOM_SwitchCustomSpeed) == "true" ? true : false;
  165. // this.GlobalVolumeAdjustment = localStorage.getItem(CUSTOM_GlobalVolumeAdjustment) == "true" ? true : false;
  166. this.GlobalVolumeAdjustment = false;//不再开启
  167. this.DefaultWideScreen = localStorage.getItem(CUSTOM_DefaultWideScreen) == "true" ? true : false;
  168. let v = localStorage.getItem(CUSTOM_Volume);
  169. if (v == null || v == "") {
  170. this.volume = -1;
  171. } else {
  172. this.volume = parseInt(v);
  173. }
  174. }
  175. create(handle) {
  176. this.popup = document.createElement("div");
  177. this.popup.innerHTML = `
  178. <div class="popup-title" id="popupTitle">
  179. <span>BiliBili 高级倍速功能</span>
  180. <button class="close-button">×</button>
  181. </div>
  182. <div class="popup-content">
  183. <label for="SpeedList">自定义倍速列表:</label>
  184. <input type="text" id="SpeedList" placeholder="以英文逗号隔开" />
  185.  
  186. <label for="ArrowRightSpeed">长按右光标键速度:</label>
  187. <input type="text" id="ArrowRightSpeed" placeholder="例: 2 为固定二倍速, 2x 为当前速度两倍" />
  188.  
  189. <label title="默认为对应速度(如 按2为2倍速、按3为3倍速)"><input type="checkbox" id="SwitchCustomSpeed" /> 0~9/Ctrl+0~9 快捷键切换自定义列表速度</label>
  190. <br />
  191. <label title="默认宽屏"><input type="checkbox" id="DefaultWideScreen" /> 默认宽屏</label>
  192. <br />
  193. <label title="[!]此功能放弃维护,因此不再可以使用\n\n↑ / ↓ 全局调整音量 ; 优化滚轮调整音量体验\n支持 0 ~ 500%(过高会有轻微失真)\n仅支持快捷键与鼠标滚轮调整\n\n[!] 本项修改需要刷新网页后生效"><input type="checkbox" id="GlobalVolumeAdjustment" disabled/> 接管音量控制</label>
  194. <br />
  195. <label title="暂不支持取消"><input type="checkbox" disabled checked /> 增加快捷键: 字幕切换(Z)</label>
  196. <br />
  197. <label title="暂不支持取消"><input type="checkbox" disabled checked /> 增加快捷键: 网页全屏(G)</label>
  198. <br />
  199. <label title="暂不支持取消"><input type="checkbox" disabled checked /> 增加快捷键: 宽屏模式(H)</label>
  200. <br />
  201. <label title="暂不支持取消"><input type="checkbox" disabled checked /> 双击字幕复制内容</label>
  202. </div>
  203. <div id="popup-tips">关闭设置窗口自动保存 | 鼠标停留查看更多信息</div>
  204. `;
  205. this.popup.classList.add("popup-container");
  206. this.popupcss = document.createElement("style");
  207. this.popupcss.innerHTML = `
  208. .popup-container {
  209. width: 330px;
  210. position: absolute;
  211. z-index: 999999;
  212. background-color: #fff;
  213. border: 1px solid #ccc;
  214. border-radius: 8px;
  215. box-shadow: 0 0 10px rgba(33,150,243,0.5);
  216. }
  217.  
  218. .popup-container .popup-title {
  219. position: relative;
  220. background-color: #3498db;
  221. color: #fff;
  222. padding: 10px;
  223. cursor: move;
  224. border-top-left-radius: 8px;
  225. border-top-right-radius: 8px;
  226. user-select: none;
  227. }
  228. .popup-container .popup-content {
  229. padding: 20px;
  230. }
  231. .popup-container .close-button {
  232. position: absolute;
  233. top: 0px;
  234. right: 0px;
  235. height: 100%;
  236. background-color: #3498db;
  237. color: #fff;
  238. border: none;
  239. padding: 0px 13px;
  240. font-size: 24px;
  241. cursor: pointer;
  242. border-top-right-radius: 8px;
  243. transition: background-color 0.3s ease, transform 0.3s ease;
  244. }
  245. .popup-container .close-button:hover {
  246. background-color: #e74c3c;
  247. }
  248. .popup-container label {
  249. font-size: 14px;
  250. }
  251. .popup-container #popup-tips {
  252. color: #555555;
  253. font-size: 14px;
  254. padding: 4px 0px 4px 10px;
  255. border-top: 1px solid #ccc;
  256. }
  257. .popup-container .button {
  258. display: block;
  259. padding: 10px;
  260. background-color: #3498db;
  261. color: #fff;
  262. text-align: center;
  263. text-decoration: none;
  264. border-radius: 5px;
  265. cursor: pointer;
  266. transition: background-color 0.3s ease;
  267. border: none;
  268. }
  269. .popup-container .button:hover {
  270. background-color: #2980b9;
  271. }
  272. .popup-container select,
  273. input[type="text"] {
  274. display: block;
  275. margin-bottom: 10px;
  276. padding: 8px;
  277. border: 1px solid #ccc;
  278. border-radius: 4px;
  279. width: 100%;
  280. box-sizing: border-box;
  281. outline: none;
  282. }
  283. .popup-container input[type="text"]:focus {
  284. border: 1px solid #2980b9;
  285. }
  286. .popup-container input[type="radio"] {
  287. margin-right: 5px;
  288. }`;
  289. document.body.appendChild(this.popup);
  290. document.head.appendChild(this.popupcss);
  291.  
  292. this.popup_dragend_move = this.popup_dragend_move.bind(this);
  293. this.popup.querySelector("#popupTitle").addEventListener("mousedown", (e) => {
  294. this.offsetX = e.clientX;
  295. this.offsetY = e.clientY;
  296. this.startX = parseInt(this.popup.style.left);
  297. this.startY = parseInt(this.popup.style.top);
  298. document.addEventListener("mousemove", this.popup_dragend_move);
  299. document.addEventListener("mouseup", (e) => {
  300. document.removeEventListener("mousemove", this.popup_dragend_move);
  301. });
  302. });
  303. this.popup.querySelector(".close-button").addEventListener("click", (e) => {
  304. this.close();
  305. });
  306. this.handle = handle;
  307. }
  308. show() {
  309. this.popup.querySelector("#SpeedList").value = this.speedlist;
  310. this.popup.querySelector("#ArrowRightSpeed").value = this.ArrowRightTime;
  311. this.popup.querySelector("#SwitchCustomSpeed").checked = this.SwitchCustomSpeed;
  312. this.popup.querySelector("#GlobalVolumeAdjustment").checked = this.GlobalVolumeAdjustment;
  313. this.popup.querySelector("#DefaultWideScreen").checked = this.DefaultWideScreen;
  314. this.popup.style.display = "block";
  315. let left = (window.innerWidth - this.popup.offsetWidth) / 2;
  316. let top = (window.innerHeight - this.popup.offsetHeight) / 2;
  317.  
  318. this.popup.style.left = left + "px";
  319. this.popup.style.top = top + "px";
  320. }
  321. close() {
  322. let sl, ars;
  323. // 读取元素的值
  324. sl = this.popup.querySelector("#SpeedList").value;
  325. ars = this.popup.querySelector("#ArrowRightSpeed").value;
  326. this.SwitchCustomSpeed = this.popup.querySelector("#SwitchCustomSpeed").checked;
  327. this.GlobalVolumeAdjustment = this.popup.querySelector("#GlobalVolumeAdjustment").checked;
  328. this.DefaultWideScreen = this.popup.querySelector("#DefaultWideScreen").checked;
  329.  
  330. let sl_ = null,
  331. ars_ = null;
  332. //进行处理
  333. //自定义速度列表
  334. if (!(sl === null || sl.trim() === "")) {
  335. let rates = sl
  336. .split(",")
  337. .map((s) => s.trim())
  338. .filter((s) => s);
  339.  
  340. if (rates.length > 0) {
  341. // 检查输入是否全部为有效数字
  342. if (rates.every((s) => isFinite(s))) {
  343. localStorage.setItem(CUSTOM_RATE_ARRAY, rates.join(","));
  344. this.speedlist = sl;
  345. sl_ = rates;
  346. }
  347. }
  348. }
  349.  
  350. //右光标键速度
  351. if (parseInt(ars) > 0) {
  352. localStorage.setItem(CUSTOM_ArrowRightSpeed, ars);
  353. this.ArrowRightTime = ars;
  354. ars_ = ars;
  355. }
  356.  
  357. localStorage.setItem(CUSTOM_SwitchCustomSpeed, this.SwitchCustomSpeed);
  358. localStorage.setItem(CUSTOM_GlobalVolumeAdjustment, this.GlobalVolumeAdjustment);
  359. localStorage.setItem(CUSTOM_DefaultWideScreen, this.DefaultWideScreen);
  360. this.handle(sl_, ars_);
  361.  
  362. this.popup.remove();
  363. }
  364. }
  365. let setting = new SettingPopup();
  366.  
  367. class PlayRateMenu {
  368. init(menu) {
  369. this.videoObj = document.querySelector("video");
  370. if (this.videoObj) {
  371. if (setting.GlobalVolumeAdjustment) {
  372. let context = new (window.AudioContext || window.webkitAudioContext)();
  373. this.gain = context.createGain();
  374. context.createMediaElementSource(this.videoObj).connect(this.gain);
  375. this.gain.connect(context.destination);
  376. this.volumeNumElem = document.querySelector(".bpx-player-ctrl-volume-number");
  377. if (setting.volume != -1) {
  378. this.setVolume(setting.volume / 100, false);
  379. }
  380. this.videoObj.addEventListener("volumechange", () => {
  381. if (this.gain) {
  382. this.gain.gain.value = 1;
  383. }
  384. });
  385. }
  386. } else {
  387. this.videoObj = document.querySelector("bwp-video"); //b站自研wasm软解视频播放器
  388. }
  389.  
  390. if (!this.videoObj) {
  391. return false;
  392. }
  393.  
  394. this.saveSetting = this.saveSetting.bind(this);
  395. this.menu = menu;
  396. this.rates = getRateArray();
  397. this.videoObj.addEventListener("loadedmetadata", () => {
  398. this.setRate(getRate());
  399. });
  400. if (setting.DefaultWideScreen) {
  401. document.querySelector("#bilibili-player .bpx-player-ctrl-wide span").click();
  402. }
  403.  
  404. return true;
  405. }
  406.  
  407. insertRate(rateValue) {
  408. this.rates.push(rateValue);
  409. this.render();
  410. }
  411.  
  412. insertItem(content, rate, event) {
  413. const item = document.createElement("li");
  414. item.textContent = content;
  415. item.classList.add(MENUCLASS_ITEM);
  416. item.setAttribute("data-value", rate);
  417. item.addEventListener("click", event);
  418. this.menu.appendChild(item);
  419. }
  420.  
  421. saveSetting(sl, ars) {
  422. if (sl != null) {
  423. this.rates = sl;
  424. this.render();
  425. let nowRate = getRate();
  426. if (this.rates.indexOf(nowRate) === -1) {
  427. this.setRate(1);
  428. } else {
  429. this.setRate(nowRate);
  430. }
  431. }
  432. }
  433.  
  434. render() {
  435. this.menu.innerHTML = "";
  436. this.rates.sort((a, b) => b - a); //排序
  437.  
  438. this.rates.forEach((rate) => {
  439. this.insertItem(rate % 1 == 0 ? rate + ".0x" : rate + "x", rate, (e) => {
  440. e.stopPropagation();
  441. const rateValue = e.target.getAttribute("data-value");
  442. this.setVideoRate(rateValue);
  443. this.setActiveRate(rateValue);
  444. localStorage.setItem(CUSTOM_RATE, rateValue);
  445. });
  446. });
  447.  
  448. //插入一个设置按钮
  449. this.insertItem("设置", 0, (e) => {
  450. e.stopPropagation();
  451. setting.create(this.saveSetting);
  452. setting.show();
  453. });
  454. }
  455.  
  456. setActiveRate(rateValue) {
  457. const items = this.menu.querySelectorAll(`.${MENUCLASS_ITEM}`);
  458. items.forEach((item) => {
  459. const value = item.getAttribute("data-value");
  460. if (value === rateValue) {
  461. item.classList.add(MENUCLASS_ACTIVE);
  462. } else {
  463. item.classList.remove(MENUCLASS_ACTIVE);
  464. }
  465. });
  466. }
  467.  
  468. getDuration() {
  469. return this.videoObj.duration;
  470. }
  471.  
  472. getCurrentTime() {
  473. return this.videoObj.currentTime;
  474. }
  475.  
  476. setVideoRate(rate) {
  477. this.videoObj.playbackRate = parseFloat(rate);
  478. }
  479. getVideoRate() {
  480. return this.videoObj.playbackRate;
  481. }
  482.  
  483. //使用此函数前提:速度列表必须存在该速度值
  484. setRate(rate) {
  485. const item = document.querySelector(`.${MENUCLASS_ITEM}[data-value="${rate}"]`);
  486. if (item) {
  487. item.classList.add(MENUCLASS_ACTIVE);
  488. item.click();
  489. } else {
  490. console.error("未找到匹配元素");
  491. }
  492. }
  493.  
  494. changeRate(up) {
  495. let nowRate = getRate();
  496. let index = this.rates.indexOf(nowRate);
  497. if ((index == 0 && up) || (index == this.rates.length && !up)) {
  498. return nowRate;
  499. } else {
  500. index += up ? -1 : 1;
  501. this.setRate(this.rates[index]);
  502. return this.rates[index];
  503. }
  504. }
  505. getVolume() {
  506. if (this.videoObj.volume == 1.0) {
  507. return this.gain.gain.value;
  508. } else {
  509. return this.videoObj.volume;
  510. }
  511. }
  512.  
  513. setVolume(volume, show) {
  514. if (!this.gain && volume > 1.0) {
  515. volume = 1.0;
  516. }
  517.  
  518. if (volume <= 1.0) {
  519. this.videoObj.volume = volume;
  520. if (this.gain) {
  521. this.gain.gain.value = 1;
  522. }
  523. } else {
  524. this.videoObj.volume = 1;
  525. this.gain.gain.value = volume;
  526. }
  527.  
  528. let sv = (volume * 100).toFixed(0);
  529. localStorage.setItem(CUSTOM_Volume, sv);
  530. this.volumeNumElem.textContent = sv;
  531. if (show == true) {
  532. showVolume(sv);
  533. }
  534. }
  535. }
  536.  
  537. let menu = new PlayRateMenu();
  538.  
  539. let _interval = setInterval(function () {
  540. let element = document.querySelector(`.${MENUCLASS}`);
  541. if (element) {
  542. if (menu.init(element)) {
  543. menu.render();
  544. menu.setRate(getRate());
  545. let bpx_player_video_warp = document.querySelector(".bpx-player-video-wrap");
  546. bpx_player_video_warp.appendChild(tipDisplay);
  547. bpx_player_video_warp.appendChild(timeDisplay);
  548. if (setting.GlobalVolumeAdjustment) {
  549. bpx_player_video_warp.addEventListener("mousewheel", (e) => {
  550. e.preventDefault();
  551. e.stopImmediatePropagation();
  552. let volume = menu.getVolume() + parseInt(e.wheelDelta / 120) * 0.05;
  553. if (volume > 5.0) {
  554. volume = 5.0;
  555. } else if (volume < 0) {
  556. volume = 0;
  557. }
  558. menu.setVolume(volume, true);
  559. });
  560. }
  561.  
  562. setShowTimeState(localStorage.getItem(CUSTOM_ShowTimeState) == "true");
  563. clearInterval(_interval);
  564. } else {
  565. console.warn("获取视频元素失败!");
  566. }
  567.  
  568. //双击复制字幕内容
  569. let subtitle_panel = document.querySelector(".bpx-player-subtitle-panel-major-group");
  570. if (subtitle_panel) {
  571. subtitle_panel.addEventListener("dblclick", function () {
  572. let text = document.querySelector(".bpx-player-subtitle-panel-major-group span").textContent;
  573.  
  574. //如果是歌词会存在音乐符号,要清除
  575. let musicSymbol = "♪";
  576. if (text.startsWith(musicSymbol)) {
  577. text = text.slice(musicSymbol.length);
  578. if (text.endsWith(musicSymbol)) {
  579. text = text.slice(0, -musicSymbol.length);
  580. }
  581. }
  582. navigator.clipboard.writeText(text);
  583. });
  584. }
  585. }
  586. }, 500);
  587.  
  588. let ArrowRightTime = 0;
  589. let OldRate = 0;
  590.  
  591. document.addEventListener(
  592. "keydown",
  593. function (e) {
  594. e = e || window.event;
  595. if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA" || e.target.tagName == "BILI-COMMENTS" || e.target.isContentEditable) {
  596. return;
  597. }
  598. if (e.ctrlKey == true && e.code == "ArrowUp") {
  599. let rate = menu.changeRate(true);
  600. showPlayRate(rate);
  601. } else if (e.ctrlKey == true && e.code == "ArrowDown") {
  602. let rate = menu.changeRate(false);
  603. showPlayRate(rate);
  604. } else if (e.code == "ArrowRight" && !e.ctrlKey && !e.shiftKey && !e.altKey) {
  605. if (ArrowRightTime == 0) {
  606. ArrowRightTime = e.timeStamp;
  607. } else {
  608. if (e.timeStamp - ArrowRightTime > 500) {
  609. if (OldRate == 0) {
  610. OldRate = getRate();
  611. if (typeof setting.ArrowRightTime === "string" && setting.ArrowRightTime.indexOf("x") != -1) {
  612. menu.setVideoRate(OldRate * parseInt(setting.ArrowRightTime));
  613. showPlayRate(OldRate * parseInt(setting.ArrowRightTime));
  614. } else {
  615. menu.setVideoRate(parseInt(setting.ArrowRightTime));
  616. showPlayRate(parseInt(setting.ArrowRightTime));
  617. }
  618. }
  619. }
  620. }
  621. } else if ("0" <= e.key && e.key <= "9") {
  622. e.preventDefault();
  623. e.stopImmediatePropagation();
  624. let num = parseInt(e.key - "0");
  625. let speed;
  626. if (setting.SwitchCustomSpeed) {
  627. if (!(1 <= num && num <= menu.rates.length)) {
  628. return;
  629. }
  630. speed = menu.rates[menu.rates.length - num];
  631. } else {
  632. if (num == 0) {
  633. speed = 0.5;
  634. } else {
  635. speed = num;
  636. }
  637. }
  638.  
  639. if (e.ctrlKey) {
  640. menu.setVideoRate(speed);
  641. menu.setActiveRate(speed);
  642. showPlayRate(speed);
  643. localStorage.setItem(CUSTOM_RATE, speed);
  644. } else {
  645. if (OldRate == 0) {
  646. OldRate = getRate();
  647. menu.setVideoRate(speed);
  648. showPlayRate(speed);
  649. }
  650. }
  651. } else if (e.code == "KeyZ" && !e.ctrlKey && !e.shiftKey && !e.altKey) {
  652. let subtitle_btn = document.querySelector("#bilibili-player .bpx-player-ctrl-subtitle span");
  653. if (subtitle_btn) {
  654. subtitle_btn.click();
  655. }
  656. } else if (e.code == "KeyG" && !e.ctrlKey && !e.shiftKey && !e.altKey) {
  657. document.querySelector("#bilibili-player .bpx-player-ctrl-web span").click();
  658. } else if (e.code == "KeyH" && !e.ctrlKey && !e.shiftKey && !e.altKey) {
  659. document.querySelector("#bilibili-player .bpx-player-ctrl-wide span").click();
  660. } else if ((e.code == "ArrowUp" || e.code == "ArrowDown") && !e.ctrlKey && !e.shiftKey && !e.altKey) {
  661. if (setting.GlobalVolumeAdjustment) {
  662. e.preventDefault();
  663. e.stopImmediatePropagation();
  664. let volume = menu.getVolume();
  665. if (e.code == "ArrowUp") {
  666. volume = volume + 0.1;
  667. if (volume > 5.0) {
  668. volume = 5.0;
  669. }
  670. } else {
  671. volume = volume - 0.1;
  672. if (volume < 0) {
  673. volume = 0;
  674. }
  675. }
  676. menu.setVolume(volume, true);
  677. }
  678. }
  679. },
  680. true
  681. );
  682.  
  683. document.addEventListener("keyup", function (e) {
  684. if (e.code == "ArrowRight" || ("0" <= e.key && e.key <= "9")) {
  685. ArrowRightTime = 0;
  686. if (OldRate != 0) {
  687. menu.setVideoRate(OldRate);
  688. showPlayRate(OldRate);
  689. OldRate = 0;
  690. e.preventDefault();
  691. }
  692. } else if (e.code == "F2") {
  693. setShowTimeState(localStorage.getItem(CUSTOM_ShowTimeState) == "false");
  694. }
  695. });
  696.  
  697. window.addEventListener("focus", function () {
  698. menu.setRate(getRate());
  699. setShowTimeState(localStorage.getItem(CUSTOM_ShowTimeState) == "true");
  700. if (setting.GlobalVolumeAdjustment) {
  701. let volume = localStorage.getItem(CUSTOM_Volume);
  702. if (volume != -1) {
  703. menu.setVolume(volume / 100, false);
  704. }
  705. }
  706. });
  707.  
  708. function formatTime(s) {
  709. var m = parseInt(s / 60);
  710. var ss = parseInt(s % 60);
  711. return (m > 9 ? `${m}` : `0${m}`) + ":" + (ss > 9 ? `${ss}` : `0${ss}`);
  712. }
  713.  
  714. function FlashShowTime() {
  715. var rate = menu.getVideoRate();
  716. timeDisplay.textContent = formatTime(menu.getCurrentTime() / rate) + "/" + formatTime(menu.getDuration() / rate);
  717. }
  718. })();