Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion maze-utils
Submodule maze-utils updated 1 files
+7 −1 src/video.ts
2 changes: 1 addition & 1 deletion public/_locales
Submodule _locales updated 1 files
+10 −1 en/messages.json
18 changes: 17 additions & 1 deletion public/options/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,22 @@ svg {
border-radius: 5px;
}

.speedOption {
background-color: #c00000;
color: white;

box-sizing: content-box;
border: none;
font-size: 14px;
border-radius: 5px;
padding: 5px;
}

.skipOption {
display: flex;
gap: 5px;
}

.categoryColorTextBox {
width: 60px;

Expand Down Expand Up @@ -731,4 +747,4 @@ svg {

.dearrow-link:hover .close-button {
opacity: 1;
}
}
108 changes: 74 additions & 34 deletions src/components/options/CategorySkipOptionsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import * as React from "react";

import Config from "../../config"
import * as CompileConfig from "../../../config.json";
import { Category, CategorySkipOption } from "../../types";
import { Category, CategorySelection, CategorySkipOption } from "../../types";

import { getCategorySuffix } from "../../utils/categoryUtils";
import ToggleOptionComponent from "./ToggleOptionComponent";

export interface CategorySkipOptionsProps {
export interface CategorySkipOptionsProps {
category: Category;
defaultColor?: string;
defaultPreviewColor?: string;
Expand All @@ -17,6 +17,8 @@ export interface CategorySkipOptionsProps {
export interface CategorySkipOptionsState {
color: string;
previewColor: string;
speed: number;
option: CategorySkipOption;
}

export interface ToggleOption {
Expand All @@ -27,36 +29,51 @@ export interface ToggleOption {

class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsProps, CategorySkipOptionsState> {
setBarColorTimeout: NodeJS.Timeout;
categorySelection: CategorySelection;

constructor(props: CategorySkipOptionsProps) {
super(props);

this.categorySelection = this.getCategorySelection();

// Setup state
this.state = {
color: props.defaultColor || Config.config.barTypes[this.props.category]?.color,
previewColor: props.defaultPreviewColor || Config.config.barTypes["preview-" + this.props.category]?.color
previewColor: props.defaultPreviewColor || Config.config.barTypes["preview-" + this.props.category]?.color,
speed: this.categorySelection.speed,
option: this.categorySelection.option,
};
}

getCategorySelection() {
let categorySelection = Config.config.categorySelections
.find(categorySelection => categorySelection.name === this.props.category)
if (!categorySelection) {
categorySelection = {
name: this.props.category,
option: CategorySkipOption.Disabled,
speed: 2,
}
Config.config.categorySelections.push(categorySelection);
}
return categorySelection;
}

render(): React.ReactElement {
let defaultOption = "disable";
// Set the default opton properly
for (const categorySelection of Config.config.categorySelections) {
if (categorySelection.name === this.props.category) {
switch (categorySelection.option) {
case CategorySkipOption.ShowOverlay:
defaultOption = "showOverlay";
break;
case CategorySkipOption.ManualSkip:
defaultOption = "manualSkip";
break;
case CategorySkipOption.AutoSkip:
defaultOption = "autoSkip";
break;
}

switch (this.categorySelection.option) {
case CategorySkipOption.ShowOverlay:
defaultOption = "showOverlay";
break;
case CategorySkipOption.ManualSkip:
defaultOption = "manualSkip";
break;
case CategorySkipOption.AutoSkip:
defaultOption = "autoSkip";
break;
case CategorySkipOption.FastForward:
defaultOption = "fastForward";
break;
}
}

return (
Expand All @@ -76,6 +93,16 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
onChange={this.skipOptionSelected.bind(this)}>
{this.getCategorySkipOptions()}
</select>
<input
className={`speedOption ${
this.state.option === CategorySkipOption.FastForward ? "" : "hidden"}`}
type="number"
min="0.1"
max="10"
step="0.1"
title={chrome.i18n.getMessage("fastForwardSpeed")}
onChange={this.forwardSpeedEntered.bind(this)}
value={this.state.speed} />
</td>

{this.props.category !== "chapter" &&
Expand Down Expand Up @@ -113,7 +140,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
</a>
</td>
</tr>

{this.getExtraOptionComponents(this.props.category)}

</>
Expand All @@ -127,6 +154,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
case "disable":
Config.config.categorySelections = Config.config.categorySelections.filter(
categorySelection => categorySelection.name !== this.props.category);
this.setState({ option: CategorySkipOption.Disabled });
return;
case "showOverlay":
option = CategorySkipOption.ShowOverlay;
Expand All @@ -146,32 +174,44 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
}

break;
}
case "fastForward":
option = CategorySkipOption.FastForward;

const existingSelection = Config.config.categorySelections.find(selection => selection.name === this.props.category);
if (existingSelection) {
existingSelection.option = option;
} else {
Config.config.categorySelections.push({
name: this.props.category,
option: option
});
break;
}

this.setState({ option });

this.categorySelection = this.getCategorySelection();
this.categorySelection.option = option;

Config.forceSyncUpdate("categorySelections");
}

forwardSpeedEntered(event: React.ChangeEvent<HTMLInputElement>): void {
const speedRaw = event.target.value;
const speed = +parseFloat(speedRaw || '1').toFixed(2);
if (speed < 0.1 || speed > 10) return;

this.setState({ speed });

this.categorySelection = this.getCategorySelection();
this.categorySelection.speed = speed;

Config.forceSyncUpdate("categorySelections");
}

getCategorySkipOptions(): JSX.Element[] {
const elements: JSX.Element[] = [];

let optionNames = ["disable", "showOverlay", "manualSkip", "autoSkip"];
let optionNames = ["disable", "showOverlay", "manualSkip", "autoSkip", "fastForward"];
if (this.props.category === "chapter") optionNames = ["disable", "showOverlay"]
else if (this.props.category === "exclusive_access") optionNames = ["disable", "showOverlay"];

for (const optionName of optionNames) {
elements.push(
<option key={optionName} value={optionName}>
{chrome.i18n.getMessage(optionName !== "disable" ? optionName + getCategorySuffix(this.props.category)
{chrome.i18n.getMessage(optionName !== "disable" ? optionName + getCategorySuffix(this.props.category)
: optionName)}
</option>
);
Expand Down Expand Up @@ -210,8 +250,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
result.push(
<tr key={option.configKey}>
<td id={`${category}_${option.configKey}`} className="categoryExtraOptions">
<ToggleOptionComponent
configKey={option.configKey}
<ToggleOptionComponent
configKey={option.configKey}
label={option.label}
style={{width: "inherit"}}
/>
Expand Down Expand Up @@ -253,4 +293,4 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
}
}

export default CategorySkipOptionsComponent;
export default CategorySkipOptionsComponent;
31 changes: 21 additions & 10 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ interface SBConfig {
"preview-poi_highlight": PreviewBarOption;
"filler": PreviewBarOption;
"preview-filler": PreviewBarOption;
"chapter": PreviewBarOption;
};
}

Expand Down Expand Up @@ -186,10 +187,9 @@ function migrateOldSyncFormats(config: SBConfig) {
if (!config.categorySelections.some((s) => s.name === "chapter")) {
config.categorySelections.push({
name: "chapter" as Category,
option: CategorySkipOption.ShowOverlay
option: CategorySkipOption.ShowOverlay,
speed: 2,
});

config.categorySelections = config.categorySelections;
}
}

Expand Down Expand Up @@ -272,6 +272,13 @@ function migrateOldSyncFormats(config: SBConfig) {
if (config["lastIsVipUpdate"]) {
chrome.storage.sync.remove("lastIsVipUpdate");
}

if (!config["fastForwardCategorySkipUpdate"]) {
config["fastForwardCategorySkipUpdate"] = true;
for (const selection of config.categorySelections) {
selection.speed = 2;
}
}
}

const syncDefaults = {
Expand Down Expand Up @@ -365,16 +372,20 @@ const syncDefaults = {

categorySelections: [{
name: "sponsor" as Category,
option: CategorySkipOption.AutoSkip
option: CategorySkipOption.AutoSkip,
speed: 2,
}, {
name: "poi_highlight" as Category,
option: CategorySkipOption.ManualSkip
option: CategorySkipOption.ManualSkip,
speed: 2,
}, {
name: "exclusive_access" as Category,
option: CategorySkipOption.ShowOverlay
option: CategorySkipOption.ShowOverlay,
speed: 2,
}, {
name: "chapter" as Category,
option: CategorySkipOption.ShowOverlay
option: CategorySkipOption.ShowOverlay,
speed: 2,
}],

payments: {
Expand Down Expand Up @@ -478,15 +489,15 @@ const syncDefaults = {
opacity: "0"
},
}
};
} satisfies SBConfig;

const localDefaults = {
downvotedSegments: {},
navigationApiAvailable: null,
alreadyInstalled: false,

unsubmittedSegments: {}
};
} satisfies SBStorage;

const Config = new ConfigClass(syncDefaults, localDefaults, migrateOldSyncFormats);
export default Config;
Expand All @@ -511,4 +522,4 @@ export function generateDebugDetails(): string {
output.config.whitelistedChannels = output.config.whitelistedChannels.length;

return JSON.stringify(output, null, 4);
}
}
41 changes: 39 additions & 2 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { ChapterVote } from "./render/ChapterVote";
import { openWarningDialog } from "./utils/warnings";
import { extensionUserAgent, isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, isOnYouTubeMusic, isOnYTTV, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline, getCurrentTime, setCurrentTime, getVideoDuration, verifyCurrentTime, waitForVideo } from "../maze-utils/src/video";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, isOnYouTubeMusic, isOnYTTV, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline, getCurrentTime, setCurrentTime, setSpeed, getVideoDuration, verifyCurrentTime, waitForVideo } from "../maze-utils/src/video";
import { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config";
import { findValidElement } from "../maze-utils/src/dom"
import { getHash, HashedValue } from "../maze-utils/src/hash";
Expand Down Expand Up @@ -1732,7 +1732,8 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
if (Config.config.disableSkipping) return;

// There will only be one submission if it is manual skip
const autoSkip: boolean = forceAutoSkip || shouldAutoSkip(skippingSegments[0]);
const autoSkip = forceAutoSkip || shouldAutoSkip(skippingSegments[0]);
const fastForward = shouldFastForward(skippingSegments[0]);
const isSubmittingSegment = sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment);

if ((autoSkip || isSubmittingSegment)
Expand Down Expand Up @@ -1789,6 +1790,10 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
})
}

if (fastForward) {
setupFastForward(skippingSegments[0]);
}

if (!autoSkip
&& skippingSegments.length === 1
&& skippingSegments[0].actionType === ActionType.Poi) {
Expand Down Expand Up @@ -1925,6 +1930,16 @@ function createButton(baseID: string, title: string, callback: () => void, image
return newButton;
}

function shouldFastForward(segment: SponsorTime): boolean {
const canSkipNonMusic = !Config.config.skipNonMusicOnlyOnYoutubeMusic || isOnYouTubeMusic();
if (segment.category === "music_offtopic" && !canSkipNonMusic) {
return false;
}

return (!Config.config.manualSkipOnFullVideo || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
&& utils.getCategorySelection(segment.category)?.option === CategorySkipOption.FastForward;
}

function shouldAutoSkip(segment: SponsorTime): boolean {
const canSkipNonMusic = !Config.config.skipNonMusicOnlyOnYoutubeMusic || isOnYouTubeMusic();
if (segment.category === "music_offtopic" && !canSkipNonMusic) {
Expand Down Expand Up @@ -2867,3 +2882,25 @@ function checkForMiniplayerPlaying() {
}
}
}

let fastForwardInterval: NodeJS.Timeout = null;
let savedVideoSpeed = 1;

function fastForwardUpdate(startTime: number, endTime: number) {
const currentTime = getCurrentTime();
if (currentTime < startTime - 1 || currentTime >= endTime) {
clearInterval(fastForwardInterval);
fastForwardInterval = null;
setSpeed(savedVideoSpeed);
}
}

function setupFastForward(segment: SponsorTime) {
if (fastForwardInterval) return;
const newSpeed = utils.getCategorySelection(segment.category)!.speed;
savedVideoSpeed = getVideo().playbackRate;
setSpeed(newSpeed);
const startTime = segment.actionType === ActionType.Poi || segment.segment.length !== 2 ? 0 : segment.segment[0];
const endTime = segment.segment[segment.segment.length - 1];
fastForwardInterval = setInterval(() => fastForwardUpdate(startTime, endTime), 400);
}
Loading