Starburst YouTube

加速跳過 YouTube 的廣告,快... 要更快! 還要更快!!!

目前为 2023-11-19 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Starburst YouTube
  3. // @namespace C2FFB7B4-83BC-11EE-85E1-21839234574D
  4. // @version 1.1.2
  5. // @description 加速跳過 YouTube 的廣告,快... 要更快! 還要更快!!!
  6. // @description:en Speed up to skip YouTube ads, Faster.. Faster!... Even Faster!!!
  7. // @author Rick0
  8. // @match https://www.youtube.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  10. // @compatible firefox Tampermonkey
  11. // @compatible chrome Tampermonkey
  12. // @run-at document-end
  13. // @license MIT
  14. // @noframes
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. "use strict";
  19. const addStyle = (cssCode) => {
  20. const style = document.createElement("style");
  21. style.innerHTML = cssCode, document.head.append(style);
  22. }, canRun = () => location.href.search(/[?&]v=/) > -1, setSkipAdObserver = () => {
  23. console.debug("[setSkipAdObserver] start.");
  24. const abortController = new AbortController(), videoContainer = document.querySelector("#movie_player"), videoPlayer = videoContainer.querySelector(".video-stream"), isAd = () => videoContainer.classList.contains("ad-showing") || videoContainer.classList.contains("ad-interrupting"), clickSkipAdsButton = (buttonContainer) => {
  25. const skipAdsButtonList = [
  26. "button.ytp-ad-skip-button",
  27. "button.ytp-ad-skip-button-modern"
  28. ];
  29. buttonContainer.querySelectorAll(skipAdsButtonList.join(",")).forEach((button) => {
  30. button.click(), console.debug("[clickSkipAdsButton]", button);
  31. });
  32. }, fadVideo = () => {
  33. videoPlayer.pause(), videoPlayer.muted = !0, videoPlayer.currentTime = videoPlayer.duration, videoPlayer.play(), console.debug("[fadVideo]", videoPlayer);
  34. };
  35. {
  36. isAd() && videoPlayer.readyState > 0 && fadVideo();
  37. const handleLoadedmetadata = () => {
  38. console.debug("[loadedmetadata] start."), isAd() && fadVideo(), console.debug("[loadedmetadata] finish.");
  39. };
  40. videoPlayer.addEventListener("loadedmetadata", handleLoadedmetadata), abortController.signal.addEventListener("abort", () => {
  41. videoPlayer.removeEventListener("loadedmetadata", handleLoadedmetadata), console.debug("[abort] loadedmetadata.");
  42. });
  43. }
  44. return new Promise((resolve) => {
  45. const adsContainer = videoContainer.querySelector(".video-ads");
  46. if (adsContainer !== null) {
  47. resolve(adsContainer);
  48. return;
  49. }
  50. const adsContainerObserver = new MutationObserver(() => {
  51. const adsContainer2 = videoContainer.querySelector(".video-ads");
  52. adsContainer2 !== null && (adsContainerObserver.disconnect(), resolve(adsContainer2));
  53. });
  54. adsContainerObserver.observe(videoContainer, { childList: !0 }), abortController.signal.addEventListener("abort", () => {
  55. adsContainerObserver.disconnect(), resolve(null), console.debug("[adsContainerObserver] disconnect.");
  56. });
  57. }).then((adsContainer) => {
  58. if (adsContainer === null)
  59. return;
  60. clickSkipAdsButton(adsContainer);
  61. const skipAdsButtonObserver = new MutationObserver(() => {
  62. clickSkipAdsButton(adsContainer);
  63. });
  64. skipAdsButtonObserver.observe(adsContainer, {
  65. childList: !0,
  66. subtree: !0
  67. }), abortController.signal.addEventListener("abort", () => {
  68. skipAdsButtonObserver.disconnect(), console.debug("[skipAdsButtonObserver] disconnect");
  69. });
  70. }).catch((err) => {
  71. console.debug(err);
  72. }), console.debug("[setSkipAdObserver] finish."), abortController;
  73. }, setCssStyle = () => {
  74. const hiddenCssCode = [
  75. // 右側聊天室上方的廣告
  76. 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]',
  77. // 右側聊天室下方的廣告
  78. "#player-ads",
  79. // 右側推薦影片最上第一個會有廣告
  80. "ytd-ad-slot-renderer",
  81. // 搜尋結果的第一個項目是廣告,火狐不支援 has,使用其他的寫法
  82. // 'ytd-rich-item-renderer:has(ytd-ad-slot-renderer)'
  83. "ytd-rich-grid-row:first-child ytd-rich-item-renderer:first-child",
  84. // 搜尋結果的上方 Banner 那塊
  85. "#masthead-ad",
  86. // == 影片相關 ==
  87. // 影片廣告的控制按鈕之類的內容
  88. ".html5-video-player > .video-ads",
  89. // 影片右上角資訊欄會彈出的內容
  90. ".html5-video-player .ytp-cards-teaser",
  91. // 影片結束後的推薦影片
  92. ".html5-video-player > .ytp-ce-element",
  93. // 影片右下角的浮水印按鈕
  94. ".html5-video-player .annotation.annotation-type-custom.iv-branding",
  95. // 影片左下角推薦商品
  96. ".html5-video-player .ytp-featured-product"
  97. ].join(",") + "{ display: none!important; }", invisibleCssCode = [
  98. // 播放中的廣告影片
  99. ".ad-showing video.video-stream, .ad-interrupting video.video-stream",
  100. // 廣告影片標題
  101. ".ad-showing > .ytp-chrome-top, .ad-interrupting > .ytp-chrome-top",
  102. // 廣告的進度條
  103. ".ad-showing .ytp-progress-list > .ytp-play-progress, .ad-interrupting .ytp-progress-list > .ytp-play-progress",
  104. ".ad-showing .ytp-progress-list > .ytp-load-progress, .ad-interrupting .ytp-progress-list > .ytp-load-progress",
  105. // 廣告的字幕
  106. ".ad-showing > .ytp-caption-window-container, .ad-interrupting > .ytp-caption-window-container",
  107. // 廣告的背景圖
  108. ".ad-showing > .ytp-cued-thumbnail-overlay, .ad-interrupting > .ytp-cued-thumbnail-overlay"
  109. ].join(",") + "{ visibility: hidden!important; }", setStyleCssCode = [
  110. // 設定播放器背景色,當處於一般影片模式+淺色主題時,隱藏廣告影片會透出一片白色,很突兀
  111. "ytd-player#ytd-player { background-color: black!important; }",
  112. // 影片上方加入 10px 的間距,避免被上方 header bar 蓋到
  113. "#player-full-bleed-container { margin-top: 10px!important; }",
  114. // 播放器控制列按鈕,只有在 hover 或暫停時才會顯示
  115. ".ytp-chrome-bottom { opacity: 0!important; transition: none!important; }",
  116. ".ytp-chrome-bottom:hover { opacity: 1!important; }",
  117. ".html5-video-player.paused-mode > .ytp-chrome-bottom { opacity: 1!important; }",
  118. // shorts 下方資訊欄 hover 時才顯示
  119. "ytd-reel-player-header-renderer.ytd-reel-player-overlay-renderer { opacity: 0; pointer-events: initial; }",
  120. "ytd-reel-player-header-renderer.ytd-reel-player-overlay-renderer:hover { opacity: 1 }",
  121. // shorts 下方的進度條
  122. "#progress-bar-line[hidden] { display: block!important; }",
  123. // shorts 上方控制列,只有 hover 時才會顯示
  124. "ytd-shorts-player-controls.ytd-reel-video-renderer { opacity: 0!important; pointer-events: initial; }",
  125. ".player-controls.ytd-reel-video-renderer:hover > ytd-shorts-player-controls.ytd-reel-video-renderer { opacity: 1!important; }"
  126. ].join("");
  127. addStyle([hiddenCssCode, invisibleCssCode, setStyleCssCode].join("")), console.debug("[setCssStyle]");
  128. }, setCloseDialogObserver = () => {
  129. const clickCloseDialogButton = (dialog) => {
  130. const closeButtonList = [
  131. 'yt-button-renderer#dismiss-button button[aria-label="不用了,謝謝"]',
  132. // 關閉推薦試用 YouTube Premium
  133. 'yt-button-renderer#confirm-button button[aria-label="是"]'
  134. // 背景播放音樂,會出現是否繼續播放,
  135. ];
  136. dialog.querySelectorAll(closeButtonList.join(",")).forEach((button) => {
  137. button.click(), console.debug("[clickCloseDialogButton]", button);
  138. });
  139. };
  140. new Promise((resolve) => {
  141. const ytdApp = document.querySelector("ytd-app"), dialog = ytdApp.querySelector("ytd-popup-container");
  142. if (dialog !== null) {
  143. resolve(dialog);
  144. return;
  145. }
  146. new MutationObserver((_m, observer) => {
  147. const dialog2 = ytdApp.querySelector("ytd-popup-container");
  148. dialog2 !== null && (observer.disconnect(), resolve(dialog2));
  149. }).observe(ytdApp, { childList: !0 });
  150. }).then((dialog) => {
  151. clickCloseDialogButton(dialog), new MutationObserver(() => {
  152. clickCloseDialogButton(dialog);
  153. }).observe(dialog, {
  154. childList: !0,
  155. subtree: !0
  156. });
  157. }).catch((err) => {
  158. console.debug(err);
  159. }), console.debug("[setCloseDialogObserver]");
  160. }, runSkipAd = (() => {
  161. let abortController = null;
  162. return () => {
  163. abortController == null || abortController.abort(), canRun() && (abortController = setSkipAdObserver());
  164. };
  165. })();
  166. setCssStyle(), runSkipAd(), setCloseDialogObserver(), document.addEventListener("yt-navigate-start", () => {
  167. console.debug(`[yt-navigate-start] ${location.href}`), runSkipAd();
  168. }), console.debug("[main] first run");
  169. })();