mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-13 03:11:46 +00:00
132 lines
3.4 KiB
TypeScript
132 lines
3.4 KiB
TypeScript
import type { RendererContext } from '@/types/contexts';
|
|
import type { SkipSilencesPluginConfig } from './index';
|
|
|
|
let config: SkipSilencesPluginConfig;
|
|
|
|
let isSilent = false;
|
|
let hasAudioStarted = false;
|
|
|
|
const smoothing = 0.1;
|
|
const threshold = -100; // DB (-100 = absolute silence, 0 = loudest)
|
|
const interval = 2; // Ms
|
|
const history = 10;
|
|
const speakingHistory = Array.from({ length: history }).fill(0) as number[];
|
|
|
|
let playOrSeekHandler: (() => void) | undefined;
|
|
|
|
const getMaxVolume = (analyser: AnalyserNode, fftBins: Float32Array) => {
|
|
let maxVolume = Number.NEGATIVE_INFINITY;
|
|
analyser.getFloatFrequencyData(fftBins);
|
|
|
|
for (let i = 4, ii = fftBins.length; i < ii; i++) {
|
|
if (fftBins[i] > maxVolume && fftBins[i] < 0) {
|
|
maxVolume = fftBins[i];
|
|
}
|
|
}
|
|
|
|
return maxVolume;
|
|
};
|
|
|
|
const audioCanPlayListener = (e: CustomEvent<Compressor>) => {
|
|
const video = document.querySelector('video');
|
|
const { audioContext } = e.detail;
|
|
const sourceNode = e.detail.audioSource;
|
|
|
|
// Use an audio analyser similar to Hark
|
|
// https://github.com/otalk/hark/blob/master/hark.bundle.js
|
|
const analyser = audioContext.createAnalyser();
|
|
analyser.fftSize = 512;
|
|
analyser.smoothingTimeConstant = smoothing;
|
|
const fftBins = new Float32Array(analyser.frequencyBinCount);
|
|
|
|
sourceNode.connect(analyser);
|
|
|
|
const looper = () => {
|
|
setTimeout(() => {
|
|
const currentVolume = getMaxVolume(analyser, fftBins);
|
|
|
|
let history = 0;
|
|
if (currentVolume > threshold && isSilent) {
|
|
// Trigger quickly, short history
|
|
for (
|
|
let i = speakingHistory.length - 3;
|
|
i < speakingHistory.length;
|
|
i++
|
|
) {
|
|
history += speakingHistory[i];
|
|
}
|
|
|
|
if (history >= 2) {
|
|
// Not silent
|
|
isSilent = false;
|
|
hasAudioStarted = true;
|
|
}
|
|
} else if (currentVolume < threshold && !isSilent) {
|
|
for (const element of speakingHistory) {
|
|
history += element;
|
|
}
|
|
|
|
if (
|
|
history == 0 && // Silent
|
|
!(
|
|
video &&
|
|
(video.paused ||
|
|
video.seeking ||
|
|
video.ended ||
|
|
video.muted ||
|
|
video.volume === 0)
|
|
)
|
|
) {
|
|
isSilent = true;
|
|
skipSilence();
|
|
}
|
|
}
|
|
|
|
speakingHistory.shift();
|
|
speakingHistory.push(Number(currentVolume > threshold));
|
|
|
|
looper();
|
|
}, interval);
|
|
};
|
|
|
|
looper();
|
|
|
|
const skipSilence = () => {
|
|
if (config.onlySkipBeginning && hasAudioStarted) {
|
|
return;
|
|
}
|
|
|
|
if (isSilent && video && !video.paused) {
|
|
video.currentTime += 0.2; // In s
|
|
}
|
|
};
|
|
|
|
playOrSeekHandler = () => {
|
|
hasAudioStarted = false;
|
|
skipSilence();
|
|
};
|
|
|
|
video?.addEventListener('play', playOrSeekHandler);
|
|
video?.addEventListener('seeked', playOrSeekHandler);
|
|
};
|
|
|
|
export const onRendererLoad = async ({
|
|
getConfig,
|
|
}: RendererContext<SkipSilencesPluginConfig>) => {
|
|
config = await getConfig();
|
|
|
|
document.addEventListener('ytmd:audio-can-play', audioCanPlayListener, {
|
|
passive: true,
|
|
});
|
|
};
|
|
|
|
export const onRendererUnload = () => {
|
|
document.removeEventListener('ytmd:audio-can-play', audioCanPlayListener);
|
|
|
|
if (playOrSeekHandler) {
|
|
const video = document.querySelector('video');
|
|
video?.removeEventListener('play', playOrSeekHandler);
|
|
video?.removeEventListener('seeked', playOrSeekHandler);
|
|
}
|
|
};
|