This commit is contained in:
Araxeus
2023-03-21 17:56:39 +02:00
parent 764f08ebfb
commit 55a442e90e
2 changed files with 125 additions and 126 deletions

View File

@ -1,8 +1,8 @@
const { ipcRenderer, ipcMain } = require('electron'); const { ipcRenderer, ipcMain } = require("electron");
const defaultConfig = require('./defaults'); const defaultConfig = require("./defaults");
const { getOptions, setOptions, setMenuOptions } = require('./plugins'); const { getOptions, setOptions, setMenuOptions } = require("./plugins");
const { sendToFront } = require('../providers/app-controls'); const { sendToFront } = require("../providers/app-controls");
const activePlugins = {}; const activePlugins = {};
/** /**
@ -10,12 +10,12 @@ const activePlugins = {};
* The method is **sync** in the main process and **async** in the renderer process. * The method is **sync** in the main process and **async** in the renderer process.
*/ */
module.exports.getActivePlugins = module.exports.getActivePlugins =
process.type === 'renderer' process.type === "renderer"
? async () => ipcRenderer.invoke('get-active-plugins') ? async () => ipcRenderer.invoke("get-active-plugins")
: () => activePlugins; : () => activePlugins;
if (process.type === 'browser') { if (process.type === "browser") {
ipcMain.handle('get-active-plugins', this.getActivePlugins); ipcMain.handle("get-active-plugins", this.getActivePlugins);
} }
/** /**
@ -23,9 +23,9 @@ if (process.type === 'browser') {
* The method is **sync** in the main process and **async** in the renderer process. * The method is **sync** in the main process and **async** in the renderer process.
*/ */
module.exports.isActive = module.exports.isActive =
process.type === 'renderer' process.type === "renderer"
? async (plugin) => ? async (plugin) =>
plugin in (await ipcRenderer.invoke('get-active-plugins')) plugin in (await ipcRenderer.invoke("get-active-plugins"))
: (plugin) => plugin in activePlugins; : (plugin) => plugin in activePlugins;
/** /**
@ -99,8 +99,8 @@ module.exports.PluginConfig = class PluginConfig {
}; };
setAll = (options) => { setAll = (options) => {
if (!options || typeof options !== 'object') if (!options || typeof options !== "object")
throw new Error('Options must be an object.'); throw new Error("Options must be an object.");
let changed = false; let changed = false;
for (const [key, val] of Object.entries(options)) { for (const [key, val] of Object.entries(options)) {
@ -148,11 +148,11 @@ module.exports.PluginConfig = class PluginConfig {
} }
#setupFront() { #setupFront() {
const ignoredMethods = ['subscribe', 'subscribeAll']; const ignoredMethods = ["subscribe", "subscribeAll"];
if (process.type === 'renderer') { if (process.type === "renderer") {
for (const [fnName, fn] of Object.entries(this)) { for (const [fnName, fn] of Object.entries(this)) {
if (typeof fn !== 'function' || fn.name in ignoredMethods) return; if (typeof fn !== "function" || fn.name in ignoredMethods) return;
this[fnName] = async (...args) => { this[fnName] = async (...args) => {
return await ipcRenderer.invoke( return await ipcRenderer.invoke(
`${this.name}-config-${fnName}`, `${this.name}-config-${fnName}`,
@ -181,9 +181,9 @@ module.exports.PluginConfig = class PluginConfig {
ipcRenderer.send(`${this.name}-config-subscribe-all`); ipcRenderer.send(`${this.name}-config-subscribe-all`);
}; };
} }
} else if (process.type === 'browser') { } else if (process.type === "browser") {
for (const [fnName, fn] of Object.entries(this)) { for (const [fnName, fn] of Object.entries(this)) {
if (typeof fn !== 'function' || fn.name in ignoredMethods) return; if (typeof fn !== "function" || fn.name in ignoredMethods) return;
ipcMain.handle(`${this.name}-config-${fnName}`, (_, ...args) => { ipcMain.handle(`${this.name}-config-${fnName}`, (_, ...args) => {
return fn(...args); return fn(...args);
}); });

View File

@ -1,156 +1,155 @@
const { ipcRenderer } = require('electron'); const { ipcRenderer } = require("electron");
const { Howl } = require('howler'); const { Howl } = require("howler");
// Extracted from https://github.com/bitfasching/VolumeFader // Extracted from https://github.com/bitfasching/VolumeFader
require('./fader'); require("./fader");
let transitionAudio; // Howler audio used to fade out the current music let transitionAudio; // Howler audio used to fade out the current music
let firstVideo = true; let firstVideo = true;
let waitForTransition; let waitForTransition;
const defaultConfig = require('../../config/defaults').plugins.crossfade; const defaultConfig = require("../../config/defaults").plugins.crossfade;
const configProvider = require('./config'); const configProvider = require("./config");
let config = configProvider.getAll(); let config;
console.log({ config });
configProvider.subscribeAll((newConfig) => {
config = newConfig;
});
const configGetNum = (key) => Number(config[key]) || defaultConfig[key]; const configGetNum = (key) => Number(config[key]) || defaultConfig[key];
const getStreamURL = async (videoID) => { const getStreamURL = async (videoID) => {
const url = await ipcRenderer.invoke('audio-url', videoID); const url = await ipcRenderer.invoke("audio-url", videoID);
return url; return url;
}; };
const getVideoIDFromURL = (url) => { const getVideoIDFromURL = (url) => {
return new URLSearchParams(url.split('?')?.at(-1)).get('v'); return new URLSearchParams(url.split("?")?.at(-1)).get("v");
}; };
const isReadyToCrossfade = () => { const isReadyToCrossfade = () => {
return transitionAudio && transitionAudio.state() === 'loaded'; return transitionAudio && transitionAudio.state() === "loaded";
}; };
const watchVideoIDChanges = (cb) => { const watchVideoIDChanges = (cb) => {
navigation.addEventListener('navigate', (event) => { navigation.addEventListener("navigate", (event) => {
const currentVideoID = getVideoIDFromURL( const currentVideoID = getVideoIDFromURL(
event.currentTarget.currentEntry.url, event.currentTarget.currentEntry.url,
); );
const nextVideoID = getVideoIDFromURL(event.destination.url); const nextVideoID = getVideoIDFromURL(event.destination.url);
if ( if (
nextVideoID && nextVideoID &&
currentVideoID && currentVideoID &&
(firstVideo || nextVideoID !== currentVideoID) (firstVideo || nextVideoID !== currentVideoID)
) { ) {
if (isReadyToCrossfade()) { if (isReadyToCrossfade()) {
crossfade(() => { crossfade(() => {
cb(nextVideoID); cb(nextVideoID);
}); });
} else { } else {
cb(nextVideoID); cb(nextVideoID);
firstVideo = false; firstVideo = false;
} }
} }
}); });
}; };
const createAudioForCrossfade = async (url) => { const createAudioForCrossfade = async (url) => {
if (transitionAudio) { if (transitionAudio) {
transitionAudio.unload(); transitionAudio.unload();
} }
transitionAudio = new Howl({ transitionAudio = new Howl({
src: url, src: url,
html5: true, html5: true,
volume: 0, volume: 0,
}); });
await syncVideoWithTransitionAudio(); await syncVideoWithTransitionAudio();
}; };
const syncVideoWithTransitionAudio = async () => { const syncVideoWithTransitionAudio = async () => {
const video = document.querySelector('video'); const video = document.querySelector("video");
const videoFader = new VolumeFader(video, { const videoFader = new VolumeFader(video, {
fadeScaling: configGetNum('fadeScaling'), fadeScaling: configGetNum("fadeScaling"),
fadeDuration: configGetNum('fadeInDuration'), fadeDuration: configGetNum("fadeInDuration"),
}); });
await transitionAudio.play(); await transitionAudio.play();
await transitionAudio.seek(video.currentTime); await transitionAudio.seek(video.currentTime);
video.onseeking = () => { video.onseeking = () => {
transitionAudio.seek(video.currentTime); transitionAudio.seek(video.currentTime);
}; };
video.onpause = () => { video.onpause = () => {
transitionAudio.pause(); transitionAudio.pause();
}; };
video.onplay = async () => { video.onplay = async () => {
await transitionAudio.play(); await transitionAudio.play();
await transitionAudio.seek(video.currentTime); await transitionAudio.seek(video.currentTime);
// Fade in // Fade in
const videoVolume = video.volume; const videoVolume = video.volume;
video.volume = 0; video.volume = 0;
videoFader.fadeTo(videoVolume); videoFader.fadeTo(videoVolume);
}; };
// Exit just before the end for the transition // Exit just before the end for the transition
const transitionBeforeEnd = () => { const transitionBeforeEnd = () => {
if ( if (
video.currentTime >= video.currentTime >= video.duration - configGetNum("secondsBeforeEnd") &&
video.duration - configGetNum('secondsBeforeEnd') && isReadyToCrossfade()
isReadyToCrossfade() ) {
) { video.removeEventListener("timeupdate", transitionBeforeEnd);
video.removeEventListener('timeupdate', transitionBeforeEnd);
// Go to next video - XXX: does not support "repeat 1" mode // Go to next video - XXX: does not support "repeat 1" mode
document.querySelector('.next-button').click(); document.querySelector(".next-button").click();
} }
}; };
video.ontimeupdate = transitionBeforeEnd; video.ontimeupdate = transitionBeforeEnd;
}; };
const onApiLoaded = () => { const onApiLoaded = () => {
watchVideoIDChanges(async (videoID) => { watchVideoIDChanges(async (videoID) => {
await waitForTransition; await waitForTransition;
const url = await getStreamURL(videoID); const url = await getStreamURL(videoID);
await createAudioForCrossfade(url); await createAudioForCrossfade(url);
}); });
}; };
const crossfade = async (cb) => { const crossfade = async (cb) => {
if (!isReadyToCrossfade()) { if (!isReadyToCrossfade()) {
cb(); cb();
return; return;
} }
let resolveTransition; let resolveTransition;
waitForTransition = new Promise(function (resolve, reject) { waitForTransition = new Promise(function (resolve, reject) {
resolveTransition = resolve; resolveTransition = resolve;
}); });
const video = document.querySelector('video'); const video = document.querySelector("video");
const fader = new VolumeFader(transitionAudio._sounds[0]._node, { const fader = new VolumeFader(transitionAudio._sounds[0]._node, {
initialVolume: video.volume, initialVolume: video.volume,
fadeScaling: configGetNum('fadeScaling'), fadeScaling: configGetNum("fadeScaling"),
fadeDuration: configGetNum('fadeOutDuration'), fadeDuration: configGetNum("fadeOutDuration"),
}); });
// Fade out the music // Fade out the music
video.volume = 0; video.volume = 0;
fader.fadeOut(() => { fader.fadeOut(() => {
resolveTransition(); resolveTransition();
cb(); cb();
}); });
}; };
module.exports = () => { module.exports = async () => {
document.addEventListener('apiLoaded', onApiLoaded, { config = await configProvider.getAll();
once: true,
passive: true, configProvider.subscribeAll((newConfig) => {
}); config = newConfig;
});
document.addEventListener("apiLoaded", onApiLoaded, {
once: true,
passive: true,
});
}; };