Coursera Custom Subtitles with Video and Mouse Control

Customize subtitles on Coursera videos with line breaks and video control shortcuts (command+left/right for rewind/forward 5 seconds, command+up/down for speed control, space for play/pause, mouse buttons for control)

  1. // ==UserScript==
  2. // @name Coursera Custom Subtitles with Video and Mouse Control
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Customize subtitles on Coursera videos with line breaks and video control shortcuts (command+left/right for rewind/forward 5 seconds, command+up/down for speed control, space for play/pause, mouse buttons for control)
  6. // @author readpan@gmail.com
  7. // @match https://www.coursera.org/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Function to add custom subtitle container
  16. function addSubtitleContainer(video) {
  17. if (!video) return;
  18.  
  19. // Check if a subtitle container already exists
  20. if (video.parentElement.querySelector('#subtitle-container')) {
  21. return;
  22. }
  23.  
  24. const subtitleContainer = document.createElement('div');
  25. subtitleContainer.id = 'subtitle-container';
  26. subtitleContainer.style.position = 'absolute';
  27. subtitleContainer.style.bottom = '30px'; // Adjust this value to change subtitle position
  28. subtitleContainer.style.width = '100%';
  29. subtitleContainer.style.textAlign = 'center';
  30. subtitleContainer.style.color = 'white';
  31. subtitleContainer.style.background = 'rgba(0, 0, 0, 0.7)';
  32. subtitleContainer.style.fontSize = '30px'; // Set subtitle font size
  33. subtitleContainer.style.pointerEvents = 'none';
  34. subtitleContainer.style.zIndex = '1000'; // Ensure subtitle container is on top
  35.  
  36. video.parentElement.style.position = 'relative';
  37. video.parentElement.appendChild(subtitleContainer);
  38.  
  39. const tracks = video.querySelectorAll('track');
  40. tracks.forEach(track => {
  41. track.addEventListener('load', () => {
  42. const textTrack = track.track;
  43. textTrack.mode = 'hidden'; // Hide default subtitles
  44.  
  45. // Listen to cue change for each track
  46. textTrack.addEventListener('cuechange', () => {
  47. if (textTrack.mode === 'hidden') {
  48. const activeCues = textTrack.activeCues;
  49. if (activeCues.length > 0) {
  50. subtitleContainer.innerHTML = formatSubtitles(activeCues[0].text);
  51. } else {
  52. subtitleContainer.textContent = '';
  53. }
  54. }
  55. });
  56. });
  57. });
  58.  
  59. // Function to handle track change
  60. function onTrackChange() {
  61. tracks.forEach(track => {
  62. const textTrack = track.track;
  63. if (textTrack.mode === 'showing') {
  64. textTrack.mode = 'hidden'; // Hide default subtitles
  65. const activeCues = textTrack.activeCues;
  66. if (activeCues.length > 0) {
  67. subtitleContainer.innerHTML = formatSubtitles(activeCues[0].text);
  68. } else {
  69. subtitleContainer.textContent = '';
  70. }
  71. }
  72. });
  73. }
  74.  
  75. // Listen for changes in track selection
  76. video.textTracks.addEventListener('change', onTrackChange);
  77. }
  78.  
  79. // Function to format subtitles with line breaks
  80. function formatSubtitles(text) {
  81. return text.replace(/\n/g, '<br>');
  82. }
  83.  
  84. // Initialize subtitle container for all existing video elements
  85. function initializeSubtitles() {
  86. const videos = document.querySelectorAll('video');
  87. videos.forEach((video) => {
  88. addSubtitleContainer(video);
  89. });
  90. }
  91.  
  92. // Function to handle keyboard shortcuts for video control
  93. function handleKeyboardShortcuts(event) {
  94. const videos = document.querySelectorAll('video');
  95. if ((event.metaKey || event.ctrlKey) && (event.key === 'ArrowLeft' || event.key === 'ArrowRight' || event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
  96. event.preventDefault(); // Prevent default browser action (navigation)
  97. videos.forEach(video => {
  98. if (event.key === 'ArrowLeft') {
  99. video.currentTime = Math.max(0, video.currentTime - 5);
  100. } else if (event.key === 'ArrowRight') {
  101. video.currentTime = Math.min(video.duration, video.currentTime + 5);
  102. } else if (event.key === 'ArrowUp') {
  103. video.playbackRate = Math.min(5, video.playbackRate + 0.25);
  104. } else if (event.key === 'ArrowDown') {
  105. video.playbackRate = Math.max(0.25, video.playbackRate - 0.25);
  106. }
  107. });
  108. }
  109. }
  110.  
  111. // Function to handle mouse buttons for video control
  112. function handleMouseButtons(event) {
  113. const videos = document.querySelectorAll('video');
  114. if (event.button === 3) { // Mouse back button
  115. event.preventDefault(); // Prevent default browser action (navigation)
  116. event.stopPropagation(); // Stop event propagation
  117. videos.forEach(video => {
  118. video.currentTime = Math.max(0, video.currentTime - 5);
  119. });
  120. } else if (event.button === 4) { // Mouse forward button
  121. event.preventDefault(); // Prevent default browser action (navigation)
  122. event.stopPropagation(); // Stop event propagation
  123. videos.forEach(video => {
  124. video.currentTime = Math.min(video.duration, video.currentTime + 5);
  125. });
  126. } else if (event.button === 1) { // Mouse middle button
  127. event.preventDefault(); // Prevent default action
  128. event.stopPropagation(); // Stop event propagation
  129. videos.forEach(video => {
  130. if (video.paused) {
  131. video.play();
  132. } else {
  133. video.pause();
  134. }
  135. });
  136. }
  137. }
  138.  
  139. // Observe the body for added video elements
  140. const observer = new MutationObserver((mutations) => {
  141. mutations.forEach((mutation) => {
  142. mutation.addedNodes.forEach((node) => {
  143. if (node.tagName === 'VIDEO') {
  144. addSubtitleContainer(node);
  145. } else if (node.querySelectorAll) {
  146. node.querySelectorAll('video').forEach((video) => {
  147. addSubtitleContainer(video);
  148. });
  149. }
  150. });
  151. });
  152. });
  153.  
  154. observer.observe(document.body, { childList: true, subtree: true });
  155.  
  156. // Initialize subtitles for existing videos
  157. initializeSubtitles();
  158.  
  159. // Add event listener for keyboard shortcuts
  160. document.addEventListener('keydown', handleKeyboardShortcuts);
  161.  
  162. // Add event listener for mouse buttons
  163. // document.addEventListener('mousedown', handleMouseButtons);
  164. document.addEventListener('mouseup', handleMouseButtons);
  165. })();

QingJ © 2025

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