您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Scroll wheel volume, "Are you there" popup bypass, Volume percent display, Infinite autoplay, Volume save
当前为
// ==UserScript== // @name Youtube Better Player // @description Scroll wheel volume, "Are you there" popup bypass, Volume percent display, Infinite autoplay, Volume save // @include /^https:\/\/www\.youtube\.com\/(?!(live_chat\?.*|ytscframe)$).*$/ // @run-at document-idle // @allFrames true // @grant GM_addStyle // @version 0.0.1.20210730180549 // @namespace https://greasyfork.org/users/286737 // ==/UserScript== class Player { constructor() { this.volumeSk = 'ytbp-volume' } async init(isEmbed) { const api = this.api = isEmbed ? unsafeWindow.movie_player : await this.getApi() this.volume = this.getSavedVolume() this.$volumeText = this.buildVolumeText() const {$video, $eventCatcher, $volumeBar, $player} = this.getEls() const onVolumeChange = this.onVolumeChange.bind(this) $video.addEventListener('volumechange', onVolumeChange) new WheelVolume(this, api, $volumeBar, $player).init($eventCatcher) if (!isEmbed) new RealAutoPlay(api).init($video) } async getApi() { let $el, api while (!($el = unsafeWindow['ytd-player'])) await wait(1000) while (!(api = $el.player_)) await wait(200) while (!api.isReady()) await wait(200) return api } getSavedVolume() { const savedVolume = localStorage.getItem(this.volumeSk) if (savedVolume == undefined) return Math.floor(this.api.getVolume()) this.api.setVolume(savedVolume) return savedVolume } getEls() { const $player = $('#movie_player') const $video = $('video', $player) const $eventCatcher = $player.parentElement const $volumeBar = $('.ytp-volume-slider', $player) return {$video, $eventCatcher, $volumeBar, $player} } buildVolumeText() { const $volumeText = document.createElement('span') $volumeText.classList.add('ytbp-volume-text') $volumeText.textContent = this.volume GM_addStyle(volumeTextStyle) $('.ytp-volume-area').insertAdjacentElement('beforeend', $volumeText) return $volumeText } onVolumeChange() { this.volume = this.$volumeText.textContent = Math.floor(this.api.getVolume()) clearTimeout(this.saveTimeout) this.saveTimeout = setTimeout(() => localStorage.setItem(this.volumeSk, this.volume), 1000) } } class WheelVolume { constructor(player, api, $volumeBar, $player) { this.player = player this.api = api this.$volumeBar = $volumeBar this.$player = $player this.events = { mouseover: new Event('mouseover', {bubbles: true}), mouseout: new Event('mouseout', {bubbles: true}), mousemove: new Event('mousemove') } } init($eventCatcher) { const onWheel = this.onWheel.bind(this) const onClick = this.onClick.bind(this) $eventCatcher.addEventListener('wheel', onWheel) $eventCatcher.addEventListener('mousedown', onClick) } onWheel(e) { e.preventDefault() e.stopImmediatePropagation() this.show() const api = this.api const now = Date.now(), since = now - this.prevScrollDate const step = (e.deltaY < 0 ? 1 : -1) * (since < 50 ? 4 : 1) if (api.isMuted()) api.unMute() api.setVolume(this.player.volume + step) this.prevScrollDate = now } onClick(e) { if (e.which != 2) return e.preventDefault() this.show() const api = this.api if (api.isMuted()) { api.unMute() api.setVolume(this.player.volume) } else api.mute() } show() { const $volumeBar = this.$volumeBar, events = this.events this.$player.dispatchEvent(events.mousemove) clearTimeout(this.showTimeout) $volumeBar.dispatchEvent(events.mouseover) this.showTimeout = setTimeout(() => $volumeBar.dispatchEvent(events.mouseout), 1000) } } class RealAutoPlay { constructor(api) { this.api = api this.popupName = 'yt-confirm-dialog-renderer' this.popupContainer = $('ytd-popup-container', unsafeWindow.document) const storedAutoNav = localStorage.getItem('yt.autonav::autonav_disabled') this.autoNavEnabled = storedAutoNav ? !JSON.parse(storedAutoNav).data : true } init($video) { const bypassPopup = this.bypassPopup.bind(this) const forceNextVideo = this.forceNextVideo.bind(this) const onToggleAutoNav = this.onToggleAutoNav.bind(this) $video.addEventListener('pause', bypassPopup) $video.addEventListener('waiting', bypassPopup) $video.addEventListener('ended', forceNextVideo) $('.ytp-autonav-toggle-button').addEventListener('click', onToggleAutoNav) } bypassPopup() { const popup = this.popupContainer.popups_[this.popupName] if (!popup) return this.api.playVideo() popup.popup.remove() delete this.popupContainer.popups_[this.popupName] } forceNextVideo() { if (this.autoNavEnabled && !document.hasFocus()) this.api.nextVideo() } onToggleAutoNav() { this.autoNavEnabled = !this.autoNavEnabled } } const init = async () => { const isEmbed = location.pathname.startsWith('/embed/') if (isEmbed) await new Promise(r => $('video').addEventListener('canplay', r, {once: true})) new Player().init(isEmbed) } const volumeTextStyle = ` .ytbp-volume-text { width: 0; text-indent: 2px; overflow: hidden; color: #ddd; font-size: 109%; line-height: 39px; text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); transition: width .2s; } .ytbp-volume-text:after { content: '%'; } .ytp-volume-control-hover:not([aria-valuenow="0"], [aria-valuenow="100"]) + .ytbp-volume-text { width: 32px; } ` const $ = (sel, el = document) => el.querySelector(sel) const wait = (ms) => new Promise(r => setTimeout(r, ms)) init()