Use same audio context/source everywhere

This commit is contained in:
TC
2023-01-08 19:17:00 +01:00
parent fcb92fda84
commit 52b67af59c
5 changed files with 135 additions and 62 deletions

View File

@ -111,7 +111,6 @@
"electron-unhandled": "^4.0.1", "electron-unhandled": "^4.0.1",
"electron-updater": "^4.6.3", "electron-updater": "^4.6.3",
"filenamify": "^4.3.0", "filenamify": "^4.3.0",
"hark": "^1.2.3",
"html-to-text": "^8.2.1", "html-to-text": "^8.2.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",

View File

@ -1,5 +1,5 @@
const applyCompressor = () => { const applyCompressor = (e) => {
const audioContext = new AudioContext(); const audioContext = e.detail.audioContext;
const compressor = audioContext.createDynamicsCompressor(); const compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -50; compressor.threshold.value = -50;
@ -8,10 +8,11 @@ const applyCompressor = () => {
compressor.attack.value = 0; compressor.attack.value = 0;
compressor.release.value = 0.25; compressor.release.value = 0.25;
const source = audioContext.createMediaElementSource(document.querySelector("video")); e.detail.audioSource.connect(compressor);
source.connect(compressor);
compressor.connect(audioContext.destination); compressor.connect(audioContext.destination);
}; };
module.exports = () => document.addEventListener('apiLoaded', applyCompressor, { once: true, passive: true }); module.exports = () =>
document.addEventListener("audioCanPlay", applyCompressor, {
passive: true,
});

View File

@ -1,53 +1,112 @@
const hark = require("hark/hark.bundle.js");
module.exports = (options) => { module.exports = (options) => {
let isSilent = false; let isSilent = false;
let hasAudioStarted = false; let hasAudioStarted = false;
document.addEventListener("apiLoaded", () => { const smoothing = 0.1;
const video = document.querySelector("video"); const threshold = -100; // dB (-100 = absolute silence, 0 = loudest)
const speechEvents = hark(video, { const interval = 2; // ms
threshold: -100, // dB (-100 = absolute silence, 0 = loudest) const history = 10;
interval: 2, // ms const speakingHistory = Array(history).fill(0);
});
const skipSilence = () => {
if (options.onlySkipBeginning && hasAudioStarted) {
return;
}
if (isSilent && !video.paused) { document.addEventListener(
video.currentTime += 0.2; // in s "audioCanPlay",
} (e) => {
}; const video = document.querySelector("video");
const audioContext = e.detail.audioContext;
const sourceNode = e.detail.audioSource;
speechEvents.on("speaking", function () { // Use an audio analyser similar to Hark
isSilent = false; // https://github.com/otalk/hark/blob/master/hark.bundle.js
hasAudioStarted = true; const analyser = audioContext.createAnalyser();
}); analyser.fftSize = 512;
analyser.smoothingTimeConstant = smoothing;
const fftBins = new Float32Array(analyser.frequencyBinCount);
speechEvents.on("stopped_speaking", function () { sourceNode.connect(analyser);
if ( analyser.connect(audioContext.destination);
!(
video.paused || const looper = () => {
video.seeking || setTimeout(() => {
video.ended || const currentVolume = getMaxVolume(analyser, fftBins);
video.muted ||
video.volume === 0 let history = 0;
) if (currentVolume > threshold && isSilent) {
) { // trigger quickly, short history
isSilent = true; 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 (let i = 0; i < speakingHistory.length; i++) {
history += speakingHistory[i];
}
if (history == 0) {
// Silent
if (
!(
video.paused ||
video.seeking ||
video.ended ||
video.muted ||
video.volume === 0
)
) {
isSilent = true;
skipSilence();
}
}
}
speakingHistory.shift();
speakingHistory.push(0 + (currentVolume > threshold));
looper();
}, interval);
};
looper();
const skipSilence = () => {
if (options.onlySkipBeginning && hasAudioStarted) {
return;
}
if (isSilent && !video.paused) {
video.currentTime += 0.2; // in s
}
};
video.addEventListener("play", function () {
hasAudioStarted = false;
skipSilence(); skipSilence();
} });
});
video.addEventListener("play", function () { video.addEventListener("seeked", function () {
hasAudioStarted = false; hasAudioStarted = false;
skipSilence(); skipSilence();
}); });
},
video.addEventListener("seeked", function () { {
hasAudioStarted = false; passive: true,
skipSilence(); }
}); );
});
}; };
function getMaxVolume(analyser, fftBins) {
var maxVolume = -Infinity;
analyser.getFloatFrequencyData(fftBins);
for (var i = 4, ii = fftBins.length; i < ii; i++) {
if (fftBins[i] > maxVolume && fftBins[i] < 0) {
maxVolume = fftBins[i];
}
}
return maxVolume;
}

View File

@ -90,6 +90,32 @@ function listenForApiLoad() {
} }
function onApiLoaded() { function onApiLoaded() {
const video = document.querySelector("video");
const audioContext = new AudioContext();
const audioSource = audioContext.createMediaElementSource(video);
video.addEventListener(
"loadstart",
() => {
// Emit "audioCanPlay" for each video
video.addEventListener(
"canplaythrough",
() => {
document.dispatchEvent(
new CustomEvent("audioCanPlay", {
detail: {
audioContext: audioContext,
audioSource: audioSource,
},
})
);
},
{ once: true }
);
},
{ passive: true }
);
document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api })); document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api }));
// Remove upgrade button // Remove upgrade button

View File

@ -3527,13 +3527,6 @@ hard-rejection@^2.1.0:
resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
hark@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/hark/-/hark-1.2.3.tgz#959981400f561be5580ecd4321a9f55b16bacbd0"
integrity sha512-u68vz9SCa38ESiFJSDjqK8XbXqWzyot7Cj6Y2b6jk2NJ+II3MY2dIrLMg/kjtIAun4Y1DHF/20hfx4rq1G5GMg==
dependencies:
wildemitter "^1.2.0"
has-ansi@^2.0.0: has-ansi@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@ -6639,11 +6632,6 @@ widest-line@^3.1.0:
dependencies: dependencies:
string-width "^4.0.0" string-width "^4.0.0"
wildemitter@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/wildemitter/-/wildemitter-1.2.1.tgz#9da3b5ca498e4378628d1783145493c70a10b774"
integrity sha512-UMmSUoIQSir+XbBpTxOTS53uJ8s/lVhADCkEbhfRjUGFDPme/XGOb0sBWLx5sTz7Wx/2+TlAw1eK9O5lw5PiEw==
word-wrap@^1.2.3: word-wrap@^1.2.3:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"