17 Commits

Author SHA1 Message Date
Josh Patra
3fed3b425e add feature in popup to force search for videos, on websites that it doesn't show up on 2025-07-18 18:00:06 -04:00
Josh Patra
d89853b4d2 Better speed controlling logic, more selective speed reapplication 2025-07-06 22:56:48 -04:00
Josh Patra
d94ab958d5 failing to appear, making it more consistent 2025-07-06 19:40:25 -04:00
Josh Patra
1277750716 versioning stuff 2025-07-03 15:28:21 -04:00
Josh Patra
247a46d430 I'm stupid, the problem was resuming, not pausing 2025-07-03 15:14:19 -04:00
Josh Patra
703658335c version bump but still broken 2025-07-03 15:02:38 -04:00
Josh Patra
b2ed0fcb41 add deploy script 2025-07-03 14:49:52 -04:00
Josh Patra
3dfee251ec fix pause reset 2025-07-03 14:42:25 -04:00
Josh Patra
8e0183d8af general fixes, mistake versioning 2025-07-03 13:49:56 -04:00
Josh Patra
73827b5ee0 Merge branch 'firefox-port' of github.com:SoPat712/videospeed into firefox-port 2025-07-02 14:36:43 -04:00
Josh Patra
b07e7cb394 youtube embeds fixed and shortcuts 2025-07-02 14:36:39 -04:00
Josh Patra
c3166cf347 Update README.md 2025-06-22 01:30:24 -04:00
Josh Patra
77a25c4f1e Merge branch 'firefox-port' of github.com:SoPat712/videospeed into firefox-port 2025-05-22 16:22:19 -04:00
Josh Patra
3fee61d2b6 fixed the reset to 1.0 on pause 2025-05-22 16:22:15 -04:00
Josh Patra
b7684aad09 Update README.md 2025-05-20 03:48:10 -04:00
Josh Patra
43dc8b773b fix appear after hiding 2025-05-19 13:15:20 -04:00
Josh Patra
2d8a4fc25f add nudge to settings 2025-05-19 12:54:35 -04:00
8 changed files with 880 additions and 841 deletions

View File

@@ -1,3 +1,7 @@
[![Add to Firefox](https://img.shields.io/badge/Add%20to-Firefox-orange?logo=firefox&logoColor=white)](https://addons.mozilla.org/en-US/firefox/addon/video-speed-controller-v1/)
# The science of accelerated playback # The science of accelerated playback
**TL;DR: faster playback translates to better engagement and retention.** **TL;DR: faster playback translates to better engagement and retention.**
@@ -74,10 +78,10 @@ You can try manually disabling Flash from the browser.
[`igrigorik/videospeed`](https://github.com/igrigorik/videospeed) repository [`igrigorik/videospeed`](https://github.com/igrigorik/videospeed) repository
is a port of [`igrigorik`](https://github.com/igrigorik)'s videospeed Chrome is a port of [`igrigorik`](https://github.com/igrigorik)'s videospeed Chrome
add-on for Firefox. This fork modifies the Chrome add-on code so that it works add-on for Firefox. This fork modifies the Chrome add-on code so that it works
in Firefox. This repo is the code behind the [Firefox Extension](https://addons.mozilla.org/en-us/firefox/addon/videospeed/) in Firefox. This repo is the code behind the [Firefox Extension](https://addons.mozilla.org/en-US/firefox/addon/video-speed-controller-v1/)
whereas the [`igrigorik/videospeed`](https://github.com/igrigorik/videospeed) whereas the [`igrigorik/videospeed`](https://github.com/igrigorik/videospeed)
repository contains the code behind the [Chrome Extension](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk). repository contains the code behind the [Chrome Extension](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk).
### License ### License
(MIT License) - Copyright (c) 2014 Ilya Grigorik (MIT License) - Copyright (c) 2025 Josh Patra

118
build.py Normal file
View 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()

1092
inject.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{ {
"name": "Video Speed Controller", "name": "Video Speed Controller",
"short_name": "videospeed", "short_name": "videospeed",
"version": "0.6.3.3", "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/codebicycle/videospeed", "homepage_url": "https://github.com/SoPat712/videospeed",
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "{7be2ba16-0f1e-4d93-9ebc-5164397477a9}" "id": "{ed860648-f54f-4dc9-9a0d-501aec4313f5}"
} }
}, },
"icons": { "icons": {

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<title>Video Speed Controller: Options</title> <title>Video Speed Controller: Options</title>
@@ -148,8 +148,13 @@
<input id="rememberSpeed" type="checkbox" /> <input id="rememberSpeed" type="checkbox" />
</div> </div>
<div class="row"> <div class="row">
<label for="forceLastSavedSpeed">Force last saved speed<br /> <label for="forceLastSavedSpeed"
<em>Useful for video players that override the speeds set by VideoSpeed</em></label> >Force last saved speed<br />
<em
>Useful for video players that override the speeds set by
VideoSpeed</em
></label
>
<input id="forceLastSavedSpeed" type="checkbox" /> <input id="forceLastSavedSpeed" type="checkbox" />
</div> </div>
<div class="row"> <div class="row">
@@ -175,6 +180,47 @@
</div> </div>
</section> </section>
<section id="nudgeSettings">
<h3>Subtitle Nudge Settings (Experimental - YouTube Only)</h3>
<div class="row">
<label for="enableSubtitleNudge"
>Enable Subtitle Nudge <br /><em
>Periodically 'nudges' video speed by a tiny amount to help keep
subtitles in sync on some sites (e.g. YouTube).</em
>
</label>
<input id="enableSubtitleNudge" type="checkbox" />
</div>
<div class="row">
<label for="subtitleNudgeInterval"
>Nudge Interval (milliseconds) <br /><em
>How often to nudge (e.g., 25-1000). Smaller values are more
frequent. Default: 25.</em
>
</label>
<input
id="subtitleNudgeInterval"
type="text"
value=""
placeholder="25"
/>
</div>
<div class="row">
<label for="subtitleNudgeAmount"
>Nudge Amount (decimal) <br /><em
>How much to change speed by (e.g., 0.001). Very small values
recommended. Default: 0.001.</em
>
</label>
<input
id="subtitleNudgeAmount"
type="text"
value=""
placeholder="0.001"
/>
</div>
</section>
<button id="save">Save</button> <button id="save">Save</button>
<button id="restore">Restore Defaults</button> <button id="restore">Restore Defaults</button>
<button id="experimental">Show Experimental Features</button> <button id="experimental">Show Experimental Features</button>
@@ -186,12 +232,12 @@
<h4>Extension controls not appearing?</h4> <h4>Extension controls not appearing?</h4>
<p> <p>
This extension is only compatible with HTML5 audio and video. If you don't This extension is only compatible with HTML5 audio and video. If you
see the controls showing up, chances are you are viewing a Flash content. don't see the controls showing up, chances are you are viewing a Flash
If you want to confirm, try right-clicking on the content and inspect the content. If you want to confirm, try right-clicking on the content and
menu: if it mentions flash, then that's the issue. That said, <b>most sites inspect the menu: if it mentions flash, then that's the issue. That
will fallback to HTML5</b> if they detect that Flash is not available. You said, <b>most sites will fallback to HTML5</b> if they detect that Flash
can try manually disabling Flash from the browser. is not available. You can try manually disabling Flash from the browser.
</p> </p>
</div> </div>
</body> </body>

View File

@@ -22,13 +22,17 @@ var tcDefaults = {
twitter.com twitter.com
imgur.com imgur.com
teams.microsoft.com teams.microsoft.com
`.replace(regStrip, "") `.replace(regStrip, ""),
// ADDED: Nudge defaults
enableSubtitleNudge: true,
subtitleNudgeInterval: 25,
subtitleNudgeAmount: 0.001
}; };
var keyBindings = []; var keyBindings = []; // This is populated during save/restore
var keyCodeAliases = { var keyCodeAliases = {
0: "null", /* ... same as your original ... */ 0: "null",
null: "null", null: "null",
undefined: "null", undefined: "null",
32: "Space", 32: "Space",
@@ -76,83 +80,53 @@ var keyCodeAliases = {
222: "'", 222: "'",
59: ";", 59: ";",
61: "+", 61: "+",
173: "-", 173: "-"
}; };
function recordKeyPress(e) { function recordKeyPress(e) {
/* ... same as your original ... */
if ( if (
(e.keyCode >= 48 && e.keyCode <= 57) || // Numbers 0-9 (e.keyCode >= 48 && e.keyCode <= 57) ||
(e.keyCode >= 65 && e.keyCode <= 90) || // Letters A-Z (e.keyCode >= 65 && e.keyCode <= 90) ||
keyCodeAliases[e.keyCode] // Other character keys keyCodeAliases[e.keyCode]
) { ) {
e.target.value = e.target.value =
keyCodeAliases[e.keyCode] || String.fromCharCode(e.keyCode); keyCodeAliases[e.keyCode] || String.fromCharCode(e.keyCode);
e.target.keyCode = e.keyCode; e.target.keyCode = e.keyCode;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
} else if (e.keyCode === 8) { } else if (e.keyCode === 8) {
// Clear input when backspace pressed
e.target.value = ""; e.target.value = "";
} else if (e.keyCode === 27) { } else if (e.keyCode === 27) {
// When esc clicked, clear input
e.target.value = "null"; e.target.value = "null";
e.target.keyCode = null; e.target.keyCode = null;
} }
} }
function inputFilterNumbersOnly(e) { function inputFilterNumbersOnly(e) {
/* ... same as your original ... */
var char = String.fromCharCode(e.keyCode); var char = String.fromCharCode(e.keyCode);
if (!/[\d\.]$/.test(char) || !/^\d+(\.\d*)?$/.test(e.target.value + char)) { if (!/[\d\.]$/.test(char) || !/^\d+(\.\d*)?$/.test(e.target.value + char)) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
} }
} }
function inputFocus(e) { function inputFocus(e) {
e.target.value = ""; /* ... same as your original ... */ e.target.value = "";
} }
function inputBlur(e) { function inputBlur(e) {
e.target.value = /* ... same as your original ... */ e.target.value =
keyCodeAliases[e.target.keyCode] || String.fromCharCode(e.target.keyCode); keyCodeAliases[e.target.keyCode] || String.fromCharCode(e.target.keyCode);
} }
// function updateShortcutInputText(inputId, keyCode) { /* ... same as your original ... */ } // Not directly used in provided options.js logic flow
function updateShortcutInputText(inputId, keyCode) {
document.getElementById(inputId).value =
keyCodeAliases[keyCode] || String.fromCharCode(keyCode);
document.getElementById(inputId).keyCode = keyCode;
}
function updateCustomShortcutInputText(inputItem, keyCode) { function updateCustomShortcutInputText(inputItem, keyCode) {
inputItem.value = keyCodeAliases[keyCode] || String.fromCharCode(keyCode); /* ... same as your original ... */ inputItem.value =
keyCodeAliases[keyCode] || String.fromCharCode(keyCode);
inputItem.keyCode = keyCode; inputItem.keyCode = keyCode;
} }
var customActionsNoValues = ["pause", "muted", "mark", "jump", "display"]; // Original
// List of custom actions for which customValue should be disabled
var customActionsNoValues = ["pause", "muted", "mark", "jump", "display"];
function add_shortcut() { function add_shortcut() {
var html = `<select class="customDo"> /* ... same as your original ... */
<option value="slower">Decrease speed</option> var html = `<select class="customDo"><option value="slower">Decrease speed</option><option value="faster">Increase speed</option><option value="rewind">Rewind</option><option value="advance">Advance</option><option value="reset">Reset speed</option><option value="fast">Preferred speed</option><option value="muted">Mute</option><option value="pause">Pause</option><option value="mark">Set marker</option><option value="jump">Jump to marker</option><option value="display">Show/hide controller</option></select><input class="customKey" type="text" placeholder="press a key"/><input class="customValue" type="text" placeholder="value (0.10)"/><select class="customForce"><option value="false">Do not disable website key bindings</option><option value="true">Disable website key bindings</option></select><button class="removeParent">X</button>`;
<option value="faster">Increase speed</option>
<option value="rewind">Rewind</option>
<option value="advance">Advance</option>
<option value="reset">Reset speed</option>
<option value="fast">Preferred speed</option>
<option value="muted">Mute</option>
<option value="pause">Pause</option>
<option value="mark">Set marker</option>
<option value="jump">Jump to marker</option>
<option value="display">Show/hide controller</option>
</select>
<input class="customKey" type="text" placeholder="press a key"/>
<input class="customValue" type="text" placeholder="value (0.10)"/>
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable website key bindings</option>
</select>
<button class="removeParent">X</button>`;
var div = document.createElement("div"); var div = document.createElement("div");
div.setAttribute("class", "row customs"); div.setAttribute("class", "row customs");
div.innerHTML = html; div.innerHTML = html;
@@ -162,14 +136,13 @@ function add_shortcut() {
customs_element.children[customs_element.childElementCount - 1] customs_element.children[customs_element.childElementCount - 1]
); );
} }
function createKeyBindings(item) { function createKeyBindings(item) {
/* ... same as your original ... */
const action = item.querySelector(".customDo").value; const action = item.querySelector(".customDo").value;
const key = item.querySelector(".customKey").keyCode; const key = item.querySelector(".customKey").keyCode;
const value = Number(item.querySelector(".customValue").value); const value = Number(item.querySelector(".customValue").value);
const force = item.querySelector(".customForce").value; const force = item.querySelector(".customForce").value;
const predefined = !!item.id; //item.id ? true : false; const predefined = !!item.id;
keyBindings.push({ keyBindings.push({
action: action, action: action,
key: key, key: key,
@@ -178,9 +151,8 @@ function createKeyBindings(item) {
predefined: predefined predefined: predefined
}); });
} }
// Validates settings before saving
function validate() { function validate() {
/* ... same as your original ... */
var valid = true; var valid = true;
var status = document.getElementById("status"); var status = document.getElementById("status");
document document
@@ -190,7 +162,7 @@ function validate() {
match = match.replace(regStrip, ""); match = match.replace(regStrip, "");
if (match.startsWith("/")) { if (match.startsWith("/")) {
try { try {
var regexp = new RegExp(match); new RegExp(match);
} catch (err) { } catch (err) {
status.textContent = status.textContent =
"Error: Invalid blacklist regex: " + match + ". Unable to save"; "Error: Invalid blacklist regex: " + match + ". Unable to save";
@@ -202,24 +174,45 @@ function validate() {
return valid; return valid;
} }
// Saves options to chrome.storage // MODIFIED: save_options to include nudge settings
function save_options() { function save_options() {
if (validate() === false) { if (validate() === false) return;
return;
} keyBindings = []; // Reset global keyBindings before populating from DOM
keyBindings = [];
Array.from(document.querySelectorAll(".customs")).forEach((item) => Array.from(document.querySelectorAll(".customs")).forEach((item) =>
createKeyBindings(item) createKeyBindings(item)
); // Remove added shortcuts );
var rememberSpeed = document.getElementById("rememberSpeed").checked; var s = {}; // Object to hold all settings to be saved
var forceLastSavedSpeed = document.getElementById("forceLastSavedSpeed").checked; s.rememberSpeed = document.getElementById("rememberSpeed").checked;
var audioBoolean = document.getElementById("audioBoolean").checked; s.forceLastSavedSpeed = document.getElementById(
var enabled = document.getElementById("enabled").checked; "forceLastSavedSpeed"
var startHidden = document.getElementById("startHidden").checked; ).checked;
var controllerOpacity = document.getElementById("controllerOpacity").value; s.audioBoolean = document.getElementById("audioBoolean").checked;
var blacklist = document.getElementById("blacklist").value; s.enabled = document.getElementById("enabled").checked;
s.startHidden = document.getElementById("startHidden").checked;
s.controllerOpacity = document.getElementById("controllerOpacity").value;
s.blacklist = document
.getElementById("blacklist")
.value.replace(regStrip, "");
s.keyBindings = keyBindings; // Use the populated global keyBindings
// ADDED: Save nudge settings
s.enableSubtitleNudge = document.getElementById(
"enableSubtitleNudge"
).checked;
s.subtitleNudgeInterval =
parseInt(document.getElementById("subtitleNudgeInterval").value, 10) ||
tcDefaults.subtitleNudgeInterval;
s.subtitleNudgeAmount =
parseFloat(document.getElementById("subtitleNudgeAmount").value) ||
tcDefaults.subtitleNudgeAmount;
// Basic validation for nudge interval and amount
if (s.subtitleNudgeInterval < 10) s.subtitleNudgeInterval = 10; // Min 10ms
if (s.subtitleNudgeAmount <= 0 || s.subtitleNudgeAmount > 0.1)
s.subtitleNudgeAmount = tcDefaults.subtitleNudgeAmount;
// Remove old flat settings (original logic)
chrome.storage.sync.remove([ chrome.storage.sync.remove([
"resetSpeed", "resetSpeed",
"speedStep", "speedStep",
@@ -233,33 +226,22 @@ function save_options() {
"advanceKeyCode", "advanceKeyCode",
"fastKeyCode" "fastKeyCode"
]); ]);
chrome.storage.sync.set(
{ chrome.storage.sync.set(s, function () {
rememberSpeed: rememberSpeed,
forceLastSavedSpeed: forceLastSavedSpeed,
audioBoolean: audioBoolean,
enabled: enabled,
startHidden: startHidden,
controllerOpacity: controllerOpacity,
keyBindings: keyBindings,
blacklist: blacklist.replace(regStrip, "")
},
function () {
// Update status to let user know options were saved.
var status = document.getElementById("status"); var status = document.getElementById("status");
status.textContent = "Options saved"; status.textContent = "Options saved";
setTimeout(function () { setTimeout(function () {
status.textContent = ""; status.textContent = "";
}, 1000); }, 1000);
} });
);
} }
// Restores options from chrome.storage // MODIFIED: restore_options to include nudge settings
function restore_options() { function restore_options() {
chrome.storage.sync.get(tcDefaults, function (storage) { chrome.storage.sync.get(tcDefaults, function (storage) {
document.getElementById("rememberSpeed").checked = storage.rememberSpeed; document.getElementById("rememberSpeed").checked = storage.rememberSpeed;
document.getElementById("forceLastSavedSpeed").checked = storage.forceLastSavedSpeed; document.getElementById("forceLastSavedSpeed").checked =
storage.forceLastSavedSpeed;
document.getElementById("audioBoolean").checked = storage.audioBoolean; document.getElementById("audioBoolean").checked = storage.audioBoolean;
document.getElementById("enabled").checked = storage.enabled; document.getElementById("enabled").checked = storage.enabled;
document.getElementById("startHidden").checked = storage.startHidden; document.getElementById("startHidden").checked = storage.startHidden;
@@ -267,65 +249,86 @@ function restore_options() {
storage.controllerOpacity; storage.controllerOpacity;
document.getElementById("blacklist").value = storage.blacklist; document.getElementById("blacklist").value = storage.blacklist;
// ensure that there is a "display" binding for upgrades from versions that had it as a separate binding // ADDED: Restore nudge settings
document.getElementById("enableSubtitleNudge").checked =
storage.enableSubtitleNudge;
document.getElementById("subtitleNudgeInterval").value =
storage.subtitleNudgeInterval;
document.getElementById("subtitleNudgeAmount").value =
storage.subtitleNudgeAmount;
// Original key binding restoration logic
if (
!Array.isArray(storage.keyBindings) ||
storage.keyBindings.length === 0
) {
// If keyBindings missing or not an array, use defaults from tcDefaults
storage.keyBindings = tcDefaults.keyBindings;
}
if (storage.keyBindings.filter((x) => x.action == "display").length == 0) { if (storage.keyBindings.filter((x) => x.action == "display").length == 0) {
storage.keyBindings.push({ storage.keyBindings.push({
action: "display", action: "display",
value: 0, value: 0,
force: false, force: false,
predefined: true predefined: true,
key: storage.displayKeyCode || tcDefaults.displayKeyCode
}); });
} }
// Clear existing dynamic shortcuts before restoring (if any were added by mistake)
const dynamicShortcuts = document.querySelectorAll(".customs:not([id])");
dynamicShortcuts.forEach((sc) => sc.remove());
for (let i in storage.keyBindings) { for (let i in storage.keyBindings) {
var item = storage.keyBindings[i]; var item = storage.keyBindings[i];
if (item.predefined) { if (item.predefined) {
//do predefined ones because their value needed for overlay
// document.querySelector("#" + item["action"] + " .customDo").value = item["action"];
if (item["action"] == "display" && typeof item["key"] === "undefined") { if (item["action"] == "display" && typeof item["key"] === "undefined") {
item["key"] = storage.displayKeyCode || tcDefaults.displayKeyCode; // V item["key"] = storage.displayKeyCode || tcDefaults.displayKeyCode;
} }
if (customActionsNoValues.includes(item["action"])) {
if (customActionsNoValues.includes(item["action"])) const el = document.querySelector(
document.querySelector(
"#" + item["action"] + " .customValue" "#" + item["action"] + " .customValue"
).disabled = true;
updateCustomShortcutInputText(
document.querySelector("#" + item["action"] + " .customKey"),
item["key"]
); );
document.querySelector("#" + item["action"] + " .customValue").value = if (el) el.disabled = true;
item["value"]; }
document.querySelector("#" + item["action"] + " .customForce").value = const keyEl = document.querySelector(
item["force"]; "#" + item["action"] + " .customKey"
);
const valEl = document.querySelector(
"#" + item["action"] + " .customValue"
);
const forceEl = document.querySelector(
"#" + item["action"] + " .customForce"
);
if (keyEl) updateCustomShortcutInputText(keyEl, item["key"]);
if (valEl) valEl.value = item["value"];
if (forceEl) forceEl.value = String(item["force"]); // Ensure string for select value
} else { } else {
// new ones // Non-predefined, dynamically added shortcuts
add_shortcut(); add_shortcut();
const dom = document.querySelector(".customs:last-of-type"); const dom = document.querySelector(".customs:last-of-type"); // Gets the newly added one
dom.querySelector(".customDo").value = item["action"]; dom.querySelector(".customDo").value = item["action"];
if (customActionsNoValues.includes(item["action"])) {
if (customActionsNoValues.includes(item["action"]))
dom.querySelector(".customValue").disabled = true; dom.querySelector(".customValue").disabled = true;
}
updateCustomShortcutInputText( updateCustomShortcutInputText(
dom.querySelector(".customKey"), dom.querySelector(".customKey"),
item["key"] item["key"]
); );
dom.querySelector(".customValue").value = item["value"]; dom.querySelector(".customValue").value = item["value"];
dom.querySelector(".customForce").value = item["force"]; dom.querySelector(".customForce").value = String(item["force"]);
} }
} }
}); });
} }
function restore_defaults() { function restore_defaults() {
/* ... same as your original, tcDefaults now includes nudge defaults ... */
// Remove all dynamically added shortcuts first
document.querySelectorAll(".customs:not([id])").forEach((el) => el.remove());
// Then set defaults and restore options, which will re-add predefined ones correctly
chrome.storage.sync.set(tcDefaults, function () { chrome.storage.sync.set(tcDefaults, function () {
restore_options(); restore_options(); // This will populate based on tcDefaults
document
.querySelectorAll(".removeParent")
.forEach((button) => button.click()); // Remove added shortcuts
// Update status to let user know options were saved.
var status = document.getElementById("status"); var status = document.getElementById("status");
status.textContent = "Default options restored"; status.textContent = "Default options restored";
setTimeout(function () { setTimeout(function () {
@@ -335,14 +338,15 @@ function restore_defaults() {
} }
function show_experimental() { function show_experimental() {
/* ... same as your original ... */
document document
.querySelectorAll(".customForce") .querySelectorAll(".customForce")
.forEach((item) => (item.style.display = "inline-block")); .forEach((item) => (item.style.display = "inline-block"));
} }
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
/* ... same as your original event listeners setup ... */
restore_options(); restore_options();
document.getElementById("save").addEventListener("click", save_options); document.getElementById("save").addEventListener("click", save_options);
document.getElementById("add").addEventListener("click", add_shortcut); document.getElementById("add").addEventListener("click", add_shortcut);
document document
@@ -353,34 +357,32 @@ document.addEventListener("DOMContentLoaded", function () {
.addEventListener("click", show_experimental); .addEventListener("click", show_experimental);
function eventCaller(event, className, funcName) { function eventCaller(event, className, funcName) {
if (!event.target.classList || !event.target.classList.contains(className)) { if (!event.target.classList || !event.target.classList.contains(className))
return; return;
}
funcName(event); funcName(event);
} }
document.addEventListener("keypress", (event) =>
document.addEventListener("keypress", (event) => { eventCaller(event, "customValue", inputFilterNumbersOnly)
eventCaller(event, "customValue", inputFilterNumbersOnly); );
}); document.addEventListener("focus", (event) =>
document.addEventListener("focus", (event) => { eventCaller(event, "customKey", inputFocus)
eventCaller(event, "customKey", inputFocus); );
}); document.addEventListener("blur", (event) =>
document.addEventListener("blur", (event) => { eventCaller(event, "customKey", inputBlur)
eventCaller(event, "customKey", inputBlur); );
}); document.addEventListener("keydown", (event) =>
document.addEventListener("keydown", (event) => { eventCaller(event, "customKey", recordKeyPress)
eventCaller(event, "customKey", recordKeyPress); );
}); document.addEventListener("click", (event) =>
document.addEventListener("click", (event) => {
eventCaller(event, "removeParent", function () { eventCaller(event, "removeParent", function () {
event.target.parentNode.remove(); event.target.parentNode.remove();
}); })
}); );
document.addEventListener("change", (event) => { document.addEventListener("change", (event) => {
eventCaller(event, "customDo", function () { eventCaller(event, "customDo", function () {
if (customActionsNoValues.includes(event.target.value)) { if (customActionsNoValues.includes(event.target.value)) {
event.target.nextElementSibling.nextElementSibling.disabled = true; event.target.nextElementSibling.nextElementSibling.disabled = true;
event.target.nextElementSibling.nextElementSibling.value = 0; event.target.nextElementSibling.nextElementSibling.value = 0; // Or "" if placeholder is preferred
} else { } else {
event.target.nextElementSibling.nextElementSibling.disabled = false; event.target.nextElementSibling.nextElementSibling.disabled = false;
} }

View File

@@ -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>

View File

@@ -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
} }
}); });
} }