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

View File

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