mirror of
				https://github.com/SoPat712/videospeed.git
				synced 2025-10-30 18:34:02 -04:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			v1.1.1
			...
			c3166cf347
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c3166cf347 | ||
|   | 77a25c4f1e | ||
|   | 3fee61d2b6 | ||
|   | b7684aad09 | 
| @@ -1,3 +1,7 @@ | |||||||
|  | [](https://addons.mozilla.org/en-US/firefox/addon/video-speed-controller-v1/) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # The science of accelerated playback | # The science of accelerated playback | ||||||
|  |  | ||||||
| **TL;DR: faster playback translates to better engagement and retention.** | **TL;DR: faster playback translates to better engagement and retention.** | ||||||
| @@ -74,10 +78,10 @@ You can try manually disabling Flash from the browser. | |||||||
| [`igrigorik/videospeed`](https://github.com/igrigorik/videospeed) repository | [`igrigorik/videospeed`](https://github.com/igrigorik/videospeed) repository | ||||||
| is a port of [`igrigorik`](https://github.com/igrigorik)'s videospeed Chrome  | is a port of [`igrigorik`](https://github.com/igrigorik)'s videospeed Chrome  | ||||||
| add-on for Firefox. This fork modifies the Chrome add-on code so that it works  | add-on for Firefox. This fork modifies the Chrome add-on code so that it works  | ||||||
| in Firefox. This repo is the code behind the [Firefox Extension](https://addons.mozilla.org/en-us/firefox/addon/videospeed/) | in Firefox. This repo is the code behind the [Firefox Extension](https://addons.mozilla.org/en-US/firefox/addon/video-speed-controller-v1/) | ||||||
| whereas the [`igrigorik/videospeed`](https://github.com/igrigorik/videospeed) | whereas the [`igrigorik/videospeed`](https://github.com/igrigorik/videospeed) | ||||||
| repository contains the code behind the [Chrome Extension](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk). | repository contains the code behind the [Chrome Extension](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk). | ||||||
|  |  | ||||||
| ### License | ### License | ||||||
|  |  | ||||||
| (MIT License) - Copyright (c) 2014 Ilya Grigorik | (MIT License) - Copyright (c) 2025 Josh Patra | ||||||
|   | |||||||
							
								
								
									
										200
									
								
								inject.js
									
									
									
									
									
								
							
							
						
						
									
										200
									
								
								inject.js
									
									
									
									
									
								
							| @@ -21,12 +21,13 @@ var tc = { | |||||||
|     `.replace(regStrip, ""), |     `.replace(regStrip, ""), | ||||||
|     defaultLogLevel: 4, |     defaultLogLevel: 4, | ||||||
|     logLevel: 3, |     logLevel: 3, | ||||||
|  |     // --- Nudge settings (ADDED) --- | ||||||
|     enableSubtitleNudge: true, |     enableSubtitleNudge: true, | ||||||
|     subtitleNudgeInterval: 25, |     subtitleNudgeInterval: 25, | ||||||
|     subtitleNudgeAmount: 0.001 |     subtitleNudgeAmount: 0.001 | ||||||
|   }, |   }, | ||||||
|   mediaElements: [], |   mediaElements: [], | ||||||
|   isNudging: false |   isNudging: false // ADDED: Flag for nudge operation | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /* Log levels */ | /* Log levels */ | ||||||
| @@ -104,8 +105,8 @@ chrome.storage.sync.get(tc.settings, function (storage) { | |||||||
|   ) { |   ) { | ||||||
|     chrome.storage.sync.set({ |     chrome.storage.sync.set({ | ||||||
|       keyBindings: tc.settings.keyBindings, |       keyBindings: tc.settings.keyBindings, | ||||||
|       version: "0.6.3.13" |       version: "0.6.3.14" | ||||||
|     }); // Incremented |     }); // Incremented version | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   tc.settings.lastSpeed = Number(storage.lastSpeed) || 1.0; |   tc.settings.lastSpeed = Number(storage.lastSpeed) || 1.0; | ||||||
| @@ -120,6 +121,7 @@ chrome.storage.sync.get(tc.settings, function (storage) { | |||||||
|   tc.settings.blacklist = String(storage.blacklist || tc.settings.blacklist); |   tc.settings.blacklist = String(storage.blacklist || tc.settings.blacklist); | ||||||
|   if (typeof storage.logLevel !== "undefined") |   if (typeof storage.logLevel !== "undefined") | ||||||
|     tc.settings.logLevel = Number(storage.logLevel); |     tc.settings.logLevel = Number(storage.logLevel); | ||||||
|  |  | ||||||
|   tc.settings.enableSubtitleNudge = |   tc.settings.enableSubtitleNudge = | ||||||
|     typeof storage.enableSubtitleNudge !== "undefined" |     typeof storage.enableSubtitleNudge !== "undefined" | ||||||
|       ? Boolean(storage.enableSubtitleNudge) |       ? Boolean(storage.enableSubtitleNudge) | ||||||
| @@ -144,7 +146,6 @@ chrome.storage.sync.get(tc.settings, function (storage) { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| function getKeyBindings(action, what = "value") { | function getKeyBindings(action, what = "value") { | ||||||
|   /* ... Same as your provided ... */ |  | ||||||
|   if (!tc.settings.keyBindings) return false; |   if (!tc.settings.keyBindings) return false; | ||||||
|   try { |   try { | ||||||
|     const binding = tc.settings.keyBindings.find( |     const binding = tc.settings.keyBindings.find( | ||||||
| @@ -163,13 +164,16 @@ function getKeyBindings(action, what = "value") { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| function setKeyBindings(action, value) { | function setKeyBindings(action, value) { | ||||||
|   /* ... Same as your provided ... */ |   // Original setKeyBindings | ||||||
|   if (!tc.settings.keyBindings) return; |   if (!tc.settings.keyBindings) return; | ||||||
|   const binding = tc.settings.keyBindings.find( |   const binding = tc.settings.keyBindings.find( | ||||||
|     (item) => item.action === action |     (item) => item.action === action | ||||||
|   ); |   ); | ||||||
|   if (binding) binding["value"] = value; |   if (binding) { | ||||||
|  |     binding["value"] = value; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| function defineVideoController() { | function defineVideoController() { | ||||||
| @@ -179,13 +183,13 @@ function defineVideoController() { | |||||||
|     target.vsc = this; |     target.vsc = this; | ||||||
|     this.video = target; |     this.video = target; | ||||||
|     this.parent = parent || target.parentElement; |     this.parent = parent || target.parentElement; | ||||||
|     this.nudgeIntervalId = null; |     this.nudgeIntervalId = null; // ADDED | ||||||
|  |  | ||||||
|     let storedSpeed; |     let storedSpeed; // Original logic for initial speed determination | ||||||
|     if (!tc.settings.rememberSpeed) { |     if (!tc.settings.rememberSpeed) { | ||||||
|       storedSpeed = tc.settings.speeds[target.currentSrc]; |       storedSpeed = tc.settings.speeds[target.currentSrc]; | ||||||
|       if (!storedSpeed) storedSpeed = 1.0; |       if (!storedSpeed) storedSpeed = 1.0; | ||||||
|       setKeyBindings("reset", getKeyBindings("fast")); |       setKeyBindings("reset", getKeyBindings("fast")); // Original call | ||||||
|     } else { |     } else { | ||||||
|       storedSpeed = |       storedSpeed = | ||||||
|         tc.settings.speeds[target.currentSrc] || tc.settings.lastSpeed; |         tc.settings.speeds[target.currentSrc] || tc.settings.lastSpeed; | ||||||
| @@ -195,7 +199,8 @@ function defineVideoController() { | |||||||
|     this.div = this.initializeControls(); |     this.div = this.initializeControls(); | ||||||
|  |  | ||||||
|     if (Math.abs(target.playbackRate - storedSpeed) > 0.001) { |     if (Math.abs(target.playbackRate - storedSpeed) > 0.001) { | ||||||
|       setSpeed(target, storedSpeed, true, false); // isInitialCall=true, isUserKeyPress=false |       // MODIFIED: Pass isUserKeyPress = false for initial/automatic speed settings | ||||||
|  |       setSpeed(target, storedSpeed, true, false); | ||||||
|     } else { |     } else { | ||||||
|       if (this.speedIndicator) |       if (this.speedIndicator) | ||||||
|         this.speedIndicator.textContent = storedSpeed.toFixed(2); |         this.speedIndicator.textContent = storedSpeed.toFixed(2); | ||||||
| @@ -207,25 +212,46 @@ function defineVideoController() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // MODIFIED: mediaEventAction to correctly maintain speed on play/pause/seek/ended | ||||||
|     var mediaEventAction = function (event) { |     var mediaEventAction = function (event) { | ||||||
|       const video = event.target; |       const video = event.target; | ||||||
|       if (!video.vsc) return; |       if (!video.vsc) return; | ||||||
|       let speedToSet = tc.settings.speeds[video.currentSrc]; |  | ||||||
|       if (!tc.settings.rememberSpeed) { |  | ||||||
|         if (!speedToSet) speedToSet = 1.0; |  | ||||||
|         setKeyBindings("reset", getKeyBindings("fast")); |  | ||||||
|       } else { |  | ||||||
|         speedToSet = tc.settings.lastSpeed; |  | ||||||
|       } |  | ||||||
|       if (tc.settings.forceLastSavedSpeed) speedToSet = tc.settings.lastSpeed; |  | ||||||
|  |  | ||||||
|       if (Math.abs(video.playbackRate - speedToSet) > 0.001) { |       let speedToMaintain; | ||||||
|         // Speed corrections from play/seek are not direct user key presses for blink |       if (tc.settings.forceLastSavedSpeed) { | ||||||
|         setSpeed(video, speedToSet, false, false); // isInitialCall=false, isUserKeyPress=false |         speedToMaintain = tc.settings.lastSpeed; | ||||||
|  |       } else if (tc.settings.rememberSpeed) { | ||||||
|  |         speedToMaintain = | ||||||
|  |           tc.settings.speeds[video.currentSrc] || tc.settings.lastSpeed; | ||||||
|  |       } else { | ||||||
|  |         // Not forcing, not remembering per-video. Maintain the current session's tc.settings.lastSpeed. | ||||||
|  |         speedToMaintain = tc.settings.lastSpeed; | ||||||
|       } |       } | ||||||
|       if (event.type === "play") video.vsc.startSubtitleNudge(); |  | ||||||
|       else if (event.type === "pause" || event.type === "ended") |       // The original setKeyBindings("reset", getKeyBindings("fast")) from old mediaEventAction | ||||||
|  |       // was related to the complex 'R' key toggle. That logic is within the main `resetSpeed` function. | ||||||
|  |       // Here, we just ensure the determined `speedToMaintain` is applied if needed. | ||||||
|  |  | ||||||
|  |       if (Math.abs(video.playbackRate - speedToMaintain) > 0.001) { | ||||||
|  |         log( | ||||||
|  |           `Media event '${event.type}': video rate ${video.playbackRate.toFixed(2)} vs target ${speedToMaintain.toFixed(2)}. Correcting.`, | ||||||
|  |           4 | ||||||
|  |         ); | ||||||
|  |         // Corrections from play/pause/seek/ended are not direct user key presses for blink. | ||||||
|  |         setSpeed(video, speedToMaintain, false, false); // isInitialCall=false, isUserKeyPress=false | ||||||
|  |       } else { | ||||||
|  |         log( | ||||||
|  |           `Media event '${event.type}': video rate ${video.playbackRate.toFixed(2)} matches target. No speed change needed.`, | ||||||
|  |           6 | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Manage nudge based on event type | ||||||
|  |       if (event.type === "play") { | ||||||
|  |         if (video.playbackRate !== 1.0) video.vsc.startSubtitleNudge(); // Only start if not 1.0x | ||||||
|  |       } else if (event.type === "pause" || event.type === "ended") { | ||||||
|         video.vsc.stopSubtitleNudge(); |         video.vsc.stopSubtitleNudge(); | ||||||
|  |       } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     target.addEventListener( |     target.addEventListener( | ||||||
| @@ -246,6 +272,7 @@ function defineVideoController() { | |||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     var srcObserver = new MutationObserver((mutations) => { |     var srcObserver = new MutationObserver((mutations) => { | ||||||
|  |       // Original srcObserver | ||||||
|       mutations.forEach((mutation) => { |       mutations.forEach((mutation) => { | ||||||
|         if ( |         if ( | ||||||
|           mutation.type === "attributes" && |           mutation.type === "attributes" && | ||||||
| @@ -280,7 +307,7 @@ function defineVideoController() { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   tc.videoController.prototype.startSubtitleNudge = function () { |   tc.videoController.prototype.startSubtitleNudge = function () { | ||||||
|     /* ... Same as your provided ... */ |     // ADDED | ||||||
|     if (!location.hostname.includes("youtube.com")) return; |     if (!location.hostname.includes("youtube.com")) return; | ||||||
|     if ( |     if ( | ||||||
|       !tc.settings.enableSubtitleNudge || |       !tc.settings.enableSubtitleNudge || | ||||||
| @@ -292,6 +319,7 @@ function defineVideoController() { | |||||||
|       this.stopSubtitleNudge(); |       this.stopSubtitleNudge(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |     log(`Nudge: Starting interval: ${tc.settings.subtitleNudgeInterval}ms.`, 5); | ||||||
|     this.nudgeIntervalId = setInterval(() => { |     this.nudgeIntervalId = setInterval(() => { | ||||||
|       if ( |       if ( | ||||||
|         !this.video || |         !this.video || | ||||||
| @@ -319,26 +347,29 @@ function defineVideoController() { | |||||||
|     }, tc.settings.subtitleNudgeInterval); |     }, tc.settings.subtitleNudgeInterval); | ||||||
|   }; |   }; | ||||||
|   tc.videoController.prototype.stopSubtitleNudge = function () { |   tc.videoController.prototype.stopSubtitleNudge = function () { | ||||||
|     /* ... Same as your provided ... */ |     // ADDED | ||||||
|     if (this.nudgeIntervalId !== null) { |     if (this.nudgeIntervalId !== null) { | ||||||
|  |       log(`Nudge: Stopping.`, 5); | ||||||
|       clearInterval(this.nudgeIntervalId); |       clearInterval(this.nudgeIntervalId); | ||||||
|       this.nudgeIntervalId = null; |       this.nudgeIntervalId = null; | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   tc.videoController.prototype.remove = function () { |   tc.videoController.prototype.remove = function () { | ||||||
|     /* ... Same as your provided ... */ |     // Original remove | ||||||
|     this.stopSubtitleNudge(); |     this.stopSubtitleNudge(); // ADDED | ||||||
|     if (this.div && this.div.parentNode) this.div.remove(); |     if (this.div && this.div.parentNode) this.div.remove(); | ||||||
|     if (this.video) { |     if (this.video) { | ||||||
|       this.video.removeEventListener("play", this.handlePlay); |       this.video.removeEventListener("play", this.handlePlay); | ||||||
|       this.video.removeEventListener("pause", this.handlePause); |       this.video.removeEventListener("pause", this.handlePause); // ADDED | ||||||
|       this.video.removeEventListener("ended", this.handleEnded); |       this.video.removeEventListener("ended", this.handleEnded); // ADDED | ||||||
|       this.video.removeEventListener("seeked", this.handleSeek); |       this.video.removeEventListener("seeked", this.handleSeek); // Original was "seek" | ||||||
|       delete this.video.vsc; |       delete this.video.vsc; | ||||||
|     } |     } | ||||||
|     let idx = tc.mediaElements.indexOf(this.video); |     let idx = tc.mediaElements.indexOf(this.video); | ||||||
|     if (idx !== -1) tc.mediaElements.splice(idx, 1); |     if (idx !== -1) tc.mediaElements.splice(idx, 1); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   tc.videoController.prototype.initializeControls = function () { |   tc.videoController.prototype.initializeControls = function () { | ||||||
|     /* ... Same as your provided ... */ |     /* ... Same as your provided ... */ | ||||||
|     const doc = this.video.ownerDocument; |     const doc = this.video.ownerDocument; | ||||||
| @@ -460,11 +491,11 @@ function refreshCoolDown() { | |||||||
| function setupListener() { | function setupListener() { | ||||||
|   if (document.vscRateListenerAttached) return; |   if (document.vscRateListenerAttached) return; | ||||||
|  |  | ||||||
|   // MODIFIED: updateSpeedFromEvent NO LONGER calls runAction("blink") |   // MODIFIED: This function NO LONGER calls runAction("blink") | ||||||
|   function updateSpeedFromEvent(video) { |   function updateSpeedFromEvent(video) { | ||||||
|     if (!video.vsc || !video.vsc.speedIndicator) return; |     if (!video.vsc || !video.vsc.speedIndicator) return; | ||||||
|     var speed = Number(video.playbackRate.toFixed(2)); |     var speed = Number(video.playbackRate.toFixed(2)); | ||||||
|     log(`updateSpeedFromEvent: Rate is ${speed}.`, 4); // Removed fromUserInput from this log |     log(`updateSpeedFromEvent: Rate is ${speed}.`, 4); | ||||||
|  |  | ||||||
|     video.vsc.speedIndicator.textContent = speed.toFixed(2); |     video.vsc.speedIndicator.textContent = speed.toFixed(2); | ||||||
|     tc.settings.speeds[video.currentSrc || "unknown_src"] = speed; |     tc.settings.speeds[video.currentSrc || "unknown_src"] = speed; | ||||||
| @@ -473,7 +504,7 @@ function setupListener() { | |||||||
|       /* ... */ |       /* ... */ | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // runAction("blink") is now called directly from setSpeed if it's a user key press. |     // Blink is now handled directly in setSpeed for user key presses | ||||||
|  |  | ||||||
|     if (video.vsc) { |     if (video.vsc) { | ||||||
|       if (speed === 1.0 || video.paused) video.vsc.stopSubtitleNudge(); |       if (speed === 1.0 || video.paused) video.vsc.stopSubtitleNudge(); | ||||||
| @@ -484,7 +515,7 @@ function setupListener() { | |||||||
|   document.addEventListener( |   document.addEventListener( | ||||||
|     "ratechange", |     "ratechange", | ||||||
|     function (event) { |     function (event) { | ||||||
|       if (tc.isNudging) return; // Ignore nudge's own rate changes for VSC UI/state logic |       if (tc.isNudging) return; // ADDED: Ignore nudge events | ||||||
|  |  | ||||||
|       if (coolDown) { |       if (coolDown) { | ||||||
|         log("Blocked by coolDown", 4); |         log("Blocked by coolDown", 4); | ||||||
| @@ -497,32 +528,30 @@ function setupListener() { | |||||||
|         return; |         return; | ||||||
|  |  | ||||||
|       const eventOrigin = event.detail && event.detail.origin; |       const eventOrigin = event.detail && event.detail.origin; | ||||||
|       // The `fromUserInput` flag that was passed to updateSpeedFromEvent is removed from here. |       // The 'fromUserInput' flag previously passed to updateSpeedFromEvent is removed. | ||||||
|       // updateSpeedFromEvent now just updates state. Blinking is handled by setSpeed. |       // Blinking is now directly triggered by `setSpeed` if it's a user key press. | ||||||
|  |  | ||||||
|       if (tc.settings.forceLastSavedSpeed) { |       if (tc.settings.forceLastSavedSpeed) { | ||||||
|         if (eventOrigin === "videoSpeed") { |         if (eventOrigin === "videoSpeed") { | ||||||
|           // This "videoSpeed" event is dispatched by setSpeed when forceLastSavedSpeed is true. |           // This event is from setSpeed's forceLastSavedSpeed path. | ||||||
|           // setSpeed itself will handle blinking if it was a user key press. |           // setSpeed would have already handled blinking if it was a user key press. | ||||||
|           if (event.detail.speed) { |           if (event.detail.speed) { | ||||||
|             const detailSpeedNum = Number(event.detail.speed); |             const detailSpeedNum = Number(event.detail.speed); | ||||||
|             if ( |             if ( | ||||||
|               !isNaN(detailSpeedNum) && |               !isNaN(detailSpeedNum) && | ||||||
|               Math.abs(video.playbackRate - detailSpeedNum) > 0.001 |               Math.abs(video.playbackRate - detailSpeedNum) > 0.001 | ||||||
|             ) { |             ) { | ||||||
|               video.playbackRate = detailSpeedNum; // As per original forceLastSavedSpeed logic |               video.playbackRate = detailSpeedNum; | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|           updateSpeedFromEvent(video); // Update state |           updateSpeedFromEvent(video); // Update state | ||||||
|           event.stopImmediatePropagation(); // Original behavior |           event.stopImmediatePropagation(); | ||||||
|         } else { |         } else { | ||||||
|           // Native event when forceLastSavedSpeed is ON |  | ||||||
|           if (Math.abs(video.playbackRate - tc.settings.lastSpeed) > 0.001) { |           if (Math.abs(video.playbackRate - tc.settings.lastSpeed) > 0.001) { | ||||||
|             video.playbackRate = tc.settings.lastSpeed; |             video.playbackRate = tc.settings.lastSpeed; | ||||||
|             event.stopImmediatePropagation(); |             event.stopImmediatePropagation(); | ||||||
|             // The next ratechange (from VSC forcing it) will call updateSpeedFromEvent. |  | ||||||
|           } else { |           } else { | ||||||
|             updateSpeedFromEvent(video); // Just confirming, no blink needed from here |             updateSpeedFromEvent(video); // Just confirming speed | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
| @@ -538,7 +567,7 @@ function setupListener() { | |||||||
|  |  | ||||||
| var vscInitializedDocuments = new Set(); | var vscInitializedDocuments = new Set(); | ||||||
| function initializeWhenReady(doc) { | function initializeWhenReady(doc) { | ||||||
|   /* ... Same robust init ... */ |   /* ... Same robust init from your code ... */ | ||||||
|   if (doc.vscInitWhenReadyUniqueFlag1 && doc.readyState !== "loading") return; |   if (doc.vscInitWhenReadyUniqueFlag1 && doc.readyState !== "loading") return; | ||||||
|   doc.vscInitWhenReadyUniqueFlag1 = true; |   doc.vscInitWhenReadyUniqueFlag1 = true; | ||||||
|   if (isBlacklisted()) return; |   if (isBlacklisted()) return; | ||||||
| @@ -584,7 +613,7 @@ function getShadow(parent) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function initializeNow(doc) { | function initializeNow(doc) { | ||||||
|   /* ... Same robust init, ensuring tc.videoController is defined ... */ |   /* ... Same robust init from your code ... */ | ||||||
|   if (vscInitializedDocuments.has(doc) || !doc.body) return; |   if (vscInitializedDocuments.has(doc) || !doc.body) return; | ||||||
|   if (!tc.settings.enabled) return; |   if (!tc.settings.enabled) return; | ||||||
|   if (!doc.body.classList.contains("vsc-initialized")) |   if (!doc.body.classList.contains("vsc-initialized")) | ||||||
| @@ -725,15 +754,28 @@ function setSpeed(video, speed, isInitialCall = false, isUserKeyPress = false) { | |||||||
|   tc.settings.lastSpeed = numericSpeed; |   tc.settings.lastSpeed = numericSpeed; | ||||||
|   video.vsc.speedIndicator.textContent = numericSpeed.toFixed(2); |   video.vsc.speedIndicator.textContent = numericSpeed.toFixed(2); | ||||||
|  |  | ||||||
|  |   // MODIFIED: Directly trigger blink here if it's a user key press and not initial setup | ||||||
|  |   if (isUserKeyPress && !isInitialCall && video.vsc) { | ||||||
|  |     // Ensure controller is available before trying to blink | ||||||
|  |     if (video.vsc.div) { | ||||||
|  |       // Check if controller div exists | ||||||
|  |       log( | ||||||
|  |         `setSpeed: User key press detected, triggering blink for controller.`, | ||||||
|  |         5 | ||||||
|  |       ); | ||||||
|  |       // Pass the specific video to runAction for blink | ||||||
|  |       runAction("blink", getKeyBindings("blink", "value") || 1000, null, video); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (tc.settings.forceLastSavedSpeed) { |   if (tc.settings.forceLastSavedSpeed) { | ||||||
|     video.dispatchEvent( |     video.dispatchEvent( | ||||||
|       new CustomEvent("ratechange", { |       new CustomEvent("ratechange", { | ||||||
|         // Pass `isUserKeyPress` as `fromUserInput` for the custom event |  | ||||||
|         detail: { |         detail: { | ||||||
|           origin: "videoSpeed", |           origin: "videoSpeed", | ||||||
|           speed: numericSpeed.toFixed(2), |           speed: numericSpeed.toFixed(2), | ||||||
|           fromUserInput: isUserKeyPress |           fromUserInput: isUserKeyPress | ||||||
|         } |         } // Pass isUserKeyPress | ||||||
|       }) |       }) | ||||||
|     ); |     ); | ||||||
|   } else { |   } else { | ||||||
| @@ -742,12 +784,7 @@ function setSpeed(video, speed, isInitialCall = false, isUserKeyPress = false) { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!isInitialCall) refreshCoolDown(); // Original call, only for non-initial sets |   if (!isInitialCall) refreshCoolDown(); // Original call | ||||||
|  |  | ||||||
|   // MODIFIED: Directly trigger blink here if it's a user key press and not initial setup |  | ||||||
|   if (isUserKeyPress && !isInitialCall && video.vsc) { |  | ||||||
|     runAction("blink", getKeyBindings("blink", "value") || 1000, null, video); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (video.vsc) { |   if (video.vsc) { | ||||||
|     if (numericSpeed === 1.0 || video.paused) video.vsc.stopSubtitleNudge(); |     if (numericSpeed === 1.0 || video.paused) video.vsc.stopSubtitleNudge(); | ||||||
| @@ -785,7 +822,12 @@ function runAction(action, value, e, specificVideo = null) { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } else mediaTagsToProcess = tc.mediaElements; |   } else mediaTagsToProcess = tc.mediaElements; | ||||||
|   if (mediaTagsToProcess.length === 0 && action !== "display") return; |   if ( | ||||||
|  |     mediaTagsToProcess.length === 0 && | ||||||
|  |     action !== "display" && | ||||||
|  |     action !== "blink" | ||||||
|  |   ) | ||||||
|  |     return; // Allow blink even if no media for global feedback | ||||||
|  |  | ||||||
|   var targetControllerFromEvent = |   var targetControllerFromEvent = | ||||||
|     e && e.target && e.target.getRootNode && e.target.getRootNode().host |     e && e.target && e.target.getRootNode && e.target.getRootNode().host | ||||||
| @@ -804,8 +846,9 @@ function runAction(action, value, e, specificVideo = null) { | |||||||
|       return; |       return; | ||||||
|     if (action === "blink" && specificVideo && v !== specificVideo) return; |     if (action === "blink" && specificVideo && v !== specificVideo) return; | ||||||
|  |  | ||||||
|     // Original showController logic (not tied to `isUserKeyPress` here, runAction("blink") is separate) |     // MODIFIED: `showController` is only called if action implies user directly interacting with speed/video state. | ||||||
|     const userDrivenActionsThatShowController = [ |     // "display" handles its own visibility. "blink" is for feedback *after* a state change. | ||||||
|  |     const actionsThatShouldShowControllerTemporarily = [ | ||||||
|       "rewind", |       "rewind", | ||||||
|       "advance", |       "advance", | ||||||
|       "faster", |       "faster", | ||||||
| @@ -818,7 +861,11 @@ function runAction(action, value, e, specificVideo = null) { | |||||||
|       "jump", |       "jump", | ||||||
|       "drag" |       "drag" | ||||||
|     ]; |     ]; | ||||||
|     if (userDrivenActionsThatShowController.includes(action)) { |     if (actionsThatShouldShowControllerTemporarily.includes(action)) { | ||||||
|  |       // The original showController is a timed visibility. | ||||||
|  |       // The "blink" action also provides timed visibility. | ||||||
|  |       // By having setSpeed call blink directly for user key presses, this might be redundant here for speed changes. | ||||||
|  |       // However, for seek, pause etc., this existing showController is still relevant. | ||||||
|       showController(controllerDiv); |       showController(controllerDiv); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -846,6 +893,7 @@ function runAction(action, value, e, specificVideo = null) { | |||||||
|       case "slower": |       case "slower": | ||||||
|         setSpeed(v, Math.max(v.playbackRate - numValue, 0.07), false, true); |         setSpeed(v, Math.max(v.playbackRate - numValue, 0.07), false, true); | ||||||
|         break; |         break; | ||||||
|  |       // MODIFIED: Calls original resetSpeed, passing currentActionContext | ||||||
|       case "reset": |       case "reset": | ||||||
|         resetSpeed(v, 1.0, currentActionContext); |         resetSpeed(v, 1.0, currentActionContext); | ||||||
|         break; |         break; | ||||||
| @@ -856,7 +904,8 @@ function runAction(action, value, e, specificVideo = null) { | |||||||
|         controllerDiv.classList.add("vsc-manual"); |         controllerDiv.classList.add("vsc-manual"); | ||||||
|         controllerDiv.classList.toggle("vsc-hidden"); |         controllerDiv.classList.toggle("vsc-hidden"); | ||||||
|         break; |         break; | ||||||
|       case "blink": // This action is now mostly called by setSpeed itself for user key presses |       case "blink": // This action is now primarily called by setSpeed itself for user key presses | ||||||
|  |         if (!controllerDiv) return; // Safety check | ||||||
|         if ( |         if ( | ||||||
|           controllerDiv.classList.contains("vsc-hidden") || |           controllerDiv.classList.contains("vsc-hidden") || | ||||||
|           controllerDiv.blinkTimeOut !== undefined |           controllerDiv.blinkTimeOut !== undefined | ||||||
| @@ -865,17 +914,21 @@ function runAction(action, value, e, specificVideo = null) { | |||||||
|           controllerDiv.classList.remove("vsc-hidden"); |           controllerDiv.classList.remove("vsc-hidden"); | ||||||
|           controllerDiv.blinkTimeOut = setTimeout( |           controllerDiv.blinkTimeOut = setTimeout( | ||||||
|             () => { |             () => { | ||||||
|  |               // If user manually set controller to be visible (vsc-manual and NOT vsc-hidden), blink should not hide it. | ||||||
|               if ( |               if ( | ||||||
|                 controllerDiv.classList.contains("vsc-manual") && |                 controllerDiv.classList.contains("vsc-manual") && | ||||||
|                 !controllerDiv.classList.contains("vsc-hidden") |                 !controllerDiv.classList.contains("vsc-hidden") | ||||||
|               ) { |               ) { | ||||||
|  |                 // Do nothing, respect manual visibility | ||||||
|               } else { |               } else { | ||||||
|  |                 // Otherwise, (it was auto-shown by blink, or was already hidden, or user manually hid it) | ||||||
|  |                 // blink will ensure it ends up hidden. | ||||||
|                 controllerDiv.classList.add("vsc-hidden"); |                 controllerDiv.classList.add("vsc-hidden"); | ||||||
|               } |               } | ||||||
|               controllerDiv.blinkTimeOut = undefined; |               controllerDiv.blinkTimeOut = undefined; | ||||||
|             }, |             }, | ||||||
|             typeof value === "number" && !isNaN(value) ? value : 1000 |             typeof value === "number" && !isNaN(value) ? value : 1000 | ||||||
|           ); |           ); // Value for blink duration | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|       case "drag": |       case "drag": | ||||||
| @@ -905,7 +958,7 @@ function pause(v) { | |||||||
|   else v.pause(); |   else v.pause(); | ||||||
| } | } | ||||||
|  |  | ||||||
| // MODIFIED: `resetSpeed` now calls `setSpeed` with `isUserKeyPress = true` | // MODIFIED: resetSpeed now calls setSpeed with isUserKeyPress = true | ||||||
| function resetSpeed(v, target, currentActionContext = null) { | function resetSpeed(v, target, currentActionContext = null) { | ||||||
|   log( |   log( | ||||||
|     `resetSpeed (original): Video current: ${v.playbackRate.toFixed(2)}, Target: ${target.toFixed(2)}, Context: ${currentActionContext}`, |     `resetSpeed (original): Video current: ${v.playbackRate.toFixed(2)}, Target: ${target.toFixed(2)}, Context: ${currentActionContext}`, | ||||||
| @@ -914,18 +967,18 @@ function resetSpeed(v, target, currentActionContext = null) { | |||||||
|   if (Math.abs(v.playbackRate - target) < 0.01) { |   if (Math.abs(v.playbackRate - target) < 0.01) { | ||||||
|     if (v.playbackRate === (getKeyBindings("reset", "value") || 1.0)) { |     if (v.playbackRate === (getKeyBindings("reset", "value") || 1.0)) { | ||||||
|       if (target !== 1.0) { |       if (target !== 1.0) { | ||||||
|         setSpeed(v, 1.0, false, true); // isInitial=false, isUserKeyPress=true |         setSpeed(v, 1.0, false, true); | ||||||
|       } else { |       } else { | ||||||
|         setSpeed(v, getKeyBindings("fast", "value"), false, true); // isInitial=false, isUserKeyPress=true |         setSpeed(v, getKeyBindings("fast", "value"), false, true); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       setSpeed(v, getKeyBindings("reset", "value") || 1.0, false, true); // isInitial=false, isUserKeyPress=true |       setSpeed(v, getKeyBindings("reset", "value") || 1.0, false, true); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     if (currentActionContext === "reset") { |     if (currentActionContext === "reset") { | ||||||
|       setKeyBindings("reset", v.playbackRate); |       setKeyBindings("reset", v.playbackRate); | ||||||
|     } |     } | ||||||
|     setSpeed(v, target, false, true); // isInitial=false, isUserKeyPress=true |     setSpeed(v, target, false, true); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -991,7 +1044,24 @@ var timer = null; | |||||||
| function showController(controller) { | function showController(controller) { | ||||||
|   /* ... Same as your original ... */ |   /* ... Same as your original ... */ | ||||||
|   if (!controller || typeof controller.classList === "undefined") return; |   if (!controller || typeof controller.classList === "undefined") return; | ||||||
|   controller.classList.add("vcs-show"); |   // If controller is manually hidden by user (V pressed to hide), don't auto-show it | ||||||
|  |   // The vsc-manual class is added when 'display' action is triggered. | ||||||
|  |   // vsc-hidden is toggled by 'display' action. | ||||||
|  |   // So, if vsc-manual AND vsc-hidden are present, user explicitly hid it. | ||||||
|  |   if ( | ||||||
|  |     controller.classList.contains("vsc-manual") && | ||||||
|  |     controller.classList.contains("vsc-hidden") | ||||||
|  |   ) { | ||||||
|  |     // log("Controller is manually hidden by user, showController will not override.", 5); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   controller.classList.add("vcs-show"); // For autohide sites like YouTube | ||||||
|  |   // The blink action has its own logic for vsc-hidden and vsc-manual. | ||||||
|  |   // This showController is for the general "make it visible for a bit" from user actions. | ||||||
|  |   // It should not remove vsc-hidden if it was there before adding vsc-show, because blink handles that. | ||||||
|  |   // The original showController just adds vsc-show and sets a timer to remove it. | ||||||
|  |   // The "blink" action in runAction explicitly removes vsc-hidden if present, then adds it back. | ||||||
|  |  | ||||||
|   if (timer) clearTimeout(timer); |   if (timer) clearTimeout(timer); | ||||||
|   timer = setTimeout(function () { |   timer = setTimeout(function () { | ||||||
|     if (controller && controller.classList) |     if (controller && controller.classList) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "name": "Video Speed Controller", |   "name": "Video Speed Controller", | ||||||
|   "short_name": "videospeed", |   "short_name": "videospeed", | ||||||
|   "version": "1.1.1", |   "version": "1.1.2", | ||||||
|   "manifest_version": 2, |   "manifest_version": 2, | ||||||
|   "description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts", |   "description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts", | ||||||
|   "homepage_url": "https://github.com/SoPat712/videospeed", |   "homepage_url": "https://github.com/SoPat712/videospeed", | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								videospeed.xpi
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videospeed.xpi
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user