您需要先安装一个扩展,例如 篡改猴、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.20211029113543
- // @namespace https://greasyfork.org/users/286737
- // ==/UserScript==
- class Player {
- constructor() {
- this.volumeSk = 'ytbp-volume'
- }
- async init(isEmbed) {
- this.api = await this.getApi(isEmbed)
- 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, $volumeBar, $player).init($eventCatcher)
- if (!isEmbed) new RealAutoPlay(this.api).init($video)
- }
- async getApi(isEmbed) {
- if (isEmbed) {
- let api
- while (!(api = unsafeWindow.movie_player)) await wait(200)
- return api
- }
- 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, $volumeBar, $player) {
- this.player = player
- this.api = player.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;
- }
- .ytp-embed-mobile-exp .ytp-time-display,.ytp-embed-mobile-exp.ytp-small-mode .ytbp-volume-text {
- line-height: 51px
- }
- .ytp-larger-tap-buttons .ytbp-volume-text {
- line-height: 47px
- }
- .ytp-music-player .ytbp-volume-text {
- line-height: 48px
- }
- .ytp-small-mode.ytp-music-player .ytbp-volume-text {
- line-height: 35px
- }
- .ytp-big-mode .ytbp-volume-text {
- line-height: 53px
- }
- .ytbp-volume-text:after { content: '%'; }
- .ytp-volume-control-hover:not([aria-valuenow="0"], [aria-valuenow="100"]) + .ytbp-volume-text {
- width: 2.5em;
- }
- `
- const $ = (sel, el = document) => el.querySelector(sel)
- const wait = (ms) => new Promise(r => setTimeout(r, ms))
- init()