YouTube Undo

Undo and redo changes in playback position on YouTube

  1. // ==UserScript==
  2. // @name YouTube Undo
  3. // @description Undo and redo changes in playback position on YouTube
  4. // @version 0.2.0
  5. // @author Adam Thompson-Sharpe
  6. // @namespace MysteryBlokHed
  7. // @license GPL-3.0
  8. // @copyright 2022 Adam Thomspon-Sharpe
  9. // @homepageURL https://gitlab.com/MysteryBlokHed/userscripts/-/tree/main/YouTubeUndo
  10. // @supportURL https://gitlab.com/MysteryBlokHed/userscripts/-/issues
  11. // @match *://*.youtube.com/watch*
  12. // @grant none
  13. // ==/UserScript==
  14. ;(() => {
  15. var _a
  16. /** Whether to log basic debug events */
  17. const DEBUG_LOGS = false
  18. const debug = DEBUG_LOGS
  19. ? (...args) => console.debug('[YouTube Undo]', ...args)
  20. : () => {}
  21. /** The interval **in seconds** to check the player's current time */
  22. const ROUGH_TIME_RATE = 2
  23. /** Keep track of the current player time while no events are in the array */
  24. let roughTime = 0
  25. setInterval(() => {
  26. const currentTime = player.getCurrentTime()
  27. roughTime = currentTime
  28. }, ROUGH_TIME_RATE * 1000)
  29. /**
  30. * Track the index in the array that matches the current state of undo's/redo's.
  31. * Used to allow undoing and redoing back and forth
  32. */
  33. let undoPoint = -1
  34. /** Time change events */
  35. const timeChanges = []
  36. const addChange = change => {
  37. debug('Adding change event to', timeChanges)
  38. timeChanges.length = undoPoint + 1
  39. timeChanges.push(change)
  40. undoPoint = timeChanges.length - 1
  41. debug('After:', timeChanges)
  42. }
  43. /** The current time change event, using `undoPoint` */
  44. const currentChange = () => {
  45. var _a
  46. return (_a = timeChanges[undoPoint]) !== null && _a !== void 0 ? _a : null
  47. }
  48. /** The last time change event in the list */
  49. const lastChange = () =>
  50. timeChanges.length ? timeChanges[timeChanges.length - 1] : null
  51. /** Get the last change's time if it exists, otherwise use the rough time */
  52. const lastOrRough = () => {
  53. var _a, _b
  54. return (_b =
  55. (_a = lastChange()) === null || _a === void 0 ? void 0 : _a.after) !==
  56. null && _b !== void 0
  57. ? _b
  58. : roughTime
  59. }
  60. // prettier-ignore
  61. const player = document.getElementById('movie_player');
  62. if (!player) {
  63. console.error('[YouTube Undo]', 'Player not found!')
  64. return
  65. }
  66. // Clear events on location changes
  67. window.addEventListener('yt-navigate-finish', () => {
  68. timeChanges.length = 0
  69. undoPoint = -1
  70. debug('New video, clearing event list')
  71. })
  72. // Watch for playbar clicks
  73. ;(_a = document.querySelector('div.ytp-progress-bar')) === null ||
  74. _a === void 0
  75. ? void 0
  76. : _a.addEventListener('click', () => {
  77. const currentTime = player.getCurrentTime()
  78. addChange({
  79. before: lastOrRough(),
  80. after: currentTime,
  81. })
  82. roughTime = currentTime
  83. debug('Added time change for playbar seek', lastChange())
  84. })
  85. // Watch for keypresses
  86. window.addEventListener('keydown', ev => {
  87. if (ev.key.match(/^(?:\d|j|l|ArrowLeft|ArrowRight)$/i)) {
  88. // A key that might change the current time
  89. const last = lastChange()
  90. const currentTime = player.getCurrentTime()
  91. debug('Time-changing key pressed')
  92. debug('Last:', last)
  93. debug('Current:', currentTime)
  94. if (!last) debug('Rough Time:', roughTime)
  95. if (
  96. (last === null || last === void 0 ? void 0 : last.after) !== currentTime
  97. ) {
  98. addChange({
  99. before: lastOrRough(),
  100. after: currentTime,
  101. })
  102. }
  103. } else if (ev.ctrlKey && ev.key.toLowerCase() === 'z') {
  104. // Ctrl + Z
  105. const undoTo = currentChange()
  106. debug('Ctrl + Z pressed')
  107. debug('Full list:', timeChanges)
  108. debug('Undoing to:', undoTo, 'at index', undoPoint)
  109. if (undoTo) player.seekTo(undoTo.before)
  110. if (undoPoint >= 0) undoPoint--
  111. } else if (ev.ctrlKey && ev.key.toLowerCase() === 'y') {
  112. // Ctrl + Y
  113. if (undoPoint < timeChanges.length - 1) undoPoint++
  114. const redoTo = currentChange()
  115. debug('Ctrl + Y pressed')
  116. debug('Full list:', timeChanges)
  117. debug('Redoing to:', redoTo, 'at index', undoPoint)
  118. if (redoTo) player.seekTo(redoTo.after)
  119. }
  120. })
  121. })()