Greasy Fork is available in English.

Youtube Better Player

Scroll wheel volume, "Are you there" popup bypass, Volume percent display, Infinite autoplay, Volume save

Installer dette scriptet?
Skaperens foreslåtte skript

Du vil kanskje også like Twitch Scroll Wheel Volume.

Installer dette scriptet
  1. // ==UserScript==
  2. // @name Youtube Better Player
  3. // @description Scroll wheel volume, "Are you there" popup bypass, Volume percent display, Infinite autoplay, Volume save
  4. // @include /^https:\/\/www\.youtube(-nocookie)?\.com\/(?!(live_chat\?.*|ytscframe)$).*$/
  5. // @run-at document-idle
  6. // @allFrames true
  7. // @grant GM_getValue
  8. // @grant GM_setValue
  9. // @grant GM_addStyle
  10. // @version 0.0.1.20240506140158
  11. // @namespace https://greatest.deepsurf.us/users/286737
  12. // ==/UserScript==
  13.  
  14. class Player {
  15. constructor() {
  16. this.volumeSk = 'volume'
  17. }
  18.  
  19. async init(isEmbed) {
  20. const api = this.api = await this.getApi(isEmbed)
  21. this.volume = Number(GM_getValue(this.volumeSk))
  22.  
  23. if (isNaN(this.volume)) {
  24. this.volume = Math.floor(api.getVolume())
  25. GM_setValue(this.volumeSk, this.volume)
  26. }
  27. else {
  28. api.unMute()
  29. api.setVolume(this.volume)
  30. }
  31.  
  32. const {$player, $video, $eventCatcher, $volumeArea, $volumeBar} = this.getEls()
  33.  
  34. this.$volumeText = this.buildVolumeText($volumeArea)
  35.  
  36. const onVolumeChange = this.onVolumeChange.bind(this)
  37. $video.addEventListener('volumechange', onVolumeChange)
  38.  
  39. new WheelVolume(this, api, $volumeBar, $player).init($eventCatcher)
  40.  
  41. if (!isEmbed) new RealAutoPlay(api).init($video)
  42. }
  43.  
  44. async getApi(isEmbed) {
  45. if (isEmbed) {
  46. let api
  47. while (!(api = unsafeWindow.movie_player)) await wait(200)
  48.  
  49. return api
  50. }
  51.  
  52. let $el, api
  53.  
  54. while (!($el = unsafeWindow['ytd-player'])) await wait(1000)
  55. while (!(api = $el.player_)) await wait(200)
  56. while (!api.isReady()) await wait(200)
  57.  
  58. return api
  59. }
  60.  
  61. getEls() {
  62. const $player = $('#movie_player')
  63. const $video = $('.html5-main-video', $player)
  64. const $eventCatcher = $player.parentElement
  65. const $volumeArea = $('.ytp-volume-area', $player)
  66. const $volumeBar = $('.ytp-volume-slider', $volumeArea)
  67.  
  68. return {$player, $video, $eventCatcher, $volumeArea, $volumeBar}
  69. }
  70.  
  71. buildVolumeText($volumeArea) {
  72. const $volumeText = document.createElement('span')
  73. $volumeText.classList.add('ytbp-volume-text')
  74. $volumeText.textContent = this.volume
  75. GM_addStyle(volumeTextStyle)
  76.  
  77. $volumeArea.insertAdjacentElement('beforeend', $volumeText)
  78.  
  79. return $volumeText
  80. }
  81.  
  82. onVolumeChange() {
  83. this.volume = this.$volumeText.textContent = Math.floor(this.api.getVolume())
  84.  
  85. clearTimeout(this.saveTimeout)
  86.  
  87. this.saveTimeout = setTimeout(() => GM_setValue(this.volumeSk, this.volume), 1000)
  88. }
  89. }
  90.  
  91. class WheelVolume {
  92. constructor(player, api, $volumeBar, $player) {
  93. this.player = player
  94. this.api = api
  95. this.$volumeBar = $volumeBar
  96. this.$player = $player
  97.  
  98. this.events = {
  99. mouseover: new Event('mouseover', {bubbles: true}),
  100. mouseout: new Event('mouseout', {bubbles: true}),
  101. mousemove: new Event('mousemove')
  102. }
  103. }
  104.  
  105. init($eventCatcher) {
  106. const onWheel = this.onWheel.bind(this)
  107. const onClick = this.onClick.bind(this)
  108.  
  109. $eventCatcher.addEventListener('wheel', onWheel)
  110. $eventCatcher.addEventListener('mousedown', onClick)
  111. }
  112.  
  113. onWheel(e) {
  114. e.preventDefault()
  115. e.stopImmediatePropagation()
  116.  
  117. this.show()
  118.  
  119. const api = this.api
  120. const now = Date.now(), since = now - this.prevScrollDate
  121. const step = (e.deltaY < 0 ? 1 : -1) * (since < 50 ? 4 : 1)
  122.  
  123. if (api.isMuted()) api.unMute()
  124.  
  125. api.setVolume(this.player.volume + step)
  126.  
  127. this.prevScrollDate = now
  128. }
  129.  
  130. onClick(e) {
  131. if (e.which != 2) return
  132.  
  133. e.preventDefault()
  134.  
  135. this.show()
  136.  
  137. const api = this.api
  138.  
  139. if (api.isMuted()) {
  140. api.unMute()
  141. api.setVolume(this.player.volume)
  142. }
  143. else api.mute()
  144. }
  145.  
  146. show() {
  147. const $volumeBar = this.$volumeBar, events = this.events
  148.  
  149. this.$player.dispatchEvent(events.mousemove)
  150.  
  151. clearTimeout(this.showTimeout)
  152.  
  153. $volumeBar.dispatchEvent(events.mouseover)
  154.  
  155. this.showTimeout = setTimeout(() => $volumeBar.dispatchEvent(events.mouseout), 1000)
  156. }
  157. }
  158.  
  159. class RealAutoPlay {
  160. constructor(api) {
  161. this.api = api
  162.  
  163. this.popupName = 'yt-confirm-dialog-renderer'
  164.  
  165. const popupEl = $('ytd-popup-container', unsafeWindow.document)
  166. this.popupContainer = popupEl.polymerController ?? popupEl.inst
  167. }
  168.  
  169. init($video) {
  170. const $autonavToggleButton = $('.ytp-autonav-toggle-button')
  171.  
  172. this.autoNavEnabled = $autonavToggleButton.ariaChecked
  173.  
  174. $autonavToggleButton.addEventListener('click', onToggleAutoNav)
  175.  
  176. const bypassPopup = this.bypassPopup.bind(this)
  177. const forceNextVideo = this.forceNextVideo.bind(this)
  178. const onToggleAutoNav = this.onToggleAutoNav.bind(this)
  179.  
  180. $video.addEventListener('pause', bypassPopup)
  181. $video.addEventListener('waiting', bypassPopup)
  182. $video.addEventListener('ended', forceNextVideo)
  183. }
  184.  
  185. bypassPopup() {
  186. const popup = this.popupContainer.popups_?.[this.popupName]
  187.  
  188. if (!popup) return
  189.  
  190. this.api.playVideo()
  191.  
  192. popup.popup.remove()
  193. delete this.popupContainer.popups_[this.popupName]
  194. }
  195.  
  196. forceNextVideo() {
  197. if (this.autoNavEnabled && !document.hasFocus()) this.api.nextVideo()
  198. }
  199.  
  200. onToggleAutoNav() {
  201. this.autoNavEnabled = !this.autoNavEnabled
  202. }
  203. }
  204.  
  205. const init = async () => {
  206. const isEmbed = location.pathname.startsWith('/embed/')
  207.  
  208. if (isEmbed) await new Promise(r => $('.html5-main-video').addEventListener('canplay', r, {once: true}))
  209.  
  210. new Player().init(isEmbed)
  211. }
  212.  
  213. const volumeTextStyle = `
  214. .ytbp-volume-text {
  215. position: relative;
  216. top: -0.5px;
  217. width: 0;
  218. text-indent: 2px;
  219. overflow: hidden;
  220. color: #ddd;
  221. font-size: 109%;
  222. text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
  223. transition: width .2s
  224. }
  225. .ytbp-volume-text:after { content: '%' }
  226. .ytp-volume-control-hover:not([aria-valuenow="0"], [aria-valuenow="100"]) + .ytbp-volume-text {
  227. width: 2.5em
  228. }
  229. `
  230.  
  231. const $ = (sel, el = document) => el.querySelector(sel)
  232.  
  233. const wait = (ms) => new Promise(r => setTimeout(r, ms))
  234.  
  235.  
  236. init()