If a user is browsing videos on YouTube.com from a web browser on a higher-end device (e.g., VR headsets), the user has to manually override the playback quality of a video through the playback-controls bar at the bottom of the video: Settings > Quality.
- Load a YouTube video. Example: https://www.youtube.com/watch?v=ELU-43DMNT4
- Then paste some of this code in the DevTools' Console.
- You'll notice the quality changes to the highest available for the video.
// Open the `Settings` menu.
document.querySelector('ytp-settings-button, .ytp-settings-button').click();
// Select the `Quality` sub-menu.
document.querySelector('ytp-settings-menu ytp-menuitem:last-child, .ytp-settings-menu .ytp-menuitem:last-child').click();
// Select the best `Quality`. (The best quality is always the first item. See below for how to cross-reference the `ytp-menuitem` rows with the shortnames - because there are no classes or identifiers in the DOM to query for.)
document.querySelector('ytp-quality-menu ytp-menuitem:first-child, .ytp-quality-menu .ytp-menuitem:first-child').click();
- See YouTube docs on supported qualities.
- Load a YouTube video. Example: https://www.youtube.com/watch?v=ELU-43DMNT4
- Then paste some of this code in the DevTools' Console.
Note: YouTube, using heuristics of hardware and software capabilities, automatically sets the quality to the most performant, performance-wise and network-wise. (This is the default Auto quality option listed in the video settings: Settings > Quality).
// To get the current playback quality of the video.
document.querySelector('#movie_player').getPlaybackQuality();
- When navigating between videos, it'd be ideal to persist the user's choice in local storage (either in
window.localStorage
or WebExtension'sbrowser.storage
).)_ - Allow a user to pass
?mozVideoQuality=
in YouTube URLs to force a quality. If the video doesn't support that quality, we could downgrade to the next available quality, ultimately falling back to the 'Auto' setting if needed.
// (function () {
// const
playerEl = document.getElementById('movie_player');
if (!playerEl) {
return;
}
// const
YOUTUBE_QUALITY_SIZES = [4320, 2880, 2160, 1440, 1080, 720, 480, 360, 240, 144];
// const
YOUTUBE_QUALITY_DEFAULT_SIZE = 1440;
// const
YOUTUBE_QUALITY_LABELS = {
4320: 'highres',
2160: 'hd2160',
1440: 'hd1440',
1080: 'hd1080',
720: 'hd720',
480: 'large',
360: 'medium',
240: 'small',
144: 'tiny'
};
// const
YOUTUBE_QUALITY_SIZES_BY_LABEL = {};
YOUTUBE_QUALITY_SIZES.forEach(size => {
YOUTUBE_QUALITY_SIZES_BY_LABEL[YOUTUBE_QUALITY_LABELS[size]] = size;
});
// const
YOUTUBE_QUALITY_DEFAULT_SIZE_LABEL = YOUTUBE_QUALITY_LABELS[YOUTUBE_QUALITY_DEFAULT_SIZE];
function getBestDefaultQuality (choices, defaultQuality) {
//let
defaultQualitySize = defaultQuality;
//let
defaultQualityLabel = YOUTUBE_QUALITY_LABELS[defaultQuality];
if (!Number.isInteger(defaultQuality)) {
defaultQualitySize = YOUTUBE_QUALITY_SIZES_BY_LABEL[defaultQuality];
defaultQualityLabel = defaultQuality;
}
if (!defaultQualitySize) {
throw new Error(`Quality "${defaultQualitySize}" not supported on YouTube`);
}
//let
idx = choices.indexOf(defaultQualityLabel);
if (idx > -1) {
return idx;
}
//let
label = '';
for (idx = 0; idx < choices.length; idx++) {
label = choices[idx];
if (label in YOUTUBE_QUALITY_SIZES_BY_LABEL && YOUTUBE_QUALITY_SIZES_BY_LABEL[label] <= YOUTUBE_QUALITY_DEFAULT_SIZE) {
return idx;
}
}
return idx;
}
// const
playbackQuality = playerEl.getPlaybackQuality();
// let
desiredQuality = YOUTUBE_QUALITY_DEFAULT_SIZE;
// const
qs = window.URLSearchParams && new URLSearchParams(window.location.search);
if (qs && qs.has('vq') || qs.has('quality')) {
desiredQuality = (qs.get('vq') || qs.get('quality') || '').trim().toLowerCase();
if (desiredQuality === 'auto' || desiredQuality === 'default') {
// Open the `Settings` menu.
console.log('Opened video "Settings" menu');
document.querySelector('ytp-settings-button, .ytp-settings-button').click();
// Select the `Quality` sub-menu.
console.log('Opened video "Quality" sub-menu');
document.querySelector('ytp-settings-menu ytp-menuitem:last-child, .ytp-settings-menu .ytp-menuitem:last-child').click();
// Select the best `Quality`.
// NOTE: The best quality is always the first item.
// See below for how to cross-reference the `ytp-menuitem` rows with the shortnames -
// because there are no classes or identifiers in the DOM to query for.)
// const
autoQualityEl = document.querySelector(`ytp-quality-menu ytp-menuitem:first-child, .ytp-quality-menu .ytp-menuitem:last-child`);
autoQualityEl.click();
console.log(`Changed changed to "Auto"`);
}
}
if (YOUTUBE_QUALITY_SIZES_BY_LABEL[playbackQuality] >= desiredQuality) {
console.log(`Quality is already playing at "${playbackQuality}" (requested "${YOUTUBE_QUALITY_LABELS[desiredQuality]}")`);
// return;
} else {
console.log(playerEl.getAvailableQualityLevels());
// const
newQualityIdx = getBestDefaultQuality(playerEl.getAvailableQualityLevels(), desiredQuality);
console.log(newQualityIdx);
// Open the `Settings` menu.
console.log('Opened video "Settings" menu');
document.querySelector('ytp-settings-button, .ytp-settings-button').click();
// Select the `Quality` sub-menu.
console.log('Opened video "Quality" sub-menu');
document.querySelector('ytp-settings-menu ytp-menuitem:last-child, .ytp-settings-menu .ytp-menuitem:last-child').click();
// Select the best `Quality`. (The best quality is always the first item. See below for how to cross-reference the `ytp-menuitem` rows with the shortnames - because there are no classes or identifiers in the DOM to query for.)
// const
newQualityEl = document.querySelector(`ytp-quality-menu ytp-menuitem:first-child, .ytp-quality-menu .ytp-menuitem:nth-child(${newQualityIdx})`);
newQualityEl.click();
console.log(`Changed changed to "${newQualityEl.textContent}" ("${playerEl.getPlaybackQuality()}")`);
}
// })();
Another way I found to get the available qualities: