mirror of
				https://github.com/SoPat712/videospeed.git
				synced 2025-10-30 18:34:02 -04:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			v1.4.1
			...
			3fed3b425e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 3fed3b425e | ||
|   | d89853b4d2 | ||
|   | d94ab958d5 | ||
|   | 1277750716 | 
							
								
								
									
										31
									
								
								build.py
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								build.py
									
									
									
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
|  | import glob | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import shutil | import shutil | ||||||
| @@ -53,6 +54,15 @@ def update_version_line(file_path, new_version): | |||||||
|  |  | ||||||
|  |  | ||||||
| def main(): | def main(): | ||||||
|  |     # Step 0: Remove all existing .xpi files upfront | ||||||
|  |     xpi_files = glob.glob("*.xpi") | ||||||
|  |     for f in xpi_files: | ||||||
|  |         try: | ||||||
|  |             os.remove(f) | ||||||
|  |             print(f"🗑️ Removed existing archive: {f}") | ||||||
|  |         except Exception as e: | ||||||
|  |             print(f"⚠️ Failed to remove {f}: {e}") | ||||||
|  |  | ||||||
|     base_version = input("Enter the new base version (e.g., 2.0.1): ").strip() |     base_version = input("Enter the new base version (e.g., 2.0.1): ").strip() | ||||||
|     if not base_version: |     if not base_version: | ||||||
|         print("❌ No version entered. Exiting.") |         print("❌ No version entered. Exiting.") | ||||||
| @@ -62,27 +72,28 @@ def main(): | |||||||
|     current_dir = os.getcwd() |     current_dir = os.getcwd() | ||||||
|     manifest_path = os.path.join(current_dir, TARGET_FILE) |     manifest_path = os.path.join(current_dir, TARGET_FILE) | ||||||
|  |  | ||||||
|     # Determine what to exclude |     # Step 1: Update manifest.json on disk to base_version | ||||||
|     exclude_files = [SCRIPT_NAME] + [ |  | ||||||
|         f for f in os.listdir(current_dir) if f.endswith(".xpi") |  | ||||||
|     ] |  | ||||||
|     exclude_dirs = [".git"] |  | ||||||
|  |  | ||||||
|     # Step 1: Update manifest.json on disk |  | ||||||
|     if os.path.exists(manifest_path): |     if os.path.exists(manifest_path): | ||||||
|         update_version_line(manifest_path, base_version) |         update_version_line(manifest_path, base_version) | ||||||
|     else: |     else: | ||||||
|         print(f"❌ {TARGET_FILE} not found. Aborting.") |         print(f"❌ {TARGET_FILE} not found. Aborting.") | ||||||
|         return |         return | ||||||
|  |  | ||||||
|     # Step 2: Create GitHub .xpi archive |     # Step 2: Create videospeed-github.xpi (exclude script, .git, AND videospeed-github.xpi itself) | ||||||
|  |     exclude_files = [SCRIPT_NAME, "videospeed-github.xpi"] | ||||||
|  |     exclude_dirs = [".git"] | ||||||
|     zip_folder("videospeed-github.xpi", current_dir, exclude_files, exclude_dirs) |     zip_folder("videospeed-github.xpi", current_dir, exclude_files, exclude_dirs) | ||||||
|     print("✅ Created videospeed-github.xpi") |     print("✅ Created videospeed-github.xpi") | ||||||
|  |  | ||||||
|     # Step 3: Prepare Firefox archive with version bumped to .0 |     # Step 3: Re-scan for .xpi files after GitHub archive creation, exclude them for Firefox zip | ||||||
|  |     current_xpi_files = set(glob.glob("*.xpi")) | ||||||
|  |     exclude_temp_files = current_xpi_files.union({SCRIPT_NAME}) | ||||||
|  |     exclude_temp_dirs = set(exclude_dirs) | ||||||
|  |  | ||||||
|  |     # Step 4: Create videospeed-firefox.xpi from temp folder with version bumped to .0 | ||||||
|     with tempfile.TemporaryDirectory() as temp_dir: |     with tempfile.TemporaryDirectory() as temp_dir: | ||||||
|         for item in os.listdir(current_dir): |         for item in os.listdir(current_dir): | ||||||
|             if item in exclude_files or item in exclude_dirs: |             if item in exclude_temp_files or item in exclude_temp_dirs: | ||||||
|                 continue |                 continue | ||||||
|             src = os.path.join(current_dir, item) |             src = os.path.join(current_dir, item) | ||||||
|             dst = os.path.join(temp_dir, item) |             dst = os.path.join(temp_dir, item) | ||||||
|   | |||||||
							
								
								
									
										118
									
								
								inject.js
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								inject.js
									
									
									
									
									
								
							| @@ -137,6 +137,31 @@ chrome.storage.sync.get(tc.settings, function (storage) { | |||||||
|       predefined: true |       predefined: true | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |   // Add a listener for messages from the popup. | ||||||
|  |   // We use a global flag to ensure the listener is only attached once. | ||||||
|  |   if (!window.vscMessageListener) { | ||||||
|  |     chrome.runtime.onMessage.addListener( | ||||||
|  |       function (request, sender, sendResponse) { | ||||||
|  |         // Check if the message is a request to re-scan the page. | ||||||
|  |         if (request.action === "rescan_page") { | ||||||
|  |           log("Re-scan command received from popup.", 4); | ||||||
|  |  | ||||||
|  |           // Call the main initialization function. It's designed to be safe | ||||||
|  |           // to run multiple times and will pick up any new videos. | ||||||
|  |           initializeWhenReady(document); | ||||||
|  |  | ||||||
|  |           // Send a response to the popup to confirm completion. | ||||||
|  |           sendResponse({ status: "complete" }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Required to allow for asynchronous responses. | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // Set the flag to prevent adding the listener again. | ||||||
|  |     window.vscMessageListener = true; | ||||||
|  |   } | ||||||
|   initializeWhenReady(document); |   initializeWhenReady(document); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -160,6 +185,8 @@ function defineVideoController() { | |||||||
|     this.video = target; |     this.video = target; | ||||||
|     this.parent = target.parentElement || parent; |     this.parent = target.parentElement || parent; | ||||||
|     this.nudgeIntervalId = null; |     this.nudgeIntervalId = null; | ||||||
|  |  | ||||||
|  |     // Determine what speed to use | ||||||
|     let storedSpeed = tc.settings.speeds[target.currentSrc]; |     let storedSpeed = tc.settings.speeds[target.currentSrc]; | ||||||
|     if (!tc.settings.rememberSpeed) { |     if (!tc.settings.rememberSpeed) { | ||||||
|       if (!storedSpeed) { |       if (!storedSpeed) { | ||||||
| @@ -171,7 +198,15 @@ function defineVideoController() { | |||||||
|     if (tc.settings.forceLastSavedSpeed) { |     if (tc.settings.forceLastSavedSpeed) { | ||||||
|       storedSpeed = tc.settings.lastSpeed; |       storedSpeed = tc.settings.lastSpeed; | ||||||
|     } |     } | ||||||
|     target.playbackRate = storedSpeed; |  | ||||||
|  |     // FIXED: Actually apply the speed to the video element | ||||||
|  |     // Use setSpeed function to properly set the speed with all the necessary logic | ||||||
|  |     setTimeout(() => { | ||||||
|  |       if (this.video && this.video.vsc) { | ||||||
|  |         setSpeed(this.video, storedSpeed, true, false); | ||||||
|  |       } | ||||||
|  |     }, 0); | ||||||
|  |  | ||||||
|     this.div = this.initializeControls(); |     this.div = this.initializeControls(); | ||||||
|  |  | ||||||
|     // Make the controller visible for 5 seconds on startup |     // Make the controller visible for 5 seconds on startup | ||||||
| @@ -183,15 +218,34 @@ function defineVideoController() { | |||||||
|       if (event.type === "play") { |       if (event.type === "play") { | ||||||
|         this.startSubtitleNudge(); |         this.startSubtitleNudge(); | ||||||
|  |  | ||||||
|         // Reapply the current speed to ensure it doesn't get reset |         // FIXED: Only reapply speed if there's a significant mismatch AND it's a new video | ||||||
|         const currentSpeed = event.target.playbackRate; |         const currentSpeed = event.target.playbackRate; | ||||||
|         if (currentSpeed !== 1.0) { |         const videoId = | ||||||
|           // Only reapply if it's not already at the correct speed |           event.target.currentSrc || event.target.src || "default"; | ||||||
|           setTimeout(() => { |  | ||||||
|             if (Math.abs(event.target.playbackRate - currentSpeed) > 0.01) { |         // Get the expected speed based on settings | ||||||
|               event.target.playbackRate = currentSpeed; |         let expectedSpeed; | ||||||
|  |         if (tc.settings.forceLastSavedSpeed) { | ||||||
|  |           expectedSpeed = tc.settings.lastSpeed; | ||||||
|  |         } else { | ||||||
|  |           expectedSpeed = tc.settings.speeds[videoId] || tc.settings.lastSpeed; | ||||||
|         } |         } | ||||||
|           }, 0); |  | ||||||
|  |         // Only reapply speed if: | ||||||
|  |         // 1. The current speed is 1.0 (default) AND we have a stored speed that's different | ||||||
|  |         // 2. OR if forceLastSavedSpeed is enabled and speeds don't match | ||||||
|  |         const shouldReapplySpeed = | ||||||
|  |           (Math.abs(currentSpeed - 1.0) < 0.01 && | ||||||
|  |             Math.abs(expectedSpeed - 1.0) > 0.01) || | ||||||
|  |           (tc.settings.forceLastSavedSpeed && | ||||||
|  |             Math.abs(currentSpeed - expectedSpeed) > 0.01); | ||||||
|  |  | ||||||
|  |         if (shouldReapplySpeed) { | ||||||
|  |           setTimeout(() => { | ||||||
|  |             if (event.target.vsc) { | ||||||
|  |               setSpeed(event.target, expectedSpeed, false, false); | ||||||
|  |             } | ||||||
|  |           }, 10); | ||||||
|         } |         } | ||||||
|       } else if (event.type === "pause" || event.type === "ended") { |       } else if (event.type === "pause" || event.type === "ended") { | ||||||
|         this.stopSubtitleNudge(); |         this.stopSubtitleNudge(); | ||||||
| @@ -220,6 +274,32 @@ function defineVideoController() { | |||||||
|       "seeked", |       "seeked", | ||||||
|       (this.handleSeek = mediaEventAction.bind(this)) |       (this.handleSeek = mediaEventAction.bind(this)) | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     // ADDITIONAL FIX: Listen for loadedmetadata to reapply speed when video source changes | ||||||
|  |     target.addEventListener("loadedmetadata", () => { | ||||||
|  |       if (this.video && this.video.vsc) { | ||||||
|  |         const currentSpeed = this.video.playbackRate; | ||||||
|  |         const videoId = this.video.currentSrc || this.video.src || "default"; | ||||||
|  |  | ||||||
|  |         // Get expected speed | ||||||
|  |         let expectedSpeed; | ||||||
|  |         if (tc.settings.forceLastSavedSpeed) { | ||||||
|  |           expectedSpeed = tc.settings.lastSpeed; | ||||||
|  |         } else { | ||||||
|  |           expectedSpeed = tc.settings.speeds[videoId] || tc.settings.lastSpeed; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Only reapply if current speed is default (1.0) and we have a different stored speed | ||||||
|  |         const shouldReapplySpeed = | ||||||
|  |           Math.abs(currentSpeed - 1.0) < 0.01 && | ||||||
|  |           Math.abs(expectedSpeed - 1.0) > 0.01; | ||||||
|  |  | ||||||
|  |         if (shouldReapplySpeed) { | ||||||
|  |           setSpeed(this.video, expectedSpeed, false, false); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     var srcObserver = new MutationObserver((mutations) => { |     var srcObserver = new MutationObserver((mutations) => { | ||||||
|       mutations.forEach((mutation) => { |       mutations.forEach((mutation) => { | ||||||
|         if ( |         if ( | ||||||
| @@ -233,6 +313,19 @@ function defineVideoController() { | |||||||
|               this.div.classList.add("vsc-nosource"); |               this.div.classList.add("vsc-nosource"); | ||||||
|             } else { |             } else { | ||||||
|               this.div.classList.remove("vsc-nosource"); |               this.div.classList.remove("vsc-nosource"); | ||||||
|  |  | ||||||
|  |               // FIXED: Reapply speed when source changes (like in shorts) | ||||||
|  |               const expectedSpeed = tc.settings.forceLastSavedSpeed | ||||||
|  |                 ? tc.settings.lastSpeed | ||||||
|  |                 : tc.settings.speeds[mutation.target.currentSrc] || | ||||||
|  |                   tc.settings.lastSpeed; | ||||||
|  |  | ||||||
|  |               setTimeout(() => { | ||||||
|  |                 if (mutation.target.vsc) { | ||||||
|  |                   setSpeed(mutation.target, expectedSpeed, false, false); | ||||||
|  |                 } | ||||||
|  |               }, 100); | ||||||
|  |  | ||||||
|               if (!mutation.target.paused) this.startSubtitleNudge(); |               if (!mutation.target.paused) this.startSubtitleNudge(); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
| @@ -315,8 +408,15 @@ function defineVideoController() { | |||||||
|   tc.videoController.prototype.initializeControls = function () { |   tc.videoController.prototype.initializeControls = function () { | ||||||
|     const doc = this.video.ownerDocument; |     const doc = this.video.ownerDocument; | ||||||
|     const speed = this.video.playbackRate.toFixed(2); |     const speed = this.video.playbackRate.toFixed(2); | ||||||
|     var top = Math.max(this.video.offsetTop, 0) + "px", |     // Fix for videos rendered after page load - use relative positioning | ||||||
|  |     var top = "10px", | ||||||
|  |       left = "10px"; | ||||||
|  |  | ||||||
|  |     // Try to get actual position, but fallback to default if not available | ||||||
|  |     if (this.video.offsetTop > 0 || this.video.offsetLeft > 0) { | ||||||
|  |       top = Math.max(this.video.offsetTop, 0) + "px"; | ||||||
|       left = Math.max(this.video.offsetLeft, 0) + "px"; |       left = Math.max(this.video.offsetLeft, 0) + "px"; | ||||||
|  |     } | ||||||
|     var wrapper = doc.createElement("div"); |     var wrapper = doc.createElement("div"); | ||||||
|     wrapper.classList.add("vsc-controller"); |     wrapper.classList.add("vsc-controller"); | ||||||
|     if (!this.video.src && !this.video.currentSrc) |     if (!this.video.src && !this.video.currentSrc) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "name": "Video Speed Controller", |   "name": "Video Speed Controller", | ||||||
|   "short_name": "videospeed", |   "short_name": "videospeed", | ||||||
|   "version": "1.3.0", |   "version": "1.5.1", | ||||||
|   "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", | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| <!DOCTYPE html> | <!doctype html> | ||||||
| <html> | <html> | ||||||
|   <head> |   <head> | ||||||
|     <title>Video Speed Controller: Popup</title> |     <title>Video Speed Controller: Popup</title> | ||||||
| @@ -6,6 +6,8 @@ | |||||||
|     <script src="popup.js"></script> |     <script src="popup.js"></script> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|  |     <button id="refresh">Re-scan Page for Videos</button> | ||||||
|  |     <hr /> | ||||||
|     <button id="enable" class="hide">Enable</button> |     <button id="enable" class="hide">Enable</button> | ||||||
|     <button id="disable">Disable</button> |     <button id="disable">Disable</button> | ||||||
|     <span id="status" class="hide"></span> |     <span id="status" class="hide"></span> | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								popup.js
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								popup.js
									
									
									
									
									
								
							| @@ -19,6 +19,31 @@ document.addEventListener("DOMContentLoaded", function () { | |||||||
|     toggleEnabled(false, settingsSavedReloadMessage); |     toggleEnabled(false, settingsSavedReloadMessage); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   // --- REVISED: "Re-scan" button functionality --- | ||||||
|  |   document.querySelector("#refresh").addEventListener("click", function () { | ||||||
|  |     setStatusMessage("Re-scanning page..."); | ||||||
|  |     chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { | ||||||
|  |       if (tabs[0] && tabs[0].id) { | ||||||
|  |         // Send a message to the content script, asking it to re-initialize. | ||||||
|  |         chrome.tabs.sendMessage( | ||||||
|  |           tabs[0].id, | ||||||
|  |           { action: "rescan_page" }, | ||||||
|  |           function (response) { | ||||||
|  |             if (chrome.runtime.lastError) { | ||||||
|  |               // This error is expected on pages where content scripts cannot run. | ||||||
|  |               setStatusMessage("Cannot run on this page."); | ||||||
|  |             } else if (response && response.status === "complete") { | ||||||
|  |               setStatusMessage("Scan complete. Closing..."); | ||||||
|  |               setTimeout(() => window.close(), 500); // Close popup on success. | ||||||
|  |             } else { | ||||||
|  |               setStatusMessage("Scan failed. Please reload the page."); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   chrome.storage.sync.get({ enabled: true }, function (storage) { |   chrome.storage.sync.get({ enabled: true }, function (storage) { | ||||||
|     toggleEnabledUI(storage.enabled); |     toggleEnabledUI(storage.enabled); | ||||||
|   }); |   }); | ||||||
| @@ -42,9 +67,9 @@ document.addEventListener("DOMContentLoaded", function () { | |||||||
|     const suffix = `${enabled ? "" : "_disabled"}.png`; |     const suffix = `${enabled ? "" : "_disabled"}.png`; | ||||||
|     chrome.browserAction.setIcon({ |     chrome.browserAction.setIcon({ | ||||||
|       path: { |       path: { | ||||||
|         "19": "icons/icon19" + suffix, |         19: "icons/icon19" + suffix, | ||||||
|         "38": "icons/icon38" + suffix, |         38: "icons/icon38" + suffix, | ||||||
|         "48": "icons/icon48" + suffix |         48: "icons/icon48" + suffix | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user