/* eslint-disable userscripts/use-download-and-update-url */ /* -eslint-disable userscripts/better-use-match -- Is this a thing? */ // ==UserScript== // @name YouTube arrow keys FIX // @version 2.0.0 // @description Fix YouTube keyboard controls (arrow keys) to be more consistent (Left,Right - jump, Up,Down - volume) after page load or clicking individual controls. // @author Calcifer // @license MIT // @namespace https://github.com/Calciferz // @homepageURL https://github.com/Calciferz/YoutubeKeysFix // @supportURL https://github.com/Calciferz/YoutubeKeysFix/issues // @icon http://youtube.com/yts/img/favicon_32-vflOogEID.png // @match https://*.youtube.com/* // @match https://youtube.googleapis.com/embed* // @grant none // @downloadURL https://update.greasyfork.icu/scripts/38643/YouTube%20arrow%20keys%20FIX.user.js // @updateURL https://update.greasyfork.icu/scripts/38643/YouTube%20arrow%20keys%20FIX.meta.js // ==/UserScript== /* eslint-disable no-multi-spaces */ /* eslint-disable no-multi-str */ (function () { 'use strict'; var playerContainer; // = document.getElementById('player-container') || document.getElementById('player') in embeds var playerElem; // = document.getElementById('movie_player') var playerObserver; var isEmbeddedUI; var subtitleObserver; var subtitleContainer; var lastFocusedPageArea; var areaOrder= [ null ], areaContainers= [ null ], areaFocusDefault= [ null ], areaFocusedSubelement= [ null ]; function formatElemIdOrClass(elem) { return elem.id ? '#' + elem.id : elem.className ? '.' + elem.className.replace(' ', '.') : elem.tagName; } function formatElemIdOrTag(elem) { return elem.id ? '#' + elem.id : elem.tagName; } function isElementWithin(elementWithin, ancestor) { if (! ancestor) return null; for (; elementWithin; elementWithin= elementWithin.parentElement) { if (elementWithin === ancestor) return true; } return false; } function getAreaOf(elementWithin) { for (var i= 1; i ytd-app // Path (DOMContentLoaded): > div#content > ytd-page-manager#page-manager // Path (created 1st step): > ytd-watch-flexy.ytd-page-manager > div#full-bleed-container > div#player-full-bleed-container // Path (created 2nd step): > div#player-container > ytd-player#ytd-player > div#container > div#movie_player.html5-video-player > html5-video-container // Path (created 3rd step): > video.html5-main-video isEmbeddedUI= playerElem.classList.contains('ytp-embed'); playerContainer= document.getElementById('player-container') // full-bleed-container > player-full-bleed-container > player-container > ytd-player > container > movie_player || isEmbeddedUI && document.getElementById('player'); // body > player > movie_player.ytp-embed console.log("[YoutubeKeysFix] initPlayer(): player=", [playerElem]); removeTabStops(); } // Disable focusing certain player controls: volume slider, progress bar, fine seeking bar, subtitle. // It was possible to focus these using TAB, but the controls (space, arrow keys) // change in a confusing manner, creating a miserable UX. // Maybe this is done for accessibility reasons? The irony... // Youtube should have rethought this design for a decade now. function removeTabStops() { //console.log("[YoutubeKeysFix] removeTabStops()"); function removeTabIndexWithSelector(rootElement, selector) { for (let elem of rootElement.querySelectorAll(selector)) { console.log("[YoutubeKeysFix] removeTabIndexWithSelector():", "tabindex=", elem.getAttribute('tabindex'), [elem]); elem.removeAttribute('tabindex'); } } // Remove tab stops from video player //playerElem.removeAttribute('tabindex'); //removeTabIndexWithSelector(document, '#' + playerElem.id + '[tabindex]'); //removeTabIndexWithSelector(document, '#' + playerElem.id); removeTabIndexWithSelector(document, '.html5-video-player'); //removeTabIndexWithSelector(playerElem, '.html5-video-container [tabindex]'); //removeTabIndexWithSelector(playerElem, '.html5-main-video[tabindex]'); removeTabIndexWithSelector(playerElem, '.html5-main-video'); // Remove tab stops from progress bar //removeTabIndexWithSelector(playerElem, '.ytp-progress-bar[tabindex]'); removeTabIndexWithSelector(playerElem, '.ytp-progress-bar'); // Remove tab stops from fine seeking bar //removeTabIndexWithSelector(playerElem, '.ytp-fine-scrubbing-container [tabindex]'); //removeTabIndexWithSelector(playerElem, '.ytp-fine-scrubbing-thumbnails[tabindex]'); removeTabIndexWithSelector(playerElem, '.ytp-fine-scrubbing-thumbnails'); // Remove tab stops from volume slider //removeTabIndexWithSelector(playerElem, '.ytp-volume-panel[tabindex]'); removeTabIndexWithSelector(playerElem, '.ytp-volume-panel'); // Remove tab stops of non-buttons and links (inclusive selector) //removeTabIndexWithSelector(playerElem, '[tabindex]:not(button):not(a):not(div.ytp-ce-element)'); // Make unfocusable all buttons in the player //removeTabIndexWithSelector(playerElem, '[tabindex]'); // Make unfocusable all buttons in the player controls (bottom bar) //removeTabIndexWithSelector(playerElem, '.ytp-chrome-bottom [tabindex]'); //removeTabIndexWithSelector(playerElem.querySelector('.ytp-chrome-bottom'), '[tabindex]'); // Remove tab stops from subtitle element when created function mutationHandler(mutations, observer) { for (let mut of mutations) { //console.log("[YoutubeKeysFix] mutationHandler():\n", mut); // spammy //removeTabIndexWithSelector(mut.target, '.caption-window[tabindex]'); removeTabIndexWithSelector(mut.target, '.caption-window'); if (subtitleContainer) continue; subtitleContainer = playerElem.querySelector('#ytp-caption-window-container'); // If subtitle container is created if (subtitleContainer) { console.log("[YoutubeKeysFix] mutationHandler(): Subtitle container created, stopped observing #movie_player", [subtitleContainer]); // Observe subtitle container instead of movie_player observer.disconnect(); observer.observe(subtitleContainer, { childList: true }); } } } // Subtitle container observer setup // #movie_player > #ytp-caption-window-container > .caption-window subtitleContainer = playerElem.querySelector('#ytp-caption-window-container'); if (!subtitleObserver) { subtitleObserver = new MutationObserver( mutationHandler ); // Observe movie_player because subtitle container is not created yet subtitleObserver.observe(subtitleContainer || playerElem, { childList: true, subtree: !subtitleContainer }); } } console.log("[YoutubeKeysFix] loading: version=" + GM_info.script.version, "sandboxMode=" + GM_info.sandboxMode, "onYouTubePlayerReady=", window.onYouTubePlayerReady); initDom(); initEvents(); initStyle(); // Run initPlayer() when #movie_player is created observePlayer(); })();