diff --git a/js&css/web-accessible/www.youtube.com/player.js b/js&css/web-accessible/www.youtube.com/player.js
index 14f709ca6..eaaa032e0 100644
--- a/js&css/web-accessible/www.youtube.com/player.js
+++ b/js&css/web-accessible/www.youtube.com/player.js
@@ -2,248 +2,478 @@
AUTOPLAY DISABLE
------------------------------------------------------------------------------*/
ImprovedTube.autoplayDisable = function (videoElement) {
- if (this.storage.player_autoplay_disable
- || this.storage.playlist_autoplay === false
- || this.storage.channel_trailer_autoplay === false) {
- const player = this.elements.player || videoElement.closest('.html5-video-player') || videoElement.closest('#movie_player'); // #movie_player: outdated since 2024?
-
- if (this.video_url !== location.href) { this.user_interacted = false; }
-
- //if (there is a player) and (no user clicks) and (no ads playing)
- // and( ((auto play is off and it is not in a playlist)
- // or (playlist auto play is off and in a playlist))
- // or (we are in a channel and the channel trailer autoplay is off) )
-
- if (player && !this.user_interacted // (=user didnt click or type)
- && !player.classList.contains('ad-showing') // (=no ads playing, needs an update?)
- && ((location.href.includes('/watch?') // #1703 // (=video page)
- // player_autoplay_disable & not playlist
- && (this.storage.player_autoplay_disable && !location.href.includes('list='))
- // !playlist_autoplay & playlist
- || (this.storage.playlist_autoplay === false && location.href.includes('list=')))
- // channel homepage & !channel_trailer_autoplay
- || (this.storage.channel_trailer_autoplay === false && this.regex.channel.test(location.href)))) {
-
- setTimeout(function () {
- try { player.pauseVideo(); } catch (error) { console.log("autoplayDisable: Pausing"); videoElement.pause(); }
- });
- } else {
- document.dispatchEvent(new CustomEvent('it-play'));
- }
- } else {
- document.dispatchEvent(new CustomEvent('it-play'));
- }
+ if (
+ this.storage.player_autoplay_disable ||
+ this.storage.playlist_autoplay === false ||
+ this.storage.channel_trailer_autoplay === false
+ ) {
+ const player =
+ this.elements.player ||
+ videoElement.closest(".html5-video-player") ||
+ videoElement.closest("#movie_player");
+
+ // ❌ DO NOT reset user_interacted on SPA URL changes
+ // YouTube changes URL internally without user navigation
+ // if (this.video_url !== location.href) {
+ // this.user_interacted = false;
+ // }
+
+ if (
+ player &&
+ !player.classList.contains("ad-showing") &&
+ location.pathname === "/watch" &&
+ // normal video autoplay disabled
+ ((this.storage.player_autoplay_disable &&
+ !location.href.includes("list=")) ||
+ // playlist autoplay disabled
+ (this.storage.playlist_autoplay === false &&
+ location.href.includes("list=")) ||
+ // channel trailer autoplay disabled
+ (this.storage.channel_trailer_autoplay === false &&
+ this.regex.channel.test(location.href)))
+ ) {
+ setTimeout(() => {
+ try {
+ player.pauseVideo();
+ } catch (error) {
+ videoElement.pause();
+ }
+ }, 0);
+ }
+ }
};
/*------------------------------------------------------------------------------
FORCED PLAY VIDEO FROM THE BEGINNING
------------------------------------------------------------------------------*/
ImprovedTube.forcedPlayVideoFromTheBeginning = function () {
- const player = this.elements.player,
- video = this.elements.video,
- paused = video?.paused;
-
- if (player && video && this.storage.forced_play_video_from_the_beginning && location.pathname == '/watch') {
- player.seekTo(0);
- // restore previous paused state
- if (paused) { player.pauseVideo(); }
- }
+ const player = this.elements.player,
+ video = this.elements.video,
+ paused = video?.paused;
+
+ if (
+ player &&
+ video &&
+ this.storage.forced_play_video_from_the_beginning &&
+ location.pathname == "/watch"
+ ) {
+ player.seekTo(0);
+ // restore previous paused state
+ if (paused) {
+ player.pauseVideo();
+ }
+ }
};
/*------------------------------------------------------------------------------
AUTOPAUSE WHEN SWITCHING TABS
------------------------------------------------------------------------------*/
ImprovedTube.playerAutopauseWhenSwitchingTabs = function () {
- const player = this.elements.player;
-
- if (this.storage.player_autopause_when_switching_tabs && player) {
- if (this.focus && this.played_before_blur && this.elements.video.paused) {
- player.playVideo();
- } else {
- this.played_before_blur = !this.elements.video.paused;
- if (!this.elements.video.paused) {
- player.pauseVideo();
- }
- }
- }
+ const player = this.elements.player;
+
+ if (this.storage.player_autopause_when_switching_tabs && player) {
+ if (this.focus && this.played_before_blur && this.elements.video.paused) {
+ player.playVideo();
+ } else {
+ this.played_before_blur = !this.elements.video.paused;
+ if (!this.elements.video.paused) {
+ player.pauseVideo();
+ }
+ }
+ }
};
/*------------------------------------------------------------------------------
PICTURE IN PICTURE (PIP)
------------------------------------------------------------------------------*/
ImprovedTube.enterPip = function (disable) {
- const video = this.elements.video;
-
- if (!disable
- && video
- && document.pictureInPictureEnabled
- && typeof video.requestPictureInPicture == 'function') {
-
- video.requestPictureInPicture().then(() => {
- if (video.paused) {
- // manually send Play message to "Auto-pause while I'm not in the tab", paused PiP wont do it automatically.
- document.dispatchEvent(new CustomEvent('it-play'));
- }
- return true;
- }).catch((err) => console.error('playerAutoPip: Failed to enter Picture-in-Picture mode', err));
- } else if (document.pictureInPictureElement && typeof document.exitPictureInPicture == 'function') {
- document.exitPictureInPicture();
- return false;
- }
+ const video = this.elements.video;
+
+ if (
+ !disable &&
+ video &&
+ document.pictureInPictureEnabled &&
+ typeof video.requestPictureInPicture == "function"
+ ) {
+ video
+ .requestPictureInPicture()
+ .then(() => {
+ if (video.paused) {
+ // manually send Play message to "Auto-pause while I'm not in the tab", paused PiP wont do it automatically.
+ document.dispatchEvent(new CustomEvent("it-play"));
+ }
+ return true;
+ })
+ .catch((err) =>
+ console.error(
+ "playerAutoPip: Failed to enter Picture-in-Picture mode",
+ err
+ )
+ );
+ } else if (
+ document.pictureInPictureElement &&
+ typeof document.exitPictureInPicture == "function"
+ ) {
+ document.exitPictureInPicture();
+ return false;
+ }
};
/*------------------------------------------------------------------------------
AUTO PIP WHEN SWITCHING TABS
------------------------------------------------------------------------------*/
ImprovedTube.playerAutoPip = function () {
- const video = this.elements.video;
-
- if (this.storage.player_autoPip && this.storage.player_autoPip_outside && this.focus) {
- this.enterPip(true);
- } else if (this.storage.player_autoPip && !this.focus && !video?.paused) {
- this.enterPip();
- }
+ const video = this.elements.video;
+
+ if (
+ this.storage.player_autoPip &&
+ this.storage.player_autoPip_outside &&
+ this.focus
+ ) {
+ this.enterPip(true);
+ } else if (this.storage.player_autoPip && !this.focus && !video?.paused) {
+ this.enterPip();
+ }
};
/*------------------------------------------------------------------------------
PLAYBACK SPEED
------------------------------------------------------------------------------*/
ImprovedTube.playbackSpeed = function (newSpeed) {
- const video = this.elements.video,
- player = this.elements.player,
- speed = video?.playbackRate ? Number(video.playbackRate.toFixed(2)) : (player?.getPlaybackRate ? Number(player.getPlaybackRate().toFixed(2)) : null);
-
- if (!speed) {
- console.error('PlaybackSpeed: Cant establish playbackRate/getPlaybackRate');
- return false;
- }
-
- // called with no option or demanded speed already set, only provide readback
- if (!newSpeed || speed == newSpeed) return speed;
-
- if (video?.playbackRate) {
- video.playbackRate = newSpeed;
- newSpeed = video.playbackRate;
- } else if (player?.setPlaybackRate && player.getPlaybackRate) {
- player.setPlaybackRate(newSpeed);
- newSpeed = player.getPlaybackRate();
- } else newSpeed = false;
-
- return newSpeed;
+ const video = this.elements.video,
+ player = this.elements.player,
+ speed = video?.playbackRate
+ ? Number(video.playbackRate.toFixed(2))
+ : player?.getPlaybackRate
+ ? Number(player.getPlaybackRate().toFixed(2))
+ : null;
+
+ if (!speed) {
+ console.error("PlaybackSpeed: Cant establish playbackRate/getPlaybackRate");
+ return false;
+ }
+
+ // called with no option or demanded speed already set, only provide readback
+ if (!newSpeed || speed == newSpeed) return speed;
+
+ if (video?.playbackRate) {
+ video.playbackRate = newSpeed;
+ newSpeed = video.playbackRate;
+ } else if (player?.setPlaybackRate && player.getPlaybackRate) {
+ player.setPlaybackRate(newSpeed);
+ newSpeed = player.getPlaybackRate();
+ } else newSpeed = false;
+
+ return newSpeed;
};
/*------------------------------------------------------------------------------
PERMANENT PLAYBACK SPEED
------------------------------------------------------------------------------*/
-ImprovedTube.playerPlaybackSpeed = function () { if (this.storage.player_forced_playback_speed === true) {
- var player = this.elements.player; if (!player) return;
- var video = this.elements.video || player.querySelector('video');
- option = this.storage.player_playback_speed;
- if (this.isset(option) === false) { option = 1; }
- else if ( option !== 1 ) {
- const speed = video?.playbackRate ? Number(video.playbackRate.toFixed(2)) : (player?.getPlaybackRate ? Number(player.getPlaybackRate().toFixed(2)) : null);
- if (speed !== option && speed !== 1 && speed !== Number((Math.floor(option / 0.05) * 0.05).toFixed(2)))
- { console.log("skipping permanent speed, since speed was manually set differently for this video to:" + video.playbackRate + ", was it?"); return; }
- }
- if (!(player.getVideoData() && player.getVideoData().isLive))
- { player.setPlaybackRate(Number(option)); if (!video) { video = { playbackRate: 1 }; }; video.playbackRate = Number(option); // #1729 q2 // hi! @raszpl
- if ( (this.storage.player_force_speed_on_music !== true || this.storage.player_dont_speed_education === true)
- && option !== 1) {
- ImprovedTube.speedException = function () {
- if (this.storage.player_dont_speed_education === true && DATA.genre === 'Education')
- {player.setPlaybackRate(Number(1)); video.playbackRate = Number(1); return;}
- if (this.storage.player_force_speed_on_music === true)
- { //player.setPlaybackRate(Number(option)); video.playbackRate = Number(option);
- return;}
- if (DATA.keywords && !keywords) { keywords = DATA.keywords.join(', ') || ''; }
- if (keywords === 'video, sharing, camera phone, video phone, free, upload') { keywords = ''; }
- var musicIdentifiers = /(official|music|lyrics?)[ -]video|(cover|studio|radio|album|alternate)[- ]version|soundtrack|unplugged|\bmedley\b|\blo-fi\b|\blofi\b|a(lla)? cappella|feat\.|(piano|guitar|jazz|ukulele|violin|reggae)[- ](version|cover)|karaok|backing[- ]track|instrumental|(sing|play)[- ]?along|卡拉OK|卡拉OK|الكاريوكي|караоке|カラオケ|노래방|bootleg|mashup|Radio edit|Guest (vocals|musician)|(title|opening|closing|bonus|hidden)[ -]track|live acoustic|interlude|featuring|recorded (at|live)/i;
- var musicIdentifiersTitleOnly = /lyrics|theme song|\bremix|\bAMV ?[^a-z0-9]|[^a-z0-9] ?AMV\b|\bfull song\b|\bsong:|\bsong[\!$]|^song\b|( - .*\bSong\b|\bSong\b.* - )|cover ?[^a-z0-9]|[^a-z0-9] ?cover|\bconcert\b/i;
- var musicIdentifiersTitle = new RegExp(musicIdentifiersTitleOnly.source + '|' + musicIdentifiers.source, "i");
- var musicRegexMatch = musicIdentifiersTitle.test(DATA.title);
- if (!musicRegexMatch) {
- var musicIdentifiersTagsOnly = /, (lyrics|remix|song|music|AMV|theme song|full song),|\(Musical Genre\)|, jazz|, reggae/i;
- var musicIdentifiersTags = new RegExp(musicIdentifiersTagsOnly.source + '|' + musicIdentifiers.source, "i");
- keywordsAmount = 1 + ((keywords || '').match(/,/) || []).length;
- if ( ((keywords || '').match(musicIdentifiersTags) || []).length / keywordsAmount > 0.08) {
- musicRegexMatch = true}}
- notMusicRegexMatch = /\bdo[ck]u|interv[iyj]|back[- ]?stage|インタビュー|entrevista|面试|面試|회견|wawancara|مقابلة|интервью|entretien|기록한 것|记录|記錄|ドキュメンタリ|وثائقي|документальный/i.test(DATA.title + " " + keywords);
- // (Tags/keywords shouldnt lie & very few songs titles might have these words)
- if (DATA.duration) {
- function parseDuration (duration) { const [_, h = 0, m = 0, s = 0] = duration.match(/PT(?:(\d+)?H)?(?:(\d+)?M)?(\d+)?S?/).map(part => parseInt(part) || 0);
- return h * 3600 + m * 60 + s; }
- DATA.lengthSeconds = parseDuration(DATA.duration); }
- function testSongDuration (s, ytMusic) {
- if (135 <= s && s <= 260) {return 'veryCommon';}
- if (105 <= s && s <= 420) {return 'common';}
- if (420 <= s && s <= 720) {return 'long';}
- if (45 <= s && s <= 105) {return 'short';}
- if (ytMusic && ytMusic > 1 && (85 <= s / ytMusic && (s / ytMusic <= 375 || ytMusic == 10))) {return 'multiple';}
- //does Youtube ever show more than 10 songs below the description?
- }
- var songDurationType = testSongDuration(DATA.lengthSeconds);
- console.log("genre: " + DATA.genre + "//title: " + DATA.title + "//keywords: " + keywords + "//music word match: " + musicRegexMatch + "// not music word match:" + notMusicRegexMatch + "//duration: " + DATA.lengthSeconds + "//song duration type: " + songDurationType);
- // check if the video is PROBABLY MUSIC:
- if ( ( DATA.genre === 'Music' && (!notMusicRegexMatch || songDurationType === 'veryCommon'))
- || ( musicRegexMatch && !notMusicRegexMatch && (typeof songDurationType !== 'undefined'
- || (/album|Álbum|专辑|專輯|एलबम|البوم|アルバム|альбом|앨범|mixtape|concert|playlist|\b(live|cd|vinyl|lp|ep|compilation|collection|symphony|suite|medley)\b/i.test(DATA.title + " " + keywords)
- && 1000 <= DATA.lengthSeconds )) ) // && 1150 <= DATA.lengthSeconds <= 5000
- || ( DATA.genre === 'Music' && musicRegexMatch && (typeof songDurationType !== 'undefined'
- || (/album|Álbum|专辑|專輯|एलबम|البوم|アルバム|альбом|앨범|mixtape|concert|playlist|\b(live|cd|vinyl|lp|ep|compilation|collection|symphony|suite|medley)\b/i.test(DATA.title + " " + keywords)
- && 1000 <= DATA.lengthSeconds )) ) // && DATA.lengthSeconds <= 5000
- || (amountOfSongs && testSongDuration(DATA.lengthSeconds, amountOfSongs ) !== 'undefined')
- // || location.href.indexOf('music.') !== -1 // (=currently we are only running on www.youtube.com anyways)
- ) { player.setPlaybackRate(1); video.playbackRate = 1; console.log ("...,thus must be music?"); }
- else { // Now this video might rarely be music
- // - however we can make extra-sure after waiting for the video descripion to load... (#1539)
- var tries = 0; var intervalMs = 210; if (location.href.indexOf('/watch?') !== -1) {var maxTries = 10;} else {var maxTries = 0;}
- // ...except when it is an embedded player?
- var waitForDescription = setInterval(() => {
- if (++tries >= maxTries) {
- subtitle = document.querySelector('#title + #subtitle:last-of-type')
- if ( subtitle && 1 <= Number((subtitle?.innerHTML?.match(/^\d+/) || [])[0]) // indicates buyable/registered music (amount of songs)
- && typeof testSongDuration(DATA.lengthSeconds, Number((subtitle?.innerHTML?.match(/^\d+/) || [])[0]) ) !== 'undefined' ) // resonable duration
- {player.setPlaybackRate(1); video.playbackRate = 1; console.log("...but YouTube shows music below the description!"); clearInterval(waitForDescription); }
- intervalMs *= 1.11; }}, intervalMs);
- window.addEventListener('load', () => { setTimeout(() => { clearInterval(waitForDescription); }, 1234); });
- }
- }
- //DATA (TO-DO: make the Data available to more/all features? #1452 #1763 (Then can replace ImprovedTube.elements.category === 'music', VideoID is also used elsewhere)
- DATA = {};
- defaultKeywords = "video,sharing,camera,phone,video phone,free,upload";
- keywords = false; amountOfSongs = false;
-
- ImprovedTube.fetchDOMData = function () {
- try { DATA = JSON.parse(document.querySelector('#microformat script')?.textContent) ?? false; DATA.title = DATA.name;}
- catch { DATA.genre = false; DATA.keywords = false; DATA.lengthSeconds = false;
- try {
- DATA.title = document.getElementsByTagName('meta')?.title?.content || false;
- DATA.genre = document.querySelector('meta[itemprop=genre]')?.content || false;
- DATA.duration = document.querySelector('meta[itemprop=duration]')?.content || false;
- } catch {}}
-
-let tries = 0; const maxTries = 11; let intervalMs = 200;
-const waitForVideoTitle = setInterval(() => { const title = ImprovedTube.videoTitle?.(); tries++;
-
-if (title && title !== 'YouTube') {
- clearInterval(waitForVideoTitle);
- DATA.videoID = ImprovedTube.videoId() || false; // console.log("SPEED: TITLE:" + ImprovedTube.videoTitle() + DATA.title);
- if ( DATA.title === ImprovedTube.videoTitle() || DATA.title.replace(/\s{2,}/g, ' ') === ImprovedTube.videoTitle() )
- { keywords = document.querySelector('meta[name="keywords"]')?.content || ''; ImprovedTube.speedException(); }
- else { keywords = ''; (async function () { try { const response = await fetch(`https://www.youtube.com/watch?v=${DATA.videoID}`);
- console.log("loading the html source:" + `https://www.youtube.com/watch?v=${DATA.videoID}`);
- const htmlContent = await response.text();
- const metaRegex = /]+(name|itemprop)=["'](keywords|genre|duration)["'][^>]+content=["']([^"']+)["'][^>]*>/gi;
- let match; while ((match = metaRegex.exec(htmlContent)) !== null) { // console.log(match);
- const [, property, value] = match;
- if (property === 'keywords') { keywords = value;} else {DATA[property] = value;}
- }
- amountOfSongs = (htmlContent.slice(-80000).match(/},"subtitle":{"simpleText":"(\d*)\s/) || [])[1] || false;
- if (keywords) { ImprovedTube.speedException(); }
- } catch (error) { console.error('Error: fetching from https://Youtube.com/watch?v=${DATA.videoID}', error); keywords = ''; }
- })();
- }
-}
+ImprovedTube.playerPlaybackSpeed = function () {
+ if (this.storage.player_forced_playback_speed === true) {
+ var player = this.elements.player;
+ if (!player) return;
+ var video = this.elements.video || player.querySelector("video");
+ option = this.storage.player_playback_speed;
+ if (this.isset(option) === false) {
+ option = 1;
+ } else if (option !== 1) {
+ const speed = video?.playbackRate
+ ? Number(video.playbackRate.toFixed(2))
+ : player?.getPlaybackRate
+ ? Number(player.getPlaybackRate().toFixed(2))
+ : null;
+ if (
+ speed !== option &&
+ speed !== 1 &&
+ speed !== Number((Math.floor(option / 0.05) * 0.05).toFixed(2))
+ ) {
+ console.log(
+ "skipping permanent speed, since speed was manually set differently for this video to:" +
+ video.playbackRate +
+ ", was it?"
+ );
+ return;
+ }
+ }
+ if (!(player.getVideoData() && player.getVideoData().isLive)) {
+ player.setPlaybackRate(Number(option));
+ if (!video) {
+ video = { playbackRate: 1 };
+ }
+ video.playbackRate = Number(option); // #1729 q2 // hi! @raszpl
+ if (
+ (this.storage.player_force_speed_on_music !== true ||
+ this.storage.player_dont_speed_education === true) &&
+ option !== 1
+ ) {
+ ImprovedTube.speedException = function () {
+ if (
+ this.storage.player_dont_speed_education === true &&
+ DATA.genre === "Education"
+ ) {
+ player.setPlaybackRate(Number(1));
+ video.playbackRate = Number(1);
+ return;
+ }
+ if (this.storage.player_force_speed_on_music === true) {
+ //player.setPlaybackRate(Number(option)); video.playbackRate = Number(option);
+ return;
+ }
+ if (DATA.keywords && !keywords) {
+ keywords = DATA.keywords.join(", ") || "";
+ }
+ if (
+ keywords ===
+ "video, sharing, camera phone, video phone, free, upload"
+ ) {
+ keywords = "";
+ }
+ var musicIdentifiers =
+ /(official|music|lyrics?)[ -]video|(cover|studio|radio|album|alternate)[- ]version|soundtrack|unplugged|\bmedley\b|\blo-fi\b|\blofi\b|a(lla)? cappella|feat\.|(piano|guitar|jazz|ukulele|violin|reggae)[- ](version|cover)|karaok|backing[- ]track|instrumental|(sing|play)[- ]?along|卡拉OK|卡拉OK|الكاريوكي|караоке|カラオケ|노래방|bootleg|mashup|Radio edit|Guest (vocals|musician)|(title|opening|closing|bonus|hidden)[ -]track|live acoustic|interlude|featuring|recorded (at|live)/i;
+ var musicIdentifiersTitleOnly =
+ /lyrics|theme song|\bremix|\bAMV ?[^a-z0-9]|[^a-z0-9] ?AMV\b|\bfull song\b|\bsong:|\bsong[\!$]|^song\b|( - .*\bSong\b|\bSong\b.* - )|cover ?[^a-z0-9]|[^a-z0-9] ?cover|\bconcert\b/i;
+ var musicIdentifiersTitle = new RegExp(
+ musicIdentifiersTitleOnly.source + "|" + musicIdentifiers.source,
+ "i"
+ );
+ var musicRegexMatch = musicIdentifiersTitle.test(DATA.title);
+ if (!musicRegexMatch) {
+ var musicIdentifiersTagsOnly =
+ /, (lyrics|remix|song|music|AMV|theme song|full song),|\(Musical Genre\)|, jazz|, reggae/i;
+ var musicIdentifiersTags = new RegExp(
+ musicIdentifiersTagsOnly.source + "|" + musicIdentifiers.source,
+ "i"
+ );
+ keywordsAmount = 1 + ((keywords || "").match(/,/) || []).length;
+ if (
+ ((keywords || "").match(musicIdentifiersTags) || []).length /
+ keywordsAmount >
+ 0.08
+ ) {
+ musicRegexMatch = true;
+ }
+ }
+ notMusicRegexMatch =
+ /\bdo[ck]u|interv[iyj]|back[- ]?stage|インタビュー|entrevista|面试|面試|회견|wawancara|مقابلة|интервью|entretien|기록한 것|记录|記錄|ドキュメンタリ|وثائقي|документальный/i.test(
+ DATA.title + " " + keywords
+ );
+ // (Tags/keywords shouldnt lie & very few songs titles might have these words)
+ if (DATA.duration) {
+ function parseDuration(duration) {
+ const [_, h = 0, m = 0, s = 0] = duration
+ .match(/PT(?:(\d+)?H)?(?:(\d+)?M)?(\d+)?S?/)
+ .map((part) => parseInt(part) || 0);
+ return h * 3600 + m * 60 + s;
+ }
+ DATA.lengthSeconds = parseDuration(DATA.duration);
+ }
+ function testSongDuration(s, ytMusic) {
+ if (135 <= s && s <= 260) {
+ return "veryCommon";
+ }
+ if (105 <= s && s <= 420) {
+ return "common";
+ }
+ if (420 <= s && s <= 720) {
+ return "long";
+ }
+ if (45 <= s && s <= 105) {
+ return "short";
+ }
+ if (
+ ytMusic &&
+ ytMusic > 1 &&
+ 85 <= s / ytMusic &&
+ (s / ytMusic <= 375 || ytMusic == 10)
+ ) {
+ return "multiple";
+ }
+ //does Youtube ever show more than 10 songs below the description?
+ }
+ var songDurationType = testSongDuration(DATA.lengthSeconds);
+ console.log(
+ "genre: " +
+ DATA.genre +
+ "//title: " +
+ DATA.title +
+ "//keywords: " +
+ keywords +
+ "//music word match: " +
+ musicRegexMatch +
+ "// not music word match:" +
+ notMusicRegexMatch +
+ "//duration: " +
+ DATA.lengthSeconds +
+ "//song duration type: " +
+ songDurationType
+ );
+ // check if the video is PROBABLY MUSIC:
+ if (
+ (DATA.genre === "Music" &&
+ (!notMusicRegexMatch || songDurationType === "veryCommon")) ||
+ (musicRegexMatch &&
+ !notMusicRegexMatch &&
+ (typeof songDurationType !== "undefined" ||
+ (/album|Álbum|专辑|專輯|एलबम|البوم|アルバム|альбом|앨범|mixtape|concert|playlist|\b(live|cd|vinyl|lp|ep|compilation|collection|symphony|suite|medley)\b/i.test(
+ DATA.title + " " + keywords
+ ) &&
+ 1000 <= DATA.lengthSeconds))) || // && 1150 <= DATA.lengthSeconds <= 5000
+ (DATA.genre === "Music" &&
+ musicRegexMatch &&
+ (typeof songDurationType !== "undefined" ||
+ (/album|Álbum|专辑|專輯|एलबम|البوم|アルバム|альбом|앨범|mixtape|concert|playlist|\b(live|cd|vinyl|lp|ep|compilation|collection|symphony|suite|medley)\b/i.test(
+ DATA.title + " " + keywords
+ ) &&
+ 1000 <= DATA.lengthSeconds))) || // && DATA.lengthSeconds <= 5000
+ (amountOfSongs &&
+ testSongDuration(DATA.lengthSeconds, amountOfSongs) !==
+ "undefined")
+ // || location.href.indexOf('music.') !== -1 // (=currently we are only running on www.youtube.com anyways)
+ ) {
+ player.setPlaybackRate(1);
+ video.playbackRate = 1;
+ console.log("...,thus must be music?");
+ } else {
+ // Now this video might rarely be music
+ // - however we can make extra-sure after waiting for the video descripion to load... (#1539)
+ var tries = 0;
+ var intervalMs = 210;
+ if (location.href.indexOf("/watch?") !== -1) {
+ var maxTries = 10;
+ } else {
+ var maxTries = 0;
+ }
+ // ...except when it is an embedded player?
+ var waitForDescription = setInterval(() => {
+ if (++tries >= maxTries) {
+ subtitle = document.querySelector(
+ "#title + #subtitle:last-of-type"
+ );
+ if (
+ subtitle &&
+ 1 <= Number((subtitle?.innerHTML?.match(/^\d+/) || [])[0]) && // indicates buyable/registered music (amount of songs)
+ typeof testSongDuration(
+ DATA.lengthSeconds,
+ Number((subtitle?.innerHTML?.match(/^\d+/) || [])[0])
+ ) !== "undefined"
+ ) {
+ // resonable duration
+ player.setPlaybackRate(1);
+ video.playbackRate = 1;
+ console.log(
+ "...but YouTube shows music below the description!"
+ );
+ clearInterval(waitForDescription);
+ }
+ intervalMs *= 1.11;
+ }
+ }, intervalMs);
+ window.addEventListener("load", () => {
+ setTimeout(() => {
+ clearInterval(waitForDescription);
+ }, 1234);
+ });
+ }
+ };
+ //DATA (TO-DO: make the Data available to more/all features? #1452 #1763 (Then can replace ImprovedTube.elements.category === 'music', VideoID is also used elsewhere)
+ DATA = {};
+ defaultKeywords = "video,sharing,camera,phone,video phone,free,upload";
+ keywords = false;
+ amountOfSongs = false;
+
+ ImprovedTube.fetchDOMData = function () {
+ try {
+ DATA =
+ JSON.parse(
+ document.querySelector("#microformat script")?.textContent
+ ) ?? false;
+ DATA.title = DATA.name;
+ } catch {
+ DATA.genre = false;
+ DATA.keywords = false;
+ DATA.lengthSeconds = false;
+ try {
+ DATA.title =
+ document.getElementsByTagName("meta")?.title?.content || false;
+ DATA.genre =
+ document.querySelector("meta[itemprop=genre]")?.content ||
+ false;
+ DATA.duration =
+ document.querySelector("meta[itemprop=duration]")?.content ||
+ false;
+ } catch {}
+ }
+
+ let tries = 0;
+ const maxTries = 11;
+ let intervalMs = 200;
+ const waitForVideoTitle = setInterval(() => {
+ const title = ImprovedTube.videoTitle?.();
+ tries++;
+
+ if (title && title !== "YouTube") {
+ clearInterval(waitForVideoTitle);
+ DATA.videoID = ImprovedTube.videoId() || false; // console.log("SPEED: TITLE:" + ImprovedTube.videoTitle() + DATA.title);
+ if (
+ DATA.title === ImprovedTube.videoTitle() ||
+ DATA.title.replace(/\s{2,}/g, " ") === ImprovedTube.videoTitle()
+ ) {
+ keywords =
+ document.querySelector('meta[name="keywords"]')?.content ||
+ "";
+ ImprovedTube.speedException();
+ } else {
+ keywords = "";
+ (async function () {
+ try {
+ const response = await fetch(
+ `https://www.youtube.com/watch?v=${DATA.videoID}`
+ );
+ console.log(
+ "loading the html source:" +
+ `https://www.youtube.com/watch?v=${DATA.videoID}`
+ );
+ const htmlContent = await response.text();
+ const metaRegex =
+ /]+(name|itemprop)=["'](keywords|genre|duration)["'][^>]+content=["']([^"']+)["'][^>]*>/gi;
+ let match;
+ while ((match = metaRegex.exec(htmlContent)) !== null) {
+ // console.log(match);
+ const [, property, value] = match;
+ if (property === "keywords") {
+ keywords = value;
+ } else {
+ DATA[property] = value;
+ }
+ }
+ amountOfSongs =
+ (htmlContent
+ .slice(-80000)
+ .match(/},"subtitle":{"simpleText":"(\d*)\s/) ||
+ [])[1] || false;
+ if (keywords) {
+ ImprovedTube.speedException();
+ }
+ } catch (error) {
+ console.error(
+ "Error: fetching from https://Youtube.com/watch?v=${DATA.videoID}",
+ error
+ );
+ keywords = "";
+ }
+ })();
+ }
+ }
-if (tries >= maxTries) { clearInterval(waitForVideoTitle); } intervalMs *= 1.11; }, intervalMs);
-window.addEventListener('load', () => { setTimeout(() => { clearInterval(waitForVideoTitle) }, 5000);});
- };
- ImprovedTube.fetchDOMData();
-/*
+ if (tries >= maxTries) {
+ clearInterval(waitForVideoTitle);
+ }
+ intervalMs *= 1.11;
+ }, intervalMs);
+ window.addEventListener("load", () => {
+ setTimeout(() => {
+ clearInterval(waitForVideoTitle);
+ }, 5000);
+ });
+ };
+ ImprovedTube.fetchDOMData();
+ /*
if ( (history && history.length === 1) || !history?.state?.endpoint?.watchEndpoint) { ImprovedTube.fetchDOMData(); }
else {
//Invidious instances. Should be updated automatically!...
@@ -264,48 +494,70 @@ window.addEventListener('load', () => { setTimeout(() => { clearInterval(waitFo
else { ImprovedTube.fetchDOMData();} }
})();
}
-*/
- } // else { }
- }
-}
-}
+*/
+ } // else { }
+ }
+ }
+};
/*------------------------------------------------------------------------------
SUBTITLES
------------------------------------------------------------------------------*/
ImprovedTube.playerSubtitles = function () {
- const player = this.elements.player;
-
- if (player && player.isSubtitlesOn && player.toggleSubtitles && player.toggleSubtitlesOn) {
- switch (this.storage.player_subtitles) {
- case true:
- case 'enabled':
- player.toggleSubtitlesOn();
- break
-
- case 'disabled':
- if (player.isSubtitlesOn()) { player.toggleSubtitles(); }
- break
- }
- }
+ const player = this.elements.player;
+
+ if (
+ player &&
+ player.isSubtitlesOn &&
+ player.toggleSubtitles &&
+ player.toggleSubtitlesOn
+ ) {
+ switch (this.storage.player_subtitles) {
+ case true:
+ case "enabled":
+ player.toggleSubtitlesOn();
+ break;
+
+ case "disabled":
+ if (player.isSubtitlesOn()) {
+ player.toggleSubtitles();
+ }
+ break;
+ }
+ }
};
/*------------------------------------------------------------------------------
SUBTITLES LANGUAGE
------------------------------------------------------------------------------*/
ImprovedTube.subtitlesLanguage = function () {
- const option = this.storage.subtitles_language,
- player = this.elements.player;
- let subtitlesState;
-
- if (option && player && player.getOption && player.setOption && player.isSubtitlesOn && player.toggleSubtitles) {
- const matchedTrack = player.getOption('captions', 'tracklist', {includeAsr: true})?.find(track => track.languageCode.includes(option) && (!track.vss_id.includes("a.") || this.storage.auto_generate));
-
- if (matchedTrack) {
- subtitlesState = player.isSubtitlesOn();
- player.setOption('captions', 'track', matchedTrack);
- // setOption forces Subtitles ON, restore state from before calling it.
- if (!subtitlesState) { player.toggleSubtitles(); }
- }
- }
+ const option = this.storage.subtitles_language,
+ player = this.elements.player;
+ let subtitlesState;
+
+ if (
+ option &&
+ player &&
+ player.getOption &&
+ player.setOption &&
+ player.isSubtitlesOn &&
+ player.toggleSubtitles
+ ) {
+ const matchedTrack = player
+ .getOption("captions", "tracklist", { includeAsr: true })
+ ?.find(
+ (track) =>
+ track.languageCode.includes(option) &&
+ (!track.vss_id.includes("a.") || this.storage.auto_generate)
+ );
+
+ if (matchedTrack) {
+ subtitlesState = player.isSubtitlesOn();
+ player.setOption("captions", "track", matchedTrack);
+ // setOption forces Subtitles ON, restore state from before calling it.
+ if (!subtitlesState) {
+ player.toggleSubtitles();
+ }
+ }
+ }
};
/*------------------------------------------------------------------------------
SUBTITLES FONT FAMILY
@@ -330,369 +582,479 @@ default = {
},
------------------------------------------------------------------------------*/
ImprovedTube.subtitlesUserSettings = function () {
- const ourSettings = {
- fontFamily: this.storage.subtitles_font_family,
- color: this.storage.subtitles_font_color,
- fontSizeIncrement: this.storage.subtitles_font_size,
- background: this.storage.subtitles_background_color,
- backgroundOpacity: this.storage.subtitles_background_opacity,
- windowColor: this.storage.subtitles_window_color,
- windowOpacity: this.storage.subtitles_window_opacity,
- charEdgeStyle: this.storage.subtitles_character_edge_style,
- textOpacity: this.storage.subtitles_font_opacity
- },
- userSettings = Object.keys(ourSettings).filter(e => ourSettings[e]),
- player = this.elements.player;
-
- if (userSettings.length && player && player.getSubtitlesUserSettings && player.updateSubtitlesUserSettings) {
- let ytSettings = player.getSubtitlesUserSettings(),
- setting;
-
- if (!ytSettings) return; //null SubtitlesUserSettings seem to mean subtitles not available
-
- for (const value of userSettings) {
- setting = null;
- switch (value) {
- case 'fontFamily':
- case 'fontSizeIncrement':
- case 'charEdgeStyle':
- setting = Number(ourSettings[value]);
- break;
-
- case 'color':
- case 'background':
- case 'windowColor':
- setting = ourSettings[value];
- break;
-
- case 'backgroundOpacity':
- case 'windowOpacity':
- case 'textOpacity':
- setting = Number(ourSettings[value]) / 100;
- break;
- }
+ const ourSettings = {
+ fontFamily: this.storage.subtitles_font_family,
+ color: this.storage.subtitles_font_color,
+ fontSizeIncrement: this.storage.subtitles_font_size,
+ background: this.storage.subtitles_background_color,
+ backgroundOpacity: this.storage.subtitles_background_opacity,
+ windowColor: this.storage.subtitles_window_color,
+ windowOpacity: this.storage.subtitles_window_opacity,
+ charEdgeStyle: this.storage.subtitles_character_edge_style,
+ textOpacity: this.storage.subtitles_font_opacity,
+ },
+ userSettings = Object.keys(ourSettings).filter((e) => ourSettings[e]),
+ player = this.elements.player;
+
+ if (
+ userSettings.length &&
+ player &&
+ player.getSubtitlesUserSettings &&
+ player.updateSubtitlesUserSettings
+ ) {
+ let ytSettings = player.getSubtitlesUserSettings(),
+ setting;
+
+ if (!ytSettings) return; //null SubtitlesUserSettings seem to mean subtitles not available
+
+ for (const value of userSettings) {
+ setting = null;
+ switch (value) {
+ case "fontFamily":
+ case "fontSizeIncrement":
+ case "charEdgeStyle":
+ setting = Number(ourSettings[value]);
+ break;
+
+ case "color":
+ case "background":
+ case "windowColor":
+ setting = ourSettings[value];
+ break;
+
+ case "backgroundOpacity":
+ case "windowOpacity":
+ case "textOpacity":
+ setting = Number(ourSettings[value]) / 100;
+ break;
+ }
- if (Object.keys(ytSettings).includes(value)) {
- ytSettings[value] = setting;
- } else {
- console.error('subtitlesUserSettings failed at: ', value, setting);
- }
- }
- player.updateSubtitlesUserSettings(ytSettings);
- }
+ if (Object.keys(ytSettings).includes(value)) {
+ ytSettings[value] = setting;
+ } else {
+ console.error("subtitlesUserSettings failed at: ", value, setting);
+ }
+ }
+ player.updateSubtitlesUserSettings(ytSettings);
+ }
};
/*------------------------------------------------------------------------------
SUBTITLES DISABLE SUBTILES FOR LYRICS
------------------------------------------------------------------------------*/
ImprovedTube.subtitlesDisableLyrics = function () {
- if (this.storage.subtitles_disable_lyrics) {
- const player = this.elements.player;
-
- if (player && player.isSubtitlesOn && player.isSubtitlesOn() && player.toggleSubtitles) {
- // Music detection only uses 3 identifiers for Lyrics: lyrics, sing-along, karaoke.
- // Easier to simply use those here. Can replace with music detection later.
- const terms = ["sing along", "sing-along", "karaoke", "lyric", "卡拉OK", "卡拉OK", "الكاريوكي", "караоке", "カラオケ", "노래방"];
- if (terms.some(term => this.videoTitle().toLowerCase().includes(term))) {
- player.toggleSubtitles();
- }
- }
- }
+ if (this.storage.subtitles_disable_lyrics) {
+ const player = this.elements.player;
+
+ if (
+ player &&
+ player.isSubtitlesOn &&
+ player.isSubtitlesOn() &&
+ player.toggleSubtitles
+ ) {
+ // Music detection only uses 3 identifiers for Lyrics: lyrics, sing-along, karaoke.
+ // Easier to simply use those here. Can replace with music detection later.
+ const terms = [
+ "sing along",
+ "sing-along",
+ "karaoke",
+ "lyric",
+ "卡拉OK",
+ "卡拉OK",
+ "الكاريوكي",
+ "караоке",
+ "カラオケ",
+ "노래방",
+ ];
+ if (
+ terms.some((term) => this.videoTitle().toLowerCase().includes(term))
+ ) {
+ player.toggleSubtitles();
+ }
+ }
+ }
};
/*------------------------------------------------------------------------------
UP NEXT AUTOPLAY
------------------------------------------------------------------------------*/
ImprovedTube.upNextAutoplay = function () {
- var option = this.storage.up_next_autoplay;
+ var option = this.storage.up_next_autoplay;
- if (this.isset(option)) {
- var toggle = document.querySelector('.ytp-autonav-toggle-button');
+ if (this.isset(option)) {
+ var toggle = document.querySelector(".ytp-autonav-toggle-button");
- if (toggle) {
- if (option !== (toggle.getAttribute('aria-checked') === 'true')) {
- toggle.click();
- }
- }
- }
+ if (toggle) {
+ if (option !== (toggle.getAttribute("aria-checked") === "true")) {
+ toggle.click();
+ }
+ }
+ }
};
/*------------------------------------------------------------------------------
ADS
------------------------------------------------------------------------------*/
ImprovedTube.playerAds = function (parent) {
-
- let button = parent.querySelector('.ytp-ad-skip-button-modern.ytp-button,[class*="ytp-ad-skip-button"].ytp-button') || parent;
- // TODO: Replace this with centralized video element pointer
- let video = document.querySelector('.video-stream.html5-main-video') || false;
- function skipAd () {
- if (video && Number.isFinite(video.duration)) video.currentTime = video.duration;
- if (button) button.click();
- }
- if (this.storage.ads === 'block_all') {
- skipAd();
- } else if (this.storage.ads === 'subscribed_channels') {
- if (!parent.querySelector('#meta paper-button[subscribed]')) {
- skipAd();
- }
- } else if (this.storage.ads === 'block_music') {
- if (ImprovedTube.elements.category === 'music') {
- skipAd();
- }
- } else if (this.storage.ads === 'small_creators') {
- let userDefiniedLimit = this.storage.smallCreatorsCount * parseInt(this.storage.smallCreatorsUnit);
- let subscribersNumber = ImprovedTube.subscriberCount;
- if (subscribersNumber > userDefiniedLimit) {
- skipAd();
- }
- }
+ let button =
+ parent.querySelector(
+ '.ytp-ad-skip-button-modern.ytp-button,[class*="ytp-ad-skip-button"].ytp-button'
+ ) || parent;
+ // TODO: Replace this with centralized video element pointer
+ let video = document.querySelector(".video-stream.html5-main-video") || false;
+ function skipAd() {
+ if (video && Number.isFinite(video.duration))
+ video.currentTime = video.duration;
+ if (button) button.click();
+ }
+ if (this.storage.ads === "block_all") {
+ skipAd();
+ } else if (this.storage.ads === "subscribed_channels") {
+ if (!parent.querySelector("#meta paper-button[subscribed]")) {
+ skipAd();
+ }
+ } else if (this.storage.ads === "block_music") {
+ if (ImprovedTube.elements.category === "music") {
+ skipAd();
+ }
+ } else if (this.storage.ads === "small_creators") {
+ let userDefiniedLimit =
+ this.storage.smallCreatorsCount *
+ parseInt(this.storage.smallCreatorsUnit);
+ let subscribersNumber = ImprovedTube.subscriberCount;
+ if (subscribersNumber > userDefiniedLimit) {
+ skipAd();
+ }
+ }
};
/*------------------------------------------------------------------------------
AUTO FULLSCREEN
------------------------------------------------------------------------------*/
ImprovedTube.playerAutofullscreen = function () {
- if (
- this.storage.player_autofullscreen === true &&
- document.documentElement.dataset.pageType === 'video' &&
- !document.fullscreenElement
- ) {
- this.elements.player.toggleFullscreen();
- }
+ if (
+ this.storage.player_autofullscreen === true &&
+ document.documentElement.dataset.pageType === "video" &&
+ !document.fullscreenElement
+ ) {
+ this.elements.player.toggleFullscreen();
+ }
};
/*------------------------------------------------------------------------------
QUALITY
------------------------------------------------------------------------------*/
ImprovedTube.playerQuality = function (quality = this.storage.player_quality) {
- let player = this.elements.player;
- if (quality && quality !== 'disabled'
- && player && player.getAvailableQualityLevels
- && (!player.dataset.defaultQuality || player.dataset.defaultQuality != quality)) {
- let available_quality_levels = player.getAvailableQualityLevels();
- try {
- const hasTrue1080pOrHigher = available_quality_levels.some(q =>
- ['hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'].includes(q)
- );
- if (
- !hasTrue1080pOrHigher &&
- ['hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'].includes(quality)
- ) {
- console.log('[ImprovedTube] Preventing AI-upscaled "Super Resolution" — capping to 720p.');
- quality = 'hd720';
- }
- } catch (e) {
- console.warn('[ImprovedTube] Error checking available quality levels', e);
- }
- function closest (num, arr) {
- let curr = arr[0];
- let diff = Math.abs(num - curr);
- for (let val = 1; val < arr.length; val++) {
- let newdiff = Math.abs(num - arr[val]);
- if (newdiff < diff) {
- diff = newdiff;
- curr = arr[val];
- }
- }
- return curr;
- };
-
- if (!available_quality_levels.includes(quality)) {
- let label = ['tiny', 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'];
- let resolution = ['144', '240', '360', '480', '720', '1080', '1440', '2160', '2880', '4320'];
- let availableresolutions = available_quality_levels.map(q => resolution[label.indexOf(q)]);
- quality = label[resolution.indexOf(closest(resolution[label.indexOf(quality)], availableresolutions))];
- }
- player.setPlaybackQualityRange(quality);
- player.setPlaybackQuality(quality);
- player.dataset.defaultQuality = quality;
- }
+ let player = this.elements.player;
+ if (
+ quality &&
+ quality !== "disabled" &&
+ player &&
+ player.getAvailableQualityLevels &&
+ (!player.dataset.defaultQuality || player.dataset.defaultQuality != quality)
+ ) {
+ let available_quality_levels = player.getAvailableQualityLevels();
+ try {
+ const hasTrue1080pOrHigher = available_quality_levels.some((q) =>
+ ["hd1080", "hd1440", "hd2160", "hd2880", "highres"].includes(q)
+ );
+ if (
+ !hasTrue1080pOrHigher &&
+ ["hd1080", "hd1440", "hd2160", "hd2880", "highres"].includes(quality)
+ ) {
+ console.log(
+ '[ImprovedTube] Preventing AI-upscaled "Super Resolution" — capping to 720p.'
+ );
+ quality = "hd720";
+ }
+ } catch (e) {
+ console.warn("[ImprovedTube] Error checking available quality levels", e);
+ }
+ function closest(num, arr) {
+ let curr = arr[0];
+ let diff = Math.abs(num - curr);
+ for (let val = 1; val < arr.length; val++) {
+ let newdiff = Math.abs(num - arr[val]);
+ if (newdiff < diff) {
+ diff = newdiff;
+ curr = arr[val];
+ }
+ }
+ return curr;
+ }
+
+ if (!available_quality_levels.includes(quality)) {
+ let label = [
+ "tiny",
+ "small",
+ "medium",
+ "large",
+ "hd720",
+ "hd1080",
+ "hd1440",
+ "hd2160",
+ "hd2880",
+ "highres",
+ ];
+ let resolution = [
+ "144",
+ "240",
+ "360",
+ "480",
+ "720",
+ "1080",
+ "1440",
+ "2160",
+ "2880",
+ "4320",
+ ];
+ let availableresolutions = available_quality_levels.map(
+ (q) => resolution[label.indexOf(q)]
+ );
+ quality =
+ label[
+ resolution.indexOf(
+ closest(resolution[label.indexOf(quality)], availableresolutions)
+ )
+ ];
+ }
+ player.setPlaybackQualityRange(quality);
+ player.setPlaybackQuality(quality);
+ player.dataset.defaultQuality = quality;
+ }
};
/*------------------------------------------------------------------------------
QUALITY WITHOUT FOCUS
------------------------------------------------------------------------------*/
ImprovedTube.playerQualityWithoutFocus = function () {
- let player = this.elements.player,
- qualityWithoutFocus = this.storage.player_quality_without_focus;
- if (qualityWithoutFocus && qualityWithoutFocus !== 'auto' && player && player.getPlaybackQuality) {
- if (this.focus) {
- if (ImprovedTube.qualityBeforeBlur) {
- ImprovedTube.playerQuality(ImprovedTube.qualityBeforeBlur);
- ImprovedTube.qualityBeforeBlur = undefined;
- }
- } else {
- if (!ImprovedTube.elements.video.paused) {
- if (!ImprovedTube.qualityBeforeBlur) {
- ImprovedTube.qualityBeforeBlur = player.getPlaybackQuality();
- }
- ImprovedTube.playerQuality(qualityWithoutFocus);
- }
- }
- }
+ let player = this.elements.player,
+ qualityWithoutFocus = this.storage.player_quality_without_focus;
+ if (
+ qualityWithoutFocus &&
+ qualityWithoutFocus !== "auto" &&
+ player &&
+ player.getPlaybackQuality
+ ) {
+ if (this.focus) {
+ if (ImprovedTube.qualityBeforeBlur) {
+ ImprovedTube.playerQuality(ImprovedTube.qualityBeforeBlur);
+ ImprovedTube.qualityBeforeBlur = undefined;
+ }
+ } else {
+ if (!ImprovedTube.elements.video.paused) {
+ if (!ImprovedTube.qualityBeforeBlur) {
+ ImprovedTube.qualityBeforeBlur = player.getPlaybackQuality();
+ }
+ ImprovedTube.playerQuality(qualityWithoutFocus);
+ }
+ }
+ }
};
/*------------------------------------------------------------------------------
QUALITY FULL SCREEN
------------------------------------------------------------------------------*/
ImprovedTube.playerQualityFullScreen = function () {
- var isFs = !!(
- document.fullscreenElement ||
- document.webkitFullscreenElement ||
- document.mozFullScreenElement ||
- document.msFullscreenElement ||
- document.webkitIsFullScreen ||
- document.mozFullScreen
- );
-
- var fsq=ImprovedTube.storage.full_screen_quality;
- var target = isFs ? fsq : ImprovedTube.storage.player_quality;
-
-
-
- var map = {
- '144p':'tiny','240p':'small','360p':'medium','480p':'large',
- '720p':'hd720','1080p':'hd1080','1440p':'hd1440','2160p':'hd2160','4320p':'highres',
- 'tiny':'tiny','small':'small','medium':'medium','large':'large',
- 'hd720':'hd720','hd1080':'hd1080','hd1440':'hd1440','hd2160':'hd2160','highres':'highres'
- };
- var desired = map[target] || target;
-
- function applyQuality(){
- var player = ImprovedTube.elements && ImprovedTube.elements.player;
- if (!player) return;
-
- if (typeof ImprovedTube.playerQuality === 'function') {
- ImprovedTube.playerQuality(desired);
-
- return;
-
- }
- try { if (typeof player.setPlaybackQualityRange === 'function') player.setPlaybackQualityRange(desired, desired); } catch(e) {console.log(e)}
- try { if (typeof player.setPlaybackQuality === 'function') player.setPlaybackQuality(desired); } catch(e) {console.log(e)}
- }
+ var isFs = !!(
+ document.fullscreenElement ||
+ document.webkitFullscreenElement ||
+ document.mozFullScreenElement ||
+ document.msFullscreenElement ||
+ document.webkitIsFullScreen ||
+ document.mozFullScreen
+ );
+
+ var fsq = ImprovedTube.storage.full_screen_quality;
+ var target = isFs ? fsq : ImprovedTube.storage.player_quality;
+
+ var map = {
+ "144p": "tiny",
+ "240p": "small",
+ "360p": "medium",
+ "480p": "large",
+ "720p": "hd720",
+ "1080p": "hd1080",
+ "1440p": "hd1440",
+ "2160p": "hd2160",
+ "4320p": "highres",
+ tiny: "tiny",
+ small: "small",
+ medium: "medium",
+ large: "large",
+ hd720: "hd720",
+ hd1080: "hd1080",
+ hd1440: "hd1440",
+ hd2160: "hd2160",
+ highres: "highres",
+ };
+ var desired = map[target] || target;
+
+ function applyQuality() {
+ var player = ImprovedTube.elements && ImprovedTube.elements.player;
+ if (!player) return;
+
+ if (typeof ImprovedTube.playerQuality === "function") {
+ ImprovedTube.playerQuality(desired);
+
+ return;
+ }
+ try {
+ if (typeof player.setPlaybackQualityRange === "function")
+ player.setPlaybackQualityRange(desired, desired);
+ } catch (e) {
+ console.log(e);
+ }
+ try {
+ if (typeof player.setPlaybackQuality === "function")
+ player.setPlaybackQuality(desired);
+ } catch (e) {
+ console.log(e);
+ }
+ }
setTimeout(applyQuality, 300);
setTimeout(applyQuality, 800);
- }
+};
-
/*------------------------------------------------------------------------------
BATTERY FEATURES; PLAYER QUALITY BASED ON POWER STATUS
------------------------------------------------------------------------------*/
ImprovedTube.batteryFeatures = async function () {
- if (ImprovedTube.storage.qualityWhenRunningOnBattery
- || ImprovedTube.storage.pauseWhileIUnplugTheCharger
- || ImprovedTube.storage.whenBatteryIslowDecreaseQuality) {
- const updateQuality = async (battery, charging) => {
- if (battery) {
- if (!battery.charging) {
- if (ImprovedTube.storage.pauseWhileIUnplugTheCharger && charging) {
- ImprovedTube.elements.player.pauseVideo();
- ImprovedTube.paused = true;
- }
- if (ImprovedTube.storage.qualityWhenRunningOnBattery) {
- ImprovedTube.playerQuality(ImprovedTube.storage.qualityWhenRunningOnBattery);
- }
- if (ImprovedTube.storage.whenBatteryIslowDecreaseQuality) {
- let quality;
- if (battery.level > 0.11 || battery.dischargingTime > 900) {
- quality = "large";
- } else if (battery.level > 0.08 || battery.dischargingTime > 600) {
- quality = "medium";
- } else if (battery.level > 0.04 || battery.dischargingTime > 360) {
- quality = "small";
- } else {
- quality = "tiny";
- }
- ImprovedTube.playerQuality(quality);
- }
- } else if (charging && ImprovedTube.paused && ImprovedTube.storage.pauseWhileIUnplugTheCharger) {
- ImprovedTube.elements.player.playVideo();
- delete ImprovedTube.paused;
- }
- }
- };
- const battery = await navigator.getBattery();
- battery.addEventListener("levelchange", () => updateQuality(battery));
- battery.addEventListener("chargingchange", () => updateQuality(battery, true));
- await updateQuality(battery);
- }
+ if (
+ ImprovedTube.storage.qualityWhenRunningOnBattery ||
+ ImprovedTube.storage.pauseWhileIUnplugTheCharger ||
+ ImprovedTube.storage.whenBatteryIslowDecreaseQuality
+ ) {
+ const updateQuality = async (battery, charging) => {
+ if (battery) {
+ if (!battery.charging) {
+ if (ImprovedTube.storage.pauseWhileIUnplugTheCharger && charging) {
+ ImprovedTube.elements.player.pauseVideo();
+ ImprovedTube.paused = true;
+ }
+ if (ImprovedTube.storage.qualityWhenRunningOnBattery) {
+ ImprovedTube.playerQuality(
+ ImprovedTube.storage.qualityWhenRunningOnBattery
+ );
+ }
+ if (ImprovedTube.storage.whenBatteryIslowDecreaseQuality) {
+ let quality;
+ if (battery.level > 0.11 || battery.dischargingTime > 900) {
+ quality = "large";
+ } else if (battery.level > 0.08 || battery.dischargingTime > 600) {
+ quality = "medium";
+ } else if (battery.level > 0.04 || battery.dischargingTime > 360) {
+ quality = "small";
+ } else {
+ quality = "tiny";
+ }
+ ImprovedTube.playerQuality(quality);
+ }
+ } else if (
+ charging &&
+ ImprovedTube.paused &&
+ ImprovedTube.storage.pauseWhileIUnplugTheCharger
+ ) {
+ ImprovedTube.elements.player.playVideo();
+ delete ImprovedTube.paused;
+ }
+ }
+ };
+ const battery = await navigator.getBattery();
+ battery.addEventListener("levelchange", () => updateQuality(battery));
+ battery.addEventListener("chargingchange", () =>
+ updateQuality(battery, true)
+ );
+ await updateQuality(battery);
+ }
};
/*------------------------------------------------------------------------------
FORCED VOLUME
------------------------------------------------------------------------------*/
ImprovedTube.playerVolume = function () {
- if (this.storage.player_forced_volume === true) {
- var volume = this.storage.player_volume;
-
- if (!this.isset(volume)) {
- volume = 100;
- } else {
- volume = Number(volume);
- }
- // Fix: Explicitly handle mute state
- if (volume === 0) {
- if (!this.elements.player.isMuted()) {
- this.elements.player.mute();
- }
- } else {
- if (this.elements.player.isMuted()) {
- this.elements.player.unMute();
- }
- }
+ if (this.storage.player_forced_volume === true) {
+ var volume = this.storage.player_volume;
- if (!this.audioContextGain && volume <= 100) {
- if (this.audioContext) {
- this.audioContext.close();
- }
+ if (!this.isset(volume)) {
+ volume = 100;
+ } else {
+ volume = Number(volume);
+ }
+ // Fix: Explicitly handle mute state
+ if (volume === 0) {
+ if (!this.elements.player.isMuted()) {
+ this.elements.player.mute();
+ }
+ } else {
+ if (this.elements.player.isMuted()) {
+ this.elements.player.unMute();
+ }
+ }
- this.elements.player.setVolume(volume);
- } else {
- if (!this.audioContext) {
- this.audioContext = new AudioContext();
+ if (!this.audioContextGain && volume <= 100) {
+ if (this.audioContext) {
+ this.audioContext.close();
+ }
- this.audioContextSource = this.audioContext.createMediaElementSource(document.querySelector('video'));
- this.audioContextGain = this.audioContext.createGain();
+ this.elements.player.setVolume(volume);
+ } else {
+ if (!this.audioContext) {
+ this.audioContext = new AudioContext();
- this.audioContextGain.gain.value = 1;
- this.audioContextSource.connect(this.audioContextGain);
- this.audioContextGain.connect(this.audioContext.destination)
- }
- if (this.elements.player.getVolume() !== 100) { this.elements.player.setVolume(100);}
- this.audioContextGain.gain.value = volume / 100;
- }
- }
+ this.audioContextSource = this.audioContext.createMediaElementSource(
+ document.querySelector("video")
+ );
+ this.audioContextGain = this.audioContext.createGain();
+
+ this.audioContextGain.gain.value = 1;
+ this.audioContextSource.connect(this.audioContextGain);
+ this.audioContextGain.connect(this.audioContext.destination);
+ }
+ if (this.elements.player.getVolume() !== 100) {
+ this.elements.player.setVolume(100);
+ }
+ this.audioContextGain.gain.value = volume / 100;
+ }
+ }
};
/*------------------------------------------------------------------------------
LOUDNESS NORMALIZATION
------------------------------------------------------------------------------*/
ImprovedTube.onvolumechange = function () {
- if (document.querySelector('.ytp-volume-panel') && ImprovedTube.storage.player_loudness_normalization === false) {
- var volume = Number(document.querySelector('.ytp-volume-panel').getAttribute('aria-valuenow'));
+ if (
+ document.querySelector(".ytp-volume-panel") &&
+ ImprovedTube.storage.player_loudness_normalization === false
+ ) {
+ var volume = Number(
+ document.querySelector(".ytp-volume-panel").getAttribute("aria-valuenow")
+ );
- this.volume = volume / 100;
- }
+ this.volume = volume / 100;
+ }
};
ImprovedTube.playerLoudnessNormalization = function () {
- var video = this.elements.video;
+ var video = this.elements.video;
- if (video) {
- video.removeEventListener('volumechange', this.onvolumechange);
- video.addEventListener('volumechange', this.onvolumechange);
- }
+ if (video) {
+ video.removeEventListener("volumechange", this.onvolumechange);
+ video.addEventListener("volumechange", this.onvolumechange);
+ }
- if (this.storage.player_loudness_normalization === false) {
- try {
- var local_storage = localStorage['yt-player-volume'];
+ if (this.storage.player_loudness_normalization === false) {
+ try {
+ var local_storage = localStorage["yt-player-volume"];
- if (this.isset(Number(this.storage.player_volume)) && this.storage.player_forced_volume === true) {
- return;
- } else if (local_storage) {
- local_storage = JSON.parse(JSON.parse(local_storage).data);
+ if (
+ this.isset(Number(this.storage.player_volume)) &&
+ this.storage.player_forced_volume === true
+ ) {
+ return;
+ } else if (local_storage) {
+ local_storage = JSON.parse(JSON.parse(local_storage).data);
- local_storage = Number(local_storage.volume);
+ local_storage = Number(local_storage.volume);
- video.volume = local_storage / 100;
- } else {
- video.volume = 100;
- }
- } catch (err) {}
- }
+ video.volume = local_storage / 100;
+ } else {
+ video.volume = 100;
+ }
+ } catch (err) {}
+ }
};
ImprovedTube.playerPlaybackSpeedButton = function () {
if (this.storage.player_playback_speed_button === true) {
@@ -774,555 +1136,674 @@ ImprovedTube.playerPlaybackSpeedButton = function () {
SCREENSHOT
------------------------------------------------------------------------------*/
ImprovedTube.screenshot = function () {
- const video = ImprovedTube.elements.video,
- cvs = document.createElement('canvas'),
- ctx = cvs.getContext('2d');
- let subText = '';
-
- cvs.width = video.videoWidth;
- cvs.height = video.videoHeight;
-
- ctx.drawImage(video, 0, 0, cvs.width, cvs.height);
-
- if (ImprovedTube.storage.embed_subtitle != false) {
- let captionElements = document.querySelectorAll('.captions-text .ytp-caption-segment');
- captionElements.forEach(function (caption) {subText += caption.textContent.trim() + ' ';});
-
- ImprovedTube.renderSubtitle(ctx, captionElements);
- }
-
- cvs.toBlob(function (blob) {
- if (ImprovedTube.storage.player_screenshot_save_as == 'clipboard') {
- window.focus();
- navigator.clipboard.write([
- new ClipboardItem({
- 'image/png': blob
- })
- ])
- .then(function () { console.log("ImprovedTube: Screeeeeeenshot tada!"); })
- .catch(function (error) {
- console.log('ImprovedTube screenshot: ', error);
- alert('ImprovedTube Screenshot to Clipboard error. Details in Debug Console.');
- });
- } else {
- let a = document.createElement('a');
- a.href = URL.createObjectURL(blob);
- a.download = (ImprovedTube.videoId() || location.href.match) + ' ' + new Date(ImprovedTube.elements.player.getCurrentTime() * 1000).toISOString().substr(11, 8).replace(/:/g, '-') + ' ' + ImprovedTube.videoTitle() + (subText ? ' - ' + subText.trim() : '') + '.png';
- a.click();
- console.log("ImprovedTube: Screeeeeeenshot tada!");
- }
- });
+ const video = ImprovedTube.elements.video,
+ cvs = document.createElement("canvas"),
+ ctx = cvs.getContext("2d");
+ let subText = "";
+
+ cvs.width = video.videoWidth;
+ cvs.height = video.videoHeight;
+
+ ctx.drawImage(video, 0, 0, cvs.width, cvs.height);
+
+ if (ImprovedTube.storage.embed_subtitle != false) {
+ let captionElements = document.querySelectorAll(
+ ".captions-text .ytp-caption-segment"
+ );
+ captionElements.forEach(function (caption) {
+ subText += caption.textContent.trim() + " ";
+ });
+
+ ImprovedTube.renderSubtitle(ctx, captionElements);
+ }
+
+ cvs.toBlob(function (blob) {
+ if (ImprovedTube.storage.player_screenshot_save_as == "clipboard") {
+ window.focus();
+ navigator.clipboard
+ .write([
+ new ClipboardItem({
+ "image/png": blob,
+ }),
+ ])
+ .then(function () {
+ console.log("ImprovedTube: Screeeeeeenshot tada!");
+ })
+ .catch(function (error) {
+ console.log("ImprovedTube screenshot: ", error);
+ alert(
+ "ImprovedTube Screenshot to Clipboard error. Details in Debug Console."
+ );
+ });
+ } else {
+ let a = document.createElement("a");
+ a.href = URL.createObjectURL(blob);
+ a.download =
+ (ImprovedTube.videoId() || location.href.match) +
+ " " +
+ new Date(ImprovedTube.elements.player.getCurrentTime() * 1000)
+ .toISOString()
+ .substr(11, 8)
+ .replace(/:/g, "-") +
+ " " +
+ ImprovedTube.videoTitle() +
+ (subText ? " - " + subText.trim() : "") +
+ ".png";
+ a.click();
+ console.log("ImprovedTube: Screeeeeeenshot tada!");
+ }
+ });
};
ImprovedTube.renderSubtitle = function (ctx, captionElements) {
- if (ctx && captionElements) {
- captionElements.forEach(function (captionElement, index) {
- var captionText = captionElement.textContent.trim();
- var captionStyles = window.getComputedStyle(captionElement);
-
- ctx.fillStyle = captionStyles.color;
- ctx.font = captionStyles.font;
- ctx.textAlign = 'center';
- ctx.textBaseline = 'bottom';
- var txtWidth = ctx.measureText(captionText).width;
- var txtHeight = parseFloat(captionStyles.fontSize);
-
- var xOfset = (ctx.canvas.width - txtWidth) / 2;
-
- var padding = 5; // Adjust the padding as needed
- var yofset = ctx.canvas.height - (captionElements.length - index) * (txtHeight + 2 * padding);
-
- ctx.fillStyle = captionStyles.backgroundColor;
- ctx.fillRect(xOfset - padding, yofset - txtHeight - padding, txtWidth + 2 * padding, txtHeight + 2 * padding);
- ctx.fillStyle = captionStyles.color;
- ctx.fillText(captionText, xOfset + txtWidth / 2, yofset);
- });
- }
+ if (ctx && captionElements) {
+ captionElements.forEach(function (captionElement, index) {
+ var captionText = captionElement.textContent.trim();
+ var captionStyles = window.getComputedStyle(captionElement);
+
+ ctx.fillStyle = captionStyles.color;
+ ctx.font = captionStyles.font;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "bottom";
+ var txtWidth = ctx.measureText(captionText).width;
+ var txtHeight = parseFloat(captionStyles.fontSize);
+
+ var xOfset = (ctx.canvas.width - txtWidth) / 2;
+
+ var padding = 5; // Adjust the padding as needed
+ var yofset =
+ ctx.canvas.height -
+ (captionElements.length - index) * (txtHeight + 2 * padding);
+
+ ctx.fillStyle = captionStyles.backgroundColor;
+ ctx.fillRect(
+ xOfset - padding,
+ yofset - txtHeight - padding,
+ txtWidth + 2 * padding,
+ txtHeight + 2 * padding
+ );
+ ctx.fillStyle = captionStyles.color;
+ ctx.fillText(captionText, xOfset + txtWidth / 2, yofset);
+ });
+ }
};
ImprovedTube.playerScreenshotButton = function () {
- if (this.storage.player_screenshot_button === true) {
- var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
- path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
-
- svg.setAttributeNS(null, 'viewBox', '0 0 24 24');
- path.setAttributeNS(null, 'd', 'M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z');
-
- svg.appendChild(path);
-
- this.createPlayerButton({
- id: 'it-screenshot-button',
- child: svg,
- opacity: 0.64,
- onclick: this.screenshot,
- title: 'Screenshot'
- });
- }
+ if (this.storage.player_screenshot_button === true) {
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
+ path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+
+ svg.setAttributeNS(null, "viewBox", "0 0 24 24");
+ path.setAttributeNS(
+ null,
+ "d",
+ "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"
+ );
+
+ svg.appendChild(path);
+
+ this.createPlayerButton({
+ id: "it-screenshot-button",
+ child: svg,
+ opacity: 0.64,
+ onclick: this.screenshot,
+ title: "Screenshot",
+ });
+ }
};
/*------------------------------------------------------------------------------
REPEAT
-------------------------------------------------------------------------------*/
ImprovedTube.playerRepeat = function () {
- setTimeout(function () {
- if (!/ad-showing/.test(ImprovedTube.elements.player.className)) {
- ImprovedTube.elements.video.setAttribute('loop', '');
- }
- //ImprovedTube.elements.buttons['it-repeat-styles'].style.opacity = '1'; //old class from version 3.x? that both repeat buttons could have
- }, 200);
-}
+ setTimeout(function () {
+ if (!/ad-showing/.test(ImprovedTube.elements.player.className)) {
+ ImprovedTube.elements.video.setAttribute("loop", "");
+ }
+ //ImprovedTube.elements.buttons['it-repeat-styles'].style.opacity = '1'; //old class from version 3.x? that both repeat buttons could have
+ }, 200);
+};
/*------------------------------------------------------------------------------
REPEAT BUTTON
------------------------------------------------------------------------------*/
ImprovedTube.playerRepeatButton = function () {
- if (this.storage.player_repeat_button === true) {
- var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
- path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- svg.setAttributeNS(null, 'viewBox', '0 0 24 24');
- path.setAttributeNS(null, 'd', 'M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4zm-4-2V9h-1l-2 1v1h1.5v4H13z');
- svg.appendChild(path);
- var transparentOrOn = 0.5; if (this.storage.player_always_repeat === true ) { transparentOrOn = 1; }
- this.createPlayerButton({
- id: 'it-repeat-button',
- child: svg,
- opacity: transparentOrOn,
- onclick: function () {
- var video = ImprovedTube.elements.video;
- function matchLoopState (opacity) {
- var thisButton = document.querySelector('#it-repeat-button');
- thisButton.style.opacity = opacity;
- if (ImprovedTube.storage.below_player_loop !== false) {
- var otherButton = document.querySelector('#it-below-player-loop');
- otherButton.children[0].style.opacity = opacity;
- }
- } if (video.hasAttribute('loop')) {
- video.removeAttribute('loop');
- matchLoopState('.5')
- } else if (!/ad-showing/.test(ImprovedTube.elements.player.className)) {
- video.setAttribute('loop', '');
- matchLoopState('1')
- }
- },
- title: 'Repeat',
- });
- }
+ if (this.storage.player_repeat_button === true) {
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
+ path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ svg.setAttributeNS(null, "viewBox", "0 0 24 24");
+ path.setAttributeNS(
+ null,
+ "d",
+ "M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4zm-4-2V9h-1l-2 1v1h1.5v4H13z"
+ );
+ svg.appendChild(path);
+ var transparentOrOn = 0.5;
+ if (this.storage.player_always_repeat === true) {
+ transparentOrOn = 1;
+ }
+ this.createPlayerButton({
+ id: "it-repeat-button",
+ child: svg,
+ opacity: transparentOrOn,
+ onclick: function () {
+ var video = ImprovedTube.elements.video;
+ function matchLoopState(opacity) {
+ var thisButton = document.querySelector("#it-repeat-button");
+ thisButton.style.opacity = opacity;
+ if (ImprovedTube.storage.below_player_loop !== false) {
+ var otherButton = document.querySelector("#it-below-player-loop");
+ otherButton.children[0].style.opacity = opacity;
+ }
+ }
+ if (video.hasAttribute("loop")) {
+ video.removeAttribute("loop");
+ matchLoopState(".5");
+ } else if (!/ad-showing/.test(ImprovedTube.elements.player.className)) {
+ video.setAttribute("loop", "");
+ matchLoopState("1");
+ }
+ },
+ title: "Repeat",
+ });
+ }
};
/*------------------------------------------------------------------------------
ROTATE
------------------------------------------------------------------------------*/
ImprovedTube.playerRotateButton = function () {
- if (this.storage.player_rotate_button === true) {
- var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
- path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
-
- svg.setAttributeNS(null, 'viewBox', '0 0 24 24');
- path.setAttributeNS(null, 'd', 'M15.55 5.55L11 1v3.07a8 8 0 0 0 0 15.86v-2.02a6 6 0 0 1 0-11.82V10l4.55-4.45zM19.93 11a7.9 7.9 0 0 0-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02a7.92 7.92 0 0 0 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41A7.9 7.9 0 0 0 19.93 13h-2.02a5.9 5.9 0 0 1-1.02 2.48z');
-
- svg.appendChild(path);
-
- this.createPlayerButton({
- id: 'it-rotate-button',
- child: svg,
- opacity: 0.85,
- onclick: function (e) {
- var player = ImprovedTube.elements.player,
- video = ImprovedTube.elements.video,
- rotate = Number(document.body.dataset.itRotate) || 0,
- transform = '';
- if(!e.ctrlKey){
- rotate += 90;
- } else {
- rotate -= 90;
- }
-
- if (rotate === 360) {
- rotate = 0;
- } else if (rotate < 0){
- rotate = 270;
- }
-
- document.body.dataset.itRotate = rotate;
-
- transform += 'rotate(' + rotate + 'deg)';
-
- if (rotate == 90 || rotate == 270) {
- var is_vertical_video = video.videoHeight > video.videoWidth;
-
- transform += ' scale(' + (is_vertical_video ? player.clientWidth : player.clientHeight) / (is_vertical_video ? player.clientHeight : player.clientWidth) + ')';
- }
-
- if (!ImprovedTube.elements.buttons['it-rotate-styles']) {
- var style = document.createElement('style');
-
- ImprovedTube.elements.buttons['it-rotate-styles'] = style;
-
- document.body.appendChild(style);
- }
-
- ImprovedTube.elements.buttons['it-rotate-styles'].textContent = 'video{transform:' + transform + '}';
- },
- title: 'Rotate'
- });
- }
+ if (this.storage.player_rotate_button === true) {
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
+ path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+
+ svg.setAttributeNS(null, "viewBox", "0 0 24 24");
+ path.setAttributeNS(
+ null,
+ "d",
+ "M15.55 5.55L11 1v3.07a8 8 0 0 0 0 15.86v-2.02a6 6 0 0 1 0-11.82V10l4.55-4.45zM19.93 11a7.9 7.9 0 0 0-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02a7.92 7.92 0 0 0 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41A7.9 7.9 0 0 0 19.93 13h-2.02a5.9 5.9 0 0 1-1.02 2.48z"
+ );
+
+ svg.appendChild(path);
+
+ this.createPlayerButton({
+ id: "it-rotate-button",
+ child: svg,
+ opacity: 0.85,
+ onclick: function (e) {
+ var player = ImprovedTube.elements.player,
+ video = ImprovedTube.elements.video,
+ rotate = Number(document.body.dataset.itRotate) || 0,
+ transform = "";
+ if (!e.ctrlKey) {
+ rotate += 90;
+ } else {
+ rotate -= 90;
+ }
+
+ if (rotate === 360) {
+ rotate = 0;
+ } else if (rotate < 0) {
+ rotate = 270;
+ }
+
+ document.body.dataset.itRotate = rotate;
+
+ transform += "rotate(" + rotate + "deg)";
+
+ if (rotate == 90 || rotate == 270) {
+ var is_vertical_video = video.videoHeight > video.videoWidth;
+
+ transform +=
+ " scale(" +
+ (is_vertical_video ? player.clientWidth : player.clientHeight) /
+ (is_vertical_video ? player.clientHeight : player.clientWidth) +
+ ")";
+ }
+
+ if (!ImprovedTube.elements.buttons["it-rotate-styles"]) {
+ var style = document.createElement("style");
+
+ ImprovedTube.elements.buttons["it-rotate-styles"] = style;
+
+ document.body.appendChild(style);
+ }
+
+ ImprovedTube.elements.buttons["it-rotate-styles"].textContent =
+ "video{transform:" + transform + "}";
+ },
+ title: "Rotate",
+ });
+ }
};
/*------------------------------------------------------------------------------
PLAYBACK SPEED BUTTON
------------------------------------------------------------------------------*/
ImprovedTube.playerPlaybackSpeedButton = function () {
- if (this.storage.player_playback_speed_button === true) {
- var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
- path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
-
- svg.setAttributeNS(null, 'viewBox', '0 0 24 24');
- path.setAttributeNS(null, 'd', 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z');
-
- svg.appendChild(path);
-
- var button = this.createPlayerButton({
- id: 'it-playback-speed-button',
- child: svg,
- opacity: 0.7,
- onclick: function (e) {
- // Left click: set to custom speed from settings
- if (e.button === 0) {
- var customSpeed = ImprovedTube.storage.player_playback_speed || 1.25;
- ImprovedTube.playbackSpeed(customSpeed);
- ImprovedTube.showStatus(customSpeed + 'x');
- }
- },
- title: 'Playback Speed (Scroll: adjust, Left: custom, Right: 1.0x)'
- });
-
- // Add right-click handler
- button.addEventListener('contextmenu', function (e) {
- e.preventDefault();
- ImprovedTube.playbackSpeed(1.0);
- ImprovedTube.showStatus('1.0x');
- });
-
- // Add wheel handler
- button.addEventListener('wheel', function (e) {
- e.preventDefault();
- var step = Number(ImprovedTube.storage.shortcuts_playback_speed_step) || 0.1;
- var currentSpeed = ImprovedTube.playbackSpeed();
- var newSpeed;
-
- if (e.deltaY < 0) {
- // Scroll up: increase speed
- newSpeed = Math.min(currentSpeed + step, 16);
- } else {
- // Scroll down: decrease speed
- newSpeed = Math.max(currentSpeed - step, 0.0625);
- }
-
- ImprovedTube.playbackSpeed(newSpeed);
- ImprovedTube.showStatus(newSpeed.toFixed(2) + 'x');
- });
- }
+ if (this.storage.player_playback_speed_button === true) {
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
+ path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+
+ svg.setAttributeNS(null, "viewBox", "0 0 24 24");
+ path.setAttributeNS(
+ null,
+ "d",
+ "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
+ );
+
+ svg.appendChild(path);
+
+ var button = this.createPlayerButton({
+ id: "it-playback-speed-button",
+ child: svg,
+ opacity: 0.7,
+ onclick: function (e) {
+ // Left click: set to custom speed from settings
+ if (e.button === 0) {
+ var customSpeed = ImprovedTube.storage.player_playback_speed || 1.25;
+ ImprovedTube.playbackSpeed(customSpeed);
+ ImprovedTube.showStatus(customSpeed + "x");
+ }
+ },
+ title: "Playback Speed (Scroll: adjust, Left: custom, Right: 1.0x)",
+ });
+
+ // Add right-click handler
+ button.addEventListener("contextmenu", function (e) {
+ e.preventDefault();
+ ImprovedTube.playbackSpeed(1.0);
+ ImprovedTube.showStatus("1.0x");
+ });
+
+ // Add wheel handler
+ button.addEventListener("wheel", function (e) {
+ e.preventDefault();
+ var step =
+ Number(ImprovedTube.storage.shortcuts_playback_speed_step) || 0.1;
+ var currentSpeed = ImprovedTube.playbackSpeed();
+ var newSpeed;
+
+ if (e.deltaY < 0) {
+ // Scroll up: increase speed
+ newSpeed = Math.min(currentSpeed + step, 16);
+ } else {
+ // Scroll down: decrease speed
+ newSpeed = Math.max(currentSpeed - step, 0.0625);
+ }
+
+ ImprovedTube.playbackSpeed(newSpeed);
+ ImprovedTube.showStatus(newSpeed.toFixed(2) + "x");
+ });
+ }
};
/*------------------------------------------------------------------------------
FIT-TO-WIN BUTTON
------------------------------------------------------------------------------*/
ImprovedTube.playerFitToWinButton = function () {
- if (this.storage.player_fit_to_win_button === true && (/watch\?/.test(location.href))) {
- let tempContainer = document.createElement("div");
- let svg;
- if (typeof trustedTypes !== 'undefined' && typeof trustedTypes.createPolicy === 'function') {
- // Create a Trusted Type policy
- const policy = trustedTypes.createPolicy('default', {
- createHTML: (string) => string,
- });
-
- // Use the policy to set innerHTML
- tempContainer.innerHTML = policy.createHTML(`
+ if (
+ this.storage.player_fit_to_win_button === true &&
+ /watch\?/.test(location.href)
+ ) {
+ let tempContainer = document.createElement("div");
+ let svg;
+ if (
+ typeof trustedTypes !== "undefined" &&
+ typeof trustedTypes.createPolicy === "function"
+ ) {
+ // Create a Trusted Type policy
+ const policy = trustedTypes.createPolicy("default", {
+ createHTML: (string) => string,
+ });
+
+ // Use the policy to set innerHTML
+ tempContainer.innerHTML = policy.createHTML(`
`);
- // Ensure the SVG element is correctly parsed
- svg = tempContainer.querySelector('svg');
- } else {tempContainer.innerHTML = `