Add silence skipper

This commit is contained in:
WardPearce
2024-09-09 19:45:40 +12:00
parent 0982f4cdb0
commit 5130628a6b
6 changed files with 130 additions and 6 deletions
+1
View File
@@ -22,6 +22,7 @@
- Sync your watch progress between Invidious sessions.
- Watch sync parties!
- Mini player.
- Silence skipper (Experimental)
- Preview video on hover.
- Sponsorblock built-in.
- Return YouTube dislikes built-in.
+100 -2
View File
@@ -4,7 +4,6 @@
import { page } from '$app/stores';
import { Capacitor } from '@capacitor/core';
import type { Page } from '@sveltejs/kit';
import { type ParsedCaptionsResult } from 'media-captions';
import { SponsorBlock, type Category } from 'sponsorblock-api';
import { onDestroy, onMount } from 'svelte';
import { _ } from 'svelte-i18n';
@@ -23,6 +22,7 @@
playerDashStore,
playerProxyVideosStore,
playerSavePlaybackPositionStore,
silenceSkipperStore,
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
@@ -43,7 +43,10 @@
let playerIsLive = false;
let playerPosSet = false;
let transcript: ParsedCaptionsResult;
let userVideoSpeed = 1;
let silenceIsFastForwarding = false;
let silenceSkipperInterval: NodeJS.Timeout;
function loadTimeFromUrl(page: Page): boolean {
if (player) {
@@ -59,8 +62,74 @@
page.subscribe((pageUpdate) => loadTimeFromUrl(pageUpdate));
let initStoreSilence = true;
silenceSkipperStore.subscribe((value) => {
if (initStoreSilence) {
initStoreSilence = false;
return;
}
if (typeof silenceSkipperInterval !== 'undefined') {
clearInterval(silenceSkipperInterval);
}
if (value) {
initSilenceSkipper();
}
});
const proxyVideos = get(playerProxyVideosStore);
function fastForwardToSound(
videoElement: HTMLAudioElement,
threshold: number = 0.01,
checkInterval: number = 100,
fastForwardRate: number = 4
): void {
if (!(videoElement instanceof HTMLMediaElement)) {
console.error('Provided element is not a valid HTMLMediaElement');
return;
}
const audioContext = new AudioContext();
const source = audioContext.createMediaElementSource(videoElement);
const analyser = audioContext.createAnalyser();
source.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 256;
const dataArray = new Uint8Array(analyser.frequencyBinCount);
const checkForSound = () => {
analyser.getByteFrequencyData(dataArray);
const averageVolume = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
const volume = averageVolume / 255;
if (volume < threshold) {
if (!silenceIsFastForwarding) {
videoElement.playbackRate = fastForwardRate;
silenceIsFastForwarding = true;
}
} else {
if (silenceIsFastForwarding) {
videoElement.playbackRate = userVideoSpeed;
silenceIsFastForwarding = false;
}
}
};
silenceSkipperInterval = setInterval(checkForSound, checkInterval);
}
function initSilenceSkipper() {
const videoContainer = document.getElementById('video') as HTMLElement;
const videoElement = videoContainer.querySelector('video') as HTMLMediaElement;
fastForwardToSound(videoElement);
}
onMount(async () => {
miniPlayerSrcStore.set(null);
@@ -105,10 +174,35 @@
player.addEventListener('pause', () => {
savePlayerPos();
if (get(silenceSkipperStore)) {
clearInterval(silenceSkipperInterval);
}
});
player.addEventListener('end', () => {
savePlayerPos();
if (get(silenceSkipperStore)) {
clearInterval(silenceSkipperInterval);
}
});
player.addEventListener('play', () => {
if (get(silenceSkipperStore)) {
initSilenceSkipper();
}
});
player.addEventListener('seeked', () => {
if (get(silenceSkipperStore)) {
clearInterval(silenceSkipperInterval);
initSilenceSkipper();
}
});
player.addEventListener('rate-change', () => {
if (silenceIsFastForwarding) return;
userVideoSpeed = player.playbackRate;
});
if (get(sponsorBlockStore) && get(sponsorBlockCategoriesStore)) {
@@ -266,6 +360,9 @@
}
onDestroy(() => {
if (typeof silenceSkipperInterval !== 'undefined') {
clearInterval(silenceSkipperInterval);
}
savePlayerPos();
player.pause();
player.destroy();
@@ -279,6 +376,7 @@
<media-player
bind:this={player}
id="video"
autoPlay={$playerAutoPlayStore && !isSyncing}
loop={$playerAlwaysLoopStore}
title={data.video.title}
+18 -1
View File
@@ -32,6 +32,7 @@
playerTheatreModeByDefaultStore,
returnYTDislikesInstanceStore,
returnYtDislikesStore,
silenceSkipperStore,
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
@@ -140,7 +141,7 @@
{/if}
<div class="settings">
<h6>Interface</h6>
<h6>{$_('layout.interface')}</h6>
<div class="field no-margin">
<nav class="no-padding">
@@ -421,6 +422,22 @@
</nav>
</div>
{/if}
<div class="field no-margin">
<nav class="no-padding">
<div class="max">
<div>{$_('layout.player.silenceSkipper')}</div>
</div>
<label class="switch">
<input
type="checkbox"
bind:checked={$silenceSkipperStore}
on:click={() => silenceSkipperStore.set(!$silenceSkipperStore)}
/>
<span></span>
</label>
</nav>
</div>
</div>
<div class="settings">
+6
View File
@@ -22,6 +22,7 @@ import {
playerTheatreModeByDefaultStore,
returnYTDislikesInstanceStore,
returnYtDislikesStore,
silenceSkipperStore,
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
@@ -177,6 +178,11 @@ const persistedStores = [
name: 'sponsorBlockDisplayToast',
store: sponsorBlockDisplayToastStore,
type: 'boolean'
},
{
name: 'silenceSkipper',
store: silenceSkipperStore,
type: 'boolean'
}
];
+3 -1
View File
@@ -75,6 +75,7 @@
"addToPlaylist": "Add to playlist"
},
"layout": {
"interface": "Interface",
"star": "Star us on Github!",
"syncParty": "Sync party",
"syncPartyWarning": "Please note your IP will be visible to users you invite.",
@@ -111,7 +112,8 @@
"miniPlayer": "Mini player",
"proxyVideos": "Proxy videos",
"backgroundPlay": "Play audio in background",
"dash": "Dash"
"dash": "Dash",
"silenceSkipper": "Silence skipper (Experimental)"
},
"bookmarklet": "Bookmarklet",
"instanceUrl": "Instance URL",
+2 -2
View File
@@ -6,7 +6,6 @@ import { persisted } from 'svelte-persisted-store';
import { writable, type Writable } from 'svelte/store';
import type { TitleCase } from './misc';
function platformDependentDefault(givenValue: any, defaultValue: any): any {
if (typeof givenValue !== 'undefined' && typeof givenValue !== null) {
return givenValue;
@@ -15,7 +14,6 @@ function platformDependentDefault(givenValue: any, defaultValue: any): any {
}
}
export const instanceStore: Writable<string> = persisted(
"invidiousInstance",
platformDependentDefault(import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE, 'https://invidious.materialio.us')
@@ -95,3 +93,5 @@ export const playlistSettingsStore: Writable<Record<string, { shuffle: boolean;
export const miniPlayerSrcStore: Writable<{ video: VideoPlay; time: number; } | null> =
writable(null);
export const silenceSkipperStore: Writable<boolean> = persisted('silenceSkipper', false);