您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Scroll Wheel volume control, no autoplay interruptions, save volume
当前为
// ==UserScript== // @name Youtube Better Player // @description Scroll Wheel volume control, no autoplay interruptions, save volume // @match https://www.youtube.com/* // @grant GM_getValue // @grant GM_setValue // @version 0.0.1.20210127035728 // @namespace https://greasyfork.org/users/286737 // ==/UserScript== class Player { constructor() { this.isEmbed = location.pathname.startsWith('/embed/') this.volumeSk = 'volume' } async init() { const isEmbed = this.isEmbed const api = this.api = await (isEmbed ? this.getApiEmbed() : this.getApi()) const savedVolume = GM_getValue(this.volumeSk) if (savedVolume != undefined) api.setVolume(savedVolume) this.volume = api.getVolume() const {$video, $eventCatcher, $volumeBar, $player} = this.getEls() this.listenEvents($video) new WheelVolume(this, api, $volumeBar, $player).init($eventCatcher) if (!isEmbed) new RealAutoPlay(api).init() } 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 } async getApiEmbed() { let api while (!(api = unsafeWindow.movie_player)) await wait(1000) // api addEventListener don't support {once} await new Promise(r => { const onStateChange = () => { api.removeEventListener('onStateChange', onStateChange) r() } api.addEventListener('onStateChange', onStateChange) }) return api } getEls() { const $player = unsafeWindow.movie_player const $video = $('video', $player) const $eventCatcher = $player.parentElement const $volumeBar = $('.ytp-volume-slider', $player) return {$video, $eventCatcher, $volumeBar, $player} } listenEvents($video) { const onVolumeChange = this.onVolumeChange.bind(this) $video.addEventListener('volumechange', onVolumeChange) addEventListener('unload', () => GM_setValue(this.volumeSk, this.volume)) } onVolumeChange() { this.volume = this.api.getVolume() } } 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 now = Date.now(), since = now - this.prevScrollDate const step = (e.deltaY < 0 ? 1 : -1) * (since < 50 ? 4 : 1) this.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.states = {unstarted: -1, ended: 0, paused: 2} this.popupName = 'yt-confirm-dialog-renderer' this.$popupContainer = $('ytd-popup-container') this.$toggleAutoNavBtn = $('.ytp-autonav-toggle-button') this.autoNavEnabled = this.$toggleAutoNavBtn.ariaChecked } init() { const onStateChange = this.onStateChange.bind(this) const onToggleAutoNav = this.onToggleAutoNav.bind(this) this.api.addEventListener('onStateChange', onStateChange) this.$toggleAutoNavBtn.addEventListener('click', onToggleAutoNav) } onStateChange(state) { const states = this.states switch (state) { case states.ended: if (this.autoNavEnabled && !document.hasFocus()) this.api.nextVideo() break case states.unstarted: case states.paused: this.bypassPopup() } } onToggleAutoNav() { this.autoNavEnabled = this.$toggleAutoNavBtn.ariaChecked } bypassPopup() { const popup = this.$popupContainer.popups_[this.popupName] if (!popup) return this.api.playVideo() popup.popup.remove() delete this.$popupContainer.popups_[this.popupName] } } const $ = (sel, el = document) => el.querySelector(sel) const wait = async (ms) => await new Promise(r => setTimeout(r, ms)) new Player().init()