mirror of
				https://github.com/SoPat712/videospeed.git
				synced 2025-10-30 18:34:02 -04:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			v2.0.0
			...
			firefox-po
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 1f8cb4411e | ||
|   | 05a8adef80 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -2,5 +2,6 @@ | |||||||
| local | local | ||||||
|  |  | ||||||
| # IntelliJ IDEA | # IntelliJ IDEA | ||||||
|  | *.xpi | ||||||
| .idea/ | .idea/ | ||||||
| node_modules | node_modules | ||||||
|   | |||||||
							
								
								
									
										267
									
								
								inject.js
									
									
									
									
									
								
							
							
						
						
									
										267
									
								
								inject.js
									
									
									
									
									
								
							| @@ -186,6 +186,8 @@ function defineVideoController() { | |||||||
|     this.parent = target.parentElement || parent; |     this.parent = target.parentElement || parent; | ||||||
|     this.nudgeIntervalId = null; |     this.nudgeIntervalId = null; | ||||||
|  |  | ||||||
|  |     log(`Creating video controller for ${target.tagName} with src: ${target.src || target.currentSrc || 'none'}`, 4); | ||||||
|  |  | ||||||
|     // Determine what speed to use |     // 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) { | ||||||
| @@ -209,6 +211,13 @@ function defineVideoController() { | |||||||
|  |  | ||||||
|     this.div = this.initializeControls(); |     this.div = this.initializeControls(); | ||||||
|  |  | ||||||
|  |     if (!this.div) { | ||||||
|  |       log("ERROR: Failed to create controller div!", 2); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     log(`Controller created and attached to DOM. Hidden: ${this.div.classList.contains('vsc-hidden')}`, 4); | ||||||
|  |  | ||||||
|     // Make the controller visible for 5 seconds on startup |     // Make the controller visible for 5 seconds on startup | ||||||
|     runAction("blink", 5000, null, this.video); |     runAction("blink", 5000, null, this.video); | ||||||
|  |  | ||||||
| @@ -471,17 +480,25 @@ function defineVideoController() { | |||||||
|     var fragment = doc.createDocumentFragment(); |     var fragment = doc.createDocumentFragment(); | ||||||
|     fragment.appendChild(wrapper); |     fragment.appendChild(wrapper); | ||||||
|     const parentEl = this.parent || this.video.parentElement; |     const parentEl = this.parent || this.video.parentElement; | ||||||
|  |  | ||||||
|  |     log(`Inserting controller: parentEl=${!!parentEl}, parentNode=${!!parentEl?.parentNode}, hostname=${location.hostname}`, 4); | ||||||
|  |  | ||||||
|     if (!parentEl || !parentEl.parentNode) { |     if (!parentEl || !parentEl.parentNode) { | ||||||
|  |       log("No suitable parent found, appending to body", 4); | ||||||
|       doc.body.appendChild(fragment); |       doc.body.appendChild(fragment); | ||||||
|       return wrapper; |       return wrapper; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|       switch (true) { |       switch (true) { | ||||||
|         case location.hostname == "www.amazon.com": |         case location.hostname == "www.amazon.com": | ||||||
|         case location.hostname == "www.reddit.com": |         case location.hostname == "www.reddit.com": | ||||||
|         case /hbogo\./.test(location.hostname): |         case /hbogo\./.test(location.hostname): | ||||||
|  |           log("Using parentElement.parentElement insertion", 5); | ||||||
|           parentEl.parentElement.insertBefore(fragment, parentEl); |           parentEl.parentElement.insertBefore(fragment, parentEl); | ||||||
|           break; |           break; | ||||||
|         case location.hostname == "www.facebook.com": |         case location.hostname == "www.facebook.com": | ||||||
|  |           log("Using Facebook-specific insertion", 5); | ||||||
|           let p = |           let p = | ||||||
|             parentEl.parentElement.parentElement.parentElement.parentElement |             parentEl.parentElement.parentElement.parentElement.parentElement | ||||||
|               .parentElement.parentElement.parentElement; |               .parentElement.parentElement.parentElement; | ||||||
| @@ -489,14 +506,23 @@ function defineVideoController() { | |||||||
|           else parentEl.insertBefore(fragment, parentEl.firstChild); |           else parentEl.insertBefore(fragment, parentEl.firstChild); | ||||||
|           break; |           break; | ||||||
|         case location.hostname == "tv.apple.com": |         case location.hostname == "tv.apple.com": | ||||||
|  |           log("Using Apple TV-specific insertion", 5); | ||||||
|           const r = parentEl.getRootNode(); |           const r = parentEl.getRootNode(); | ||||||
|           const s = r && r.querySelector ? r.querySelector(".scrim") : null; |           const s = r && r.querySelector ? r.querySelector(".scrim") : null; | ||||||
|           if (s) s.prepend(fragment); |           if (s) s.prepend(fragment); | ||||||
|           else parentEl.insertBefore(fragment, parentEl.firstChild); |           else parentEl.insertBefore(fragment, parentEl.firstChild); | ||||||
|           break; |           break; | ||||||
|         default: |         default: | ||||||
|  |           log("Using default insertion method", 5); | ||||||
|           parentEl.insertBefore(fragment, parentEl.firstChild); |           parentEl.insertBefore(fragment, parentEl.firstChild); | ||||||
|       } |       } | ||||||
|  |       log("Controller successfully inserted into DOM", 4); | ||||||
|  |     } catch (error) { | ||||||
|  |       log(`Error inserting controller: ${error.message}`, 2); | ||||||
|  |       // Fallback to body insertion | ||||||
|  |       doc.body.appendChild(fragment); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return wrapper; |     return wrapper; | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| @@ -583,12 +609,18 @@ function setupListener() { | |||||||
| } | } | ||||||
|  |  | ||||||
| var vscInitializedDocuments = new Set(); | var vscInitializedDocuments = new Set(); | ||||||
| function initializeWhenReady(doc) { | function initializeWhenReady(doc, forceReinit = false) { | ||||||
|   if (vscInitializedDocuments.has(doc) || !doc.body) return; |   if (!forceReinit && vscInitializedDocuments.has(doc) || !doc.body) return; | ||||||
|  |  | ||||||
|  |   // For navigation changes, we want to re-scan even if already initialized | ||||||
|  |   if (forceReinit) { | ||||||
|  |     log("Force re-initialization requested", 4); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (doc.readyState === "complete") { |   if (doc.readyState === "complete") { | ||||||
|     initializeNow(doc); |     initializeNow(doc, forceReinit); | ||||||
|   } else { |   } else { | ||||||
|     doc.addEventListener("DOMContentLoaded", () => initializeNow(doc), { |     doc.addEventListener("DOMContentLoaded", () => initializeNow(doc, forceReinit), { | ||||||
|       once: true |       once: true | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @@ -608,7 +640,17 @@ function getShadow(parent) { | |||||||
|       do { |       do { | ||||||
|         r.push(c); |         r.push(c); | ||||||
|         gC(c); |         gC(c); | ||||||
|         if (c.shadowRoot) r.push(...getShadow(c.shadowRoot)); |         if (c.shadowRoot) { | ||||||
|  |           r.push(...getShadow(c.shadowRoot)); | ||||||
|  |           // Also check for videos in shadow DOM | ||||||
|  |           const shadowVideos = c.shadowRoot.querySelectorAll(tc.settings.audioBoolean ? "video,audio" : "video"); | ||||||
|  |           shadowVideos.forEach(video => { | ||||||
|  |             if (!video.vsc) { | ||||||
|  |               log(`Found video in shadow DOM`, 5); | ||||||
|  |               checkForVideo(video, video.parentElement, true); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|         c = c.nextElementSibling; |         c = c.nextElementSibling; | ||||||
|       } while (c); |       } while (c); | ||||||
|     } |     } | ||||||
| @@ -617,9 +659,10 @@ function getShadow(parent) { | |||||||
|   return r; |   return r; | ||||||
| } | } | ||||||
|  |  | ||||||
| function initializeNow(doc) { | function initializeNow(doc, forceReinit = false) { | ||||||
|   if (vscInitializedDocuments.has(doc) || !doc.body) return; |   if (!forceReinit && (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")) | ||||||
|     doc.body.classList.add("vsc-initialized"); |     doc.body.classList.add("vsc-initialized"); | ||||||
|   if (typeof tc.videoController === "undefined") defineVideoController(); |   if (typeof tc.videoController === "undefined") defineVideoController(); | ||||||
| @@ -688,9 +731,16 @@ function initializeNow(doc) { | |||||||
|                 }); |                 }); | ||||||
|                 break; |                 break; | ||||||
|               case "attributes": |               case "attributes": | ||||||
|                 if ( |                 // Enhanced attribute monitoring for video detection | ||||||
|                   mutation.target.attributes["aria-hidden"] && |                 const target = mutation.target; | ||||||
|                   mutation.target.attributes["aria-hidden"].value == "false" |                 if (target.tagName === "VIDEO" || target.tagName === "AUDIO") { | ||||||
|  |                   // Video/audio element had attributes changed - check if it needs controller | ||||||
|  |                   if (!target.vsc && (target.src || target.currentSrc)) { | ||||||
|  |                     checkForVideo(target, target.parentNode, true); | ||||||
|  |                   } | ||||||
|  |                 } else if ( | ||||||
|  |                   target.attributes["aria-hidden"] && | ||||||
|  |                   target.attributes["aria-hidden"].value == "false" | ||||||
|                 ) { |                 ) { | ||||||
|                   var flattenedNodes = getShadow(document.body); |                   var flattenedNodes = getShadow(document.body); | ||||||
|                   var node = flattenedNodes.filter( |                   var node = flattenedNodes.filter( | ||||||
| @@ -719,9 +769,30 @@ function initializeNow(doc) { | |||||||
|         (node.nodeName === "AUDIO" && tc.settings.audioBoolean) |         (node.nodeName === "AUDIO" && tc.settings.audioBoolean) | ||||||
|       ) { |       ) { | ||||||
|         if (added) { |         if (added) { | ||||||
|           if (!node.vsc) node.vsc = new tc.videoController(node, parent); |           if (!node.vsc) { | ||||||
|  |             log(`Creating controller for ${node.tagName}: ${node.src || node.currentSrc || 'no src'}`, 4); | ||||||
|  |             node.vsc = new tc.videoController(node, parent); | ||||||
|  |  | ||||||
|  |             // Verify controller was created successfully | ||||||
|  |             if (!node.vsc || !node.vsc.div) { | ||||||
|  |               log(`ERROR: Controller creation failed for ${node.tagName}`, 2); | ||||||
|             } else { |             } else { | ||||||
|           if (node.vsc) node.vsc.remove(); |               log(`Controller created successfully, div in DOM: ${document.contains(node.vsc.div)}`, 4); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Add to intersection observer if available | ||||||
|  |             if (doc.vscVideoIntersectionObserver) { | ||||||
|  |               doc.vscVideoIntersectionObserver.observe(node); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           if (node.vsc) { | ||||||
|  |             node.vsc.remove(); | ||||||
|  |             // Remove from intersection observer if available | ||||||
|  |             if (doc.vscVideoIntersectionObserver) { | ||||||
|  |               doc.vscVideoIntersectionObserver.unobserve(node); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } else if (node.children != undefined) { |       } else if (node.children != undefined) { | ||||||
|         for (var i = 0; i < node.children.length; i++) { |         for (var i = 0; i < node.children.length; i++) { | ||||||
| @@ -731,9 +802,10 @@ function initializeNow(doc) { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     observer.observe(doc, { |     observer.observe(doc, { | ||||||
|       attributeFilter: ["aria-hidden"], |       attributeFilter: ["aria-hidden", "src", "currentSrc", "style", "class"], | ||||||
|       childList: true, |       childList: true, | ||||||
|       subtree: true |       subtree: true, | ||||||
|  |       attributes: true | ||||||
|     }); |     }); | ||||||
|     doc.vscMutationObserverAttached = true; |     doc.vscMutationObserverAttached = true; | ||||||
|   } |   } | ||||||
| @@ -744,6 +816,45 @@ function initializeNow(doc) { | |||||||
|     if (!v.vsc) new tc.videoController(v, v.parentElement); |     if (!v.vsc) new tc.videoController(v, v.parentElement); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   // Enhanced video detection via media events | ||||||
|  |   if (!doc.vscMediaEventListenersAttached) { | ||||||
|  |     const mediaEvents = ['loadstart', 'loadedmetadata', 'canplay', 'play']; | ||||||
|  |     mediaEvents.forEach(eventType => { | ||||||
|  |       doc.addEventListener(eventType, function (event) { | ||||||
|  |         const target = event.target; | ||||||
|  |         if ((target.tagName === 'VIDEO' || (target.tagName === 'AUDIO' && tc.settings.audioBoolean)) && !target.vsc) { | ||||||
|  |           log(`Media event ${eventType} detected new ${target.tagName.toLowerCase()}`, 5); | ||||||
|  |           checkForVideo(target, target.parentElement, true); | ||||||
|  |         } | ||||||
|  |       }, true); | ||||||
|  |     }); | ||||||
|  |     doc.vscMediaEventListenersAttached = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Intersection Observer for lazy-loaded videos | ||||||
|  |   if (!doc.vscIntersectionObserverAttached && 'IntersectionObserver' in window) { | ||||||
|  |     const videoIntersectionObserver = new IntersectionObserver((entries) => { | ||||||
|  |       entries.forEach(entry => { | ||||||
|  |         if (entry.isIntersecting) { | ||||||
|  |           const target = entry.target; | ||||||
|  |           if ((target.tagName === 'VIDEO' || (target.tagName === 'AUDIO' && tc.settings.audioBoolean)) && !target.vsc) { | ||||||
|  |             log(`Intersection observer detected visible ${target.tagName.toLowerCase()}`, 5); | ||||||
|  |             checkForVideo(target, target.parentElement, true); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }, { threshold: 0.1 }); | ||||||
|  |  | ||||||
|  |     // Observe existing videos that might not have been processed | ||||||
|  |     doc.querySelectorAll(q).forEach(video => { | ||||||
|  |       videoIntersectionObserver.observe(video); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Store observer to add new videos to it | ||||||
|  |     doc.vscVideoIntersectionObserver = videoIntersectionObserver; | ||||||
|  |     doc.vscIntersectionObserverAttached = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Array.from(doc.getElementsByTagName("iframe")).forEach((f) => { |   Array.from(doc.getElementsByTagName("iframe")).forEach((f) => { | ||||||
|     if (f.vscLoadListenerAttached) return; |     if (f.vscLoadListenerAttached) return; | ||||||
|     f.addEventListener("load", () => { |     f.addEventListener("load", () => { | ||||||
| @@ -764,6 +875,115 @@ function initializeNow(doc) { | |||||||
|       // Silently ignore CORS errors |       // Silently ignore CORS errors | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |   // Navigation change detection for SPAs | ||||||
|  |   if (!doc.vscNavigationListenersAttached) { | ||||||
|  |     let currentUrl = location.href; | ||||||
|  |  | ||||||
|  |     const handleNavigation = (source) => { | ||||||
|  |       if (location.href !== currentUrl) { | ||||||
|  |         const oldUrl = currentUrl; | ||||||
|  |         currentUrl = location.href; | ||||||
|  |         log(`Navigation detected via ${source}: ${oldUrl} -> ${currentUrl}`, 4); | ||||||
|  |  | ||||||
|  |         // Wait a bit for the new content to load, then force re-scan | ||||||
|  |         setTimeout(() => { | ||||||
|  |           const q = tc.settings.audioBoolean ? "video,audio" : "video"; | ||||||
|  |           const videos = document.querySelectorAll(q); | ||||||
|  |           log(`Post-navigation scan found ${videos.length} videos`, 4); | ||||||
|  |  | ||||||
|  |           videos.forEach(video => { | ||||||
|  |             if (!video.vsc) { | ||||||
|  |               log(`Adding controller to post-navigation video`, 4); | ||||||
|  |               checkForVideo(video, video.parentElement, true); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         }, 500); // Increased delay for content to load | ||||||
|  |  | ||||||
|  |         // Also do a quicker scan | ||||||
|  |         setTimeout(() => { | ||||||
|  |           const q = tc.settings.audioBoolean ? "video,audio" : "video"; | ||||||
|  |           const videos = document.querySelectorAll(q); | ||||||
|  |           videos.forEach(video => { | ||||||
|  |             if (!video.vsc && (video.src || video.currentSrc)) { | ||||||
|  |               checkForVideo(video, video.parentElement, true); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         }, 100); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Listen for popstate (back/forward navigation) | ||||||
|  |     window.addEventListener('popstate', () => handleNavigation('popstate')); | ||||||
|  |  | ||||||
|  |     // Override pushState and replaceState to catch programmatic navigation | ||||||
|  |     const originalPushState = history.pushState; | ||||||
|  |     const originalReplaceState = history.replaceState; | ||||||
|  |  | ||||||
|  |     history.pushState = function (...args) { | ||||||
|  |       originalPushState.apply(this, args); | ||||||
|  |       handleNavigation('pushState'); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     history.replaceState = function (...args) { | ||||||
|  |       originalReplaceState.apply(this, args); | ||||||
|  |       handleNavigation('replaceState'); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Listen for hashchange as well | ||||||
|  |     window.addEventListener('hashchange', () => handleNavigation('hashchange')); | ||||||
|  |  | ||||||
|  |     // Also intercept fetch and XMLHttpRequest for AJAX-heavy sites | ||||||
|  |     const originalFetch = window.fetch; | ||||||
|  |     window.fetch = function (...args) { | ||||||
|  |       return originalFetch.apply(this, args).then(response => { | ||||||
|  |         // After any fetch completes, check for new videos | ||||||
|  |         setTimeout(() => { | ||||||
|  |           const q = tc.settings.audioBoolean ? "video,audio" : "video"; | ||||||
|  |           const videos = document.querySelectorAll(q); | ||||||
|  |           videos.forEach(video => { | ||||||
|  |             if (!video.vsc && (video.src || video.currentSrc || video.readyState > 0)) { | ||||||
|  |               log(`Post-fetch scan found video`, 5); | ||||||
|  |               checkForVideo(video, video.parentElement, true); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         }, 200); | ||||||
|  |         return response; | ||||||
|  |       }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     doc.vscNavigationListenersAttached = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Periodic fallback scan for missed videos | ||||||
|  |   if (!doc.vscPeriodicScanAttached) { | ||||||
|  |     const periodicScan = () => { | ||||||
|  |       const q = tc.settings.audioBoolean ? "video,audio" : "video"; | ||||||
|  |       const allVideos = doc.querySelectorAll(q); | ||||||
|  |       let foundNew = false; | ||||||
|  |  | ||||||
|  |       allVideos.forEach(video => { | ||||||
|  |         if (!video.vsc && (video.src || video.currentSrc || video.readyState > 0)) { | ||||||
|  |           log(`Periodic scan found missed ${video.tagName.toLowerCase()}`, 4); | ||||||
|  |           checkForVideo(video, video.parentElement, true); | ||||||
|  |           foundNew = true; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       if (foundNew) { | ||||||
|  |         log(`Periodic scan found ${foundNew} new videos`, 4); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Run periodic scan every 3 seconds, but only if we have videos on the page | ||||||
|  |     setInterval(() => { | ||||||
|  |       if (tc.mediaElements.length > 0 || doc.querySelector(tc.settings.audioBoolean ? "video,audio" : "video")) { | ||||||
|  |         periodicScan(); | ||||||
|  |       } | ||||||
|  |     }, 3000); | ||||||
|  |  | ||||||
|  |     doc.vscPeriodicScanAttached = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   vscInitializedDocuments.add(doc); |   vscInitializedDocuments.add(doc); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -889,12 +1109,17 @@ function runAction(action, value, e) { | |||||||
|         controller.classList.toggle("vsc-hidden"); |         controller.classList.toggle("vsc-hidden"); | ||||||
|         break; |         break; | ||||||
|       case "blink": |       case "blink": | ||||||
|         if ( |         log(`Blink action: controller hidden=${controller.classList.contains("vsc-hidden")}, timeout=${controller.blinkTimeOut !== undefined}, duration=${numValue}`, 5); | ||||||
|           controller.classList.contains("vsc-hidden") || |  | ||||||
|           controller.blinkTimeOut !== undefined |         // Always clear existing timeout and show controller | ||||||
|         ) { |         if (controller.blinkTimeOut !== undefined) { | ||||||
|           clearTimeout(controller.blinkTimeOut); |           clearTimeout(controller.blinkTimeOut); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Always show the controller | ||||||
|         controller.classList.remove("vsc-hidden"); |         controller.classList.remove("vsc-hidden"); | ||||||
|  |         log(`Controller shown, setting timeout for ${numValue || 1000}ms`, 5); | ||||||
|  |  | ||||||
|         controller.blinkTimeOut = setTimeout(() => { |         controller.blinkTimeOut = setTimeout(() => { | ||||||
|           if ( |           if ( | ||||||
|             !( |             !( | ||||||
| @@ -903,10 +1128,12 @@ function runAction(action, value, e) { | |||||||
|             ) |             ) | ||||||
|           ) { |           ) { | ||||||
|             controller.classList.add("vsc-hidden"); |             controller.classList.add("vsc-hidden"); | ||||||
|  |             log("Controller auto-hidden after blink timeout", 5); | ||||||
|  |           } else { | ||||||
|  |             log("Controller kept visible (manual mode)", 5); | ||||||
|           } |           } | ||||||
|           controller.blinkTimeOut = undefined; |           controller.blinkTimeOut = undefined; | ||||||
|           }, numValue || 1000); // FIXED: Use numValue for consistency |         }, numValue || 1000); | ||||||
|         } |  | ||||||
|         break; |         break; | ||||||
|       case "drag": |       case "drag": | ||||||
|         if (e) handleDrag(v, e); |         if (e) handleDrag(v, e); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "name": "Video Speed Controller", |   "name": "Video Speed Controller", | ||||||
|   "short_name": "videospeed", |   "short_name": "videospeed", | ||||||
|   "version": "1.6.1", |   "version": "2.0.0", | ||||||
|   "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", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user