From 8c311bf63068027791d9bcd857f21c8194847e0d Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Sun, 12 Mar 2023 01:59:21 +0200 Subject: [PATCH 01/10] bump custom-electron-prompt --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e989a1ddcc..fef4f42fb3 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "butterchurn": "^2.6.7", "butterchurn-presets": "^2.4.7", "chokidar": "^3.5.3", - "custom-electron-prompt": "^1.5.1", + "custom-electron-prompt": "^1.5.4", "custom-electron-titlebar": "^4.1.6", "electron-better-web-request": "^1.0.1", "electron-debug": "^3.2.0", diff --git a/yarn.lock b/yarn.lock index da6c595487..05338cf02f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2425,12 +2425,12 @@ __metadata: languageName: node linkType: hard -"custom-electron-prompt@npm:^1.5.1": - version: 1.5.1 - resolution: "custom-electron-prompt@npm:1.5.1" +"custom-electron-prompt@npm:^1.5.4": + version: 1.5.4 + resolution: "custom-electron-prompt@npm:1.5.4" peerDependencies: electron: ">=10.0.0" - checksum: 43a0d72a7a3471135822cb210d580285f70080d9d3a7b03f82cd4be403059fe20ea05ebdd1f9534928c386ab25a353e678f2cfb3f4ca016b41f3366bff700767 + checksum: 93995b5f0e9d14401a8c4fdd358af32d8b7585b59b111667cfa55f9505109c08914f3140953125b854e5d09e811de8c76c7fec718934c13e8a1ad09fe1b85270 languageName: node linkType: hard @@ -8981,7 +8981,7 @@ __metadata: butterchurn: ^2.6.7 butterchurn-presets: ^2.4.7 chokidar: ^3.5.3 - custom-electron-prompt: ^1.5.1 + custom-electron-prompt: ^1.5.4 custom-electron-titlebar: ^4.1.6 del-cli: ^5.0.0 electron: ^22.0.2 From 66ccd71b7ce83c5c4cea693fc435559384ee2cd7 Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Sun, 12 Mar 2023 02:14:23 +0200 Subject: [PATCH 02/10] [crossfade] add menu options --- config/defaults.js | 7 ++++ plugins/crossfade/front.js | 23 ++++-------- plugins/crossfade/menu.js | 73 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 plugins/crossfade/menu.js diff --git a/config/defaults.js b/config/defaults.js index d01d059a6f..bc8b9b7022 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -104,6 +104,13 @@ const defaultConfig = { "skip-silences": { onlySkipBeginning: false, }, + "crossfade": { + enabled: false, + fadeInDuration: 1500, // ms + fadeOutDuration: 5000, // ms + exitMusicBeforeEnd: 10, // s + fadeScaling: "linear", // 'linear', 'logarithmic' or a positive number in dB + }, visualizer: { enabled: false, type: "butterchurn", diff --git a/plugins/crossfade/front.js b/plugins/crossfade/front.js index f08d7d872b..a00bc2fd29 100644 --- a/plugins/crossfade/front.js +++ b/plugins/crossfade/front.js @@ -8,13 +8,7 @@ let transitionAudio; // Howler audio used to fade out the current music let firstVideo = true; let waitForTransition; -// Crossfade options that can be overridden in plugin options -let crossfadeOptions = { - fadeInDuration: 1500, // ms - fadeOutDuration: 5000, // ms - exitMusicBeforeEnd: 10, // s - fadeScaling: "linear", -}; +const defaultOptions = require('../../config/defaults').plugins.crossfade; const getStreamURL = async (videoID) => { const url = await ipcRenderer.invoke("audio-url", videoID); @@ -68,8 +62,8 @@ const createAudioForCrossfade = async (url) => { const syncVideoWithTransitionAudio = async () => { const video = document.querySelector("video"); const videoFader = new VolumeFader(video, { - fadeScaling: crossfadeOptions.fadeScaling, - fadeDuration: crossfadeOptions.fadeInDuration, + fadeScaling: defaultOptions.fadeScaling, + fadeDuration: defaultOptions.fadeInDuration, }); await transitionAudio.play(); @@ -95,7 +89,7 @@ const syncVideoWithTransitionAudio = async () => { const transitionBeforeEnd = () => { if ( video.currentTime >= - video.duration - crossfadeOptions.exitMusicBeforeEnd && + video.duration - defaultOptions.exitMusicBeforeEnd && isReadyToCrossfade() ) { video.removeEventListener("timeupdate", transitionBeforeEnd); @@ -130,8 +124,8 @@ const crossfade = (cb) => { const fader = new VolumeFader(transitionAudio._sounds[0]._node, { initialVolume: video.volume, - fadeScaling: crossfadeOptions.fadeScaling, - fadeDuration: crossfadeOptions.fadeOutDuration, + fadeScaling: defaultOptions.fadeScaling, + fadeDuration: defaultOptions.fadeOutDuration, }); // Fade out the music @@ -143,10 +137,7 @@ const crossfade = (cb) => { }; module.exports = (options) => { - crossfadeOptions = { - ...crossfadeOptions, - options, - }; + Object.assign(defaultOptions, options); document.addEventListener("apiLoaded", onApiLoaded, { once: true, diff --git a/plugins/crossfade/menu.js b/plugins/crossfade/menu.js new file mode 100644 index 0000000000..c21a2e14ca --- /dev/null +++ b/plugins/crossfade/menu.js @@ -0,0 +1,73 @@ +const { setOptions } = require("../../config/plugins"); +const defaultOptions = require("../../config/defaults").plugins.crossfade; + +const prompt = require("custom-electron-prompt"); +const promptOptions = require("../../providers/prompt-options"); + +module.exports = (win, options) => [ + { + label: "Advanced", + click: async () => { + const newOptions = await promptCrossfadeValues(win, options); + setOptions("crossfade", { ...options, ...newOptions }); + }, + }, +]; + +async function promptCrossfadeValues(win, options) { + const res = await prompt( + { + title: "Crossfade Options", + label: "", + type: "multiInput", + multiInputOptions: [ + { + label: "Fade in duration (ms)", + value: options.fadeInDuration || defaultOptions.fadeInDuration, + inputAttrs: { + type: "number", + required: true, + min: 0, + step: 100, + }, + }, + { + label: "Fade out duration (ms)", + value: options.fadeOutDuration || defaultOptions.fadeOutDuration, + inputAttrs: { + type: "number", + required: true, + min: 0, + step: 100, + }, + }, + { + label: "Crossfade x seconds before end", + value: + options.exitMusicBeforeEnd || defaultOptions.exitMusicBeforeEnd, + inputAttrs: { + type: "number", + required: true, + min: 0, + }, + }, + { + label: "Fade scaling", + selectOptions: { linear: "Linear", exponential: "Exponential" }, + value: options.fadeScaling || defaultOptions.fadeScaling, + }, + ], + resizable: true, + height: 355, + ...promptOptions(), + }, + win, + ).catch(console.error); + if (!res) return undefined; + return { + fadeInDuration: res[0], + fadeOutDuration: res[1], + exitMusicBeforeEnd: res[2], + fadeScaling: res[3], + }; +} From 848bb36c64822c2073aedce47afda8af976dbbcb Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:40:46 +0200 Subject: [PATCH 03/10] rename `defaultOptions` to `config` --- plugins/crossfade/front.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/crossfade/front.js b/plugins/crossfade/front.js index a00bc2fd29..5494387ada 100644 --- a/plugins/crossfade/front.js +++ b/plugins/crossfade/front.js @@ -8,7 +8,7 @@ let transitionAudio; // Howler audio used to fade out the current music let firstVideo = true; let waitForTransition; -const defaultOptions = require('../../config/defaults').plugins.crossfade; +const config = require('../../config/defaults').plugins.crossfade; const getStreamURL = async (videoID) => { const url = await ipcRenderer.invoke("audio-url", videoID); @@ -62,8 +62,8 @@ const createAudioForCrossfade = async (url) => { const syncVideoWithTransitionAudio = async () => { const video = document.querySelector("video"); const videoFader = new VolumeFader(video, { - fadeScaling: defaultOptions.fadeScaling, - fadeDuration: defaultOptions.fadeInDuration, + fadeScaling: config.fadeScaling, + fadeDuration: config.fadeInDuration, }); await transitionAudio.play(); @@ -89,7 +89,7 @@ const syncVideoWithTransitionAudio = async () => { const transitionBeforeEnd = () => { if ( video.currentTime >= - video.duration - defaultOptions.exitMusicBeforeEnd && + video.duration - config.exitMusicBeforeEnd && isReadyToCrossfade() ) { video.removeEventListener("timeupdate", transitionBeforeEnd); @@ -124,8 +124,8 @@ const crossfade = (cb) => { const fader = new VolumeFader(transitionAudio._sounds[0]._node, { initialVolume: video.volume, - fadeScaling: defaultOptions.fadeScaling, - fadeDuration: defaultOptions.fadeOutDuration, + fadeScaling: config.fadeScaling, + fadeDuration: config.fadeOutDuration, }); // Fade out the music @@ -137,7 +137,7 @@ const crossfade = (cb) => { }; module.exports = (options) => { - Object.assign(defaultOptions, options); + Object.assign(config, options); document.addEventListener("apiLoaded", onApiLoaded, { once: true, From 023258f1d73e8591d59b687e1ce907978a552270 Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Wed, 15 Mar 2023 08:24:50 +0200 Subject: [PATCH 04/10] fix crossfade option prompt height --- plugins/crossfade/menu.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/crossfade/menu.js b/plugins/crossfade/menu.js index c21a2e14ca..124c2fee2d 100644 --- a/plugins/crossfade/menu.js +++ b/plugins/crossfade/menu.js @@ -18,7 +18,6 @@ async function promptCrossfadeValues(win, options) { const res = await prompt( { title: "Crossfade Options", - label: "", type: "multiInput", multiInputOptions: [ { @@ -58,7 +57,7 @@ async function promptCrossfadeValues(win, options) { }, ], resizable: true, - height: 355, + height: 360, ...promptOptions(), }, win, From 9f9e991aecf7901251b818b010e583cde2b97329 Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Thu, 16 Mar 2023 19:50:54 +0200 Subject: [PATCH 05/10] [crossfade] fix options not saving as numbers --- plugins/crossfade/menu.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/crossfade/menu.js b/plugins/crossfade/menu.js index 124c2fee2d..b51750343c 100644 --- a/plugins/crossfade/menu.js +++ b/plugins/crossfade/menu.js @@ -52,7 +52,7 @@ async function promptCrossfadeValues(win, options) { }, { label: "Fade scaling", - selectOptions: { linear: "Linear", exponential: "Exponential" }, + selectOptions: { linear: "Linear", logarithmic: "Logarithmic" }, value: options.fadeScaling || defaultOptions.fadeScaling, }, ], @@ -64,9 +64,9 @@ async function promptCrossfadeValues(win, options) { ).catch(console.error); if (!res) return undefined; return { - fadeInDuration: res[0], - fadeOutDuration: res[1], - exitMusicBeforeEnd: res[2], + fadeInDuration: Number(res[0]), + fadeOutDuration: Number(res[1]), + exitMusicBeforeEnd: Number(res[2]), fadeScaling: res[3], }; } From 9d6a78bc57ef118ee490a53d694f80c967da3057 Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Thu, 16 Mar 2023 22:31:01 +0200 Subject: [PATCH 06/10] bump custom-electron-prompt * enable onwheel in number inputs * enable wheel event on keybind prompt --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 328e0f16db..9e6b02396e 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "butterchurn": "^2.6.7", "butterchurn-presets": "^2.4.7", "chokidar": "^3.5.3", - "custom-electron-prompt": "^1.5.4", + "custom-electron-prompt": "^1.5.7", "custom-electron-titlebar": "^4.1.6", "electron-better-web-request": "^1.0.1", "electron-debug": "^3.2.0", diff --git a/yarn.lock b/yarn.lock index 05338cf02f..d3fc5cf373 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2425,12 +2425,12 @@ __metadata: languageName: node linkType: hard -"custom-electron-prompt@npm:^1.5.4": - version: 1.5.4 - resolution: "custom-electron-prompt@npm:1.5.4" +"custom-electron-prompt@npm:^1.5.7": + version: 1.5.7 + resolution: "custom-electron-prompt@npm:1.5.7" peerDependencies: electron: ">=10.0.0" - checksum: 93995b5f0e9d14401a8c4fdd358af32d8b7585b59b111667cfa55f9505109c08914f3140953125b854e5d09e811de8c76c7fec718934c13e8a1ad09fe1b85270 + checksum: 7dd7b2fb6e0acdee35474893d0e98b5e701c411c76be716cc02c5c9ac42db4fdaa7d526e22fd8c7047c2f143559d185bed8731bd455a1d11982404512d5f5021 languageName: node linkType: hard @@ -8981,7 +8981,7 @@ __metadata: butterchurn: ^2.6.7 butterchurn-presets: ^2.4.7 chokidar: ^3.5.3 - custom-electron-prompt: ^1.5.4 + custom-electron-prompt: ^1.5.7 custom-electron-titlebar: ^4.1.6 del-cli: ^5.0.0 electron: ^22.0.2 From 212009a69b458808496b6ce32edf0f28a0038f83 Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Sun, 19 Mar 2023 23:42:27 +0200 Subject: [PATCH 07/10] create `sendToFront()` TODO: replace all `webcontents.send ` with `sendToFront = require('../providers/app-controls')` --- providers/app-controls.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/providers/app-controls.js b/providers/app-controls.js index 3567e22abc..bed99157c6 100644 --- a/providers/app-controls.js +++ b/providers/app-controls.js @@ -1,12 +1,10 @@ const path = require("path"); -const is = require("electron-is"); - const { app, BrowserWindow, ipcMain, ipcRenderer } = require("electron"); const config = require("../config"); module.exports.restart = () => { - is.main() ? restart() : ipcRenderer.send('restart'); + process.type === 'browser' ? restart() : ipcRenderer.send('restart'); }; module.exports.setupAppControls = () => { @@ -21,3 +19,16 @@ function restart() { // execPath will be undefined if not running portable app, resulting in default behavior app.quit(); } + +function sendToFront(channel, ...args) { + BrowserWindow.getAllWindows().forEach(win => { + win.webContents.send(channel, ...args); + }); +} + +module.exports.sendToFront = + process.type === 'browser' + ? sendToFront + : () => { + console.error('sendToFront called from renderer'); + }; From 648d10210125b38c0a2436b5ebe1904010f38bef Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Sun, 19 Mar 2023 23:47:29 +0200 Subject: [PATCH 08/10] add `subscribe` and `subscribeAll` to config this primarily allows front.js to have an up to date config without requesting it over ipc every second for example the crossfade plugin uses its `options.secondsBeforeEnd` every second - so `subscribeAll` would be much more efficient in this case --- config/dynamic.js | 94 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 14 deletions(-) diff --git a/config/dynamic.js b/config/dynamic.js index 34cb0ac363..6f78187a58 100644 --- a/config/dynamic.js +++ b/config/dynamic.js @@ -1,7 +1,8 @@ -const { ipcRenderer, ipcMain } = require("electron"); +const { ipcRenderer, ipcMain } = require('electron'); -const defaultConfig = require("./defaults"); -const { getOptions, setOptions, setMenuOptions } = require("./plugins"); +const defaultConfig = require('./defaults'); +const { getOptions, setOptions, setMenuOptions } = require('./plugins'); +const { sendToFront } = require('../providers/app-controls'); const activePlugins = {}; /** @@ -9,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); } /** @@ -22,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; /** @@ -58,6 +59,9 @@ module.exports.PluginConfig = class PluginConfig { #defaultConfig; #enableFront; + #subscribers = {}; + #allSubscribers = []; + constructor(name, { enableFront = false, initialOptions = undefined } = {}) { const pluginDefaultConfig = defaultConfig.plugins[name] || {}; const pluginConfig = initialOptions || getOptions(name) || {}; @@ -80,11 +84,13 @@ module.exports.PluginConfig = class PluginConfig { set = (option, value) => { this.#config[option] = value; + this.#onChange(option); this.#save(); }; toggle = (option) => { this.#config[option] = !this.#config[option]; + this.#onChange(option); this.#save(); }; @@ -93,7 +99,18 @@ module.exports.PluginConfig = class PluginConfig { }; setAll = (options) => { - this.#config = { ...this.#config, ...options }; + if (!options || typeof options !== 'object') + throw new Error('Options must be an object.'); + + let changed = false; + for (const [key, val] of Object.entries(options)) { + if (this.#config[key] !== val) { + this.#config[key] = val; + this.#onChange(key, false); + changed = true; + } + } + if (changed) this.#allSubscribers.forEach((fn) => fn(this.#config)); this.#save(); }; @@ -109,6 +126,15 @@ module.exports.PluginConfig = class PluginConfig { setAndMaybeRestart = (option, value) => { this.#config[option] = value; setMenuOptions(this.#name, this.#config); + this.#onChange(option); + }; + + subscribe = (valueName, fn) => { + this.#subscribers[valueName] = fn; + }; + + subscribeAll = (fn) => { + this.#allSubscribers.push(fn); }; /** Called only from back */ @@ -116,24 +142,64 @@ module.exports.PluginConfig = class PluginConfig { setOptions(this.#name, this.#config); } + #onChange(valueName, single = true) { + this.#subscribers[valueName]?.(this.#config[valueName]); + if (single) this.#allSubscribers.forEach((fn) => fn(this.#config)); + } + #setupFront() { - if (process.type === "renderer") { + const ignoredMethods = ['subscribe', 'subscribeAll']; + + if (process.type === 'renderer') { for (const [fnName, fn] of Object.entries(this)) { - if (typeof fn !== "function") return; + if (typeof fn !== 'function' || fn.name in ignoredMethods) return; this[fnName] = async (...args) => { return await ipcRenderer.invoke( `${this.name}-config-${fnName}`, ...args, ); }; + + this.subscribe = (valueName, fn) => { + if (valueName in this.#subscribers) { + console.error(`Already subscribed to ${valueName}`); + } + this.#subscribers[valueName] = fn; + ipcRenderer.on( + `${this.name}-config-changed-${valueName}`, + (_, value) => { + fn(value); + }, + ); + ipcRenderer.send(`${this.name}-config-subscribe`, valueName); + }; + + this.subscribeAll = (fn) => { + ipcRenderer.on(`${this.name}-config-changed`, (_, value) => { + fn(value); + }); + 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") return; + if (typeof fn !== 'function' || fn.name in ignoredMethods) return; ipcMain.handle(`${this.name}-config-${fnName}`, (_, ...args) => { return fn(...args); }); } + + ipcMain.on(`${this.name}-config-subscribe`, (_, valueName) => { + this.subscribe(valueName, (value) => { + sendToFront(`${this.name}-config-changed-${valueName}`, value); + }); + }); + + ipcMain.on(`${this.name}-config-subscribe-all`, () => { + this.subscribeAll((value) => { + sendToFront(`${this.name}-config-changed`, value); + }); + }); } } }; From 2ad097c743e44665133bd0195150f06b3a7abd68 Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Sun, 19 Mar 2023 23:47:55 +0200 Subject: [PATCH 09/10] use new dynamic config --- config/defaults.js | 2 +- plugins/crossfade/back.js | 4 +- plugins/crossfade/config.js | 3 + plugins/crossfade/front.js | 238 +++++++++++++++++++----------------- plugins/crossfade/menu.js | 12 +- 5 files changed, 137 insertions(+), 122 deletions(-) create mode 100644 plugins/crossfade/config.js diff --git a/config/defaults.js b/config/defaults.js index bc8b9b7022..1c762e2d52 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -108,7 +108,7 @@ const defaultConfig = { enabled: false, fadeInDuration: 1500, // ms fadeOutDuration: 5000, // ms - exitMusicBeforeEnd: 10, // s + secondsBeforeEnd: 10, // s fadeScaling: "linear", // 'linear', 'logarithmic' or a positive number in dB }, visualizer: { diff --git a/plugins/crossfade/back.js b/plugins/crossfade/back.js index ee2dc679c2..0443c95abc 100644 --- a/plugins/crossfade/back.js +++ b/plugins/crossfade/back.js @@ -1,7 +1,9 @@ const { ipcMain } = require("electron"); const { Innertube } = require("youtubei.js"); -module.exports = async (win, options) => { +require("./config"); + +module.exports = async () => { const yt = await Innertube.create(); ipcMain.handle("audio-url", async (_, videoID) => { diff --git a/plugins/crossfade/config.js b/plugins/crossfade/config.js new file mode 100644 index 0000000000..6db3c562d9 --- /dev/null +++ b/plugins/crossfade/config.js @@ -0,0 +1,3 @@ +const { PluginConfig } = require("../../config/dynamic"); +const config = new PluginConfig("crossfade", { enableFront: true }); +module.exports = { ...config }; diff --git a/plugins/crossfade/front.js b/plugins/crossfade/front.js index 5494387ada..1eddaef161 100644 --- a/plugins/crossfade/front.js +++ b/plugins/crossfade/front.js @@ -1,146 +1,156 @@ -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 config = 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 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); - - if ( - nextVideoID && - currentVideoID && - (firstVideo || nextVideoID !== currentVideoID) - ) { - if (isReadyToCrossfade()) { - crossfade(() => { - cb(nextVideoID); - }); - } else { - cb(nextVideoID); - firstVideo = false; - } - } - }); + 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; + } + } + }); }; 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 videoFader = new VolumeFader(video, { - fadeScaling: config.fadeScaling, - fadeDuration: config.fadeInDuration, - }); - - 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); - }; - - // Exit just before the end for the transition - const transitionBeforeEnd = () => { - if ( - video.currentTime >= - video.duration - config.exitMusicBeforeEnd && - isReadyToCrossfade() - ) { - video.removeEventListener("timeupdate", transitionBeforeEnd); - - // Go to next video - XXX: does not support "repeat 1" mode - document.querySelector(".next-button").click(); - } - }; - video.ontimeupdate = transitionBeforeEnd; + const video = document.querySelector('video'); + + const videoFader = new VolumeFader(video, { + fadeScaling: configGetNum('fadeScaling'), + fadeDuration: configGetNum('fadeInDuration'), + }); + + 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); + }; + + // 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; }; 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 = (cb) => { - if (!isReadyToCrossfade()) { - cb(); - return; - } - - let resolveTransition; - waitForTransition = new Promise(function (resolve, reject) { - resolveTransition = resolve; - }); - - const video = document.querySelector("video"); - - const fader = new VolumeFader(transitionAudio._sounds[0]._node, { - initialVolume: video.volume, - fadeScaling: config.fadeScaling, - fadeDuration: config.fadeOutDuration, - }); - - // Fade out the music - video.volume = 0; - fader.fadeOut(() => { - resolveTransition(); - cb(); - }); +const crossfade = async (cb) => { + if (!isReadyToCrossfade()) { + cb(); + return; + } + + let resolveTransition; + waitForTransition = new Promise(function (resolve, reject) { + resolveTransition = resolve; + }); + + const video = document.querySelector('video'); + + 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(); + }); }; -module.exports = (options) => { - Object.assign(config, options); - - document.addEventListener("apiLoaded", onApiLoaded, { - once: true, - passive: true, - }); +module.exports = () => { + document.addEventListener('apiLoaded', onApiLoaded, { + once: true, + passive: true, + }); }; diff --git a/plugins/crossfade/menu.js b/plugins/crossfade/menu.js index b51750343c..5ee728c752 100644 --- a/plugins/crossfade/menu.js +++ b/plugins/crossfade/menu.js @@ -1,15 +1,15 @@ -const { setOptions } = require("../../config/plugins"); +const config = require("./config"); const defaultOptions = require("../../config/defaults").plugins.crossfade; const prompt = require("custom-electron-prompt"); const promptOptions = require("../../providers/prompt-options"); -module.exports = (win, options) => [ +module.exports = (win) => [ { label: "Advanced", click: async () => { - const newOptions = await promptCrossfadeValues(win, options); - setOptions("crossfade", { ...options, ...newOptions }); + const newOptions = await promptCrossfadeValues(win, config.getAll()); + if (newOptions) config.setAll(newOptions); }, }, ]; @@ -43,7 +43,7 @@ async function promptCrossfadeValues(win, options) { { label: "Crossfade x seconds before end", value: - options.exitMusicBeforeEnd || defaultOptions.exitMusicBeforeEnd, + options.secondsBeforeEnd || defaultOptions.secondsBeforeEnd, inputAttrs: { type: "number", required: true, @@ -66,7 +66,7 @@ async function promptCrossfadeValues(win, options) { return { fadeInDuration: Number(res[0]), fadeOutDuration: Number(res[1]), - exitMusicBeforeEnd: Number(res[2]), + secondsBeforeEnd: Number(res[2]), fadeScaling: res[3], }; } From 55a442e90e8000f477e1381aacf21634bc64b565 Mon Sep 17 00:00:00 2001 From: Araxeus <78568641+Araxeus@users.noreply.github.com> Date: Tue, 21 Mar 2023 17:56:39 +0200 Subject: [PATCH 10/10] lint --- config/dynamic.js | 34 +++--- plugins/crossfade/front.js | 239 ++++++++++++++++++------------------- 2 files changed, 136 insertions(+), 137 deletions(-) diff --git a/config/dynamic.js b/config/dynamic.js index 6f78187a58..55a38f3cac 100644 --- a/config/dynamic.js +++ b/config/dynamic.js @@ -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); }); diff --git a/plugins/crossfade/front.js b/plugins/crossfade/front.js index 1eddaef161..7b52a5dab3 100644 --- a/plugins/crossfade/front.js +++ b/plugins/crossfade/front.js @@ -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); - - if ( - nextVideoID && - currentVideoID && - (firstVideo || nextVideoID !== currentVideoID) - ) { - if (isReadyToCrossfade()) { - crossfade(() => { - cb(nextVideoID); - }); - } else { - cb(nextVideoID); - firstVideo = false; - } - } - }); + 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; + } + } + }); }; 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 videoFader = new VolumeFader(video, { - fadeScaling: configGetNum('fadeScaling'), - fadeDuration: configGetNum('fadeInDuration'), - }); - - 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); - }; - - // 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; + const video = document.querySelector("video"); + + const videoFader = new VolumeFader(video, { + fadeScaling: configGetNum("fadeScaling"), + fadeDuration: configGetNum("fadeInDuration"), + }); + + 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); + }; + + // 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; }; 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; - } - - let resolveTransition; - waitForTransition = new Promise(function (resolve, reject) { - resolveTransition = resolve; - }); - - const video = document.querySelector('video'); - - 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(); - }); + if (!isReadyToCrossfade()) { + cb(); + return; + } + + let resolveTransition; + waitForTransition = new Promise(function (resolve, reject) { + resolveTransition = resolve; + }); + + const video = document.querySelector("video"); + + 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(); + }); }; -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, + }); };