Plex Playback Speed

Add playback speed controls to plex web player with keyboard shortcuts

当前为 2022-09-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Plex Playback Speed
  3. // @namespace https://github.com/ZigZagT
  4. // @version 1.2.5
  5. // @description Add playback speed controls to plex web player with keyboard shortcuts
  6. // @author ZigZagT
  7. // @include /^https?://[^/]*plex[^/]*/
  8. // @include /^https?://[^/]*:32400/
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14. const console_log = (...args) => {
  15. console.log(`PlexPlaybackSpeed:`, ...args)
  16. }
  17. const cycleSpeeds = [
  18. 0.5, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.5, 3, 3, 5, 4, 5, 6, 7, 8, 9, 10, 15, 20
  19. ];
  20. const quickSetSpeeds = {
  21. 1: 1,
  22. 2: 1.5,
  23. 3: 2,
  24. 4: 3,
  25. 5: 4,
  26. 6: 5,
  27. 7: 7,
  28. 8: 8,
  29. 9: 10,
  30. };
  31.  
  32. function prompt(txt) {
  33. const existingPrompt = document.querySelector("#playback-speed-prompt");
  34. if (existingPrompt) {
  35. document.body.removeChild(existingPrompt);
  36. }
  37. const prompt = document.createElement("div");
  38. prompt.id = "playback-speed-prompt";
  39. prompt.innerText = txt;
  40. document.body.appendChild(prompt);
  41. prompt.style = `
  42. position: fixed;
  43. top: 0;
  44. left: 0;
  45. width: 8em;
  46. height: 2em;
  47. background-color: rgba(0, 0, 0, 0.5);
  48. color: white;
  49. font-size: 2em;
  50. text-align: center;
  51. z-index: 99999;
  52. pointer-events: none;
  53. `;
  54. setTimeout(() => {
  55. try {
  56. document.body.removeChild(prompt);
  57. } catch (e) {}
  58. }, 2000);
  59. }
  60.  
  61. function getNextCycleSpeed(direction, currentSpeed) {
  62. let newSpeed = currentSpeed;
  63. for (const speed of cycleSpeeds) {
  64. if (direction === 'slowdown') {
  65. if (speed < currentSpeed) {
  66. newSpeed = speed;
  67. } else {
  68. break;
  69. }
  70. } else if (direction === 'speedup') {
  71. if (speed > currentSpeed) {
  72. newSpeed = speed;
  73. break;
  74. }
  75. } else {
  76. console.error(`invalid change speed direction ${direction}`)
  77. break;
  78. }
  79. }
  80. return newSpeed;
  81. }
  82.  
  83. function keyboardUpdateSpeed(e) {
  84. const videoElem = document.querySelector("video");
  85. if (videoElem == null) {
  86. return;
  87. }
  88. const currentSpeed = videoElem.playbackRate;
  89. let newSpeed = currentSpeed;
  90. console_log({currentSpeed, key: e.key});
  91. if (e.key in quickSetSpeeds) {
  92. newSpeed = quickSetSpeeds[e.key];
  93. } else if (["<", ","].includes(e.key)) {
  94. newSpeed = getNextCycleSpeed('slowdown', currentSpeed);
  95. } else if ([">", "."].includes(e.key)) {
  96. newSpeed = getNextCycleSpeed('speedup', currentSpeed);
  97. } else {
  98. return;
  99. }
  100. console_log('change speed to', newSpeed);
  101. videoElem.playbackRate = newSpeed;
  102. prompt(`Speed: ${newSpeed}x`);
  103. }
  104.  
  105. function btnSpeedUpFn() {
  106. const currentSpeed = document.querySelector("video").playbackRate;
  107. let newSpeed = getNextCycleSpeed('speedup', currentSpeed);
  108. console_log('change speed to', newSpeed);
  109. document.querySelector("video").playbackRate = newSpeed;
  110. prompt(`Speed: ${newSpeed}x`);
  111. };
  112.  
  113. function btnSlowdownFn() {
  114. const currentSpeed = document.querySelector("video").playbackRate;
  115. let newSpeed = getNextCycleSpeed('slowdown', currentSpeed);
  116. console_log('change speed to', newSpeed);
  117. document.querySelector("video").playbackRate = newSpeed;
  118. prompt(`Speed: ${newSpeed}x`);
  119. };
  120.  
  121. function addPlaybackButtonControls() {
  122. const btnStyle = `
  123. align-items: center;
  124. border-radius: 15px;
  125. display: flex;
  126. font-size: 18px;
  127. height: 30px;
  128. justify-content: center;
  129. margin-left: 5px;
  130. text-align: center;
  131. width: 30px;
  132. `;
  133.  
  134. const containers = document.querySelectorAll('[class*="PlayerControls-buttonGroupRight"]');
  135. containers.forEach(container => {
  136. if (container.querySelector('#playback-speed-btn-slowdown')) {
  137. return;
  138. }
  139.  
  140. const btnSlowDown = document.createElement('button');
  141. btnSlowDown.id = 'playback-speed-btn-slowdown';
  142. btnSlowDown.style = btnStyle;
  143. btnSlowDown.innerHTML = '🐢';
  144. btnSlowDown.addEventListener('click', btnSlowdownFn);
  145.  
  146. const btnSpeedUp = document.createElement('button');
  147. btnSpeedUp.id = 'playback-speed-btn-speedup';
  148. btnSpeedUp.style = btnStyle;
  149. btnSpeedUp.innerHTML = '🐇';
  150. btnSpeedUp.addEventListener('click', btnSpeedUpFn);
  151.  
  152. console_log('adding speed controls to', container);
  153. container.prepend(btnSlowDown, btnSpeedUp);
  154. })
  155.  
  156. }
  157.  
  158. function scheduleLoopFrame() {
  159. setTimeout(() => {
  160. requestAnimationFrame(() => {
  161. addPlaybackButtonControls();
  162. scheduleLoopFrame();
  163. });
  164. }, 500);
  165. };
  166.  
  167. console_log('registering playback speed controls');
  168. window.addEventListener("keydown", keyboardUpdateSpeed);
  169. scheduleLoopFrame();
  170. })();