I’ve updated the script for Violentmonkey.
New features:
- Buttons for: max quality, 1440p, 1080p, 720p
- Show a toast and loading animation after clicking a button
It’s the only way I can play 4k videos reliably. For 1440p and below I can do it in the browser but mpv uses way less resources (cpu,gpu) than playing the video in the browser.
It’s now on github: userscript-youtube-play-in-mpv/user-script.js at main · dariusmihai/userscript-youtube-play-in-mpv · GitHub
Here it is for convenience, but the most up to date will be on github.
// ==UserScript==
// @name YouTube ▶ MPV Handler + Toast
// @version 1.4
// @match https://www.youtube.com/watch*
// @grant none
// @namespace Violentmonkey Scripts
// @description Adds buttons below the youtube player to allow the user to watch the video in mpv.
// @author Darius
// ==/UserScript==
/**
* Designed to work on Linux.
*
* Dependencies:
* - mpv player
* - yt-dlp
* - mpv-handler https://github.com/akiirui/mpv-handler.git
* - the Violentmonkey browser extension
*
* For details and installation instructions, see Readme
*
*/
(function() {
const btnStyle = `
margin-left:8px;
padding:6px 12px;
background:#555;
color:#fff;
border:none;
border-radius:4px;
cursor:pointer;
position:relative;
`;
const styles = `
.spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 6px;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.mpv-toast {
position: fixed;
bottom: 30px;
right: 30px;
background: #323232;
color: white;
padding: 60px 76px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
font-size: 14px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 9999;
}
.mpv-toast.show {
opacity: 1;
}
`;
const styleEl = document.createElement('style');
styleEl.textContent = styles;
document.head.appendChild(styleEl);
function showToast(msg) {
const toast = document.createElement('div');
toast.className = 'mpv-toast';
toast.textContent = msg;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 500);
}, 3000);
}
function getMpvUrl(quality = '') {
const data = btoa(window.location.href);
const safe = data.replace(/\//g, "_").replace(/\+/g, "-").replace(/\=/g, "");
return `mpv://play/${safe}${quality ? ('/?quality=' + quality) : ''}`;
}
function handleClick(btn, quality = '') {
const mpvUrl = getMpvUrl(quality);
window.open(mpvUrl);
document.querySelector('video')?.pause();
showToast(`Launching in MPV ${quality ? `(${quality})` : ''}...`);
if (!btn.querySelector('.spinner')) {
const spinner = document.createElement('span');
spinner.className = 'spinner';
btn.appendChild(spinner);
}
const allButtons = document.querySelectorAll('.mpv-handler-btn');
allButtons.forEach(b => b.disabled = true);
setTimeout(() => {
allButtons.forEach(b => {
b.disabled = false;
b.querySelector('.spinner')?.remove();
});
}, 10000);
}
function insertButtons() {
const controls = document.querySelector('.ytp-left-controls');
if (!controls || controls.querySelector('.mpv-handler-btn')) return;
const qualities = [
{ label: '▶ MPV (max)', quality: '' },
{ label: '▶ MPV (1440)', quality: '1440p' },
{ label: '▶ MPV (1080)', quality: '1080p' },
{ label: '▶ MPV (720)', quality: '720p' },
];
for (const { label, quality } of qualities) {
const btn = document.createElement('button');
btn.className = 'mpv-handler-btn';
btn.style = btnStyle;
btn.textContent = label;
btn.onclick = () => handleClick(btn, quality);
controls.insertBefore(btn, controls.firstChild);
}
}
const waitAndInsert = () => {
const interval = setInterval(() => {
const ready = document.querySelector('.ytp-left-controls');
if (ready) {
clearInterval(interval);
insertButtons();
}
}, 500);
};
waitAndInsert();
document.addEventListener('yt-navigate-finish', waitAndInsert);
})();