Greasy Fork is available in English.

YouTube Exit Fullscreen on Video End (Modified)

Exit YouTube fullscreen when a video finishes playing (disabled for playlists)

  1. // ==UserScript==
  2. // @name YouTube Exit Fullscreen on Video End (Modified)
  3. // @namespace https://www.youtube.com/
  4. // @version 1.3.2
  5. // @description Exit YouTube fullscreen when a video finishes playing (disabled for playlists)
  6. // @author CY Fung
  7. // @match *://www.youtube.com/*
  8. // @license MIT
  9. // @icon https://www.google.com/s2/favicons?domain=youtube.com
  10. // @grant none
  11. // @run-at document-start
  12. // ==/UserScript==
  13.  
  14. (() => {
  15.  
  16. /** @type {globalThis.PromiseConstructor} */
  17. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  18.  
  19. let lastPause = 0;
  20. let lastFullscreenEnded = 0;
  21. let mutObserver = null;
  22. let dt0 = Date.now() - 2000;
  23. const nowFn = () => Date.now() - dt0;
  24.  
  25. const check = () => {
  26. let t = nowFn();
  27. return t - lastFullscreenEnded < 800 && t - lastPause < 800 && Math.abs(lastFullscreenEnded - lastPause) < 400;
  28. };
  29.  
  30. const mCallback = (mutations) => {
  31. if (!document.fullscreenElement || !mutations || !mutations.length) return;
  32. let detected = false;
  33. let video = null;
  34. for (const mutation of mutations) {
  35. video = (mutation || 0).target;
  36. if (!video) continue;
  37. const newValue = video.className;
  38. const oldValue = mutation.oldValue;
  39. if (newValue.indexOf("ended-mode") >= 0 && oldValue.indexOf("ended-mode") < 0) {
  40. detected = true;
  41. break;
  42. }
  43. }
  44. if (detected) {
  45. if (video && video.classList.contains("ended-mode")) {
  46. lastFullscreenEnded = nowFn();
  47. check() && endFullscreen(video);
  48. }
  49. }
  50. };
  51.  
  52. const endFullscreen = (elm) => {
  53. lastPause = 0;
  54. lastFullscreenEnded = 0;
  55. if(location.search.includes("list=")) return;
  56. const movie_player = HTMLElement.prototype.closest.call(elm, '#movie_player');
  57. const btn = movie_player ? HTMLElement.prototype.querySelector.call(movie_player, '.ytp-fullscreen-button') : null;
  58. Promise.resolve(btn).then(btn => {
  59. if (btn) {
  60. btn.click();
  61. } else {
  62. document.exitFullscreen();
  63. }
  64. })
  65. };
  66.  
  67. const setup = () => {
  68. const movie_player = location.pathname.includes("/watch") ? document.getElementById('movie_player') : null;
  69. if (movie_player) {
  70. if (!mutObserver) {
  71. mutObserver = new MutationObserver(mCallback);
  72. } else {
  73. mutObserver.disconnect();
  74. mutObserver.takeRecords();
  75. }
  76. mutObserver.observe(movie_player, {
  77. attributes: true,
  78. attributeFilter: ['class'],
  79. attributeOldValue: true
  80. });
  81. } else if (mutObserver) {
  82. mutObserver.disconnect();
  83. mutObserver.takeRecords();
  84. mutObserver = null;
  85. }
  86. };
  87.  
  88. const onPause = (evt) => {
  89. const target = ((evt || 0).target || 0);
  90. if (!(target instanceof HTMLVideoElement)) return;
  91. if (mutObserver === null) return;
  92. const movie_player = HTMLElement.prototype.closest.call(target, '#movie_player');
  93. if (!movie_player) return;
  94. lastPause = nowFn();
  95. check() && endFullscreen(target);
  96. };
  97.  
  98. document.addEventListener('yt-navigate-finish', setup, false);
  99. document.addEventListener('spfdone', setup, true);
  100. document.addEventListener("pause", onPause, true);
  101. setup();
  102. })();