Youtube Better Player

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

Fra og med 22.05.2021. Se den nyeste version.

// ==UserScript==
// @name         Youtube Better Player
// @description  Scroll wheel volume, "Are you there" popup bypass, Infinite autoplay, Volume save
// @include      /^https:\/\/www\.youtube\.com\/(?!(live_chat\?.*|ytscframe)$).*$/
// @run-at       document-idle
// @allFrames    true
// @version 0.0.1.20210522094546
// @namespace https://greasyfork.dpdns.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()

        api.setVolume(localStorage.getItem(this.volumeSk))

        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($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
    }

    getEls() {
        const $player = $('#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', () => localStorage.setItem(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 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 $ = (sel, el = document) => el.querySelector(sel)

const wait = async (ms) => await new Promise(r => setTimeout(r, ms))


init()
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元