Floatplane YouTube Hotkeys

Replicates YouTube's hotkeys for use in the Floatplane player

  1. // ==UserScript==
  2. // @name Floatplane YouTube Hotkeys
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @license GPL-v3
  6. // @description Replicates YouTube's hotkeys for use in the Floatplane player
  7. // @author German
  8. // @match https://*.floatplane.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=floatplane.com
  10. // @grant none
  11. // @run-at document-end
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. "use strict";
  16.  
  17. const playbackRates = [0.25, 0.5, 1, 1.5, 2, 2.5, 3];
  18. let controlsSetUp = false;
  19.  
  20. function getPlayer() {
  21. if (window.PLAYER && window.PLAYER.el) {
  22. selfLog("Player found");
  23. return window.PLAYER;
  24. }
  25. selfLog("Player not found");
  26. return null;
  27. }
  28.  
  29. function handleKeydown(e) {
  30. const player = getPlayer();
  31. if (!player || !document.activeElement.innerHTML.includes("player")) {
  32. selfLog("Keyboard event ignored: player not focused or undefined");
  33. return;
  34. }
  35.  
  36. selfLog("Key pressed:", e.key);
  37. switch (e.key) {
  38. case "k":
  39. case "MediaPlayPause":
  40. if (player.paused()) {
  41. selfLog("Playing video");
  42. player.play();
  43. } else {
  44. selfLog("Pausing video");
  45. player.pause();
  46. }
  47. break;
  48. case "StopMedia":
  49. selfLog("Stopping video");
  50. player.pause();
  51. break;
  52. case "j":
  53. selfLog("Seeking backward 10 seconds");
  54. player.currentTime(player.currentTime() - 10);
  55. createPlayerModal(player, "-10s");
  56. break;
  57. case "l":
  58. selfLog("Seeking forward 10 seconds");
  59. player.currentTime(player.currentTime() + 10);
  60. createPlayerModal(player, "+10s");
  61. break;
  62. case ",":
  63. if (player.paused()) {
  64. selfLog("Seeking one frame backward");
  65. player.currentTime(player.currentTime() - 1 / 30);
  66. }
  67. break;
  68. case ".":
  69. if (player.paused()) {
  70. selfLog("Seeking one frame forward");
  71. player.currentTime(player.currentTime() + 1 / 30);
  72. }
  73. break;
  74. case "0":
  75. selfLog("Seeking to beginning of video");
  76. player.currentTime(0);
  77. break;
  78. case "i":
  79. selfLog("Toggling picture in picture");
  80. if (document.pictureInPictureElement) {
  81. document.exitPictureInPicture();
  82. } else {
  83. player.requestPictureInPicture();
  84. }
  85. break;
  86. case ">":
  87. selfLog("Increasing playback rate");
  88. changePlaybackRate(1);
  89. break;
  90. case "<":
  91. selfLog("Decreasing playback rate");
  92. changePlaybackRate(-1);
  93. break;
  94. case "Home":
  95. selfLog("Seeking to beginning of video");
  96. player.currentTime(0);
  97. break;
  98. case "End":
  99. selfLog("Seeking to end of video");
  100. player.currentTime(player.duration());
  101. break;
  102. case "1":
  103. case "2":
  104. case "3":
  105. case "4":
  106. case "5":
  107. case "6":
  108. case "7":
  109. case "8":
  110. case "9":
  111. selfLog("Seeking to percentage of video:", parseInt(e.key) / 10);
  112. player.currentTime((player.duration() * parseInt(e.key)) / 10);
  113. createPlayerModal(player, parseInt(e.key) * 10 + "%");
  114. break;
  115. }
  116. }
  117.  
  118. function changePlaybackRate(direction) {
  119. const player = getPlayer();
  120. if (!player) return;
  121. const currentRate = player.playbackRate();
  122. const index = playbackRates.indexOf(currentRate);
  123. if (direction === 1 && index < playbackRates.length - 1) {
  124. selfLog("Increasing playback rate to:", playbackRates[index + 1]);
  125. player.playbackRate(playbackRates[index + 1]);
  126. createPlayerModal(player, playbackRates[index + 1] + "x");
  127. } else if (direction === -1 && index > 0) {
  128. selfLog("Decreasing playback rate to:", playbackRates[index - 1]);
  129. createPlayerModal(player, playbackRates[index - 1] + "x");
  130. player.playbackRate(playbackRates[index - 1]);
  131. }
  132. }
  133.  
  134. function setupKeyboardControls() {
  135. if (controlsSetUp) return;
  136. selfLog("Setting up keyboard controls");
  137. document.addEventListener("keydown", handleKeydown);
  138. controlsSetUp = true;
  139. }
  140.  
  141. function observePlayerInit() {
  142. const observer = new MutationObserver((mutations) => {
  143. if (getPlayer()) {
  144. selfLog("Player (re)initialization detected");
  145. observer.disconnect(); // Disconnect observer to reset if necessary
  146. setupKeyboardControls(); // Ensure controls are setup after (re)initialization
  147. }
  148. });
  149. observer.observe(document.body, { childList: true, subtree: true });
  150. selfLog("Observer set up to detect player initialization");
  151. }
  152.  
  153. function createPlayerModal(player, text) {
  154. const existingModals = document.getElementsByClassName(
  155. "fp-yt-hotkeys-modal"
  156. );
  157. for (let i = 0; i < existingModals.length; i++) {
  158. existingModals[i].remove();
  159. }
  160.  
  161. const element = document.createElement("div");
  162. element.className = "fp-yt-hotkeys-modal";
  163. element.innerHTML = text;
  164.  
  165. element.style.position = "absolute";
  166. element.style.top = "10%";
  167. element.style.left = "50%";
  168. element.style.transform = "translate(-50%, -50%)";
  169. element.style.padding = "20px";
  170. element.style.fontSize = "16px";
  171. element.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
  172. element.style.color = "white";
  173. element.style.borderRadius = "3px";
  174. element.style.zIndex = "10000";
  175. element.style.transition = "opacity 0.2s";
  176. element.style.opacity = "1";
  177.  
  178. player.el().appendChild(element);
  179.  
  180. setTimeout(() => {
  181. element.style.opacity = "0";
  182. setTimeout(() => {
  183. element.remove();
  184. }, 500);
  185. }, 750);
  186. }
  187.  
  188. if (getPlayer()) {
  189. setupKeyboardControls();
  190. } else {
  191. observePlayerInit();
  192. }
  193.  
  194. function selfLog(...args) {
  195. return; // Comment out to enable logging
  196. console.log("%cFloatplane YouTube Hotkeys:", "color: #FF0000;", ...args);
  197. }
  198. })();

QingJ © 2025

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