您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Scroll wheel volume control
当前为
// ==UserScript== // @name Twitch Scroll Wheel Volume // @description Scroll wheel volume control // @include https://www.twitch.tv/* // @include /^https:\/\/(?!supervisor).*\.ext-twitch\.tv\/.*anchor=video_overlay.*$/ // @run-at document-idle // @allFrames true // @version 0.0.1.20211105231337 // @namespace https://greasyfork.org/users/286737 // ==/UserScript== class Player { constructor() { this.playerTypeObserver = new MutationObserver(this.onPlayerTypeChange.bind(this)) this.wheelVolume = new WheelVolume() } async init() { let $root while (!($root = $('.root-scrollable__wrapper'))) await wait(2000) this.$root = $root const onRootMutation = this.onRootMutation.bind(this) new MutationObserver(onRootMutation).observe($root, {childList: true}) onRootMutation() } onRootMutation() { const $player = $('.persistent-player', this.$root) if ($player == this.$player) return this.$player = $player if ($player) this.onNewPlayer() } async onNewPlayer() { const api = this.api = await this.getApi() this.wheelVolume.init(api, this.get$eventCatcher(), this.get$volumeBar()) this.$layout = $('.video-player', this.$player) this.playerTypeObserver.observe(this.$layout, {attributeFilter: ['data-a-player-type']}) } async getApi() { let $el, api while (!($el = $('.video-player__container', unsafeWindow.document))) await wait(2000) while (!(api = this.getReactPlayerApi($el))) await wait(500) return api } getReactPlayerApi($el) { let instance for (const key in $el) { if (key.startsWith('__reactInternalInstance$')) { instance = $el[key] } } let parent = instance.return for (let i = 0; i < 30; i++) { const player = parent.memoizedProps.mediaPlayerInstance if (player) return player.core parent = parent.return } } get$eventCatcher() { return $('.video-player__container', this.$player) } get$volumeBar() { return $('[id^=player-volume-slider]', this.$player) } onPlayerTypeChange() { if (this.$layout.dataset.aPlayerType == 'site') this.wheelVolume.$volumeBar = this.get$volumeBar() } } class WheelVolume { constructor() { this.onWheelHandler = this.onWheel.bind(this) this.onMousedownHandler = this.onMousedown.bind(this) this.events = { mouseover: new Event('mouseover', {bubbles: true}), mouseout: new Event('mouseout', {bubbles: true}), mouseenter: new Event('mouseenter') } const onExtMessage = this.onExtMessage.bind(this) addEventListener('message', onExtMessage) } init(api, $eventCatcher, $volumeBar) { this.api = api this.$eventCatcher = $eventCatcher this.$volumeBar = $volumeBar $eventCatcher.addEventListener('wheel', this.onWheelHandler) $eventCatcher.addEventListener('mousedown', this.onMousedownHandler) } onWheel(e) { e.preventDefault() e.stopImmediatePropagation() this.updateVolume(e.deltaY < 0) } onMousedown(e) { if (e.which != 2) return e.preventDefault() this.toggleMute() } onExtMessage(e) { const event = e.data.wheelEvent if (!event) return switch (event) { case 'up': this.updateVolume(true) break case 'down': this.updateVolume(false) break case 'click': this.toggleMute() } } updateVolume(shouldIncrease) { this.show() const api = this.api, volume = api.getVolume() if ((volume == 0 && !shouldIncrease) || (volume == 1 && shouldIncrease)) return const now = Date.now(), since = now - this.prevScrollDate const step = (shouldIncrease ? 1 : -1) * (since < 50 ? 4 : 1) * .01 if (api.isMuted()) api.setMuted(false) api.setVolume(volume + step) this.prevScrollDate = now } toggleMute() { this.show() const api = this.api api.setMuted(!api.isMuted()) } show() { const $volumeBar = this.$volumeBar, events = this.events this.$eventCatcher.dispatchEvent(events.mouseenter) clearTimeout(this.showTimeout) $volumeBar.dispatchEvent(events.mouseover) this.showTimeout = setTimeout(() => $volumeBar.dispatchEvent(events.mouseout), 1000) } } class ExtFrame { init() { const onWheel = this.onWheel.bind(this) const onMousedown = this.onMousedown.bind(this) addEventListener('wheel', onWheel, {passive: false}) addEventListener('mousedown', onMousedown, {passive: false}) } onWheel(e) { e.preventDefault() e.stopPropagation() this.sendEvent(e.deltaY < 0 ? 'up' : 'down') } onMousedown(e) { if (e.which != 2) return e.preventDefault() this.sendEvent('click') } sendEvent(name) { parent.postMessage({wheelEvent: name}, 'https://supervisor.ext-twitch.tv/') } } const init = async () => { if (location.host == 'www.twitch.tv') return new Player().init() new ExtFrame().init() } const $ = (sel, el = document) => el.querySelector(sel) const wait = async (ms) => await new Promise(r => setTimeout(r, ms)) init()