Enhanced Audio Speed Controller with Time Info, Speed Highlight, and Toggle

Adds time information (duration, currentTime, etc.), adjusts for playback speed, highlights the active speed button, and has a toggle for hiding/showing the control panel.

当前为 2024-09-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Enhanced Audio Speed Controller with Time Info, Speed Highlight, and Toggle
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.0
  5. // @description Adds time information (duration, currentTime, etc.), adjusts for playback speed, highlights the active speed button, and has a toggle for hiding/showing the control panel.
  6. // @author Josh Gough
  7. // @match *://*/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. let startTime = Date.now(); // Track the real-time start
  16. let isPanelVisible = true; // Track panel visibility
  17.  
  18. const odIcon = '🕰️';
  19. const adIcon = '⏰';
  20. const ctIcon = '⌚';
  21. const pcIcon = '➗';
  22. const trIcon = '⏳';
  23. const wcIcon = '🕛';
  24.  
  25. // Helper function to convert fractional minutes into hh:mm:ss format
  26. function convertToTimeFormat(minutes) {
  27. const totalSeconds = Math.floor(minutes * 60); // Convert minutes to seconds
  28. const hours = Math.floor(totalSeconds / 3600); // Calculate full hours
  29. const remainingSeconds = totalSeconds % 3600; // Remaining seconds after hours
  30. const mins = Math.floor(remainingSeconds / 60); // Full minutes
  31. const secs = remainingSeconds % 60; // Remaining seconds
  32.  
  33. // Format the time string to always show two digits
  34. const formattedTime =
  35. (hours > 9 ? hours : '0' + hours) + ':' +
  36. (mins > 9 ? mins : '0' + mins) + ':' +
  37. (secs > 9 ? secs : '0' + secs);
  38.  
  39. return formattedTime;
  40. }
  41.  
  42. // Function to calculate and update time stats
  43. function updateTimeStats() {
  44. const audioElement = document.querySelector('audio');
  45. if (audioElement && audioElement.duration && !isNaN(audioElement.duration)) {
  46. const duration = audioElement.duration;
  47. const adjustedDuration = audioElement.duration / audioElement.playbackRate / 60;
  48. const currentTime = audioElement.currentTime;
  49. const playbackRate = audioElement.playbackRate;
  50. const currentTimeDisp = currentTime / 60 / playbackRate;
  51. const percentComplete = (currentTime / duration) * 100; // Correct percentage format
  52.  
  53. // Time remaining at current speed
  54. const timeRemaining = (adjustedDuration - currentTimeDisp);
  55. // Total elapsed wall clock time (accounting for pauses)
  56. const elapsedWallClockTime = (Date.now() - startTime) / 1000 / 60;
  57.  
  58. // Update the DOM elements with the values
  59. document.getElementById('original-duration').innerHTML = `${odIcon}<br>${convertToTimeFormat(duration / 60)}`;
  60. document.getElementById('adjusted-duration').innerHTML = `${adIcon}<br>${convertToTimeFormat(adjustedDuration)}`;
  61. document.getElementById('current-time').innerHTML = `${ctIcon}<br>${convertToTimeFormat(currentTimeDisp)}`;
  62. document.getElementById('percent-complete').innerHTML = `<span class="rotate">${pcIcon}</span><br>${percentComplete.toFixed(2)}%`;
  63. document.getElementById('time-remaining').innerHTML = `${trIcon}<br>${convertToTimeFormat(timeRemaining)}`;
  64. document.getElementById('elapsed-wall-clock').innerHTML = `${wcIcon}<br>${convertToTimeFormat(elapsedWallClockTime)}`;
  65. }
  66. }
  67.  
  68. // Function to create the control panel with buttons and time information
  69. function createControlPanel() {
  70. // Check if control panel already exists
  71. if (document.getElementById('audio-speed-control')) return;
  72.  
  73. const controlDiv = document.createElement('div');
  74. controlDiv.id = 'audio-speed-control';
  75. controlDiv.style.position = 'fixed';
  76. controlDiv.style.top = '20%'; // Position it at 20% from the top (adjust as needed)
  77. controlDiv.style.right = '0'; // Align to the far right edge
  78. controlDiv.style.background = 'rgba(0, 0, 0, 0.15)'; // 85% transparent background
  79. controlDiv.style.padding = '5px';
  80. controlDiv.style.borderRadius = '5px';
  81. controlDiv.style.zIndex = '999999'; // High z-index
  82. controlDiv.style.display = 'flex';
  83. controlDiv.style.flexDirection = 'column'; // Stack buttons vertically
  84. controlDiv.style.fontSize = '7pt'; // Make font smaller as requested
  85. controlDiv.style.transition = 'transform 0.5s ease'; // Smooth horizontal sliding transition
  86.  
  87. // Section for time stats
  88. const timeStats = document.createElement('div');
  89. timeStats.style.marginBottom = '4px'; // Spacing above the buttons
  90. timeStats.style.fontSize = '6pt';
  91. timeStats.style.color = 'white';
  92. timeStats.style.fontWeight = "bold";
  93. timeStats.style.textAlign = 'center';
  94.  
  95. // Define an array of objects, where each object contains the id and innerHTML
  96. const timeStatsData = [
  97. { id: 'original-duration', label: odIcon },
  98. { id: 'adjusted-duration', label: adIcon },
  99. { id: 'current-time', label: ctIcon },
  100. { id: 'percent-complete', label: pcIcon },
  101. { id: 'time-remaining', label: trIcon },
  102. { id: 'elapsed-wall-clock', label: wcIcon }
  103. ];
  104.  
  105. // Loop over the timeStatsData array to create and append each stat element
  106. timeStatsData.forEach(stat => {
  107. const statDiv = document.createElement('div');
  108. statDiv.id = stat.id;
  109. statDiv.innerHTML = stat.label + "<br>--:--:--"; // Placeholder until data is available
  110. timeStats.appendChild(statDiv);
  111. });
  112.  
  113. // Append all the time stats
  114. controlDiv.appendChild(timeStats);
  115.  
  116. // Now we create the speed control buttons as before
  117. const speeds = [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5];
  118. let activeButton = null; // Track the currently active button
  119.  
  120. speeds.forEach(speed => {
  121. const button = document.createElement('button');
  122. button.innerText = speed.toFixed(2); // Label each button with the speed
  123. button.style.padding = '3px 5px'; // Small button size
  124. button.style.marginBottom = '2px'; // Small margin between buttons
  125. button.style.backgroundColor = '#bada55';
  126. button.style.border = 'none';
  127. button.style.borderRadius = '3px';
  128. button.style.cursor = 'pointer';
  129. button.style.fontSize = '8px';
  130. button.style.fontWeight = 'bold'; // Small font size
  131. button.style.color = '#222';
  132. button.style.width = '36px'; // Small width for the buttons
  133.  
  134. // Function to set button as active
  135. function setActiveButton() {
  136. if (activeButton) {
  137. // Reset previously active button's style
  138. activeButton.style.backgroundColor = '#bada55';
  139. activeButton.style.color = '#222';
  140. activeButton.style.fontWeight = 'normal';
  141. }
  142. // Set new active button's style
  143. button.style.backgroundColor = '#006400'; // Dark green background
  144. button.style.color = '#fff'; // White text
  145. button.style.fontWeight = 'bold'; // Bold text
  146. activeButton = button; // Set this as the active button
  147. }
  148.  
  149. // Set audio speed when button is clicked
  150. button.addEventListener('click', function () {
  151. const audioElement = document.querySelector('audio');
  152. if (audioElement) {
  153. audioElement.playbackRate = speed;
  154. setActiveButton(); // Highlight the active button
  155. }
  156. });
  157.  
  158. controlDiv.appendChild(button);
  159. });
  160.  
  161. // Create toggle button attached to the top-left of the control panel
  162. const toggleButton = document.createElement('button');
  163. toggleButton.innerText = '◀'; // Icon for sliding out/in
  164. toggleButton.style.position = 'absolute';
  165. toggleButton.style.top = '5px';
  166. toggleButton.style.left = '-15px'; // Positioned just outside the left of the control panel
  167. toggleButton.style.backgroundColor = '#bada55';
  168. toggleButton.style.border = 'none';
  169. toggleButton.style.borderRadius = '20%';
  170. toggleButton.style.cursor = 'pointer';
  171. toggleButton.style.padding = '2px';
  172. toggleButton.style.zIndex = '1000'; // Ensure it stays on top
  173.  
  174. // Event listener for the toggle button
  175. toggleButton.addEventListener('click', () => {
  176. if (isPanelVisible) {
  177. controlDiv.style.transform = 'translateX(100%)'; // Slide horizontally to the right
  178. toggleButton.innerText = '▶'; // Change icon to indicate sliding back
  179. } else {
  180. controlDiv.style.transform = 'translateX(0)'; // Slide back to the original position
  181. toggleButton.innerText = '◀'; // Change icon back
  182. }
  183. isPanelVisible = !isPanelVisible; // Toggle visibility flag
  184. });
  185.  
  186. // Append toggle button to the control panel
  187. controlDiv.appendChild(toggleButton);
  188.  
  189. // Simplify the control panel sliding transition
  190. controlDiv.style.transition = 'transform 0.5s ease'; // Smooth horizontal sliding transition
  191.  
  192. // Append the control panel to the body
  193. document.body.appendChild(controlDiv);
  194.  
  195.  
  196. }
  197.  
  198. // CSS to rotate the emoji
  199. const style = document.createElement('style');
  200. style.innerHTML = `
  201. .rotate {
  202. display: inline-block;
  203. transform: rotate(45deg); /* Rotates emoji */
  204. }
  205. `;
  206. document.head.appendChild(style);
  207.  
  208. // Update the time stats periodically
  209. setInterval(updateTimeStats, 1000); // Update every second
  210.  
  211. // Wait for the document to fully load and ensure audio element exists
  212. const observer = new MutationObserver((mutations, observer) => {
  213. const audioElement = document.querySelector('audio');
  214. if (audioElement) {
  215. audioElement.addEventListener('loadedmetadata', () => {
  216. audioElement.playbackRate = 2.0; // Set default playback speed to 2.0
  217. createControlPanel();
  218. updateTimeStats();
  219. });
  220. observer.disconnect(); // Stop observing once the audio is found
  221. }
  222. });
  223.  
  224. // Start observing the document for changes
  225. observer.observe(document, {
  226. childList: true,
  227. subtree: true
  228. });
  229.  
  230. })();