diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java index c0f7dd4d4..a44fc180c 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java @@ -59,7 +59,7 @@ import static org.mozilla.vrbrowser.utils.ServoUtils.isServoAvailable; public class SessionStore implements ContentBlocking.Delegate, GeckoSession.NavigationDelegate, - GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, GeckoSession.TextInputDelegate, + GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, GeckoSession.TextInputDelegate, GeckoSession.PromptDelegate, GeckoSession.MediaDelegate, SharedPreferences.OnSharedPreferenceChangeListener { private static SessionStore mInstance; @@ -1042,7 +1042,9 @@ public void onCanGoForward(@NonNull GeckoSession aSession, boolean aCanGoForward private String checkYoutubeOverride(String aUri) { try { Uri uri = Uri.parse(aUri); - if (!uri.getHost().toLowerCase().contains("www.youtube.")) { + String hostLower = uri.getHost().toLowerCase(); + if (!hostLower.endsWith(".youtube.com") && + !hostLower.endsWith(".youtube-nocookie.com")) { return null; } String query = uri.getQueryParameter("disable_polymer"); diff --git a/app/src/main/assets/web_extensions/youtube_webcompat/main.css b/app/src/main/assets/web_extensions/youtube_webcompat/main.css index f948d9203..2e13a7b52 100644 --- a/app/src/main/assets/web_extensions/youtube_webcompat/main.css +++ b/app/src/main/assets/web_extensions/youtube_webcompat/main.css @@ -1,5 +1,6 @@ -/* To hide harmless warning about 360° video playback. */ -div.alert-with-button-renderer, -ytm-alert-with-button-renderer { +/* To hide harmless warnings. */ +.alert-with-button-renderer, .ytm-alert-with-button-renderer, ytm-alert-with-button-renderer, +.ytp-ad-module, ytp-ad-module +.ytp-generic-popup, ytp-generic-popup { display: none; } diff --git a/app/src/main/assets/web_extensions/youtube_webcompat/main.js b/app/src/main/assets/web_extensions/youtube_webcompat/main.js index 7b39baf02..3a27f5bdd 100644 --- a/app/src/main/assets/web_extensions/youtube_webcompat/main.js +++ b/app/src/main/assets/web_extensions/youtube_webcompat/main.js @@ -1,19 +1,174 @@ (function () { // If missing, inject a `` tag to trigger YouTube's mobile layout. - const viewport = document.head.querySelector('meta[name="viewport"]'); - if (!viewport) { - viewport = document.createElement('meta'); - viewport.name = 'viewport'; - viewport.content = 'width=user-width, initial-scale=1'; - document.head.appendChild(viewport); + window.addEventListener('load', () => { + let viewport = document.head.querySelector('meta[name="viewport"]'); + if (!viewport) { + viewport = document.createElement('meta'); + viewport.name = 'viewport'; + viewport.content = 'width=device-width, initial-scale=1'; + document.head.appendChild(viewport); + } + }); + + const LOGTAG = '[firefoxreality:webcompat]' + const qs = new URLSearchParams(window.location.search); + let retryTimeout = null; + + function getTruthyQS (key) { + if (!qs || !qs.has(key)) { + return false; + } + const valueLower = (qs.get('key') || '').trim().toLowerCase(); + return valueLower === '' || valueLower === '1' || valueLower === 'true' || valueLower === 'yes' || valueLower === 'on'; } - // Open the `Settings` menu. - document.querySelector('ytp-settings-button, .ytp-settings-button').click(); + const prefs = { + hd: false, + quality: 1440, + log: qs.get('mozDebug') ? getTruthyQS('mozDebug') : true, + retryAttempts: parseInt(qs.get('retryAttempts') || qs.get('retryattempts') || '5', 10), + retryTimeout: parseInt(qs.get('retryTimeout') || qs.get('retrytimeout') || '500', 10) + }; + + const printLog = String(prefs.log) === 'true'; + + const log = (...args) => printLog && console.log(LOGTAG, ...args); + const logError = (...args) => printLog && console.error(LOGTAG, ...args); + const logWarn = (...args) => printLog && console.warn(LOGTAG, ...args); + + const ytImprover = window.ytImprover = (state, attempts) => { + if (ytImprover.completed) { + return; + } + + if (typeof attempts === 'undefined') { + attempts = 1; + } + if (attempts >= prefs.retryAttempts) { + return; + } + + const player = document.getElementById('movie_player'); + if (state !== 1 || !player) { + attempts++; + retryTimeout = setTimeout(() => { + ytImprover(state, attempts); + }, prefs.retryInterval); + return; + } + + const levels = player.getAvailableQualityLevels(); + if (!levels || !levels.length) { + logWarn('Cannot read `player.getAvailableQualityLevels()`'); + return; + } + + clearTimeout(retryTimeout); + ytImprover.completed = true; + + const prefs.qualities = [ + 'highres', 'h2880', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'auto' + ]; + const prefs.qualityLabels = { + '4320': 'highres', // 8K / 4320p / QUHD + '2880': 'hd2880', // 5K / 2880p / UHD+ + '2160': 'hd2160', // 4K / 2160p / UHD + '1440': 'hd1440', // 1440p / QHD + '1080': 'hd1080', // 1080p / FHD + '720': 'hd720', // 720p / HD + '480': 'large', // 480p + '360': 'medium', // 360p + '240': 'small', // 240p + '144': 'tiny', // 144p + '0': 'auto' + }; - // Select the `Quality` sub-menu. - document.querySelector('ytp-settings-menu ytp-menuitem:last-child, .ytp-settings-menu .ytp-menuitem:last-child').click(); + const getDesiredQuality = () => { + const qsQuality = (qs.get('vq') || qs.get('quality') || '').trim().toLowerCase(); + if (qsQuality) { + if (qsQuality in prefs.qualityLabels) { + prefs.quality = prefs.qualityLabels[qsQuality]; + } else { + const qsQualityNumber = parseInt(qsQuality, 10); + if (Number.isInteger(qsQualityNumber)) { + prefs.quality = qsQualityNumber; + } else { + prefs.quality = qsQuality; + } + } + } + prefs.quality = String(prefs.quality).toLowerCase(); + if (qsQuality === 'auto' || qsQuality === 'default') { + prefs.quality = 'auto'; + } + if (prefs.quality in prefs.qualityLabels) { + prefs.quality = prefs.qualityLabels[prefs.quality]; + } + return prefs.quality; + }; - // Select the best `Quality`. - document.querySelector('ytp-quality-menu ytp-menuitem:first-child, .ytp-quality-menu .ytp-menuitem:first-child').click(); + prefs.quality = getDesiredQuality(); + if (prefs.quality === 'auto') { + return log(`Desired quality is fine (${prefs.quality})`); + } + + const currentQuality = player.getPlaybackQuality(); + if (prefs.quality === currentQuality) { + return log(`Current quality is desired quality (${currentQuality})`); + } + + const findBestQuality = increase => { + if (prefs.quality === 'highest' || prefs.quality === 'best' || prefs.quality === 'max' || prefs.quality === 'maximum') { + return levels[0]; + } + if (prefs.quality === 'lowest' || prefs.quality === 'worst' || prefs.quality === 'min' || prefs.quality === 'minimum') { + return levels[levels.length - 1]; + } + if (increase) { + prefs.quality = prefs.qualities[prefs.qualities.indexOf(prefs.quality) - 1] || levels[0]; + } + const index = levels.indexOf(prefs.quality); + if (index !== -1) { + return prefs.quality; + } + return findBestQuality(true); + }; + const newBestQuality = findBestQuality(); + if (currentQuality === newBestQuality) { + return log(`Current quality "${currentQuality}" is the best available quality`); + } + + if (!player.setPlaybackQuality) { + return logError('`player.setPlaybackQuality` not available'); + } + player.setPlaybackQuality(newBestQuality); + + if (!player.setPlaybackQualityRange) { + return logError('`player.setPlaybackQualityRange` not available'); + } + try { + player.setPlaybackQualityRange(newBestQuality, newBestQuality); + } catch (e) { + } + + log(`Changed quality from "${currentQuality}" to "${newBestQuality}"`); + }; + + if (window.location.pathname.startsWith('/watch')) { + const onYouTubePlayerReady = window.onYouTubePlayerReady = evt => { + log('`onYouTubePlayerReady` called'); + window.ytImprover(1); + evt.addEventListener('onStateChange', 'ytImprover'); + }; + + window.addEventListener('spfready', () => { + log('`spfready` event fired'); + if (typeof window.ytplayer === 'object' && window.ytplayer.config) { + log('`window.ytplayer.config.args.jsapicallback` set'); + window.ytplayer.config.args.jsapicallback = 'onYouTubePlayerReady'; + } + }); + + ytImprover(1); + } })(); diff --git a/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json b/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json index 04d38f012..0949b2854 100644 --- a/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json +++ b/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json @@ -6,7 +6,8 @@ "content_scripts": [ { "matches": [ - "*://*.youtube.com/*" + "*://*.youtube.com/*", + "*://*.youtube-nocookie.com/*" ], "css": [ "main.css" @@ -14,7 +15,8 @@ "js": [ "main.js" ], - "run_at": "document_end" + "run_at": "document_start", + "all_frames": true } ] }