AutoPlay Disabled for HTML5 Videos + Pause on Switch Tab

Prevents auto-play HTML5 videos in new tabs on any page (not just YouTube) and pauses videos when leave/switch/change the current tab, to prevent any video playing in the background. This auto play blocker is an alternative to Click-to-Play / click to play. On some times like YouTube, click twice to begin first playback — From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com)

  1. // ==UserScript==
  2. // @name AutoPlay Disabled for HTML5 Videos + Pause on Switch Tab
  3. // @namespace xeonx1
  4. // @version 1.83
  5. // @description Prevents auto-play HTML5 videos in new tabs on any page (not just YouTube) and pauses videos when leave/switch/change the current tab, to prevent any video playing in the background. This auto play blocker is an alternative to Click-to-Play / click to play. On some times like YouTube, click twice to begin first playback — From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com)
  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. // For websites you won't to disable this script on, you can add domains (with or without subdomain, must not include http://, etc.) to always allow auto-play (doesn't affect pause on leave tab)
  23. var autoPlaySitesWhitelist = [
  24. // "youtube.com"
  25. ];
  26. // For video hosting sources (eg. YouTube used for videos embedded on other sites), you can add domains (with or without subdomain, must not include http://, etc.) to always allow auto-play (doesn't affect pause on leave tab)
  27. var autoPlaySourcesWhitelist = [
  28. // "youtube.com"
  29. ];
  30. //Advanced preferences from controlling side compatibility / testing:
  31. //seems required for:
  32. var handlePlayingInAdditionToPlayEvent = false;
  33. var allowPauseAgainAfterFirstFound = false;
  34. var treatPlayingLikeOnPlay = false;
  35. // ******** End Preferences ********
  36.  
  37. /* Test Pages:
  38. https://www.youtube.com/watch?v=OMOVFvcNfvE
  39. http://www.imdb.com/title/tt2527336/videoplayer/vi1488632089
  40. https://trailers.apple.com/trailers/lucasfilm/star-wars-the-last-jedi/
  41. https://www.theguardian.com/film/video/2017/apr/14/star-wars-last-jedi-trailer-film-current-sequel-trilogy-video
  42. http://www.politico.com/video
  43. http://www.nfl.com/videos
  44. Known Issues:
  45. Have to click twice the first time to start:
  46. https://www.youtube.com/watch?v=OMOVFvcNfvE
  47. https://www.theguardian.com/film/video/2017/apr/14/star-wars-last-jedi-trailer-film-current-sequel-trilogy-video
  48. Clicking anywhere except Play button causes to pause (so seeking, or click on video itself to unpause)
  49. https://www.usatoday.com/story/life/movies/2017/04/14/star-wars-the-last-jedi-trailer-analysis/100466154/
  50. Still Auto Plays:
  51. http://www.cnn.com/videos (eventually auto-plays, stops for long time first, seems to switch between a few videos)
  52. Test JS Snippets:
  53. document.querySelector("video.html5-main-video").pause() //for pause on YouTube
  54. TODO (MAYBE):
  55. Allow pressing play after pressing spacebar?
  56. Delay YouTube pausing to avoid having to double click first time to play?
  57. */
  58. //TODO-MAYBE: We could add support for click anywhere on video to play/pause, but some video players may not update their play button status and therefore could be out-of-sync and many of them already support that (though not Apple Trailers, etc.)
  59. var hasAutoPlaySourcesWhitelist = autoPlaySourcesWhitelist.length > 0;
  60. var hasAutoPlaySitesWhitelist = autoPlaySitesWhitelist.length > 0;
  61. var lastClickTimeMs = 0;
  62. function isUrlMatch(url, pattern) {
  63. var regex = "https?\:\/\/[a-zA-Z0-9\.\-]*?\.?" + pattern.replace(/\./, "\.") + "\/";
  64. var reg = new RegExp(regex, "i");
  65. return url.match(reg) !== null;
  66. }
  67. //returns true if auto-play is always allowed for the *website* video is shown on (not source its hosted on)
  68. function isAutoPlayAllowedForSite(url) { // Check if video src is whitelisted.
  69.  
  70. if (hasAutoPlaySitesWhitelist) {
  71. for (var i = 0; i < autoPlaySitesWhitelist.length; i++) {
  72. if (isUrlMatch(url, autoPlaySitesWhitelist[i]))
  73. return true;
  74. }
  75. }
  76. return false;
  77. }
  78. //exit, if the page shown in tab is whitelisted (regardless of where video is hosted / embedded from)
  79. if (isAutoPlayAllowedForSite(document.url)) {
  80. return;
  81. }
  82.  
  83. //determine name of event for switched away from tab, based on the browser
  84. var tabHiddenPropertyName, tabVisibleChangedEventName;
  85.  
  86. if ("undefined" !== typeof document.hidden) {
  87. tabHiddenPropertyName = "hidden";
  88. tabVisibleChangedEventName = "visibilitychange";
  89. } else if ("undefined" !== typeof document.webkitHidden) {
  90. tabHiddenPropertyName = "webkitHidden";
  91. tabVisibleChangedEventName = "webkitvisibilitychange";
  92. } else if ("undefined" !== typeof document.msHidden) {
  93. tabHiddenPropertyName = "msHidden";
  94. tabVisibleChangedEventName = "msvisibilitychange";
  95. }
  96.  
  97. function safeAddHandler(element, event, handler) {
  98. element.removeEventListener(event, handler);
  99. element.addEventListener(event, handler);
  100. }
  101. function getVideos() {
  102. //OR: Can also add audio elements
  103. return document.getElementsByTagName("video");
  104. }
  105.  
  106. function isPlaying(vid) {
  107. return !!(vid.currentTime > 0 && !vid.paused && !vid.ended && vid.readyState > 2);
  108. }
  109.  
  110. function onTabVisibleChanged() {
  111.  
  112. //console.log("Tab visibility changed for Video auto-player disabling user script. Document is hidden status: ", document[tabHiddenPropertyName]);
  113.  
  114. var videos = getVideos();
  115.  
  116. //if doc is hidden (switched away from that tab), then pause all its videos
  117. if (document[tabHiddenPropertyName]) {
  118.  
  119. //remember had done this
  120. document.wasPausedOnChangeTab = true;
  121.  
  122. //pause all videos, since
  123. for (var i = 0; i < videos.length; i++) {
  124. var vid = videos[i];
  125. pauseVideo(vid, true);
  126. }
  127. }
  128. //document is now the active tab
  129. else {
  130. document.wasPausedOnChangeTab = false; //reset state (unless need to use this field or delay this)
  131. //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
  132. // getVideos().forEach( function(vid) {
  133. // if (vid.wasPausedOnChangeTab == true) {
  134. // vid.wasPausedOnChangeTab = false;
  135. // vid.play();
  136. // }
  137. // } );
  138. }
  139. }
  140.  
  141. //handle active tab change events for this document/tab
  142. if (pauseOnLeaveTab) {
  143. safeAddHandler(document, tabVisibleChangedEventName, onTabVisibleChanged);
  144. }
  145. //returns true if auto-play is always allowed for the *website* video is shown on (not source its hosted on)
  146. //so YouTube videos embedded onto other sites will be blocked if YouTube is blocked here
  147. function isAutoPlayAllowedForSource(url) { // Check if video src is whitelisted.
  148. //NOTE: URL can start with blob: like on YouTube
  149. if (hasAutoPlaySourcesWhitelist) {
  150. for (var i = 0; i < autoPlaySitesWhitelist.length; i++) {
  151. if (isUrlMatch(url, hasAutoPlaySourcesWhitelist[i]))
  152. return true;
  153. }
  154. }
  155. return false;
  156. }
  157. //on pause or ended/finished, change playing state back to not-playing, so know to start preventing playback again unless after a click
  158. function onPaused(e)
  159. {
  160. e.target.isPlaying = false;
  161. }
  162. function pauseVideo(vid, isLeavingTab) {
  163. var eventName = "auto-play";
  164. if (isLeavingTab == true) //also handle undefind/unknown states
  165. {
  166. //OR: if wan't to avoid logging in some cases if not sure if playing:
  167. //if {vid.isPlaying != false) console.log("Paused video playback because switched away from this tab for video with source: ", vid.currentSrc); else logIt = false;
  168. vid.wasPausedOnChangeTab = true;
  169. eventName = "on leaving tab";
  170. //console.log("Paused video playback because switched away from this tab for video with source: ", vid.currentSrc);
  171. }
  172. console.log("Paused video " + eventName + " from source: ", vid.currentSrc);
  173. //remember video is no longer playing - just in case, though event handler for pause should also set this
  174. vid.isPlaying = false;
  175. //always pause regardless of isPlaying or isVideoPlaying() since aren't always reliable
  176. vid.pause();
  177. }
  178. function onPlay(e)
  179. {
  180. onPlayOrLoaded(e, true);
  181. }
  182. function onPlaying(e)
  183. {
  184. onPlayOrLoaded(e, false);
  185. }
  186. function onPlayOrLoaded(e, isPlayConfirmed) { // React when a video begins playing
  187.  
  188. var msSinceLastClick = Date.now() - lastClickTimeMs;
  189. var vid = e.target;
  190.  
  191. //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
  192. if(vid.isPlaying == true) {
  193. //return;
  194. }
  195.  
  196. //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)
  197. if (msSinceLastClick > allowAutoPlayWithinMillisecondsOfClick && !isAutoPlayAllowedForSource(vid.currentSrc)) {
  198.  
  199. pauseVideo(vid);
  200. } else
  201. {
  202. vid.isPlaying = isPlayConfirmed || treatPlayingLikeOnPlay;
  203. }
  204. }
  205. function addListenersToVideo(vid, srcChanged)
  206. {
  207. var pauseNow = false;
  208. //if this is first time found this video
  209. if (vid.hasAutoPlayHandlers != true) {
  210. vid.hasAutoPlayHandlers = true;
  211.  
  212. safeAddHandler(vid, "play", onPlay);
  213. //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?
  214. if (handlePlayingInAdditionToPlayEvent)
  215. safeAddHandler(vid, "playing", onPlaying);
  216.  
  217. safeAddHandler(vid, "pause", onPaused);
  218. safeAddHandler(vid, "ended", onPaused);
  219. pauseNow = true;
  220. }
  221. //if video source URL has NOT changed and had already hooked up and paused this video before, then exit, don't pause again (in case user had clicked to play it earlier but another video injected into the page caused inspecting all videos again)
  222. //else if (srcChanged != true)
  223. // return; //exit, don't pause it again
  224. //pause the video since this is the first time was found OR src attribute had changed
  225. if (pauseNow || srcChanged == true) {
  226. pauseVideo(vid);
  227. if (allowPauseAgainAfterFirstFound) {
  228. vid.isPlaying = false; //allow upcoming first play event to cause pausing too this first time
  229. }
  230. }
  231. }
  232. function addListeners() {
  233.  
  234. var videos = getVideos();
  235. //OR: Can get audio elements too
  236.  
  237. for (var i = 0; i < videos.length; i++) {
  238. // Due to the way some sites dynamically add videos, the "playing" event is not always sufficient.
  239. // Also, in order to handle dynamically added videos, this function may be called on the same elements.
  240. // Must remove any existing instances of this event listener before adding. Prevent duplicate listeners.
  241. var vid = videos[i];
  242.  
  243. addListenersToVideo(vid);
  244. }
  245. }
  246.  
  247. //handle click event so can limit auto play until X time after a click
  248. safeAddHandler(document, "click", function () {
  249. lastClickTimeMs = Date.now();
  250. });
  251.  
  252. var observer = new MutationObserver(function(mutations) {
  253. // Listen for elements being added. Add event listeners when video elements are added.
  254. mutations.forEach(function(mutation) {
  255.  
  256. if (mutation.type == "attributes" && mutation.target.tagName == "VIDEO") { //&& mutation.attributeName == "src"
  257.  
  258. videoAdded = true;
  259.  
  260. addListenersToVideo(mutation.target, true);
  261. }
  262.  
  263. if (mutation.addedNodes.length > 0) {
  264.  
  265. addListeners();
  266.  
  267. //faster to use getElementsByTagName() for rarely added types vs. iterating over all added elements, checking tagName
  268. // for (var i = 0; i < mutation.addedNodes.length; i++) {
  269. // var added = mutation.addedNodes[i];
  270. // if (added.nodeType == 1 && added.tagName == "VIDEO") {
  271. // videoAdded = true;
  272. // }
  273. // }
  274. }
  275. });
  276. });
  277.  
  278. //subscribe to documents events for node added and src attribute changed via MutatorObserver, limiting to only src attribute changes
  279. observer.observe(document, { attributes: true, childList: true, subtree: true, characterData: false, attributeFilter: ['src'] });
  280.  
  281. //don't also need to handle "spfdone" event
  282.  
  283. //hookup event handlers for all videos that exist now (will still add to any that are inserted later)
  284. addListeners();
  285.  
  286. })();