Disable HTML5 Video AutoPlay and Background Play (Pause On Leave Tab)

Prevents HTML5 videos from auto-playing in new tabs on any page (not just YouTube) and pauses videos when leave tab. NOTE: On YouTube, click the main video area to play the video so don't have to click the Pause/Play button twice for the first time. From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB).

当前为 2017-06-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Disable HTML5 Video AutoPlay and Background Play (Pause On Leave Tab)
  3. // @namespace xeonx1
  4. // @version 1.5
  5. // @description Prevents HTML5 videos from auto-playing in new tabs on any page (not just YouTube) and pauses videos when leave tab. NOTE: On YouTube, click the main video area to play the video so don't have to click the Pause/Play button twice for the first time. From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB).
  6. // @author Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB) and
  7. // @match http://*/*
  8. // @match https://*/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. // **** User Preferences ****
  16. //whether to pause videos when leaving a tab they are playing on
  17. var pauseOnLeaveTab = true;
  18.  
  19. // Number of milliseconds after clicking where a video is allowed to autoplay.
  20. var allowAutoPlayWithinMillisecondsOfClick = 500;
  21.  
  22. //you can add domains (with or without subdomain, must not include http://, etc.) to always allow auto-play
  23. //pause on leave tab will still function however
  24. var autoPlayDomains = [
  25. /*"youtube.com"*/
  26. ];
  27. var hasAutoPlayDomains = autoPlayDomains.length > 0;
  28. // **** End Preferences ***
  29. /*
  30. Beyond this point is the logic of the script.
  31. Do not edit unless you know what you are doing.
  32. */
  33. var lastClickTimeMs = 0;
  34.  
  35. //determine name of event for switched away from tab, based on the browser
  36. var tabHiddenPropertyName, tabVisibleChangedEventName;
  37.  
  38. if ("undefined" !== typeof document.hidden) {
  39. tabHiddenPropertyName = "hidden";
  40. tabVisibleChangedEventName = "visibilitychange";
  41. } else if ("undefined" !== typeof document.webkitHidden) {
  42. tabHiddenPropertyName = "webkitHidden";
  43. tabVisibleChangedEventName = "webkitvisibilitychange";
  44. } else if ("undefined" !== typeof document.msHidden) {
  45. tabHiddenPropertyName = "msHidden";
  46. tabVisibleChangedEventName = "msvisibilitychange";
  47. }
  48.  
  49. function safeAddHandler(element, event, handler) {
  50. element.removeEventListener(event, handler);
  51. element.addEventListener(event, handler);
  52. }
  53. function getVideos() {
  54. //OR: Can also add audio elements
  55. return document.getElementsByTagName("video");
  56. }
  57.  
  58. function isPlaying(vid) {
  59. return !!(vid.currentTime > 0 && !vid.paused && !vid.ended && vid.readyState > 2);
  60. }
  61.  
  62. function onTabVisibleChanged() {
  63.  
  64. //console.log("Tab visibility changed for Video auto-player disabling user script. Document is hidden status: ", document[tabHiddenPropertyName]);
  65.  
  66. var videos = getVideos();
  67.  
  68. //if doc is hidden (switched away from that tab), then pause all its videos
  69. if (document[tabHiddenPropertyName]) {
  70.  
  71. //remember had done this
  72. document.wasPausedOnChangeTab = true;
  73.  
  74. //pause all videos, since
  75. for (var i = 0; i < videos.length; i++) {
  76. var vid = videos[i];
  77. if (vid.isPlaying) {
  78. vid.wasPausedOnChangeTab = true;
  79. console.log("Paused video playback because switched away from this tab for video with source: ", vid.currentSrc);
  80. }
  81.  
  82. //always pause just in case isPlaying isn't always reliable
  83. vid.pause();
  84. }
  85. }
  86. //document is now the active tab
  87. else {
  88. document.wasPausedOnChangeTab = false; //reset state (unless need to use this field or delay this)
  89. //TODO-MAYBE: if want to auto-play once switch back to a tab if had paused before, then uncomment below, after changing from forEach() to for loop
  90. // getVideos().forEach( function(vid) {
  91. // if (vid.wasPausedOnChangeTab == true) {
  92. // vid.wasPausedOnChangeTab = false;
  93. // vid.play();
  94. // }
  95. // } );
  96. }
  97. }
  98.  
  99. //handle active tab change events for this document/tab
  100. if (pauseOnLeaveTab) {
  101. safeAddHandler(document, tabVisibleChangedEventName, onTabVisibleChanged);
  102. }
  103.  
  104. //returns true if auto-play is always allowed, whitelisted, so should do nothing to it
  105. function isAutoPlayAllowedDomain(s) { // Check if video src is whitelisted.
  106.  
  107. if (hasAutoPlayDomains) {
  108. for (var i = 0; i < autoPlayDomains.length; i++) {
  109. var reg = new RegExp("https?\:\/\/[a-zA-Z0-9\.\-]*?\.?" + autoPlayDomains[i].replace(/\./, "\.") + "\/", "i");
  110. if (s.match(reg) !== null) {
  111. return true;
  112. }
  113. }
  114. }
  115. return false;
  116. }
  117. //on pause or ended/finished, change playing state back to not-playing, so know to start preventing playback again unless after a click
  118. function onPaused(e)
  119. {
  120. e.target.isPlaying = false;
  121.  
  122. }
  123. function onPlay(e) { // React when a video begins playing
  124.  
  125. var msSinceLastClick = Date.now() - lastClickTimeMs;
  126. var vid = e.target;
  127.  
  128. //exit, do nothing if is already playing (but not if undefined/unknown), in case clicked on seekbar, volume, etc. - don't toggle to paused state on each click
  129. if(vid.isPlaying == true) {
  130. return;
  131. }
  132. vid.isPlaying = true;
  133.  
  134. //if haven't clicked recently on video, consider it auto-started, so prevent playback by pausing it (unless whitelisted source domain to always play from)
  135. if (msSinceLastClick > allowAutoPlayWithinMillisecondsOfClick && !isAutoPlayAllowedDomain(vid.currentSrc)) {
  136.  
  137. vid.pause();
  138. //remember video is no longer playing - just in case, though event handler for pause should also set this
  139. vid.isPlaying = false;
  140. console.log("Paused video from source: ", vid.currentSrc);
  141. }
  142. }
  143. function addListenersToVideo(vid)
  144. {
  145. if (vid.hasAutoPlayHandlers != true) {
  146. vid.hasAutoPlayHandlers = true;
  147.  
  148. safeAddHandler(vid, "play", onPlay);
  149. //NOTE: Seems playing is needed in addition to play event, but isn't this just supposed to occur whenever play, plus after play once buffering is finished?
  150. safeAddHandler(vid, "playing", onPlay);
  151.  
  152. safeAddHandler(vid, "pause", onPaused);
  153. safeAddHandler(vid, "ended", onPaused);
  154. }
  155. }
  156. function addListeners() {
  157.  
  158. var videos = getVideos();
  159. //OR: Can get audio elements too
  160.  
  161. for (var i = 0; i < videos.length; i++) {
  162. // Due to the way some sites dynamically add videos, the "playing" event is not always sufficient.
  163. // Also, in order to handle dynamically added videos, this function may be called on the same elements.
  164. // Must remove any existing instances of this event listener before adding. Prevent duplicate listeners.
  165. var vid = videos[i];
  166.  
  167. addListenersToVideo(vid);
  168. }
  169. }
  170.  
  171. //handle click event so can limit auto play until X time after a click
  172. safeAddHandler(document, "click", function () {
  173. lastClickTimeMs = Date.now();
  174. });
  175.  
  176. var observer = new MutationObserver(function(mutations) {
  177. // Listen for elements being added. Add event listeners when video elements are added.
  178. mutations.forEach(function(mutation) {
  179.  
  180. if (mutation.type == "attributes" && mutation.target.tagName == "VIDEO") { //&& mutation.attributeName == "src"
  181.  
  182. videoAdded = true;
  183.  
  184. addListenersToVideo(mutation.target);
  185. }
  186.  
  187. if (mutation.addedNodes.length > 0) {
  188.  
  189. addListeners();
  190.  
  191. //faster to use getElementsByTagName() for rarely added types vs. iterating over all added elements, checking tagName
  192. // for (var i = 0; i < mutation.addedNodes.length; i++) {
  193. // var added = mutation.addedNodes[i];
  194. // if (added.nodeType == 1 && added.tagName == "VIDEO") {
  195. // videoAdded = true;
  196. // }
  197. // }
  198. }
  199. });
  200. });
  201.  
  202. //subscribe to documents events for node added and src attribute changed via MutatorObserver, limiting to only src attribute changes
  203. observer.observe(document, { attributes: true, childList: true, subtree: true, characterData: false, attributeFilter: ['src'] });
  204.  
  205. //don't also need to handle "spfdone" event
  206.  
  207. //hookup event handlers for all videos that exist now (will still add to any that are inserted later)
  208. addListeners();
  209.  
  210. })();