Add silence skipper
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user