mirror of
				https://github.com/SoPat712/videospeed.git
				synced 2025-10-29 18:30:35 -04:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v1.2.1
			...
			d89853b4d2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d89853b4d2 | ||
|   | d94ab958d5 | ||
|   | 1277750716 | ||
|   | 247a46d430 | ||
|   | 703658335c | ||
|   | b2ed0fcb41 | ||
|   | 3dfee251ec | 
							
								
								
									
										118
									
								
								build.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								build.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import glob | ||||
| import os | ||||
| import re | ||||
| import shutil | ||||
| import tempfile | ||||
| import zipfile | ||||
|  | ||||
| SCRIPT_NAME = os.path.basename(__file__) | ||||
| TARGET_FILE = "manifest.json" | ||||
|  | ||||
|  | ||||
| def zip_folder(output_name, folder, exclude_files, exclude_dirs): | ||||
|     with zipfile.ZipFile(output_name, "w", zipfile.ZIP_DEFLATED) as zipf: | ||||
|         for root, dirs, files in os.walk(folder): | ||||
|             dirs[:] = [ | ||||
|                 d | ||||
|                 for d in dirs | ||||
|                 if os.path.relpath(os.path.join(root, d), folder) not in exclude_dirs | ||||
|             ] | ||||
|             for file in files: | ||||
|                 rel_path = os.path.relpath(os.path.join(root, file), folder) | ||||
|                 if ( | ||||
|                     file in exclude_files | ||||
|                     or rel_path in exclude_files | ||||
|                     or any(rel_path.startswith(ed + os.sep) for ed in exclude_dirs) | ||||
|                 ): | ||||
|                     continue | ||||
|                 zipf.write(os.path.join(root, file), arcname=rel_path) | ||||
|  | ||||
|  | ||||
| def update_version_line(file_path, new_version): | ||||
|     with open(file_path, "r", encoding="utf-8") as f: | ||||
|         lines = f.readlines() | ||||
|  | ||||
|     updated = False | ||||
|     for i, line in enumerate(lines): | ||||
|         match = re.match(r'\s*"version":\s*"([^"]+)"', line) | ||||
|         if match: | ||||
|             old_version = match.group(1) | ||||
|             lines[i] = re.sub( | ||||
|                 r'"version":\s*".+?"', f'"version": "{new_version}"', line | ||||
|             ) | ||||
|             updated = True | ||||
|             print( | ||||
|                 f"🛠️ Changed version in {file_path} from {old_version} ➜ {new_version}" | ||||
|             ) | ||||
|             break | ||||
|  | ||||
|     if updated: | ||||
|         with open(file_path, "w", encoding="utf-8") as f: | ||||
|             f.writelines(lines) | ||||
|     else: | ||||
|         print(f"⚠️ No version line found in {file_path}.") | ||||
|  | ||||
|  | ||||
| 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() | ||||
|     if not base_version: | ||||
|         print("❌ No version entered. Exiting.") | ||||
|         return | ||||
|  | ||||
|     firefox_version = f"{base_version}.0" | ||||
|     current_dir = os.getcwd() | ||||
|     manifest_path = os.path.join(current_dir, TARGET_FILE) | ||||
|  | ||||
|     # Step 1: Update manifest.json on disk to base_version | ||||
|     if os.path.exists(manifest_path): | ||||
|         update_version_line(manifest_path, base_version) | ||||
|     else: | ||||
|         print(f"❌ {TARGET_FILE} not found. Aborting.") | ||||
|         return | ||||
|  | ||||
|     # 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) | ||||
|     print("✅ Created videospeed-github.xpi") | ||||
|  | ||||
|     # 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: | ||||
|         for item in os.listdir(current_dir): | ||||
|             if item in exclude_temp_files or item in exclude_temp_dirs: | ||||
|                 continue | ||||
|             src = os.path.join(current_dir, item) | ||||
|             dst = os.path.join(temp_dir, item) | ||||
|             if os.path.isdir(src): | ||||
|                 shutil.copytree(src, dst) | ||||
|             else: | ||||
|                 shutil.copy2(src, dst) | ||||
|  | ||||
|         temp_manifest = os.path.join(temp_dir, TARGET_FILE) | ||||
|         if os.path.exists(temp_manifest): | ||||
|             update_version_line(temp_manifest, firefox_version) | ||||
|         else: | ||||
|             print(f"⚠️ {TARGET_FILE} not found in temp folder.") | ||||
|  | ||||
|         zip_folder( | ||||
|             "videospeed-firefox.xpi", temp_dir, exclude_files=[], exclude_dirs=[] | ||||
|         ) | ||||
|         print("✅ Created videospeed-firefox.xpi") | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
							
								
								
									
										127
									
								
								inject.js
									
									
									
									
									
								
							
							
						
						
									
										127
									
								
								inject.js
									
									
									
									
									
								
							| @@ -160,6 +160,8 @@ function defineVideoController() { | ||||
|     this.video = target; | ||||
|     this.parent = target.parentElement || parent; | ||||
|     this.nudgeIntervalId = null; | ||||
|  | ||||
|     // Determine what speed to use | ||||
|     let storedSpeed = tc.settings.speeds[target.currentSrc]; | ||||
|     if (!tc.settings.rememberSpeed) { | ||||
|       if (!storedSpeed) { | ||||
| @@ -171,45 +173,63 @@ function defineVideoController() { | ||||
|     if (tc.settings.forceLastSavedSpeed) { | ||||
|       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(); | ||||
|  | ||||
|     // FIXED: Make the controller visible for 5 seconds on startup | ||||
|     // Make the controller visible for 5 seconds on startup | ||||
|     runAction("blink", 5000, null, this.video); | ||||
|  | ||||
|     // FIXED: Rewritten mediaEventAction to prevent speed reset on pause. | ||||
|     // Rewritten mediaEventAction to prevent speed reset on pause. | ||||
|     var mediaEventAction = function (event) { | ||||
|       // Subtitle Nudge logic is based on play/pause state. | ||||
|       // Handle subtitle nudging based on the event type first. | ||||
|       if (event.type === "play") { | ||||
|         this.startSubtitleNudge(); | ||||
|  | ||||
|         // FIXED: Only reapply speed if there's a significant mismatch AND it's a new video | ||||
|         const currentSpeed = event.target.playbackRate; | ||||
|         const videoId = | ||||
|           event.target.currentSrc || event.target.src || "default"; | ||||
|  | ||||
|         // Get the expected speed based on settings | ||||
|         let expectedSpeed; | ||||
|         if (tc.settings.forceLastSavedSpeed) { | ||||
|           expectedSpeed = tc.settings.lastSpeed; | ||||
|         } else { | ||||
|           expectedSpeed = tc.settings.speeds[videoId] || tc.settings.lastSpeed; | ||||
|         } | ||||
|  | ||||
|         // 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") { | ||||
|         this.stopSubtitleNudge(); | ||||
|         // On pause or end, DO NOT proceed to change speed. | ||||
|         // This is the key fix for the pause-resets-speed bug. | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // Speed restoration logic (for "play" and non-user "seeked" events) | ||||
|       // For seek events, don't mess with speed | ||||
|       if (event.type === "seeked" && isUserSeek) { | ||||
|         isUserSeek = false; // Reset flag | ||||
|         return; // Don't change speed on user-initiated seeks. | ||||
|       } | ||||
|  | ||||
|       // Determine the speed that *should* be set, in case the site changed it. | ||||
|       let targetSpeed; | ||||
|       if (tc.settings.forceLastSavedSpeed) { | ||||
|         targetSpeed = tc.settings.lastSpeed; | ||||
|       } else if (tc.settings.rememberSpeed) { | ||||
|         targetSpeed = tc.settings.lastSpeed; | ||||
|       } else { | ||||
|         // Fallback to per-video speed or 1.0 | ||||
|         targetSpeed = tc.settings.speeds[event.target.currentSrc] || 1.0; | ||||
|       } | ||||
|  | ||||
|       // Only set the speed if the site has actually changed it. | ||||
|       // This avoids unnecessary "ratechange" events. | ||||
|       if (Math.abs(event.target.playbackRate - targetSpeed) > 0.01) { | ||||
|         setSpeed(event.target, targetSpeed); | ||||
|         isUserSeek = false; | ||||
|         return; | ||||
|       } | ||||
|     }; | ||||
|  | ||||
| @@ -229,6 +249,32 @@ function defineVideoController() { | ||||
|       "seeked", | ||||
|       (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) => { | ||||
|       mutations.forEach((mutation) => { | ||||
|         if ( | ||||
| @@ -242,6 +288,19 @@ function defineVideoController() { | ||||
|               this.div.classList.add("vsc-nosource"); | ||||
|             } else { | ||||
|               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(); | ||||
|             } | ||||
|           } | ||||
| @@ -324,8 +383,15 @@ function defineVideoController() { | ||||
|   tc.videoController.prototype.initializeControls = function () { | ||||
|     const doc = this.video.ownerDocument; | ||||
|     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"; | ||||
|     } | ||||
|     var wrapper = doc.createElement("div"); | ||||
|     wrapper.classList.add("vsc-controller"); | ||||
|     if (!this.video.src && !this.video.currentSrc) | ||||
| @@ -390,7 +456,7 @@ function defineVideoController() { | ||||
|         const r = parentEl.getRootNode(); | ||||
|         const s = r && r.querySelector ? r.querySelector(".scrim") : null; | ||||
|         if (s) s.prepend(fragment); | ||||
|         else parentEl.insertBefore(fragment, pEl.firstChild); | ||||
|         else parentEl.insertBefore(fragment, parentEl.firstChild); | ||||
|         break; | ||||
|       default: | ||||
|         parentEl.insertBefore(fragment, parentEl.firstChild); | ||||
| @@ -831,7 +897,6 @@ function pause(v) { | ||||
|   else v.pause(); | ||||
| } | ||||
|  | ||||
| // FIXED: Using the improved resetSpeed function for toggling | ||||
| function resetSpeed(v, target, isFastKey = false) { | ||||
|   const videoId = v.currentSrc || v.src || "default"; | ||||
|   const currentSpeed = v.playbackRate; | ||||
| @@ -879,7 +944,6 @@ function jumpToMark(v) { | ||||
|   if (v.vsc && typeof v.vsc.mark === "number") v.currentTime = v.vsc.mark; | ||||
| } | ||||
| function handleDrag(video, e) { | ||||
|   /* ... Same original logic ... */ | ||||
|   const c = video.vsc.div; | ||||
|   const sC = c.shadowRoot.querySelector("#controller"); | ||||
|   var pE = c.parentElement; | ||||
| @@ -911,7 +975,6 @@ function handleDrag(video, e) { | ||||
| } | ||||
| var timer = null; | ||||
| function showController(controller) { | ||||
|   /* ... Same original logic ... */ | ||||
|   if (!controller || typeof controller.classList === "undefined") return; | ||||
|   controller.classList.add("vsc-show"); | ||||
|   if (timer) clearTimeout(timer); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "Video Speed Controller", | ||||
|   "short_name": "videospeed", | ||||
|   "version": "1.2.1", | ||||
|   "version": "1.4.5", | ||||
|   "manifest_version": 2, | ||||
|   "description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts", | ||||
|   "homepage_url": "https://github.com/SoPat712/videospeed", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user