您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhances the volume control on YouTube by providing additional information and features.
当前为
- // ==UserScript==
- // @name YouTube Volume Assistant
- // @namespace http://tampermonkey.net/
- // @version 0.1.2
- // @description Enhances the volume control on YouTube by providing additional information and features.
- // @author CY Fung
- // @license MIT License
- // @match https://www.youtube.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
- // @grant none
- // @run-at document-start
- // @unwrap
- // @allFrames
- // @inject-into page
- // ==/UserScript==
- (function () {
- 'use strict';
- // AudioContext.prototype._createGain = AudioContext.prototype.createGain;
- let wm = new WeakMap();
- /*
- AudioContext.prototype.createGain = function(...args){
- return this.createdGain || (this.createdGain = this._createGain(...args));
- }
- */
- function getMediaElementSource() {
- return wm.get(this) || null;
- }
- function getGainNode() {
- return wm.get(this) || null;
- }
- AudioContext.prototype._createMediaElementSource = AudioContext.prototype.createMediaElementSource;
- AudioContext.prototype.createMediaElementSource = function (video, ...args) {
- let createdMediaElementSource = wm.get(video);
- if (createdMediaElementSource) return createdMediaElementSource;
- wm.set(video, createdMediaElementSource = this._createMediaElementSource(video, ...args));
- video.getMediaElementSource = getMediaElementSource;
- return createdMediaElementSource;
- }
- MediaElementAudioSourceNode.prototype._connect = MediaElementAudioSourceNode.prototype.connect;
- MediaElementAudioSourceNode.prototype.connect = function (gainNode, ...args) {
- this._connect(gainNode, ...args);
- wm.set(this, gainNode);
- this.getGainNode = getGainNode;
- }
- let finished = false;
- const onNavigateFinish = () => {
- if (finished) return;
- finished = true;
- document.removeEventListener('yt-navigate-finish', onNavigateFinish, true);
- document.head.appendChild(document.createElement('style')).textContent = `
- .video-tip-offseted {
- margin-top:-1em;
- }
- .volume-tip-gain{
- opacity:0.52;
- }
- .volume-tip-normalized{
- opacity:0.4;
- }
- `;
- setTimeout(() => {
- let volumeSlider = document.querySelector('.ytp-volume-panel[role="slider"][title]');
- let volumeTitle = volumeSlider.getAttribute('title');
- function addDblTap(element, doubleClick) {
- // https://stackoverflow.com/questions/45804917/dblclick-doesnt-work-on-touch-devices
- let expired
- let doubleTouch = function (e) {
- if (e.touches.length === 1) {
- if (!expired) {
- expired = e.timeStamp + 400
- } else if (e.timeStamp <= expired) {
- // remove the default of this event ( Zoom )
- e.preventDefault()
- doubleClick(e)
- // then reset the variable for other "double Touches" event
- expired = null
- } else {
- // if the second touch was expired, make it as it's the first
- expired = e.timeStamp + 400
- }
- }
- }
- element.addEventListener('touchstart', doubleTouch)
- element.addEventListener('dblclick', doubleClick)
- }
- addDblTap(volumeSlider, (e) => {
- let target = null;
- try {
- target = e.target.closest('.ytp-volume-area').querySelector('.ytp-mute-button');
- } catch (e) { }
- console.log(target)
- const e2 = new MouseEvent('contextmenu', {
- bubbles: true,
- cancelable: true,
- view: window
- });
- if (target) target.dispatchEvent(e2);
- })
- let volumeSpan = null;
- let lastContent = null;
- let video = document.querySelector('#player video[src]');
- let source = null;
- let gainNode = null;
- video.addEventListener('volumechange', changeVolumeText, false)
- let ktid = 0;
- let template = document.createElement('template');
- function changeVolumeText() {
- let video = document.querySelector('#player video[src]');
- if (!video) return;
- if (gainNode === null) {
- source = video.getMediaElementSource ? video.getMediaElementSource() : null;
- if (source) {
- gainNode = source.getGainNode ? source.getGainNode() : null;
- }
- }
- let gainValue = (((gainNode || 0).gain || 0).value || 0);
- let m = gainValue || 1.0;
- let actualVolume = document.querySelector('ytd-player').player_.getVolume();
- let normalized = video.volume * 100;
- let gainText = gainValue ? `<span class="volume-tip-gain">Gain = ${+(gainValue.toFixed(2))}</span><br>` : '';
- template.innerHTML = `
- <span class="volume-tip-offset">
- ${gainText}
- <span class="volume-tip-volume">Volume: ${(m * actualVolume).toFixed(1)}% </span><br>
- <span class="volume-tip-normalized"> Normalized: ${(m * normalized).toFixed(1)}%</span>
- </span>`.trim();
- if (volumeSpan.textContent !== template.content.textContent && lastContent === volumeSpan.textContent) {
- volumeSpan.innerHTML = template.innerHTML;
- lastContent = volumeSpan.textContent;
- }
- }
- setInterval(() => {
- if (!volumeSpan) {
- let elms = [...document.querySelectorAll('#player .ytp-tooltip div.ytp-tooltip-text-wrapper span.ytp-tooltip-text')];
- elms = elms.filter(t => t.textContent === volumeTitle);
- if (elms[0]) {
- HTMLElement.prototype.closest.call(elms[0],'#player .ytp-tooltip').classList.add('video-tip-offseted');
- volumeSpan = elms[0];
- lastContent = volumeSpan.textContent;
- }
- }
- if (volumeSpan && (!volumeSpan.isConnected || volumeSpan.textContent !== lastContent)) {
- // volumeSpan.textContent = volumeTitle;
- let p = document.querySelector('.video-tip-offseted');
- if(p) p.classList.remove('video-tip-offseted');
- let m = document.querySelector('.volume-tip-offset');
- if(m) m.remove();
- volumeSpan = null;
- lastContent = null;
- }
- if (!volumeSpan) return;
- let tid = Date.now();
- ktid = tid;
- requestAnimationFrame(() => {
- if (ktid !== tid) return;
- changeVolumeText();
- });
- }, 80)
- }, 300);
- };
- document.addEventListener('yt-navigate-finish', onNavigateFinish, true);
- setTimeout(onNavigateFinish, 800);
- })();