mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-07-02 13:46:41 -04:00
v5.3.0.0-beta.1
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Available for Firefox
|
||||
# Available for Firefox
|
||||
|
||||
[](https://addons.mozilla.org/firefox/addon/speeder/)
|
||||
|
||||
|
||||
# The science of accelerated playback
|
||||
## The science of accelerated playback
|
||||
|
||||
**TL;DR: faster playback translates to better engagement and retention.**
|
||||
|
||||
@@ -33,9 +33,11 @@ last point to listen to it a few more times.
|
||||
|
||||

|
||||
|
||||
#### *Install [Chrome](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk) or [Firefox](https://addons.mozilla.org/en-us/firefox/addon/speeder/) Extension*
|
||||
## Using the extension
|
||||
|
||||
\*\* Once the extension is installed simply navigate to any page that offers
|
||||
[](https://addons.mozilla.org/firefox/addon/speeder/)
|
||||
|
||||
Once the extension is installed simply navigate to any page that offers
|
||||
HTML5 video ([example](https://www.youtube.com/watch?v=E9FxNzv1Tr8)), and you'll
|
||||
see a speed indicator in top left corner. Hover over the indicator to reveal the
|
||||
controls to accelerate, slowdown, and quickly rewind or advance the video. Or,
|
||||
@@ -65,21 +67,25 @@ listens both for lower and upper case values (i.e. you can use
|
||||
key. This is not a perfect solution, as some sites may listen to both, but works
|
||||
most of the time.
|
||||
|
||||
### FAQ
|
||||
## FAQ
|
||||
|
||||
**The video controls are not showing up?** This extension is only compatible
|
||||
### The video controls are not showing up?
|
||||
|
||||
This extension is only compatible
|
||||
with HTML5 video. If you don't see the controls showing up, chances are you are
|
||||
viewing a Flash video. If you want to confirm, try right-clicking on the video
|
||||
and inspect the menu: if it mentions flash, then that's the issue. That said,
|
||||
most sites will fallback to HTML5 if they detect that Flash is not available.
|
||||
You can try manually disabling Flash from the browser.
|
||||
|
||||
**What is this fork all about?** This is a fork of
|
||||
[CodeBicycle's Video Speed Controller extension for Firefox](https://github.com/codebicycle/videospeed)
|
||||
### What is this fork all about?
|
||||
|
||||
This is a fork of
|
||||
[CodeBicycle's Video Speed Controller extension for Firefox](https://github.com/codebicycle/videospeed)
|
||||
which is a fork of [Igrigorik's Video Speed Controller extension for Chromium](https://github.com/igrigorik/videospeed).
|
||||
|
||||
The goal of this fork is fix bugs in the upstream code as well as add new features.
|
||||
|
||||
### License
|
||||
## License
|
||||
|
||||
(GPLv3) - Copyright (c) 2025 Josh Patra
|
||||
|
||||
@@ -39,6 +39,7 @@ var tc = {
|
||||
defaultLogLevel: 3,
|
||||
logLevel: 3,
|
||||
enableSubtitleNudge: false,
|
||||
subtitleNudgeEnabledByDefault: true,
|
||||
subtitleNudgeInterval: 50, // Default 50ms balances subtitle tracking with CPU cost
|
||||
subtitleNudgeAmount: 0.001,
|
||||
customButtonIcons: {}
|
||||
@@ -384,6 +385,7 @@ function captureSiteRuleBase() {
|
||||
controllerMarginTop: tc.settings.controllerMarginTop,
|
||||
controllerMarginBottom: tc.settings.controllerMarginBottom,
|
||||
enableSubtitleNudge: tc.settings.enableSubtitleNudge,
|
||||
subtitleNudgeEnabledByDefault: tc.settings.subtitleNudgeEnabledByDefault,
|
||||
subtitleNudgeInterval: tc.settings.subtitleNudgeInterval,
|
||||
controllerButtons: Array.isArray(tc.settings.controllerButtons)
|
||||
? tc.settings.controllerButtons.slice()
|
||||
@@ -410,6 +412,7 @@ function resetSettingsFromSiteRuleBase() {
|
||||
tc.settings.controllerMarginTop = base.controllerMarginTop;
|
||||
tc.settings.controllerMarginBottom = base.controllerMarginBottom;
|
||||
tc.settings.enableSubtitleNudge = base.enableSubtitleNudge;
|
||||
tc.settings.subtitleNudgeEnabledByDefault = base.subtitleNudgeEnabledByDefault;
|
||||
tc.settings.subtitleNudgeInterval = base.subtitleNudgeInterval;
|
||||
tc.settings.controllerButtons = Array.isArray(base.controllerButtons)
|
||||
? base.controllerButtons.slice()
|
||||
@@ -659,13 +662,15 @@ function isSubtitleNudgeAvailableForVideo(video) {
|
||||
function isSubtitleNudgeEnabledForVideo(video) {
|
||||
if (!isSubtitleNudgeAvailableForVideo(video)) return false;
|
||||
|
||||
if (!video || !video.vsc) return true;
|
||||
if (!video || !video.vsc) {
|
||||
return Boolean(tc.settings.subtitleNudgeEnabledByDefault);
|
||||
}
|
||||
|
||||
if (typeof video.vsc.subtitleNudgeEnabledOverride === "boolean") {
|
||||
return video.vsc.subtitleNudgeEnabledOverride;
|
||||
}
|
||||
|
||||
return true;
|
||||
return Boolean(tc.settings.subtitleNudgeEnabledByDefault);
|
||||
}
|
||||
|
||||
function setSubtitleNudgeEnabledForVideo(video, enabled) {
|
||||
@@ -1195,6 +1200,10 @@ chrome.storage.sync.get(tc.settings, function(storage) {
|
||||
typeof storage.enableSubtitleNudge !== "undefined"
|
||||
? Boolean(storage.enableSubtitleNudge)
|
||||
: tc.settings.enableSubtitleNudge;
|
||||
tc.settings.subtitleNudgeEnabledByDefault =
|
||||
typeof storage.subtitleNudgeEnabledByDefault !== "undefined"
|
||||
? Boolean(storage.subtitleNudgeEnabledByDefault)
|
||||
: tc.settings.subtitleNudgeEnabledByDefault;
|
||||
tc.settings.subtitleNudgeInterval = Math.min(
|
||||
1000,
|
||||
Math.max(10, Number(storage.subtitleNudgeInterval) || 50)
|
||||
@@ -2051,6 +2060,7 @@ function applySiteRuleOverrides() {
|
||||
"controllerMarginTop",
|
||||
"controllerMarginBottom",
|
||||
"enableSubtitleNudge",
|
||||
"subtitleNudgeEnabledByDefault",
|
||||
"subtitleNudgeInterval"
|
||||
];
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Speeder",
|
||||
"short_name": "Speeder",
|
||||
"version": "5.2.7.0",
|
||||
"version": "5.3.0.0",
|
||||
"manifest_version": 2,
|
||||
"description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts (New and improved version of \"Video Speed Controller\")",
|
||||
"homepage_url": "https://github.com/SoPat712/speeder",
|
||||
|
||||
+20
-1
@@ -394,11 +394,21 @@
|
||||
<div class="row row-checkbox">
|
||||
<label for="enableSubtitleNudge"
|
||||
>Enable subtitle nudge<br /><em
|
||||
>Makes tiny playback changes to help keep subtitles aligned.</em
|
||||
>Makes tiny playback changes to help keep subtitles aligned and
|
||||
allows nudge toggle controls.</em
|
||||
>
|
||||
</label>
|
||||
<input id="enableSubtitleNudge" type="checkbox" />
|
||||
</div>
|
||||
<div class="row row-checkbox">
|
||||
<label for="subtitleNudgeEnabledByDefault"
|
||||
>Start subtitle nudge enabled<br /><em
|
||||
>When off, each video starts with nudging disabled until you
|
||||
toggle it.</em
|
||||
>
|
||||
</label>
|
||||
<input id="subtitleNudgeEnabledByDefault" type="checkbox" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="subtitleNudgeInterval"
|
||||
>Nudge interval (milliseconds)<br /><em
|
||||
@@ -827,6 +837,15 @@
|
||||
>
|
||||
<input type="checkbox" class="site-enableSubtitleNudge" />
|
||||
</div>
|
||||
<div class="site-rule-option site-rule-option-checkbox">
|
||||
<label
|
||||
>Start subtitle nudge enabled:<br /><em
|
||||
>When off, matching videos start with nudging disabled
|
||||
until you toggle it.</em
|
||||
></label
|
||||
>
|
||||
<input type="checkbox" class="site-subtitleNudgeEnabledByDefault" />
|
||||
</div>
|
||||
<div class="site-rule-option site-rule-option-field">
|
||||
<label
|
||||
>Nudge interval (10–1000ms):<br /><em
|
||||
|
||||
@@ -210,6 +210,7 @@ var tcDefaults = {
|
||||
popupMatchHoverControls: true,
|
||||
popupControllerButtons: ["rewind", "slower", "faster", "advance", "display"],
|
||||
enableSubtitleNudge: false,
|
||||
subtitleNudgeEnabledByDefault: true,
|
||||
subtitleNudgeInterval: 50,
|
||||
subtitleNudgeAmount: 0.001
|
||||
};
|
||||
@@ -823,6 +824,8 @@ function save_options() {
|
||||
settings.keyBindings = keyBindings;
|
||||
settings.enableSubtitleNudge =
|
||||
document.getElementById("enableSubtitleNudge").checked;
|
||||
settings.subtitleNudgeEnabledByDefault =
|
||||
document.getElementById("subtitleNudgeEnabledByDefault").checked;
|
||||
settings.subtitleNudgeInterval =
|
||||
parseInt(document.getElementById("subtitleNudgeInterval").value, 10) ||
|
||||
tcDefaults.subtitleNudgeInterval;
|
||||
@@ -906,6 +909,8 @@ function save_options() {
|
||||
if (ruleEl.querySelector(".override-subtitleNudge").checked) {
|
||||
rule.enableSubtitleNudge =
|
||||
ruleEl.querySelector(".site-enableSubtitleNudge").checked;
|
||||
rule.subtitleNudgeEnabledByDefault =
|
||||
ruleEl.querySelector(".site-subtitleNudgeEnabledByDefault").checked;
|
||||
var nudgeIv = parseInt(
|
||||
ruleEl.querySelector(".site-subtitleNudgeInterval").value,
|
||||
10
|
||||
@@ -1162,10 +1167,12 @@ function createSiteRule(rule) {
|
||||
var hasSubtitleNudgeOverride = Boolean(
|
||||
rule &&
|
||||
(rule.enableSubtitleNudge !== undefined ||
|
||||
rule.subtitleNudgeEnabledByDefault !== undefined ||
|
||||
rule.subtitleNudgeInterval !== undefined)
|
||||
);
|
||||
ruleEl.querySelector(".override-subtitleNudge").checked = hasSubtitleNudgeOverride;
|
||||
syncSiteRuleField(ruleEl, rule, "enableSubtitleNudge", true);
|
||||
syncSiteRuleField(ruleEl, rule, "subtitleNudgeEnabledByDefault", true);
|
||||
syncSiteRuleField(ruleEl, rule, "subtitleNudgeInterval", false);
|
||||
applySiteRuleOverrideState(
|
||||
ruleEl,
|
||||
@@ -1650,6 +1657,8 @@ function restore_options() {
|
||||
storage.showPopupControlBar !== false;
|
||||
document.getElementById("enableSubtitleNudge").checked =
|
||||
storage.enableSubtitleNudge;
|
||||
document.getElementById("subtitleNudgeEnabledByDefault").checked =
|
||||
storage.subtitleNudgeEnabledByDefault;
|
||||
document.getElementById("subtitleNudgeInterval").value =
|
||||
storage.subtitleNudgeInterval;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"audioBoolean",
|
||||
"controllerOpacity",
|
||||
"enableSubtitleNudge",
|
||||
"subtitleNudgeEnabledByDefault",
|
||||
"subtitleNudgeInterval",
|
||||
"controllerButtons",
|
||||
"showPopupControlBar",
|
||||
@@ -43,6 +44,7 @@
|
||||
"popupMatchHoverControls",
|
||||
"popupControllerButtons",
|
||||
"enableSubtitleNudge",
|
||||
"subtitleNudgeEnabledByDefault",
|
||||
"subtitleNudgeInterval",
|
||||
"subtitleNudgeAmount"
|
||||
];
|
||||
@@ -180,6 +182,7 @@
|
||||
popupMatchHoverControls: true,
|
||||
popupControllerButtons: DEFAULT_BUTTONS.slice(),
|
||||
enableSubtitleNudge: false,
|
||||
subtitleNudgeEnabledByDefault: true,
|
||||
subtitleNudgeInterval: 50,
|
||||
subtitleNudgeAmount: 0.001
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"speed",
|
||||
"startHidden",
|
||||
"subtitleNudgeAmount",
|
||||
"subtitleNudgeEnabledByDefault",
|
||||
"subtitleNudgeInterval"
|
||||
]);
|
||||
|
||||
|
||||
@@ -112,4 +112,52 @@ describe("inject runtime", () => {
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
it("uses the configured subtitle nudge default until a video is toggled", async () => {
|
||||
await bootInject({
|
||||
sync: {
|
||||
enableSubtitleNudge: true,
|
||||
subtitleNudgeEnabledByDefault: false
|
||||
}
|
||||
});
|
||||
|
||||
const startSubtitleNudge = vi.fn();
|
||||
const video = {
|
||||
paused: false,
|
||||
playbackRate: 1.5,
|
||||
vsc: {
|
||||
startSubtitleNudge,
|
||||
stopSubtitleNudge: vi.fn(),
|
||||
subtitleNudgeEnabledOverride: null,
|
||||
subtitleNudgeIndicator: null,
|
||||
nudgeFlashIndicator: document.createElement("span")
|
||||
}
|
||||
};
|
||||
|
||||
expect(window.isSubtitleNudgeEnabledForVideo(video)).toBe(false);
|
||||
expect(window.setSubtitleNudgeEnabledForVideo(video, true)).toBe(true);
|
||||
expect(video.vsc.subtitleNudgeEnabledOverride).toBe(true);
|
||||
expect(window.isSubtitleNudgeEnabledForVideo(video)).toBe(true);
|
||||
expect(startSubtitleNudge).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("applies subtitle nudge default state from matching site rules", async () => {
|
||||
await bootInject();
|
||||
|
||||
window.tc.settings.enableSubtitleNudge = true;
|
||||
window.tc.settings.subtitleNudgeEnabledByDefault = true;
|
||||
window.tc.settings.siteRules = [
|
||||
{
|
||||
pattern: "example.org",
|
||||
subtitleNudgeEnabledByDefault: false
|
||||
}
|
||||
];
|
||||
window.captureSiteRuleBase();
|
||||
|
||||
expect(window.applySiteRuleOverrides()).toBe(false);
|
||||
expect(window.tc.settings.subtitleNudgeEnabledByDefault).toBe(false);
|
||||
|
||||
window.resetSettingsFromSiteRuleBase();
|
||||
expect(window.tc.settings.subtitleNudgeEnabledByDefault).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ describe("options page", () => {
|
||||
sync: {
|
||||
rememberSpeed: true,
|
||||
enabled: false,
|
||||
subtitleNudgeEnabledByDefault: false,
|
||||
popupMatchHoverControls: false,
|
||||
popupControllerButtons: ["rewind", "settings", "advance", "advance"],
|
||||
keyBindings: [
|
||||
@@ -39,6 +40,7 @@ describe("options page", () => {
|
||||
{
|
||||
pattern: "youtube.com",
|
||||
enabled: true,
|
||||
subtitleNudgeEnabledByDefault: false,
|
||||
showPopupControlBar: false,
|
||||
popupControllerButtons: ["advance", "settings", "advance"]
|
||||
}
|
||||
@@ -49,12 +51,22 @@ describe("options page", () => {
|
||||
expect(document.getElementById("app-version").textContent).toBe("5.1.7.0");
|
||||
expect(document.getElementById("rememberSpeed").checked).toBe(true);
|
||||
expect(document.getElementById("enabled").checked).toBe(false);
|
||||
expect(document.getElementById("subtitleNudgeEnabledByDefault").checked).toBe(
|
||||
false
|
||||
);
|
||||
expect(document.querySelector('.shortcut-row[data-action="pause"]')).not.toBe(
|
||||
null
|
||||
);
|
||||
expect(document.getElementById("siteRulesContainer").children.length).toBe(
|
||||
1
|
||||
);
|
||||
expect(document.querySelector(".site-rule .override-subtitleNudge").checked).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
document.querySelector(".site-rule .site-subtitleNudgeEnabledByDefault")
|
||||
.checked
|
||||
).toBe(false);
|
||||
expect(globalThis.getPopupControlBarOrder()).toEqual(["rewind", "advance"]);
|
||||
});
|
||||
|
||||
@@ -159,6 +171,7 @@ describe("options page", () => {
|
||||
document.getElementById("controllerMarginTop").value = "250";
|
||||
document.getElementById("controllerMarginBottom").value = "-4";
|
||||
document.getElementById("enableSubtitleNudge").checked = true;
|
||||
document.getElementById("subtitleNudgeEnabledByDefault").checked = false;
|
||||
document.getElementById("subtitleNudgeInterval").value = "5";
|
||||
document.getElementById("popupMatchHoverControls").checked = false;
|
||||
document.getElementById("showPopupControlBar").checked = false;
|
||||
@@ -177,6 +190,10 @@ describe("options page", () => {
|
||||
rule.querySelector(".site-rememberSpeed").checked = true;
|
||||
rule.querySelector(".override-opacity").checked = true;
|
||||
rule.querySelector(".site-controllerOpacity").value = "0";
|
||||
rule.querySelector(".override-subtitleNudge").checked = true;
|
||||
rule.querySelector(".site-enableSubtitleNudge").checked = true;
|
||||
rule.querySelector(".site-subtitleNudgeEnabledByDefault").checked = false;
|
||||
rule.querySelector(".site-subtitleNudgeInterval").value = "75";
|
||||
rule.querySelector(".override-popup-controlbar").checked = true;
|
||||
rule.querySelector(".site-showPopupControlBar").checked = false;
|
||||
globalThis.populateControlBarZones(
|
||||
@@ -201,6 +218,7 @@ describe("options page", () => {
|
||||
expect(savedSettings.controllerOpacity).toBe(0);
|
||||
expect(savedSettings.controllerMarginTop).toBe(200);
|
||||
expect(savedSettings.controllerMarginBottom).toBe(0);
|
||||
expect(savedSettings.subtitleNudgeEnabledByDefault).toBe(false);
|
||||
expect(savedSettings.subtitleNudgeInterval).toBe(10);
|
||||
expect(savedSettings.showPopupControlBar).toBe(false);
|
||||
expect(savedSettings.popupMatchHoverControls).toBe(false);
|
||||
@@ -211,6 +229,9 @@ describe("options page", () => {
|
||||
pattern: "youtube.com",
|
||||
rememberSpeed: true,
|
||||
controllerOpacity: 0,
|
||||
enableSubtitleNudge: true,
|
||||
subtitleNudgeEnabledByDefault: false,
|
||||
subtitleNudgeInterval: 75,
|
||||
showPopupControlBar: false,
|
||||
popupControllerButtons: ["advance"]
|
||||
})
|
||||
|
||||
@@ -152,6 +152,16 @@ describe("shared helpers", () => {
|
||||
localSettings: null
|
||||
});
|
||||
|
||||
expect(
|
||||
importExportUtils.extractImportSettings({
|
||||
subtitleNudgeEnabledByDefault: false
|
||||
})
|
||||
).toEqual({
|
||||
isWrappedBackup: false,
|
||||
settings: { subtitleNudgeEnabledByDefault: false },
|
||||
localSettings: null
|
||||
});
|
||||
|
||||
expect(
|
||||
importExportUtils.extractImportSettings({ enabled: true })
|
||||
).toEqual({
|
||||
|
||||
Reference in New Issue
Block a user