2 Commits

Author SHA1 Message Date
Josh Patra
ef86a70ca5 general performance updates 2025-11-25 10:44:52 -05:00
Josh Patra
5009e83f62 updates for gpu usage 2025-11-19 14:11:55 -05:00
3 changed files with 194 additions and 117 deletions

View File

@@ -63,14 +63,26 @@ def main():
except Exception as e: except Exception as e:
print(f"⚠️ Failed to remove {f}: {e}") print(f"⚠️ Failed to remove {f}: {e}")
# Read current version from manifest.json
current_dir = os.getcwd()
manifest_path = os.path.join(current_dir, TARGET_FILE)
current_version = "unknown"
if os.path.exists(manifest_path):
with open(manifest_path, "r", encoding="utf-8") as f:
for line in f:
match = re.match(r'\s*"version":\s*"([^"]+)"', line)
if match:
current_version = match.group(1)
break
print(f"📦 Current version: {current_version}")
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.")
return return
firefox_version = f"{base_version}.0" 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 # Step 1: Update manifest.json on disk to base_version
if os.path.exists(manifest_path): if os.path.exists(manifest_path):

187
inject.js
View File

@@ -24,8 +24,8 @@ var tc = {
`.replace(regStrip, ""), `.replace(regStrip, ""),
defaultLogLevel: 4, defaultLogLevel: 4,
logLevel: 5, // Set to 5 to see your debug logs logLevel: 5, // Set to 5 to see your debug logs
enableSubtitleNudge: true, enableSubtitleNudge: true, // Enabled by default, but only activates on YouTube
subtitleNudgeInterval: 25, subtitleNudgeInterval: 100, // Reduced from 25ms to 100ms (10x/sec instead of 40x/sec)
subtitleNudgeAmount: 0.001 subtitleNudgeAmount: 0.001
}, },
mediaElements: [], mediaElements: [],
@@ -123,7 +123,7 @@ chrome.storage.sync.get(tc.settings, function (storage) {
? Boolean(storage.enableSubtitleNudge) ? Boolean(storage.enableSubtitleNudge)
: tc.settings.enableSubtitleNudge; : tc.settings.enableSubtitleNudge;
tc.settings.subtitleNudgeInterval = tc.settings.subtitleNudgeInterval =
Number(storage.subtitleNudgeInterval) || 25; Number(storage.subtitleNudgeInterval) || 100; // Default 100ms for better performance
tc.settings.subtitleNudgeAmount = tc.settings.subtitleNudgeAmount =
Number(storage.subtitleNudgeAmount) || tc.settings.subtitleNudgeAmount; Number(storage.subtitleNudgeAmount) || tc.settings.subtitleNudgeAmount;
if ( if (
@@ -184,7 +184,7 @@ function defineVideoController() {
target.vsc = this; target.vsc = this;
this.video = target; this.video = target;
this.parent = target.parentElement || parent; this.parent = target.parentElement || parent;
this.nudgeIntervalId = null; this.nudgeAnimationId = null;
log(`Creating video controller for ${target.tagName} with src: ${target.src || target.currentSrc || 'none'}`, 4); log(`Creating video controller for ${target.tagName} with src: ${target.src || target.currentSrc || 'none'}`, 4);
@@ -367,62 +367,103 @@ function defineVideoController() {
this.video.currentSrc && this.video.currentSrc &&
this.video.currentSrc.includes("googlevideo.com")) || this.video.currentSrc.includes("googlevideo.com")) ||
location.hostname.includes("youtube.com"); location.hostname.includes("youtube.com");
if (!isYouTube) return;
if ( if (
!isYouTube ||
!tc.settings.enableSubtitleNudge || !tc.settings.enableSubtitleNudge ||
this.nudgeIntervalId !== null || this.nudgeAnimationId !== null ||
!this.video
) {
return;
}
if (this.video.paused || this.video.playbackRate === 1.0) {
this.stopSubtitleNudge();
return;
}
// Additional check to not start if paused
if (this.video.paused) {
return;
}
log(`Nudge: Starting interval: ${tc.settings.subtitleNudgeInterval}ms.`, 5);
this.nudgeIntervalId = setInterval(() => {
if (
!this.video || !this.video ||
this.video.paused || this.video.paused ||
this.video.ended || this.video.playbackRate === 1.0
this.video.playbackRate === 1.0 ||
tc.isNudging
) { ) {
return;
}
// Store the target speed so we can always revert to it
this.targetSpeed = this.video.playbackRate;
const performNudge = () => {
// Check if we should stop
if (!this.video || this.video.paused || this.video.playbackRate === 1.0) {
this.stopSubtitleNudge(); this.stopSubtitleNudge();
return; return;
} }
// Double-check pause state before nudging
if (this.video.paused) { // CRITICAL: Don't nudge if tab is hidden - prevents speed drift
this.stopSubtitleNudge(); if (document.hidden) {
this.nudgeAnimationId = setTimeout(performNudge, tc.settings.subtitleNudgeInterval);
return; return;
} }
const currentRate = this.video.playbackRate;
const nudgeAmount = tc.settings.subtitleNudgeAmount; // Set flag to prevent ratechange listener from interfering
tc.isNudging = true; tc.isNudging = true;
this.video.playbackRate = currentRate + nudgeAmount;
requestAnimationFrame(() => { // Cache values to avoid repeated property access
if ( const targetSpeed = this.targetSpeed;
this.video && const nudgeAmount = tc.settings.subtitleNudgeAmount;
Math.abs(this.video.playbackRate - (currentRate + nudgeAmount)) <
nudgeAmount * 1.5 // Apply nudge from the stored target speed (not current rate)
) { this.video.playbackRate = targetSpeed + nudgeAmount;
this.video.playbackRate = currentRate;
// Revert synchronously after a microtask to ensure it happens immediately
Promise.resolve().then(() => {
if (this.video && targetSpeed) {
this.video.playbackRate = targetSpeed;
} }
tc.isNudging = false; tc.isNudging = false;
}); });
}, tc.settings.subtitleNudgeInterval);
// Schedule next nudge
this.nudgeAnimationId = setTimeout(performNudge, tc.settings.subtitleNudgeInterval);
};
// Start the first nudge
this.nudgeAnimationId = setTimeout(performNudge, tc.settings.subtitleNudgeInterval);
log(`Nudge: Starting with interval ${tc.settings.subtitleNudgeInterval}ms.`, 5);
}; };
tc.videoController.prototype.stopSubtitleNudge = function () { tc.videoController.prototype.stopSubtitleNudge = function () {
if (this.nudgeIntervalId !== null) { if (this.nudgeAnimationId !== null) {
clearTimeout(this.nudgeAnimationId);
this.nudgeAnimationId = null;
log(`Nudge: Stopping.`, 5); log(`Nudge: Stopping.`, 5);
clearInterval(this.nudgeIntervalId);
this.nudgeIntervalId = null;
} }
// Clear the target speed when stopping
this.targetSpeed = null;
};
tc.videoController.prototype.performImmediateNudge = function () {
const isYouTube =
(this.video &&
this.video.currentSrc &&
this.video.currentSrc.includes("googlevideo.com")) ||
location.hostname.includes("youtube.com");
if (
!isYouTube ||
!tc.settings.enableSubtitleNudge ||
!this.video ||
this.video.paused ||
this.video.playbackRate === 1.0 ||
document.hidden
) {
return;
}
const targetRate = this.targetSpeed || this.video.playbackRate;
const nudgeAmount = tc.settings.subtitleNudgeAmount;
tc.isNudging = true;
this.video.playbackRate = targetRate + nudgeAmount;
// Revert synchronously via microtask
Promise.resolve().then(() => {
if (this.video) {
this.video.playbackRate = targetRate;
}
tc.isNudging = false;
});
log(`Immediate nudge performed at rate ${targetRate.toFixed(2)}`, 5);
}; };
tc.videoController.prototype.initializeControls = function () { tc.videoController.prototype.initializeControls = function () {
@@ -711,10 +752,21 @@ function initializeNow(doc, forceReinit = false) {
}); });
if (!doc.vscMutationObserverAttached) { if (!doc.vscMutationObserverAttached) {
// Throttle mutation processing to reduce CPU usage
let mutationProcessingScheduled = false;
let pendingMutations = [];
const observer = new MutationObserver(function (mutations) { const observer = new MutationObserver(function (mutations) {
pendingMutations.push(...mutations);
if (!mutationProcessingScheduled) {
mutationProcessingScheduled = true;
requestIdleCallback( requestIdleCallback(
(_) => { (_) => {
mutations.forEach(function (mutation) { const mutationsToProcess = pendingMutations.splice(0);
mutationProcessingScheduled = false;
mutationsToProcess.forEach(function (mutation) {
switch (mutation.type) { switch (mutation.type) {
case "childList": case "childList":
mutation.addedNodes.forEach(function (node) { mutation.addedNodes.forEach(function (node) {
@@ -742,6 +794,7 @@ function initializeNow(doc, forceReinit = false) {
target.attributes["aria-hidden"] && target.attributes["aria-hidden"] &&
target.attributes["aria-hidden"].value == "false" target.attributes["aria-hidden"].value == "false"
) { ) {
// Only scan shadow DOM if absolutely necessary (expensive operation)
var flattenedNodes = getShadow(document.body); var flattenedNodes = getShadow(document.body);
var node = flattenedNodes.filter( var node = flattenedNodes.filter(
(x) => x.tagName == "VIDEO" (x) => x.tagName == "VIDEO"
@@ -761,6 +814,7 @@ function initializeNow(doc, forceReinit = false) {
}, },
{ timeout: 1000 } { timeout: 1000 }
); );
}
}); });
function checkForVideo(node, parent, added) { function checkForVideo(node, parent, added) {
if (!added && document.body.contains(node)) return; if (!added && document.body.contains(node)) return;
@@ -802,7 +856,7 @@ function initializeNow(doc, forceReinit = false) {
} }
} }
observer.observe(doc, { observer.observe(doc, {
attributeFilter: ["aria-hidden", "src", "currentSrc", "style", "class"], attributeFilter: ["aria-hidden", "src", "currentSrc"], // Removed "style" and "class" for better performance
childList: true, childList: true,
subtree: true, subtree: true,
attributes: true attributes: true
@@ -932,11 +986,17 @@ function initializeNow(doc, forceReinit = false) {
// Listen for hashchange as well // Listen for hashchange as well
window.addEventListener('hashchange', () => handleNavigation('hashchange')); window.addEventListener('hashchange', () => handleNavigation('hashchange'));
// Also intercept fetch and XMLHttpRequest for AJAX-heavy sites // Throttle fetch-based video scanning to reduce CPU usage
let lastFetchScanTime = 0;
const FETCH_SCAN_THROTTLE = 2000; // Only scan once every 2 seconds max
const originalFetch = window.fetch; const originalFetch = window.fetch;
window.fetch = function (...args) { window.fetch = function (...args) {
return originalFetch.apply(this, args).then(response => { return originalFetch.apply(this, args).then(response => {
// After any fetch completes, check for new videos // Throttle video scanning after fetch to avoid excessive CPU usage
const now = Date.now();
if (now - lastFetchScanTime > FETCH_SCAN_THROTTLE) {
lastFetchScanTime = now;
setTimeout(() => { setTimeout(() => {
const q = tc.settings.audioBoolean ? "video,audio" : "video"; const q = tc.settings.audioBoolean ? "video,audio" : "video";
const videos = document.querySelectorAll(q); const videos = document.querySelectorAll(q);
@@ -947,6 +1007,7 @@ function initializeNow(doc, forceReinit = false) {
} }
}); });
}, 200); }, 200);
}
return response; return response;
}); });
}; };
@@ -959,27 +1020,27 @@ function initializeNow(doc, forceReinit = false) {
const periodicScan = () => { const periodicScan = () => {
const q = tc.settings.audioBoolean ? "video,audio" : "video"; const q = tc.settings.audioBoolean ? "video,audio" : "video";
const allVideos = doc.querySelectorAll(q); const allVideos = doc.querySelectorAll(q);
let foundNew = false; let foundNewCount = 0;
allVideos.forEach(video => { allVideos.forEach(video => {
if (!video.vsc && (video.src || video.currentSrc || video.readyState > 0)) { if (!video.vsc && (video.src || video.currentSrc || video.readyState > 0)) {
log(`Periodic scan found missed ${video.tagName.toLowerCase()}`, 4); log(`Periodic scan found missed ${video.tagName.toLowerCase()}`, 4);
checkForVideo(video, video.parentElement, true); checkForVideo(video, video.parentElement, true);
foundNew = true; foundNewCount++;
} }
}); });
if (foundNew) { if (foundNewCount > 0) {
log(`Periodic scan found ${foundNew} new videos`, 4); log(`Periodic scan found ${foundNewCount} new videos`, 4);
} }
}; };
// Run periodic scan every 3 seconds, but only if we have videos on the page // Run periodic scan every 5 seconds (reduced frequency), only if we have videos
setInterval(() => { setInterval(() => {
if (tc.mediaElements.length > 0 || doc.querySelector(tc.settings.audioBoolean ? "video,audio" : "video")) { if (tc.mediaElements.length > 0 || doc.querySelector(tc.settings.audioBoolean ? "video,audio" : "video")) {
periodicScan(); periodicScan();
} }
}, 3000); }, 5000); // Increased from 3s to 5s for better performance
doc.vscPeriodicScanAttached = true; doc.vscPeriodicScanAttached = true;
} }
@@ -999,6 +1060,9 @@ 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);
// Update the target speed for nudge so it knows what to revert to
video.vsc.targetSpeed = numericSpeed;
if (isUserKeyPress && !isInitialCall && video.vsc && video.vsc.div) { if (isUserKeyPress && !isInitialCall && video.vsc && video.vsc.div) {
runAction("blink", 1000, null, video); // Pass video to blink runAction("blink", 1000, null, video); // Pass video to blink
} }
@@ -1085,18 +1149,19 @@ function runAction(action, value, e) {
v.currentTime += numValue; v.currentTime += numValue;
break; break;
case "faster": case "faster":
setSpeed( // Round to the step precision to avoid floating-point issues (e.g., 1.80 + 0.1 = 1.9000000000000001)
v, var fasterStep = numValue;
Math.min( var fasterPrecision = Math.round(1 / fasterStep); // e.g., 0.1 -> 10, 0.05 -> 20, 0.25 -> 4
(v.playbackRate < 0.07 ? 0.07 : v.playbackRate) + numValue, var newFasterSpeed = (v.playbackRate < 0.07 ? 0.07 : v.playbackRate) + fasterStep;
16 newFasterSpeed = Math.round(newFasterSpeed * fasterPrecision) / fasterPrecision;
), setSpeed(v, Math.min(newFasterSpeed, 16), false, true);
false,
true
);
break; break;
case "slower": case "slower":
setSpeed(v, Math.max(v.playbackRate - numValue, 0.07), false, true); var slowerStep = numValue;
var slowerPrecision = Math.round(1 / slowerStep);
var newSlowerSpeed = v.playbackRate - slowerStep;
newSlowerSpeed = Math.round(newSlowerSpeed * slowerPrecision) / slowerPrecision;
setSpeed(v, Math.max(newSlowerSpeed, 0.07), false, true);
break; break;
case "reset": case "reset":
resetSpeed(v, 1.0, false); // Use enhanced resetSpeed resetSpeed(v, 1.0, false); // Use enhanced resetSpeed

View File

@@ -1,7 +1,7 @@
{ {
"name": "Video Speed Controller", "name": "Video Speed Controller",
"short_name": "videospeed", "short_name": "videospeed",
"version": "2.0.0", "version": "2.1.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",