Add an option to skip sponsorblock segments manually

Fixes #894
This commit is contained in:
novenary 2023-02-01 17:37:43 +02:00
parent c1da96ebb1
commit e1b355e92a
4 changed files with 200 additions and 109 deletions

View File

@ -184,83 +184,77 @@
/> />
</label> </label>
<div v-if="sponsorBlock"> <div v-if="sponsorBlock">
<label class="pref" for="chkSkipSponsors"> <label class="pref" for="ddlSkipSponsors">
<strong v-t="'actions.skip_sponsors'" /> <strong v-t="'actions.skip_sponsors'" />
<input <select id="ddlSkipSponsors" v-model="skipSponsor" class="select w-auto" @change="onChange($event)">
id="chkSkipSponsors" <option v-t="'actions.no'" value="no" />
v-model="skipSponsor" <option v-t="'actions.skip_button_only'" value="button" />
class="checkbox" <option v-t="'actions.skip_automatically'" value="auto" />
type="checkbox" </select>
@change="onChange($event)"
/>
</label> </label>
<label class="pref" for="chkSkipIntro"> <label class="pref" for="ddlSkipIntro">
<strong v-t="'actions.skip_intro'" /> <strong v-t="'actions.skip_intro'" />
<input id="chkSkipIntro" v-model="skipIntro" class="checkbox" type="checkbox" @change="onChange($event)" /> <select id="ddlSkipIntro" v-model="skipIntro" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.no'" value="no" />
<option v-t="'actions.skip_button_only'" value="button" />
<option v-t="'actions.skip_automatically'" value="auto" />
</select>
</label> </label>
<label class="pref" for="chkSkipOutro"> <label class="pref" for="ddlSkipOutro">
<strong v-t="'actions.skip_outro'" /> <strong v-t="'actions.skip_outro'" />
<input id="chkSkipOutro" v-model="skipOutro" class="checkbox" type="checkbox" @change="onChange($event)" /> <select id="ddlSkipOutro" v-model="skipOutro" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.no'" value="no" />
<option v-t="'actions.skip_button_only'" value="button" />
<option v-t="'actions.skip_automatically'" value="auto" />
</select>
</label> </label>
<label class="pref" for="chkSkipPreview"> <label class="pref" for="ddlSkipPreview">
<strong v-t="'actions.skip_preview'" /> <strong v-t="'actions.skip_preview'" />
<input <select id="ddlSkipPreview" v-model="skipPreview" class="select w-auto" @change="onChange($event)">
id="chkSkipPreview" <option v-t="'actions.no'" value="no" />
v-model="skipPreview" <option v-t="'actions.skip_button_only'" value="button" />
class="checkbox" <option v-t="'actions.skip_automatically'" value="auto" />
type="checkbox" </select>
@change="onChange($event)"
/>
</label> </label>
<label class="pref" for="chkSkipInteraction"> <label class="pref" for="ddlSkipInteraction">
<strong v-t="'actions.skip_interaction'" /> <strong v-t="'actions.skip_interaction'" />
<input <select id="ddlSkipInteraction" v-model="skipInteraction" class="select w-auto" @change="onChange($event)">
id="chkSkipInteraction" <option v-t="'actions.no'" value="no" />
v-model="skipInteraction" <option v-t="'actions.skip_button_only'" value="button" />
class="checkbox" <option v-t="'actions.skip_automatically'" value="auto" />
type="checkbox" </select>
@change="onChange($event)"
/>
</label> </label>
<label class="pref" for="chkSkipSelfPromo"> <label class="pref" for="ddlSkipSelfPromo">
<strong v-t="'actions.skip_self_promo'" /> <strong v-t="'actions.skip_self_promo'" />
<input <select id="ddlSkipSelfPromo" v-model="skipSelfPromo" class="select w-auto" @change="onChange($event)">
id="chkSkipSelfPromo" <option v-t="'actions.no'" value="no" />
v-model="skipSelfPromo" <option v-t="'actions.skip_button_only'" value="button" />
class="checkbox" <option v-t="'actions.skip_automatically'" value="auto" />
type="checkbox" </select>
@change="onChange($event)"
/>
</label> </label>
<label class="pref" for="chkSkipNonMusic"> <label class="pref" for="ddlSkipNonMusic">
<strong v-t="'actions.skip_non_music'" /> <strong v-t="'actions.skip_non_music'" />
<input <select id="ddlSkipNonMusic" v-model="skipMusicOffTopic" class="select w-auto" @change="onChange($event)">
id="chkSkipNonMusic" <option v-t="'actions.no'" value="no" />
v-model="skipMusicOffTopic" <option v-t="'actions.skip_button_only'" value="button" />
class="checkbox" <option v-t="'actions.skip_automatically'" value="auto" />
type="checkbox" </select>
@change="onChange($event)"
/>
</label> </label>
<label class="pref" for="chkSkipHighlight"> <label class="pref" for="ddlSkipHighlight">
<strong v-t="'actions.skip_highlight'" /> <strong v-t="'actions.skip_highlight'" />
<input <select id="ddlSkipHighlight" v-model="skipHighlight" class="select w-auto" @change="onChange($event)">
id="chkSkipHighlight" <option v-t="'actions.no'" value="no" />
v-model="skipHighlight" <option v-t="'actions.skip_button_only'" value="button" />
class="checkbox" <option v-t="'actions.skip_automatically'" value="auto" />
type="checkbox" </select>
@change="onChange($event)"
/>
</label> </label>
<label class="pref" for="chkSkipFiller"> <label class="pref" for="ddlSkipFiller">
<strong v-t="'actions.skip_filler_tangent'" /> <strong v-t="'actions.skip_filler_tangent'" />
<input <select id="ddlSkipFiller" v-model="skipFiller" class="select w-auto" @change="onChange($event)">
id="chkSkipFiller" <option v-t="'actions.no'" value="no" />
v-model="skipFiller" <option v-t="'actions.skip_button_only'" value="button" />
class="checkbox" <option v-t="'actions.skip_automatically'" value="auto" />
type="checkbox" </select>
@change="onChange($event)"
/>
</label> </label>
<label class="pref" for="chkShowMarkers"> <label class="pref" for="chkShowMarkers">
<strong v-t="'actions.show_markers'" /> <strong v-t="'actions.show_markers'" />
@ -391,15 +385,15 @@ export default {
selectedAuthInstance: null, selectedAuthInstance: null,
instances: [], instances: [],
sponsorBlock: true, sponsorBlock: true,
skipSponsor: true, skipSponsor: "auto",
skipIntro: false, skipIntro: "no",
skipOutro: false, skipOutro: "no",
skipPreview: false, skipPreview: "no",
skipInteraction: true, skipInteraction: "auto",
skipSelfPromo: true, skipSelfPromo: "auto",
skipMusicOffTopic: true, skipMusicOffTopic: "auto",
skipHighlight: false, skipHighlight: "no",
skipFiller: false, skipFiller: "no",
showMarkers: true, showMarkers: true,
selectedTheme: "dark", selectedTheme: "dark",
autoPlayVideo: true, autoPlayVideo: true,
@ -497,7 +491,18 @@ export default {
this.selectedAuthInstance = this.getPreferenceString("auth_instance_url", this.selectedInstance); this.selectedAuthInstance = this.getPreferenceString("auth_instance_url", this.selectedInstance);
this.sponsorBlock = this.getPreferenceBoolean("sponsorblock", true); this.sponsorBlock = this.getPreferenceBoolean("sponsorblock", true);
if (localStorage.getItem("selectedSkip") !== null) { if (localStorage.getItem("skipOptions") !== null) {
const skipOptions = JSON.parse(localStorage.getItem("skipOptions"));
if (skipOptions.sponsor !== undefined) this.skipSponsor = skipOptions.sponsor;
if (skipOptions.intro !== undefined) this.skipIntro = skipOptions.intro;
if (skipOptions.outro !== undefined) this.skipOutro = skipOptions.outro;
if (skipOptions.preview !== undefined) this.skipPreview = skipOptions.preview;
if (skipOptions.interaction !== undefined) this.skipInteraction = skipOptions.interaction;
if (skipOptions.selfpromo !== undefined) this.skipSelfPromo = skipOptions.selfpromo;
if (skipOptions.music_offtopic !== undefined) this.skipMusicOffTopic = skipOptions.music_offtopic;
if (skipOptions.poi_highlight !== undefined) this.skipHighlight = skipOptions.poi_highlight;
if (skipOptions.filler !== undefined) this.skipFiller = skipOptions.filler;
} else if (localStorage.getItem("selectedSkip") !== null) {
var skipList = localStorage.getItem("selectedSkip").split(","); var skipList = localStorage.getItem("selectedSkip").split(",");
this.skipSponsor = this.skipSponsor =
this.skipIntro = this.skipIntro =
@ -508,35 +513,35 @@ export default {
this.skipMusicOffTopic = this.skipMusicOffTopic =
this.skipHighlight = this.skipHighlight =
this.skipFiller = this.skipFiller =
false; "no";
skipList.forEach(skip => { skipList.forEach(skip => {
switch (skip) { switch (skip) {
case "sponsor": case "sponsor":
this.skipSponsor = true; this.skipSponsor = "auto";
break; break;
case "intro": case "intro":
this.skipIntro = true; this.skipIntro = "auto";
break; break;
case "outro": case "outro":
this.skipOutro = true; this.skipOutro = "auto";
break; break;
case "preview": case "preview":
this.skipPreview = true; this.skipPreview = "auto";
break; break;
case "interaction": case "interaction":
this.skipInteraction = true; this.skipInteraction = "auto";
break; break;
case "selfpromo": case "selfpromo":
this.skipSelfPromo = true; this.skipSelfPromo = "auto";
break; break;
case "music_offtopic": case "music_offtopic":
this.skipMusicOffTopic = true; this.skipMusicOffTopic = "auto";
break; break;
case "poi_highlight": case "poi_highlight":
this.skipHighlight = true; this.skipHighlight = "auto";
break; break;
case "filler": case "filler":
this.skipFiller = true; this.skipFiller = "auto";
break; break;
default: default:
console.log("Unknown sponsor type: " + skip); console.log("Unknown sponsor type: " + skip);
@ -594,17 +599,18 @@ export default {
localStorage.setItem("auth_instance_url", this.selectedAuthInstance); localStorage.setItem("auth_instance_url", this.selectedAuthInstance);
localStorage.setItem("sponsorblock", this.sponsorBlock); localStorage.setItem("sponsorblock", this.sponsorBlock);
var sponsorSelected = []; const skipOptions = {
if (this.skipSponsor) sponsorSelected.push("sponsor"); sponsor: this.skipSponsor,
if (this.skipIntro) sponsorSelected.push("intro"); intro: this.skipIntro,
if (this.skipOutro) sponsorSelected.push("outro"); outro: this.skipOutro,
if (this.skipPreview) sponsorSelected.push("preview"); preview: this.skipPreview,
if (this.skipInteraction) sponsorSelected.push("interaction"); interaction: this.skipInteraction,
if (this.skipSelfPromo) sponsorSelected.push("selfpromo"); selfpromo: this.skipSelfPromo,
if (this.skipMusicOffTopic) sponsorSelected.push("music_offtopic"); music_offtopic: this.skipMusicOffTopic,
if (this.skipHighlight) sponsorSelected.push("poi_highlight"); poi_highlight: this.skipHighlight,
if (this.skipFiller) sponsorSelected.push("filler"); filler: this.skipFiller,
localStorage.setItem("selectedSkip", sponsorSelected); };
localStorage.setItem("skipOptions", JSON.stringify(skipOptions));
localStorage.setItem("showMarkers", this.showMarkers); localStorage.setItem("showMarkers", this.showMarkers);
localStorage.setItem("theme", this.selectedTheme); localStorage.setItem("theme", this.selectedTheme);

View File

@ -88,7 +88,7 @@ export default {
this.hotkeysPromise.then(() => { this.hotkeysPromise.then(() => {
var self = this; var self = this;
this.$hotkeys( this.$hotkeys(
"f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.", "f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.,return",
function (e, handler) { function (e, handler) {
const videoEl = self.$refs.videoEl; const videoEl = self.$refs.videoEl;
switch (handler.key) { switch (handler.key) {
@ -184,6 +184,9 @@ export default {
case "shift+.": case "shift+.":
self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2)); self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
break; break;
case "return":
self.skipSegment(videoEl);
break;
} }
}, },
); );
@ -361,17 +364,16 @@ export default {
this.$emit("timeupdate", time); this.$emit("timeupdate", time);
this.updateProgressDatabase(time); this.updateProgressDatabase(time);
if (this.sponsors && this.sponsors.segments) { if (this.sponsors && this.sponsors.segments) {
this.sponsors.segments.map(segment => { const segment = this.findCurrentSegment(time);
if (!segment.skipped || this.selectedAutoLoop) { const segmentUpdate = new CustomEvent("segmentupdate", {
const end = segment.segment[1]; detail: {
if (time >= segment.segment[0] && time < end) { inSegment: !!segment,
console.log("Skipped segment at " + time); },
videoEl.currentTime = end;
segment.skipped = true;
return;
}
}
}); });
videoEl.dispatchEvent(segmentUpdate);
if (segment?.autoskip && (!segment.skipped || this.selectedAutoLoop)) {
this.skipSegment(videoEl, segment);
}
} }
}); });
@ -398,6 +400,17 @@ export default {
//TODO: Add sponsors on seekbar: https://github.com/ajayyy/SponsorBlock/blob/e39de9fd852adb9196e0358ed827ad38d9933e29/src/js-components/previewBar.ts#L12 //TODO: Add sponsors on seekbar: https://github.com/ajayyy/SponsorBlock/blob/e39de9fd852adb9196e0358ed827ad38d9933e29/src/js-components/previewBar.ts#L12
}, },
findCurrentSegment(time) {
return this.sponsors?.segments?.find(s => time >= s.segment[0] && time < s.segment[1]);
},
skipSegment(videoEl, segment) {
const time = videoEl.currentTime;
if (!segment) segment = this.findCurrentSegment(time);
if (!segment) return;
console.log("Skipped segment at " + time);
videoEl.currentTime = segment.segment[1];
segment.skipped = true;
},
setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) { setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) {
const url = "/watch?v=" + this.video.id; const url = "/watch?v=" + this.video.id;
@ -442,6 +455,54 @@ export default {
shaka.ui.OverflowMenu.registerElement("open_new_tab", new OpenButton.Factory()); shaka.ui.OverflowMenu.registerElement("open_new_tab", new OpenButton.Factory());
const videoPlayerComponent = this;
const SkipSegmentButton = class extends shaka.ui.Element {
constructor(parent, controls) {
super(parent, controls);
// FIXME: this layout is by no means final,
// I just needed something to test with.
this.button_ = document.createElement("button");
this.button_.classList.add("shaka-small-play-button");
this.button_.classList.add("shaka-tooltip");
this.button_.classList.add("shaka-hidden");
this.button_.ariaPressed = "false";
this.icon_ = document.createElement("i");
this.icon_.classList.add("material-icons-round");
this.icon_.textContent = "skip_next";
this.button_.appendChild(this.icon_);
const label = document.createElement("label");
this.newTabNameSpan_ = document.createElement("span");
this.newTabNameSpan_.classList.add("shaka-current-time");
this.newTabNameSpan_.innerText = "Skip segment";
label.appendChild(this.newTabNameSpan_);
this.button_.appendChild(label);
this.parent.appendChild(this.button_);
this.eventManager.listen(this.button_, "click", () => {
videoPlayerComponent.skipSegment(videoEl);
});
videoEl.addEventListener("segmentupdate", e => {
const inSegment = e.detail.inSegment;
if (inSegment) this.button_.classList.remove("shaka-hidden");
else this.button_.classList.add("shaka-hidden");
});
}
};
SkipSegmentButton.Factory = class {
create(rootElement, controls) {
return new SkipSegmentButton(rootElement, controls);
}
};
shaka.ui.Controls.registerElement("skip_segment", new SkipSegmentButton.Factory());
this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl); this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl);
const overflowMenuButtons = [ const overflowMenuButtons = [
@ -458,6 +519,17 @@ export default {
} }
const config = { const config = {
controlPanelElements: [
"play_pause",
"time_and_duration",
"spacer",
"skip_segment",
"spacer",
"mute",
"volume",
"fullscreen",
"overflow_menu",
],
overflowMenuButtons: overflowMenuButtons, overflowMenuButtons: overflowMenuButtons,
seekBarColors: { seekBarColors: {
base: "rgba(255, 255, 255, 0.3)", base: "rgba(255, 255, 255, 0.3)",
@ -678,6 +750,11 @@ export default {
}, },
watch: { watch: {
sponsors() { sponsors() {
const skipOptions = JSON.parse(this.getPreferenceString("skipOptions", "{}"));
this.sponsors?.segments?.forEach(segment => {
const option = skipOptions[segment.category];
segment.autoskip = option === undefined || option === "auto";
});
if (this.getPreferenceBoolean("showMarkers", true)) { if (this.getPreferenceBoolean("showMarkers", true)) {
this.shakaPromise.then(() => { this.shakaPromise.then(() => {
this.updateMarkers(); this.updateMarkers();

View File

@ -367,14 +367,20 @@ export default {
return this.fetchJson(this.apiUrl() + "/streams/" + this.getVideoId()); return this.fetchJson(this.apiUrl() + "/streams/" + this.getVideoId());
}, },
async fetchSponsors() { async fetchSponsors() {
var selectedSkip = this.getPreferenceString(
"selectedSkip",
"sponsor,interaction,selfpromo,music_offtopic",
).split(",");
const skipOptionsJSON = localStorage.getItem("skipOptions");
if (skipOptionsJSON !== null) {
const skipOptions = JSON.parse(skipOptionsJSON);
selectedSkip = Object.keys(skipOptions).filter(
k => skipOptions[k] !== undefined && skipOptions[k] !== "no",
);
}
return await this.fetchJson(this.apiUrl() + "/sponsors/" + this.getVideoId(), { return await this.fetchJson(this.apiUrl() + "/sponsors/" + this.getVideoId(), {
category: category: JSON.stringify(selectedSkip),
'["' +
this.getPreferenceString("selectedSkip", "sponsor,interaction,selfpromo,music_offtopic").replaceAll(
",",
'","',
) +
'"]',
}); });
}, },
toggleComments() { toggleComments() {

View File

@ -30,6 +30,8 @@
"back": "Back", "back": "Back",
"uses_api_from": "Uses the API from ", "uses_api_from": "Uses the API from ",
"enable_sponsorblock": "Enable Sponsorblock", "enable_sponsorblock": "Enable Sponsorblock",
"skip_button_only": "Show skip button",
"skip_automatically": "Automatically",
"skip_sponsors": "Skip Sponsors", "skip_sponsors": "Skip Sponsors",
"skip_intro": "Skip Intermission/Intro Animation", "skip_intro": "Skip Intermission/Intro Animation",
"skip_outro": "Skip Endcards/Credits", "skip_outro": "Skip Endcards/Credits",