From 43dc8b773b50e8fb9de3f6a61fced35a092d7c4a Mon Sep 17 00:00:00 2001 From: Josh Patra <30350506+SoPat712@users.noreply.github.com> Date: Mon, 19 May 2025 13:15:20 -0400 Subject: [PATCH] fix appear after hiding --- inject.js | 480 +++++++++++++++++++++----------------------------- manifest.json | 2 +- 2 files changed, 205 insertions(+), 277 deletions(-) diff --git a/inject.js b/inject.js index 201aec2..7cc1cac 100644 --- a/inject.js +++ b/inject.js @@ -21,13 +21,12 @@ var tc = { `.replace(regStrip, ""), defaultLogLevel: 4, logLevel: 3, - // --- Nudge settings (ADDED) --- enableSubtitleNudge: true, subtitleNudgeInterval: 25, subtitleNudgeAmount: 0.001 }, mediaElements: [], - isNudging: false // ADDED: Flag for nudge operation + isNudging: false }; /* Log levels */ @@ -48,7 +47,6 @@ function log(message, level) { } chrome.storage.sync.get(tc.settings, function (storage) { - // MODIFIED: Robust keyBinding initialization tc.settings.keyBindings = Array.isArray(storage.keyBindings) && storage.keyBindings.length > 0 && @@ -104,11 +102,10 @@ chrome.storage.sync.get(tc.settings, function (storage) { (storage.keyBindings.length > 0 && !storage.keyBindings[0].hasOwnProperty("predefined")) ) { - log("Initializing/Updating keybindings in storage.", 4); chrome.storage.sync.set({ keyBindings: tc.settings.keyBindings, - version: "0.6.3.10" - }); // Update version + version: "0.6.3.13" + }); // Incremented } tc.settings.lastSpeed = Number(storage.lastSpeed) || 1.0; @@ -121,11 +118,8 @@ chrome.storage.sync.get(tc.settings, function (storage) { tc.settings.startHidden = Boolean(storage.startHidden); tc.settings.controllerOpacity = Number(storage.controllerOpacity) || 0.3; tc.settings.blacklist = String(storage.blacklist || tc.settings.blacklist); - if (typeof storage.logLevel !== "undefined") tc.settings.logLevel = Number(storage.logLevel); - - // ADDED: Load nudge settings tc.settings.enableSubtitleNudge = typeof storage.enableSubtitleNudge !== "undefined" ? Boolean(storage.enableSubtitleNudge) @@ -150,14 +144,13 @@ chrome.storage.sync.get(tc.settings, function (storage) { }); function getKeyBindings(action, what = "value") { - // Original getKeyBindings + /* ... Same as your provided ... */ if (!tc.settings.keyBindings) return false; try { const binding = tc.settings.keyBindings.find( (item) => item.action === action ); if (binding) return binding[what]; - // Fallbacks from original if (what === "value") { if (action === "slower" || action === "faster") return 0.1; if (action === "rewind" || action === "advance") return 10; @@ -170,30 +163,25 @@ function getKeyBindings(action, what = "value") { return false; } } - -// Original setKeyBindings from your provided code (used by original resetSpeed) function setKeyBindings(action, value) { + /* ... Same as your provided ... */ if (!tc.settings.keyBindings) return; const binding = tc.settings.keyBindings.find( (item) => item.action === action ); - if (binding) { - binding["value"] = value; - } + if (binding) binding["value"] = value; } function defineVideoController() { tc.videoController = function (target, parent) { if (target.vsc) return target.vsc; - log(`Creating VSC controller for ${target.src || "video"}.`, 4); tc.mediaElements.push(target); target.vsc = this; - this.video = target; this.parent = parent || target.parentElement; - this.nudgeIntervalId = null; // ADDED for nudge + this.nudgeIntervalId = null; - let storedSpeed; // Original logic + let storedSpeed; if (!tc.settings.rememberSpeed) { storedSpeed = tc.settings.speeds[target.currentSrc]; if (!storedSpeed) storedSpeed = 1.0; @@ -207,7 +195,7 @@ function defineVideoController() { this.div = this.initializeControls(); if (Math.abs(target.playbackRate - storedSpeed) > 0.001) { - setSpeed(target, storedSpeed, true); // MODIFIED: Pass true for isInitialSet + setSpeed(target, storedSpeed, true, false); // isInitialCall=true, isUserKeyPress=false } else { if (this.speedIndicator) this.speedIndicator.textContent = storedSpeed.toFixed(2); @@ -220,7 +208,6 @@ function defineVideoController() { } var mediaEventAction = function (event) { - // Original mediaEventAction const video = event.target; if (!video.vsc) return; let speedToSet = tc.settings.speeds[video.currentSrc]; @@ -233,9 +220,9 @@ function defineVideoController() { if (tc.settings.forceLastSavedSpeed) speedToSet = tc.settings.lastSpeed; if (Math.abs(video.playbackRate - speedToSet) > 0.001) { - setSpeed(video, speedToSet, false); // MODIFIED: isInitialSet is false + // Speed corrections from play/seek are not direct user key presses for blink + setSpeed(video, speedToSet, false, false); // isInitialCall=false, isUserKeyPress=false } - // ADDED: Manage nudge if (event.type === "play") video.vsc.startSubtitleNudge(); else if (event.type === "pause" || event.type === "ended") video.vsc.stopSubtitleNudge(); @@ -248,18 +235,17 @@ function defineVideoController() { target.addEventListener( "pause", (this.handlePause = mediaEventAction.bind(this)) - ); // ADDED + ); target.addEventListener( "ended", (this.handleEnded = mediaEventAction.bind(this)) - ); // ADDED + ); target.addEventListener( "seeked", (this.handleSeek = mediaEventAction.bind(this)) ); var srcObserver = new MutationObserver((mutations) => { - // Original srcObserver mutations.forEach((mutation) => { if ( mutation.type === "attributes" && @@ -267,7 +253,7 @@ function defineVideoController() { mutation.attributeName === "currentSrc") ) { if (!this.div) return; - this.stopSubtitleNudge(); // ADDED + this.stopSubtitleNudge(); if (!mutation.target.src && !mutation.target.currentSrc) this.div.classList.add("vsc-nosource"); else { @@ -280,9 +266,9 @@ function defineVideoController() { } if (tc.settings.forceLastSavedSpeed) newSrcSpeed = tc.settings.lastSpeed; - setSpeed(mutation.target, newSrcSpeed, true); // MODIFIED: isInitialSet = true + setSpeed(mutation.target, newSrcSpeed, true, false); // isInitialCall=true, isUserKeyPress=false if (!mutation.target.paused && mutation.target.playbackRate !== 1.0) - this.startSubtitleNudge(); // ADDED + this.startSubtitleNudge(); } } }); @@ -290,12 +276,12 @@ function defineVideoController() { srcObserver.observe(target, { attributeFilter: ["src", "currentSrc"] }); if (!target.paused && target.playbackRate !== 1.0) - this.startSubtitleNudge(); // ADDED + this.startSubtitleNudge(); }; - // --- Nudge Methods (ADDED) --- tc.videoController.prototype.startSubtitleNudge = function () { - if (!location.hostname.includes("youtube.com")) return; // ADDED: Nudge only on YouTube + /* ... Same as your provided ... */ + if (!location.hostname.includes("youtube.com")) return; if ( !tc.settings.enableSubtitleNudge || this.nudgeIntervalId !== null || @@ -306,7 +292,6 @@ function defineVideoController() { this.stopSubtitleNudge(); return; } - log(`Nudge: Starting interval: ${tc.settings.subtitleNudgeInterval}ms.`, 5); this.nudgeIntervalId = setInterval(() => { if ( !this.video || @@ -334,31 +319,28 @@ function defineVideoController() { }, tc.settings.subtitleNudgeInterval); }; tc.videoController.prototype.stopSubtitleNudge = function () { + /* ... Same as your provided ... */ if (this.nudgeIntervalId !== null) { - log(`Nudge: Stopping.`, 5); clearInterval(this.nudgeIntervalId); this.nudgeIntervalId = null; } }; - tc.videoController.prototype.remove = function () { - // Original remove - this.stopSubtitleNudge(); // ADDED + /* ... Same as your provided ... */ + this.stopSubtitleNudge(); if (this.div && this.div.parentNode) this.div.remove(); if (this.video) { this.video.removeEventListener("play", this.handlePlay); - this.video.removeEventListener("pause", this.handlePause); // ADDED - this.video.removeEventListener("ended", this.handleEnded); // ADDED - this.video.removeEventListener("seeked", this.handleSeek); // Original had "seek" + this.video.removeEventListener("pause", this.handlePause); + this.video.removeEventListener("ended", this.handleEnded); + this.video.removeEventListener("seeked", this.handleSeek); delete this.video.vsc; } let idx = tc.mediaElements.indexOf(this.video); if (idx !== -1) tc.mediaElements.splice(idx, 1); }; - tc.videoController.prototype.initializeControls = function () { - // Original initializeControls - log("initializeControls Begin", 5); + /* ... Same as your provided ... */ const doc = this.video.ownerDocument; const speedForUI = this.video.playbackRate.toFixed(2); var top = Math.max(this.video.offsetTop, 0) + "px", @@ -369,21 +351,8 @@ function defineVideoController() { wrapper.classList.add("vsc-nosource"); if (tc.settings.startHidden) wrapper.classList.add("vsc-hidden"); var shadow = wrapper.attachShadow({ mode: "open" }); - shadow.innerHTML = ` - -
- ${speedForUI} - - - - - - - -
`; - this.speedIndicator = shadow.querySelector(".draggable"); // MODIFIED: Original was "span" - - // MODIFIED: Pass this.video as 4th arg to runAction + shadow.innerHTML = `
${speedForUI}
`; + this.speedIndicator = shadow.querySelector(".draggable"); shadow.querySelector(".draggable").addEventListener( "mousedown", (e) => { @@ -425,7 +394,6 @@ function defineVideoController() { doc.body.appendChild(fragment); return wrapper; } - // Original placement logic switch (true) { case location.hostname == "www.amazon.com": case location.hostname == "www.reddit.com": @@ -456,70 +424,58 @@ function defineVideoController() { function escapeStringRegExp(str) { const m = /[|\\{}()[\]^$+*?.]/g; return str.replace(m, "\\$&"); -} // Original +} function isBlacklisted() { - /* ... same original logic (with robust regex) ... */ + /* ... Same as your provided ... */ let blacklisted = false; - const blacklistLines = tc.settings.blacklist - ? tc.settings.blacklist.split("\n") - : []; - blacklistLines.forEach((match) => { + const bl = tc.settings.blacklist ? tc.settings.blacklist.split("\n") : []; + bl.forEach((m) => { if (blacklisted) return; - match = match.replace(regStrip, ""); - if (match.length == 0) return; - let regexp; - if (match.startsWith("/") && match.lastIndexOf("/") > 0) { + m = m.replace(regStrip, ""); + if (m.length == 0) return; + let rgx; + if (m.startsWith("/") && m.lastIndexOf("/") > 0) { try { - const ls = match.lastIndexOf("/"); - regexp = new RegExp(match.substring(1, ls), match.substring(ls + 1)); - } catch (err) { - log(`Invalid regex in blacklist: ${match}. Error: ${err.message}`, 2); + const ls = m.lastIndexOf("/"); + rgx = new RegExp(m.substring(1, ls), m.substring(ls + 1)); + } catch (e) { + log(`Invalid regex: ${m}. ${e.message}`, 2); return; } - } else regexp = new RegExp(escapeStringRegExp(match)); - if (regexp && regexp.test(location.href)) blacklisted = true; + } else rgx = new RegExp(escapeStringRegExp(m)); + if (rgx && rgx.test(location.href)) blacklisted = true; }); - if (blacklisted) log(`Page ${location.href} is blacklisted.`, 4); + if (blacklisted) log(`Page ${location.href} blacklisted.`, 4); return blacklisted; } - -var coolDown = false; // Original coolDown +var coolDown = false; function refreshCoolDown() { - // Original refreshCoolDown - log("Begin refreshCoolDown", 5); + /* ... Same as your provided ... */ if (coolDown) clearTimeout(coolDown); - coolDown = setTimeout(function () { + coolDown = setTimeout(() => { coolDown = false; }, 1000); } function setupListener() { - if (document.vscRateListenerAttached) return; // Original flag was vscRateChangeListenerAttached + if (document.vscRateListenerAttached) return; - // MODIFIED: fromUserInput parameter added - function updateSpeedFromEvent(video, fromUserInput = false) { + // MODIFIED: updateSpeedFromEvent NO LONGER calls runAction("blink") + function updateSpeedFromEvent(video) { if (!video.vsc || !video.vsc.speedIndicator) return; var speed = Number(video.playbackRate.toFixed(2)); - log( - `updateSpeedFromEvent: Rate is ${speed}. FromUserInput: ${fromUserInput}`, - 4 - ); + log(`updateSpeedFromEvent: Rate is ${speed}.`, 4); // Removed fromUserInput from this log video.vsc.speedIndicator.textContent = speed.toFixed(2); tc.settings.speeds[video.currentSrc || "unknown_src"] = speed; tc.settings.lastSpeed = speed; chrome.storage.sync.set({ lastSpeed: speed }, () => { - /* error handling if needed */ + /* ... */ }); - // MODIFIED: Only "blink" (show controller) if change was from user input - if (fromUserInput) { - // The original runAction("blink", null, null) implies value is taken from keybinding or default - runAction("blink", getKeyBindings("blink", "value") || 1000, null, video); - } + // runAction("blink") is now called directly from setSpeed if it's a user key press. if (video.vsc) { - // MODIFIED: Manage nudge based on new speed if (speed === 1.0 || video.paused) video.vsc.stopSubtitleNudge(); else video.vsc.startSubtitleNudge(); } @@ -528,14 +484,10 @@ function setupListener() { document.addEventListener( "ratechange", function (event) { - // ADDED: Check tc.isNudging at the very start - if (tc.isNudging) { - return; - } + if (tc.isNudging) return; // Ignore nudge's own rate changes for VSC UI/state logic - // Original coolDown logic if (coolDown) { - log("Speed event propagation blocked by coolDown", 4); + log("Blocked by coolDown", 4); event.stopImmediatePropagation(); return; } @@ -545,58 +497,51 @@ function setupListener() { return; const eventOrigin = event.detail && event.detail.origin; - let isFromUserInputForBlink = false; + // The `fromUserInput` flag that was passed to updateSpeedFromEvent is removed from here. + // updateSpeedFromEvent now just updates state. Blinking is handled by setSpeed. if (tc.settings.forceLastSavedSpeed) { if (eventOrigin === "videoSpeed") { + // This "videoSpeed" event is dispatched by setSpeed when forceLastSavedSpeed is true. + // setSpeed itself will handle blinking if it was a user key press. if (event.detail.speed) { const detailSpeedNum = Number(event.detail.speed); if ( !isNaN(detailSpeedNum) && Math.abs(video.playbackRate - detailSpeedNum) > 0.001 ) { - video.playbackRate = detailSpeedNum; + video.playbackRate = detailSpeedNum; // As per original forceLastSavedSpeed logic } } - // Use fromUserInput from event.detail if present (set by setSpeed) - isFromUserInputForBlink = event.detail.fromUserInput !== false; - updateSpeedFromEvent(video, isFromUserInputForBlink); - event.stopImmediatePropagation(); + updateSpeedFromEvent(video); // Update state + event.stopImmediatePropagation(); // Original behavior } else { + // Native event when forceLastSavedSpeed is ON if (Math.abs(video.playbackRate - tc.settings.lastSpeed) > 0.001) { video.playbackRate = tc.settings.lastSpeed; event.stopImmediatePropagation(); + // The next ratechange (from VSC forcing it) will call updateSpeedFromEvent. } else { - updateSpeedFromEvent(video, false); + updateSpeedFromEvent(video); // Just confirming, no blink needed from here } } } else { // forceLastSavedSpeed is OFF - // If setSpeed was called (which sets video.vscIsDirectSetByVSC) - // then it's a user-driven change (or initial set). - isFromUserInputForBlink = video.vscIsDirectSetByVSC === true; - if (video.hasOwnProperty("vscIsDirectSetByVSC")) - delete video.vscIsDirectSetByVSC; // Consume flag - - updateSpeedFromEvent(video, isFromUserInputForBlink); - // DO NOT stop propagation when forceLastSavedSpeed is OFF + updateSpeedFromEvent(video); // Update state + // DO NOT stop propagation } }, true ); - document.vscRateListenerAttached = true; // Original flag name + document.vscRateListenerAttached = true; } -// Original initializeWhenReady and initializeNow structure, with unique flags for re-entrancy checks var vscInitializedDocuments = new Set(); function initializeWhenReady(doc) { + /* ... Same robust init ... */ if (doc.vscInitWhenReadyUniqueFlag1 && doc.readyState !== "loading") return; doc.vscInitWhenReadyUniqueFlag1 = true; if (isBlacklisted()) return; - log( - `initializeWhenReady for: ${doc.location ? doc.location.href : "iframe"}. RS: ${doc.readyState}`, - 5 - ); if (doc === window.document && !window.vscPageLoadListenerUniqueFlag1) { window.addEventListener("load", () => initializeNow(window.document), { once: true @@ -615,40 +560,33 @@ function initializeWhenReady(doc) { } } function inIframe() { - try { + /* ... Same ... */ try { return window.self !== window.top; } catch (e) { return true; } } function getShadow(parent) { - /* ... original logic ... */ - let result = []; - function getChild(p) { + /* ... Same ... */ let r = []; + function gC(p) { if (p.firstElementChild) { var c = p.firstElementChild; do { - result.push(c); - getChild(c); - if (c.shadowRoot) result.push(...getShadow(c.shadowRoot)); + r.push(c); + gC(c); + if (c.shadowRoot) r.push(...getShadow(c.shadowRoot)); c = c.nextElementSibling; } while (c); } } - getChild(parent); - return result; + gC(parent); + return r; } function initializeNow(doc) { + /* ... Same robust init, ensuring tc.videoController is defined ... */ if (vscInitializedDocuments.has(doc) || !doc.body) return; - log( - `initializeNow for doc: ${doc.location ? doc.location.href : "iframe"}`, - 4 - ); - if (!tc.settings.enabled) { - log("VSC disabled.", 4); - return; - } + if (!tc.settings.enabled) return; if (!doc.body.classList.contains("vsc-initialized")) doc.body.classList.add("vsc-initialized"); if (typeof tc.videoController === "undefined") defineVideoController(); @@ -658,78 +596,69 @@ function initializeNow(doc) { doc !== window.top.document && !doc.head.querySelector('link[href*="inject.css"]') ) { - var link = doc.createElement("link"); - link.href = chrome.runtime.getURL("inject.css"); - link.type = "text/css"; - link.rel = "stylesheet"; - doc.head.appendChild(link); + var l = doc.createElement("link"); + l.href = chrome.runtime.getURL("inject.css"); + l.type = "text/css"; + l.rel = "stylesheet"; + doc.head.appendChild(l); } - const docsForKeydown = new Set([doc]); + const dFK = new Set([doc]); try { - if (inIframe() && window.top.document) - docsForKeydown.add(window.top.document); + if (inIframe() && window.top.document) dFK.add(window.top.document); } catch (e) {} - docsForKeydown.forEach((lDoc) => { - if (!lDoc.vscKeydownListenerUniqueFlagC) { - // Unique flag name - lDoc.addEventListener( + dFK.forEach((lD) => { + if (!lD.vscKDLFlagC) { + lD.addEventListener( "keydown", - function (event) { + function (evt) { if (!tc.settings.enabled) return; - const target = event.target; + const tgt = evt.target; if ( - target.nodeName === "INPUT" || - target.nodeName === "TEXTAREA" || - target.isContentEditable + tgt.nodeName === "INPUT" || + tgt.nodeName === "TEXTAREA" || + tgt.isContentEditable ) return; if ( - event.getModifierState && - (event.getModifierState("Alt") || - event.getModifierState("Control") || - event.getModifierState("Meta") || - event.getModifierState("Fn") || - event.getModifierState("Hyper") || - event.getModifierState("OS")) + evt.getModifierState && + (evt.getModifierState("Alt") || + evt.getModifierState("Control") || + evt.getModifierState("Meta") || + evt.getModifierState("Fn") || + evt.getModifierState("Hyper") || + evt.getModifierState("OS")) ) return; - if ( - tc.mediaElements.length === 0 && - !lDoc.querySelector("video,audio") - ) + if (tc.mediaElements.length === 0 && !lD.querySelector("video,audio")) return; - var item = tc.settings.keyBindings.find( - (kb) => kb.key === event.keyCode - ); - if (item) { - runAction(item.action, item.value, event); - if (item.force === "true" || item.force === true) { - event.preventDefault(); - event.stopPropagation(); + var itm = tc.settings.keyBindings.find((k) => k.key === evt.keyCode); + if (itm) { + runAction(itm.action, itm.value, evt); + if (itm.force === "true" || itm.force === true) { + evt.preventDefault(); + evt.stopPropagation(); } } }, true ); - lDoc.vscKeydownListenerUniqueFlagC = true; + lD.vscKDLFlagC = true; } }); - if (!doc.vscMutationObserverUniqueFlagC) { - // Unique flag name - const obs = new MutationObserver((muts) => { + if (!doc.vscMOFlagC) { + const o = new MutationObserver((m) => { if (typeof requestIdleCallback === "function") - requestIdleCallback(() => processMutations(muts), { timeout: 1000 }); - else setTimeout(() => processMutations(muts), 200); + requestIdleCallback(() => pM(m), { timeout: 1000 }); + else setTimeout(() => pM(m), 200); }); - function processMutations(mList) { - for (const m of mList) { + function pM(ml) { + for (const m of ml) { if (m.type === "childList") { m.addedNodes.forEach((n) => { - if (n instanceof Element) chkVid(n, n.parentNode || m.target, true); + if (n instanceof Element) cV(n, n.parentNode || m.target, true); }); m.removedNodes.forEach((n) => { - if (n instanceof Element) - chkVid(n, n.parentNode || m.target, false); + if (n instanceof Element) cV(n, n.parentNode || m.target, false); }); } else if ( m.type === "attributes" && @@ -737,59 +666,59 @@ function initializeNow(doc) { m.target instanceof Element && m.target.getAttribute("aria-hidden") === "false" ) { - const vidsInTgt = Array.from(getShadow(m.target)).filter( + const vIT = Array.from(getShadow(m.target)).filter( (el) => el.tagName === "VIDEO" ); - vidsInTgt.forEach((vEl) => { - if (!vEl.vsc) chkVid(vEl, vEl.parentNode || m.target, true); + vIT.forEach((vE) => { + if (!vE.vsc) cV(vE, vE.parentNode || m.target, true); }); } } } - function chkVid(n, p, add) { - if (!add && !n.isConnected) { - } else if (!add && n.isConnected) return; + function cV(n, p, a) { + if (!a && !n.isConnected) { + } else if (!a && n.isConnected) return; if ( n.nodeName === "VIDEO" || (n.nodeName === "AUDIO" && tc.settings.audioBoolean) ) { - if (add) { + if (a) { if (!n.vsc) new tc.videoController(n, p); } else { if (n.vsc) n.vsc.remove(); } } else if (n.children && n.children.length > 0) { for (let i = 0; i < n.children.length; i++) - chkVid(n.children[i], n.children[i].parentNode || p, add); + cV(n.children[i], n.children[i].parentNode || p, a); } } - obs.observe(doc, { + o.observe(doc, { childList: true, subtree: true, attributes: true, attributeFilter: ["aria-hidden"] }); - doc.vscMutationObserverUniqueFlagC = true; + doc.vscMOFlagC = true; } const q = tc.settings.audioBoolean ? "video,audio" : "video"; - doc.querySelectorAll(q).forEach((vid) => { - if (!vid.vsc) new tc.videoController(vid, vid.parentElement); + doc.querySelectorAll(q).forEach((v) => { + if (!v.vsc) new tc.videoController(v, v.parentElement); }); - Array.from(doc.getElementsByTagName("iframe")).forEach((fr) => { + Array.from(doc.getElementsByTagName("iframe")).forEach((f) => { try { - if (fr.contentDocument) initializeWhenReady(fr.contentDocument); + if (f.contentDocument) initializeWhenReady(f.contentDocument); } catch (e) {} }); vscInitializedDocuments.add(doc); } -// MODIFIED setSpeed to accept `isInitialCall` and pass it for `fromUserInput` in custom event -function setSpeed(video, speed, isInitialCall = false) { +// MODIFIED: setSpeed now takes `isInitialCall` and `isUserKeyPress` +function setSpeed(video, speed, isInitialCall = false, isUserKeyPress = false) { const numericSpeed = Number(speed); if (isNaN(numericSpeed) || numericSpeed <= 0 || numericSpeed > 16) return; if (!video || !video.vsc || !video.vsc.speedIndicator) return; log( - `setSpeed: Target ${numericSpeed.toFixed(2)}. Initial: ${isInitialCall}`, + `setSpeed: Target ${numericSpeed.toFixed(2)}. Initial: ${isInitialCall}. UserKeyPress: ${isUserKeyPress}`, 4 ); @@ -799,53 +728,56 @@ function setSpeed(video, speed, isInitialCall = false) { if (tc.settings.forceLastSavedSpeed) { video.dispatchEvent( new CustomEvent("ratechange", { + // Pass `isUserKeyPress` as `fromUserInput` for the custom event detail: { origin: "videoSpeed", speed: numericSpeed.toFixed(2), - fromUserInput: !isInitialCall + fromUserInput: isUserKeyPress } }) ); } else { if (Math.abs(video.playbackRate - numericSpeed) > 0.001) { - // ADDED: Set a temporary flag on the video element itself before changing playbackRate - // This helps the native ratechange event handler determine if VSC initiated this change. - if (!isInitialCall) { - video.vscIsDirectlySettingRate = true; - } video.playbackRate = numericSpeed; } } - if (!isInitialCall) refreshCoolDown(); // Original call + + if (!isInitialCall) refreshCoolDown(); // Original call, only for non-initial sets + + // 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 (numericSpeed === 1.0 || video.paused) video.vsc.stopSubtitleNudge(); else video.vsc.startSubtitleNudge(); } } -// MODIFIED runAction for specificVideo targeting and passing `isInitialCall=false` to setSpeed +// MODIFIED: runAction passes `isUserKeyPress=true` to setSpeed for relevant actions function runAction(action, value, e, specificVideo = null) { - var mediaTagsToProcess = []; + var mediaTagsToProcess = []; // ... (same robust mediaTagsToProcess logic as before) ... if (specificVideo) mediaTagsToProcess = [specificVideo]; else if (e && e.target) { - const docContext = e.target.ownerDocument || document; - let activeVideo = tc.mediaElements.find( + const dC = e.target.ownerDocument || document; + let aV = tc.mediaElements.find( (v) => - v.ownerDocument === docContext && - (docContext.activeElement === v || v.contains(docContext.activeElement)) + v.ownerDocument === dC && + (dC.activeElement === v || v.contains(dC.activeElement)) ); - if (activeVideo) mediaTagsToProcess = [activeVideo]; + if (aV) mediaTagsToProcess = [aV]; else { - activeVideo = tc.mediaElements.find( + aV = tc.mediaElements.find( (v) => - v.ownerDocument === docContext && + v.ownerDocument === dC && v.offsetParent !== null && (!v.paused || v.readyState > 0) ); - if (activeVideo) mediaTagsToProcess = [activeVideo]; + if (aV) mediaTagsToProcess = [aV]; else { mediaTagsToProcess = tc.mediaElements.filter( - (v) => v.ownerDocument === docContext + (v) => v.ownerDocument === dC ); if (mediaTagsToProcess.length === 0 && tc.mediaElements.length > 0) mediaTagsToProcess = [tc.mediaElements[0]]; @@ -859,8 +791,6 @@ function runAction(action, value, e, specificVideo = null) { e && e.target && e.target.getRootNode && e.target.getRootNode().host ? e.target.getRootNode().host : null; - - // Store current action for original resetSpeed context (local to this runAction call) const currentActionContext = action; mediaTagsToProcess.forEach(function (v) { @@ -874,7 +804,7 @@ function runAction(action, value, e, specificVideo = null) { return; if (action === "blink" && specificVideo && v !== specificVideo) return; - // MODIFIED: showController only for explicit user-driven actions, not "blink" itself + // Original showController logic (not tied to `isUserKeyPress` here, runAction("blink") is separate) const userDrivenActionsThatShowController = [ "rewind", "advance", @@ -901,6 +831,7 @@ function runAction(action, value, e, specificVideo = null) { case "advance": v.currentTime += numValue; break; + // MODIFIED: Pass `isUserKeyPress = true` case "faster": setSpeed( v, @@ -908,13 +839,13 @@ function runAction(action, value, e, specificVideo = null) { (v.playbackRate < 0.07 ? 0.07 : v.playbackRate) + numValue, 16 ), - false + false, + true ); break; case "slower": - setSpeed(v, Math.max(v.playbackRate - numValue, 0.07), false); + setSpeed(v, Math.max(v.playbackRate - numValue, 0.07), false, true); break; - // MODIFIED: Passing currentActionContext to original resetSpeed case "reset": resetSpeed(v, 1.0, currentActionContext); break; @@ -925,7 +856,7 @@ function runAction(action, value, e, specificVideo = null) { controllerDiv.classList.add("vsc-manual"); controllerDiv.classList.toggle("vsc-hidden"); break; - case "blink": + case "blink": // This action is now mostly called by setSpeed itself for user key presses if ( controllerDiv.classList.contains("vsc-hidden") || controllerDiv.blinkTimeOut !== undefined @@ -967,101 +898,98 @@ function runAction(action, value, e, specificVideo = null) { } }); } -// Removed global actionBeingProcessedForReset_global function pause(v) { - if (v.paused) v.play().catch((e) => log(`Play err:${e.message}`, 2)); + /* ... Same as your original ... */ if (v.paused) + v.play().catch((e) => log(`Play err:${e.message}`, 2)); else v.pause(); } -// MODIFIED: Original resetSpeed function now takes currentActionContext +// MODIFIED: `resetSpeed` now calls `setSpeed` with `isUserKeyPress = true` function resetSpeed(v, target, currentActionContext = null) { log( `resetSpeed (original): Video current: ${v.playbackRate.toFixed(2)}, Target: ${target.toFixed(2)}, Context: ${currentActionContext}`, 4 ); - if (Math.abs(v.playbackRate - target) < 0.01) { - // Using Math.abs for float comparison - // If current speed IS the target of THIS action if (v.playbackRate === (getKeyBindings("reset", "value") || 1.0)) { if (target !== 1.0) { - setSpeed(v, 1.0, false); + setSpeed(v, 1.0, false, true); // isInitial=false, isUserKeyPress=true } else { - // Target is 1.0 and current is 1.0 (or what reset key last stored) -> go to fast - setSpeed(v, getKeyBindings("fast", "value"), false); + setSpeed(v, getKeyBindings("fast", "value"), false, true); // isInitial=false, isUserKeyPress=true } } else { - // Current is target, but not what 'reset' binding value holds (e.g. G pressed, current is fast speed) - setSpeed(v, getKeyBindings("reset", "value") || 1.0, false); + setSpeed(v, getKeyBindings("reset", "value") || 1.0, false, true); // isInitial=false, isUserKeyPress=true } } else { - // Current speed is NOT the target of this action. Set to the target. if (currentActionContext === "reset") { - // Only do this for 'reset' action context - setKeyBindings("reset", v.playbackRate); // Original call to store current rate + setKeyBindings("reset", v.playbackRate); } - setSpeed(v, target, false); + setSpeed(v, target, false, true); // isInitial=false, isUserKeyPress=true } } function muted(v) { - v.muted = !v.muted; + /* ... Same as your original ... */ v.muted = !v.muted; log(`Mute: ${v.muted}`, 5); } function setMark(v) { - if (!v.vsc) v.vsc = {}; + /* ... Same as your original ... */ if (!v.vsc) v.vsc = {}; v.vsc.mark = v.currentTime; log(`Mark: ${v.vsc.mark.toFixed(2)}`, 5); } function jumpToMark(v) { - if (v.vsc && typeof v.vsc.mark === "number") v.currentTime = v.vsc.mark; + /* ... Same as your original ... */ if ( + v.vsc && + typeof v.vsc.mark === "number" + ) + v.currentTime = v.vsc.mark; else log("No mark.", 4); } function handleDrag(video, e) { - /* ... same original logic ... */ + /* ... Same as your original ... */ if (!video || !video.vsc || !video.vsc.div || !video.vsc.div.shadowRoot) return; - const controller = video.vsc.div; - const shadowController = controller.shadowRoot.querySelector("#controller"); - if (!shadowController) return; - var parentElement = controller.parentElement; + const ctl = video.vsc.div; + const sCtl = ctl.shadowRoot.querySelector("#controller"); + if (!sCtl) return; + var pE = ctl.parentElement; while ( - parentElement && - parentElement.parentNode && - parentElement.parentNode !== document && - parentElement.parentNode.offsetHeight === parentElement.offsetHeight && - parentElement.parentNode.offsetWidth === parentElement.offsetWidth + pE && + pE.parentNode && + pE.parentNode !== document && + pE.parentNode.offsetHeight === pE.offsetHeight && + pE.parentNode.offsetWidth === pE.offsetWidth ) - parentElement = parentElement.parentNode; - const dragBoundary = parentElement || video.ownerDocument.body; + pE = pE.parentNode; + const dB = pE || video.ownerDocument.body; video.classList.add("vcs-dragging"); - shadowController.classList.add("dragging"); + sCtl.classList.add("dragging"); const iXY = [e.clientX, e.clientY], - iCtrlXY = [ - parseInt(shadowController.style.left, 10) || 0, - parseInt(shadowController.style.top, 10) || 0 + iCtlXY = [ + parseInt(sCtl.style.left, 10) || 0, + parseInt(sCtl.style.top, 10) || 0 ]; - const sD = (mvE) => { - let s = shadowController.style; - s.left = iCtrlXY[0] + mvE.clientX - iXY[0] + "px"; - s.top = iCtrlXY[1] + mvE.clientY - iXY[1] + "px"; - mvE.preventDefault(); + const sD = (mE) => { + let s = sCtl.style; + s.left = iCtlXY[0] + mE.clientX - iXY[0] + "px"; + s.top = iCtlXY[1] + mE.clientY - iXY[1] + "px"; + mE.preventDefault(); }; const eD = () => { - dragBoundary.removeEventListener("mousemove", sD); - dragBoundary.removeEventListener("mouseup", eD); - dragBoundary.removeEventListener("mouseleave", eD); - shadowController.classList.remove("dragging"); + dB.removeEventListener("mousemove", sD); + dB.removeEventListener("mouseup", eD); + dB.removeEventListener("mouseleave", eD); + sCtl.classList.remove("dragging"); video.classList.remove("vcs-dragging"); }; - dragBoundary.addEventListener("mousemove", sD); - dragBoundary.addEventListener("mouseup", eD); - dragBoundary.addEventListener("mouseleave", eD); + dB.addEventListener("mousemove", sD); + dB.addEventListener("mouseup", eD); + dB.addEventListener("mouseleave", eD); } var timer = null; function showController(controller) { - /* ... same original logic ... */ + /* ... Same as your original ... */ if (!controller || typeof controller.classList === "undefined") return; controller.classList.add("vcs-show"); if (timer) clearTimeout(timer); diff --git a/manifest.json b/manifest.json index 3203e1e..27b4451 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Video Speed Controller", "short_name": "videospeed", - "version": "1.1.0", + "version": "1.1.1", "manifest_version": 2, "description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts", "homepage_url": "https://github.com/SoPat712/videospeed",