您需要先安装一个扩展,例如 篡改猴、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()