Yes Continue

Kiss the annoying "Video paused. Continue watching?" confirmation goodbye!

  1. // ==UserScript==
  2. // @name Yes Continue
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.0
  5. // @description Kiss the annoying "Video paused. Continue watching?" confirmation goodbye!
  6. // @author elliottophellia
  7. // @license GPL-3.0
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @homepageURL https://github.com/elliottophellia/yescontinue
  10. // @supportURL https://github.com/elliottophellia/yescontinue/issues
  11. // @match https://www.youtube.com/*
  12. // @match https://music.youtube.com/*
  13. // @run-at document-idle
  14. // @compatible chrome
  15. // @compatible firefox
  16. // @compatible opera
  17. // @compatible edge
  18. // ==/UserScript==
  19.  
  20. (async () => {
  21. 'use strict';
  22.  
  23. const tag = '[Yes Continue]';
  24. const isYoutubeMusic = window.location.hostname === 'music.youtube.com';
  25. const popupEventNodename = isYoutubeMusic ? 'YTMUSIC-YOU-THERE-RENDERER' : 'YT-CONFIRM-DIALOG-RENDERER';
  26. const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  27. const appName = isYoutubeMusic ? 'ytmusic-app' : 'ytd-app';
  28. const popupContainer = isYoutubeMusic ? 'ytmusic-popup-container' : 'ytd-popup-container';
  29. const pauseRequestedTimeoutMillis = 5000;
  30. const idleTimeoutMillis = 5000;
  31.  
  32. let appObserver = null;
  33. let pauseRequested = false;
  34. let pauseRequestedTimeout;
  35. let lastInteractionTime = Date.now();
  36. let videoElement = null;
  37.  
  38. const log = message => console.log(`${tag}[${getTimestamp()}] ${message}`);
  39. const debug = message => console.debug(`${tag}[${getTimestamp()}] ${message}`);
  40.  
  41. const asDoubleDigit = value => value < 10 ? `0${value}` : value;
  42.  
  43. const getTimestamp = () => {
  44. const dt = new Date();
  45. return `${asDoubleDigit(dt.getHours())}:${asDoubleDigit(dt.getMinutes())}:${asDoubleDigit(dt.getSeconds())}`;
  46. };
  47.  
  48. const isIdle = () => Date.now() - lastInteractionTime >= idleTimeoutMillis;
  49.  
  50. const listenForMediaKeys = () => {
  51. if (!navigator.mediaSession) {
  52. log("Your browser doesn't seem to support navigator.mediaSession yet :/");
  53. return;
  54. }
  55. navigator.mediaSession.setActionHandler('pause', pauseVideo);
  56. navigator.mediaSession.yns_setActionHandler = navigator.mediaSession.setActionHandler;
  57. navigator.mediaSession.setActionHandler = (action, fn) => {
  58. if (action !== 'pause') {
  59. navigator.mediaSession.yns_setActionHandler(action, fn);
  60. }
  61. };
  62. };
  63.  
  64. const listenForMouse = () => {
  65. const eventName = window.PointerEvent ? 'pointer' : 'mouse';
  66. ['down', 'up'].forEach(type =>
  67. document.addEventListener(`${eventName}${type}`, () => processInteraction(`${eventName}${type}`))
  68. );
  69. };
  70.  
  71. const listenForKeyboard = () => {
  72. ['keydown', 'keyup'].forEach(type =>
  73. document.addEventListener(type, () => processInteraction(type))
  74. );
  75. };
  76.  
  77. const processInteraction = action => {
  78. if (pauseRequested) {
  79. pauseVideo();
  80. return;
  81. }
  82. lastInteractionTime = Date.now();
  83. };
  84.  
  85. const observeApp = () => {
  86. appObserver = new MutationObserver(overrideVideoPause);
  87. appObserver.observe(document.querySelector(appName), {
  88. childList: true,
  89. subtree: true
  90. });
  91. };
  92.  
  93. const listenForPopupEvent = () => {
  94. document.addEventListener('yt-popup-opened', (e) => {
  95. if (isIdle() && e.detail.nodeName === popupEventNodename) {
  96. document.querySelector(popupContainer).click();
  97. pauseVideo();
  98. videoElement?.play();
  99. }
  100. });
  101. };
  102.  
  103. const overrideVideoPause = () => {
  104. if (videoElement?.yns_pause !== undefined) return;
  105. videoElement = document.querySelector('video');
  106. if (!videoElement) return;
  107.  
  108. listenForMediaKeys();
  109. videoElement.yns_pause = videoElement.pause;
  110. videoElement.pause = () => {
  111. if (!isIdle()) {
  112. pauseVideo();
  113. return;
  114. }
  115. pauseRequested = true;
  116. setPauseRequestedTimeout();
  117. };
  118. };
  119.  
  120. const setPauseRequestedTimeout = (justClear = false) => {
  121. clearTimeout(pauseRequestedTimeout);
  122. if (!justClear) {
  123. pauseRequestedTimeout = setTimeout(() => {
  124. pauseRequested = false;
  125. }, pauseRequestedTimeoutMillis);
  126. }
  127. };
  128.  
  129. const pauseVideo = () => {
  130. videoElement?.yns_pause();
  131. pauseRequested = false;
  132. setPauseRequestedTimeout(true);
  133. };
  134.  
  135. const waitForApp = () => {
  136. return new Promise(resolve => {
  137. const checkApp = () => {
  138. if (document.querySelector(appName)) {
  139. resolve();
  140. } else {
  141. setTimeout(checkApp, 100);
  142. }
  143. };
  144. checkApp();
  145. });
  146. };
  147.  
  148. await waitForApp();
  149.  
  150. listenForMouse();
  151. listenForKeyboard();
  152. listenForPopupEvent();
  153. observeApp();
  154. log(`Monitoring YouTube ${isYoutubeMusic ? 'Music ' : ''}for 'Video paused. Continue watching?' action...`);
  155. })();