// ==UserScript==
// @name Botão Duplo de Velocidade/Qualidade para o YouTube.
// @namespace https://greasyfork.org/pt-BR/scripts/543571-modified
// @version 3.2.3
// @description Exibe dois botões, um deles é um ícone de Cronômetro para a Velocidade e o outro é uma Engrenagem para a Qualidade de Vídeo. A Qualidade e Velocidade escolhidas são salvas e usadas automaticamente nos próximos vídeos.
// @author matheus felipen
// @match *://*.youtube.com/*
// @grant none
// @license GNU GPLv3
// @icon https://i.ibb.co/WNzVD92X/cronometro.png
// ==/UserScript==
(function () {
'use strict';
const texts = {
pt: {
speedLabel: '⏱️ Velocidade',
speedMsg: v => `Velocidade: ${v.toFixed(2)}x`,
quality: '⚙️ Qualidade',
qualityMsg: q => `Qualidade: ${q}`
},
en: {
speedLabel: '⏱️ Speed',
speedMsg: v => `Speed: ${v.toFixed(2)}x`,
quality: '⚙️ Quality',
qualityMsg: q => `Quality: ${q}`
}
};
const lang = (navigator.language || 'pt').slice(0, 2);
const t = texts[lang] || texts.pt;
const styles = {
button: { backgroundColor: 'rgba(0,0,0,0.7)', color: 'white', border: 'none', padding: '10px', borderRadius: '5px', cursor: 'pointer', fontSize: '16px', minWidth: '85px', width: '100%', textAlign: 'center', boxSizing: 'border-box' },
dropdown: { position: 'absolute', right: '100%', bottom: '0', backgroundColor: 'rgba(0, 0, 0, 0.9)', padding: '5px', borderRadius: '5px', display: 'none', flexDirection: 'column', gap: '3px', zIndex: '9999' },
option: { backgroundColor: '#222', color: 'white', padding: '5px 10px', border: 'none', cursor: 'pointer', fontSize: '14px', whiteSpace: 'nowrap' }
};
function applyStyle(el, styleObj) { Object.assign(el.style, styleObj); }
function createButton(text, onClick) {
const btn = document.createElement('button');
btn.textContent = text;
applyStyle(btn, styles.button);
btn.addEventListener('click', onClick);
return btn;
}
function showMessage(text) {
let msg = document.getElementById('custom-yt-message');
if (!msg) {
msg = document.createElement('div');
msg.id = 'custom-yt-message';
Object.assign(msg.style, { position: 'fixed', bottom: '270px', right: '20px', backgroundColor: 'rgba(0,0,0,0.8)', color: 'white', padding: '5px 10px', borderRadius: '5px', fontSize: '16px', textAlign: 'center', pointerEvents: 'none', zIndex: '9999', display: 'none' });
document.body.appendChild(msg);
}
msg.textContent = text;
msg.style.display = 'block';
clearTimeout(msg.timer);
msg.timer = setTimeout(() => { msg.style.display = 'none'; }, 1500);
}
function isAdPlaying() {
return !!document.querySelector('.ad-showing');
}
function waitForVideo(callback) {
const interval = setInterval(() => {
const video = document.querySelector('video');
if (video) {
clearInterval(interval);
callback(video);
const observer = new MutationObserver(() => {
const savedSpeed = parseFloat(localStorage.getItem('youtubePlaybackSpeed')) || 1.0;
if (!isAdPlaying()) {
video.playbackRate = savedSpeed;
}
});
observer.observe(video, { attributes: true, attributeFilter: ['src'] });
}
}, 100);
}
function adjustSpeed(delta) {
waitForVideo(video => {
if (isAdPlaying()) return;
const newRate = delta === 0 ? 1.0 : Math.min(Math.max(video.playbackRate + delta, 0.25), 16);
video.playbackRate = newRate;
localStorage.setItem('youtubePlaybackSpeed', newRate);
showMessage(t.speedMsg(newRate));
});
}
function setSpeed(value) {
waitForVideo(video => {
if (isAdPlaying()) return;
video.playbackRate = value;
localStorage.setItem('youtubePlaybackSpeed', value);
showMessage(t.speedMsg(value));
});
}
function getSavedQuality() { return localStorage.getItem('youtubeQuality') || 'Auto'; }
function saveQuality(quality) { localStorage.setItem('youtubeQuality', quality); }
function setVideoQuality(qualityLabel) {
if (qualityLabel === 'Auto') {
saveQuality('Auto');
showMessage(t.qualityMsg('Automática'));
return;
}
waitForVideo(() => {
if (isAdPlaying()) return;
const settingsButton = document.querySelector('.ytp-settings-button');
if (!settingsButton) return;
settingsButton.click();
setTimeout(() => {
const menuItems = document.querySelectorAll('.ytp-menuitem');
const qualityMenuItem = Array.from(menuItems).find(item => (item.textContent || '').includes('Qualidade') || (item.textContent || '').includes('Quality'));
if (!qualityMenuItem) {
settingsButton.click();
return;
}
qualityMenuItem.click();
setTimeout(() => {
const qualityOptions = document.querySelectorAll('.ytp-quality-menu .ytp-menuitem');
const targetQualityOption = Array.from(qualityOptions).find(item => (item.textContent || '').startsWith(qualityLabel));
if (targetQualityOption) {
targetQualityOption.click();
saveQuality(qualityLabel);
showMessage(t.qualityMsg(qualityLabel));
} else {
settingsButton.click();
}
}, 300);
}, 300);
});
}
function applySpeedAfterAd(speed) {
const interval = setInterval(() => {
const video = document.querySelector('video');
if (video && !isAdPlaying()) {
clearInterval(interval);
video.playbackRate = speed;
}
}, 500);
}
function applyQualityAfterAd(quality) {
const interval = setInterval(() => {
if (!isAdPlaying()) {
clearInterval(interval);
setVideoQuality(quality);
}
}, 500);
}
function addControls() {
if (document.getElementById('controls-container')) return;
const container = document.createElement('div');
container.id = 'controls-container';
Object.assign(container.style, { position: 'fixed', bottom: '40px', right: '20px', display: 'flex', flexDirection: 'column', gap: '5px', zIndex: '9999' });
const qualityWrapper = document.createElement('div');
qualityWrapper.style.position = 'relative';
const qualityBtn = createButton(t.quality, () => {});
const qualityDropdown = document.createElement('div');
applyStyle(qualityDropdown, styles.dropdown);
const qualityOptions = ['Auto', '144p', '240p', '360p', '480p', '720p', '1080p', '1440p', '2160p'];
qualityOptions.forEach(q => {
const opt = createButton(q, e => {
e.stopPropagation();
setVideoQuality(q);
qualityDropdown.style.display = 'none';
});
applyStyle(opt, styles.option);
opt.addEventListener('mouseenter', () => opt.style.backgroundColor = '#444');
opt.addEventListener('mouseleave', () => opt.style.backgroundColor = '#222');
qualityDropdown.appendChild(opt);
});
qualityWrapper.appendChild(qualityBtn);
qualityWrapper.appendChild(qualityDropdown);
qualityWrapper.addEventListener('mouseenter', () => qualityDropdown.style.display = 'flex');
qualityWrapper.addEventListener('mouseleave', () => qualityDropdown.style.display = 'none');
const speedWrapper = document.createElement('div');
speedWrapper.style.position = 'relative';
const speedBtn = createButton(t.speedLabel, () => adjustSpeed(0));
const speedDropdown = document.createElement('div');
applyStyle(speedDropdown, styles.dropdown);
const speeds = [3.0, 2.0, 1.5, 1.25, 1.0, 0.75, 0.5];
speeds.forEach(spd => {
const opt = createButton(`${spd}x`, e => {
e.stopPropagation();
setSpeed(spd);
speedDropdown.style.display = 'none';
});
applyStyle(opt, styles.option);
opt.addEventListener('mouseenter', () => opt.style.backgroundColor = '#444');
opt.addEventListener('mouseleave', () => opt.style.backgroundColor = '#222');
speedDropdown.appendChild(opt);
});
speedWrapper.appendChild(speedBtn);
speedWrapper.appendChild(speedDropdown);
speedWrapper.addEventListener('mouseenter', () => speedDropdown.style.display = 'flex');
speedWrapper.addEventListener('mouseleave', () => speedDropdown.style.display = 'none');
container.appendChild(qualityWrapper);
container.appendChild(speedWrapper);
document.body.appendChild(container);
function updateVisibility() {
const controls = document.getElementById('controls-container');
if (controls) {
controls.style.display = document.fullscreenElement ? 'none' : 'flex';
}
}
updateVisibility();
document.addEventListener('fullscreenchange', updateVisibility);
}
function removeControls() {
const container = document.getElementById('controls-container');
if (container) container.remove();
}
function handlePageChange() {
setTimeout(() => {
const isVideoPage = location.pathname.startsWith('/watch') || location.pathname.startsWith('/shorts');
if (isVideoPage) {
addControls();
waitForVideo(video => {});
const savedQuality = getSavedQuality();
if (savedQuality && savedQuality !== 'Auto') {
applyQualityAfterAd(savedQuality);
}
const savedSpeed = parseFloat(localStorage.getItem('youtubePlaybackSpeed')) || 1.0;
applySpeedAfterAd(savedSpeed);
} else {
removeControls();
}
}, 500);
}
document.addEventListener('yt-navigate-finish', handlePageChange);
if (document.body) { handlePageChange(); }
else { document.addEventListener('DOMContentLoaded', handlePageChange); }
window.addEventListener('keydown', e => {
if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) || document.activeElement.isContentEditable) return;
const keyMap = { '+': 0.25, ']': 0.25, '-': -0.25, '[': -0.25 };
if (keyMap[e.key] !== undefined) { adjustSpeed(keyMap[e.key]); }
else if (e.key === '=') { adjustSpeed(0); }
});
})();