diff --git a/.eslintrc.js b/.eslintrc.js index 976dfccc..b2bfdd44 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,6 +46,7 @@ module.exports = { allowTemplateLiterals: false, }], 'quote-props': ['error', 'consistent'], + 'semi': ['error', 'always'], }, env: { browser: true, diff --git a/config/plugins.js b/config/plugins.js index 6802ecb4..5589600a 100644 --- a/config/plugins.js +++ b/config/plugins.js @@ -4,10 +4,9 @@ const { restart } = require('../providers/app-controls'); function getEnabled() { const plugins = store.get('plugins'); - const enabledPlugins = Object.entries(plugins).filter(([plugin, options]) => + return Object.entries(plugins).filter(([plugin]) => isEnabled(plugin), ); - return enabledPlugins; } function isEnabled(plugin) { diff --git a/docs/js/main.js b/docs/js/main.js index 8ca35d2a..d7587ce9 100644 --- a/docs/js/main.js +++ b/docs/js/main.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + // Constants const element = document.documentElement; const { body } = document; @@ -40,7 +42,8 @@ const bubbleCanvas = function (t) { e.mouseX = 0; e.mouseY = 0; window.addEventListener('mousemove', (t) => { - (e.mouseX = t.clientX), (e.mouseY = t.clientY); + e.mouseX = t.clientX; + e.mouseY = t.clientY; }); e.randomise(); }; diff --git a/index.js b/index.js index ef3090d1..f88d71a1 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,6 @@ if (!gotTheLock) { } app.commandLine.appendSwitch('enable-features', 'SharedArrayBuffer'); // Required for downloader -app.allowRendererProcessReuse = true; // https://github.com/electron/electron/issues/18397 if (config.get('options.disableHardwareAcceleration')) { if (is.dev()) { console.log('Disabling hardware acceleration'); @@ -60,9 +59,9 @@ require('electron-debug')({ }); let icon = 'assets/youtube-music.png'; -if (process.platform == 'win32') { +if (process.platform === 'win32') { icon = 'assets/generated/icon.ico'; -} else if (process.platform == 'darwin') { +} else if (process.platform === 'darwin') { icon = 'assets/generated/icon.icns'; } @@ -393,7 +392,7 @@ app.on('ready', () => { setupProtocolHandler(mainWindow); - app.on('second-instance', (_event, commandLine, _workingDirectory) => { + app.on('second-instance', (_, commandLine) => { const uri = `${APP_PROTOCOL}://`; const protocolArgv = commandLine.find((arg) => arg.startsWith(uri)); if (protocolArgv) { @@ -541,7 +540,7 @@ function removeContentSecurityPolicy( // When multiple listeners are defined, apply them all session.webRequest.setResolver('onHeadersReceived', (listeners) => { - const response = listeners.reduce( + return listeners.reduce( async (accumulator, listener) => { if (accumulator.cancel) { return accumulator; @@ -552,7 +551,5 @@ function removeContentSecurityPolicy( }, { cancel: false }, ); - - return response; }); } diff --git a/package-lock.json b/package-lock.json index a905e620..4792fc5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "node-fetch": "2.7.0", "simple-youtube-age-restriction-bypass": "git+https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass.git#v2.5.9", "vudio": "2.1.1", - "youtubei.js": "4.3.0", + "youtubei.js": "6.1.0", "ytpl": "2.3.0" }, "devDependencies": { @@ -1919,11 +1919,6 @@ "bluebird": "^3.5.5" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -2607,37 +2602,6 @@ "node": "*" } }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" - }, "node_modules/custom-electron-prompt": { "version": "1.5.7", "resolved": "https://registry.npmjs.org/custom-electron-prompt/-/custom-electron-prompt-1.5.7.tgz", @@ -5018,11 +4982,6 @@ "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz", "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg==" }, - "node_modules/html-escaper": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", - "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==" - }, "node_modules/html-to-text": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", @@ -5937,18 +5896,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "node_modules/linkedom": { - "version": "0.14.26", - "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.26.tgz", - "integrity": "sha512-mK6TrydfFA7phrnp+1j57ycBwFI5bGSW6YXlw9acHoqF+mP/y+FooEYYyniOt5Ot57FSKB3iwmnuQ1UUyNLm5A==", - "dependencies": { - "css-select": "^5.1.0", - "cssom": "^0.5.0", - "html-escaper": "^3.0.3", - "htmlparser2": "^8.0.1", - "uhyphen": "^0.2.0" - } - }, "node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -6579,17 +6526,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -8407,11 +8343,6 @@ "node": ">=0.8.0" } }, - "node_modules/uhyphen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", - "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==" - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -8815,15 +8746,14 @@ } }, "node_modules/youtubei.js": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-4.3.0.tgz", - "integrity": "sha512-HdU6Awdr1nUWy0Ph7WdmoYPWL0ovx+S4w40eeTzAENr5xiUENsLuXcvULRc2fRCIxi+n7Q6142VVhmM4yK/g5g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-6.1.0.tgz", + "integrity": "sha512-EJmPuQ1pLimrcp5nPDSeZHVthT7KJLxp9Z5sYmihXKn1ct8e7cntKaFnPvy5QsbjpipICpDpQEs+d+owDhjgIg==", "funding": [ "https://github.com/sponsors/LuanRT" ], "dependencies": { - "jintr": "^1.0.0", - "linkedom": "^0.14.12", + "jintr": "^1.1.0", "tslib": "^2.5.0", "undici": "^5.19.1" } diff --git a/package.json b/package.json index 73f063e4..e89f893d 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "node-fetch": "2.7.0", "simple-youtube-age-restriction-bypass": "git+https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass.git#v2.5.9", "vudio": "2.1.1", - "youtubei.js": "4.3.0", + "youtubei.js": "6.1.0", "ytpl": "2.3.0" }, "overrides": { diff --git a/plugins/adblocker/inject.js b/plugins/adblocker/inject.js index be3eb165..8fadec0e 100644 --- a/plugins/adblocker/inject.js +++ b/plugins/adblocker/inject.js @@ -2,9 +2,9 @@ // https://robwu.nl/crxviewer/?crx=https%3A%2F%2Faddons.mozilla.org%2Fen-US%2Ffirefox%2Faddon%2Fadblock-for-youtube%2F /* - Parts of this code is derived from set-constant.js: - https://github.com/gorhill/uBlock/blob/5de0ce975753b7565759ac40983d31978d1f84ca/assets/resources/scriptlets.js#L704 - */ + Parts of this code is derived from set-constant.js: + https://github.com/gorhill/uBlock/blob/5de0ce975753b7565759ac40983d31978d1f84ca/assets/resources/scriptlets.js#L704 + */ { const pruner = function (o) { diff --git a/plugins/crossfade/fader.js b/plugins/crossfade/fader.js index c7d15810..df3c8db2 100644 --- a/plugins/crossfade/fader.js +++ b/plugins/crossfade/fader.js @@ -15,347 +15,342 @@ * v0.2.0, 07/2016 */ -(function (root) { - 'use strict'; +'use strict'; - // Internal utility: check if value is a valid volume level and throw if not - const validateVolumeLevel = (value) => { - // Number between 0 and 1? - if (!Number.isNaN(value) && value >= 0 && value <= 1) { - // Yup, that's fine +// Internal utility: check if value is a valid volume level and throw if not +const validateVolumeLevel = (value) => { + // Number between 0 and 1? + if (!Number.isNaN(value) && value >= 0 && value <= 1) { + // Yup, that's fine + } else { + // Abort and throw an exception + throw new TypeError('Number between 0 and 1 expected as volume!'); + } +}; + +// Main class +class VolumeFader { + /** + * VolumeFader Constructor + * + * @param media {HTMLMediaElement} - audio or video element to be controlled + * @param options {Object} - an object with optional settings + * @throws {TypeError} if options.initialVolume or options.fadeDuration are invalid + * + * options: + * .logger: {Function} logging `function(stuff, …)` for execution information (default: no logging) + * .fadeScaling: {Mixed} either 'linear', 'logarithmic' or a positive number in dB (default: logarithmic) + * .initialVolume: {Number} media volume 0…1 to apply during setup (volume not touched by default) + * .fadeDuration: {Number} time in milliseconds to complete a fade (default: 1000 ms) + */ + constructor(media, options) { + // Passed media element of correct type? + if (media instanceof HTMLMediaElement) { + // Save reference to media element + this.media = media; } else { // Abort and throw an exception - throw new TypeError('Number between 0 and 1 expected as volume!'); - } - }; - - // Main class - class VolumeFader { - /** - * VolumeFader Constructor - * - * @param media {HTMLMediaElement} - audio or video element to be controlled - * @param options {Object} - an object with optional settings - * @throws {TypeError} if options.initialVolume or options.fadeDuration are invalid - * - * options: - * .logger: {Function} logging `function(stuff, …)` for execution information (default: no logging) - * .fadeScaling: {Mixed} either 'linear', 'logarithmic' or a positive number in dB (default: logarithmic) - * .initialVolume: {Number} media volume 0…1 to apply during setup (volume not touched by default) - * .fadeDuration: {Number} time in milliseconds to complete a fade (default: 1000 ms) - */ - constructor(media, options) { - // Passed media element of correct type? - if (media instanceof HTMLMediaElement) { - // Save reference to media element - this.media = media; - } else { - // Abort and throw an exception - throw new TypeError('Media element expected!'); - } - - // Make sure options is an object - options = options || {}; - - // Log function passed? - if (typeof options.logger === 'function') { - // Set log function to the one specified - this.logger = options.logger; - } else { - // Set log function explicitly to false - this.logger = false; - } - - // Linear volume fading? - if (options.fadeScaling == 'linear') { - // Pass levels unchanged - this.scale = { - internalToVolume: (level) => level, - volumeToInternal: (level) => level, - }; - - // Log setting - this.logger && this.logger('Using linear fading.'); - } - // No linear, but logarithmic fading… - else { - let dynamicRange; - - // Default dynamic range? - if ( - options.fadeScaling === undefined - || options.fadeScaling == 'logarithmic' - ) { - // Set default of 60 dB - dynamicRange = 3; - } - // Custom dynamic range? - else if ( - !Number.isNaN(options.fadeScaling) - && options.fadeScaling > 0 - ) { - // Turn amplitude dB into a multiple of 10 power dB - dynamicRange = options.fadeScaling / 2 / 10; - } - // Unsupported value - else { - // Abort and throw exception - throw new TypeError( - "Expected 'linear', 'logarithmic' or a positive number as fade scaling preference!", - ); - } - - // Use exponential/logarithmic scaler for expansion/compression - this.scale = { - internalToVolume: (level) => - this.exponentialScaler(level, dynamicRange), - volumeToInternal: (level) => - this.logarithmicScaler(level, dynamicRange), - }; - - // Log setting if not default - options.fadeScaling - && this.logger - && this.logger( - 'Using logarithmic fading with ' - + String(10 * dynamicRange) - + ' dB dynamic range.', - ); - } - - // Set initial volume? - if (options.initialVolume !== undefined) { - // Validate volume level and throw if invalid - validateVolumeLevel(options.initialVolume); - - // Set initial volume - this.media.volume = options.initialVolume; - - // Log setting - this.logger - && this.logger( - 'Set initial volume to ' + String(this.media.volume) + '.', - ); - } - - // Fade duration given? - if (options.fadeDuration === undefined) { - // Set default fade duration (1000 ms) - this.fadeDuration = 1000; - } else { - // Try to set given fade duration (will log if successful and throw if not) - this.setFadeDuration(options.fadeDuration); - } - - // Indicate that fader is not active yet - this.active = false; - - // Initialization done - this.logger && this.logger('Initialized for', this.media); + throw new TypeError('Media element expected!'); } - /** - * Re(start) the update cycle. - * (this.active must be truthy for volume updates to take effect) - * - * @return {Object} VolumeFader instance for chaining - */ - start() { - // Set fader to be active - this.active = true; + // Make sure options is an object + options = options || {}; - // Start by running the update method - this.updateVolume(); - - // Return instance for chaining - return this; + // Log function passed? + if (typeof options.logger === 'function') { + // Set log function to the one specified + this.logger = options.logger; + } else { + // Set log function explicitly to false + this.logger = false; } - /** - * Stop the update cycle. - * (interrupting any fade) - * - * @return {Object} VolumeFader instance for chaining - */ - stop() { - // Set fader to be inactive - this.active = false; - - // Return instance for chaining - return this; - } - - /** - * Set fade duration. - * (used for future calls to fadeTo) - * - * @param {Number} fadeDuration - fading length in milliseconds - * @throws {TypeError} if fadeDuration is not a number greater than zero - * @return {Object} VolumeFader instance for chaining - */ - setFadeDuration(fadeDuration) { - // If duration is a valid number > 0… - if (!Number.isNaN(fadeDuration) && fadeDuration > 0) { - // Set fade duration - this.fadeDuration = fadeDuration; - - // Log setting - this.logger - && this.logger('Set fade duration to ' + String(fadeDuration) + ' ms.'); - } else { - // Abort and throw an exception - throw new TypeError('Positive number expected as fade duration!'); - } - - // Return instance for chaining - return this; - } - - /** - * Define a new fade and start fading. - * - * @param {Number} targetVolume - level to fade to in the range 0…1 - * @param {Function} callback - (optional) function to be called when fade is complete - * @throws {TypeError} if targetVolume is not in the range 0…1 - * @return {Object} VolumeFader instance for chaining - */ - fadeTo(targetVolume, callback) { - // Validate volume and throw if invalid - validateVolumeLevel(targetVolume); - - // Define new fade - this.fade = { - // Volume start and end point on internal fading scale - volume: { - start: this.scale.volumeToInternal(this.media.volume), - end: this.scale.volumeToInternal(targetVolume), - }, - // Time start and end point - time: { - start: Date.now(), - end: Date.now() + this.fadeDuration, - }, - // Optional callback function - callback, + // Linear volume fading? + if (options.fadeScaling === 'linear') { + // Pass levels unchanged + this.scale = { + internalToVolume: (level) => level, + volumeToInternal: (level) => level, }; - // Start fading - this.start(); - - // Log new fade - this.logger && this.logger('New fade started:', this.fade); - - // Return instance for chaining - return this; + // Log setting + this.logger && this.logger('Using linear fading.'); } + // No linear, but logarithmic fading… + else { + let dynamicRange; - // Convenience shorthand methods for common fades - fadeIn(callback) { - this.fadeTo(1, callback); - } - - fadeOut(callback) { - this.fadeTo(0, callback); - } - - /** - * Internal: Update media volume. - * (calls itself through requestAnimationFrame) - * - * @param {Number} targetVolume - linear level to fade to (0…1) - * @param {Function} callback - (optional) function to be called when fade is complete - */ - updateVolume() { - // Fader active and fade available to process? - if (this.active && this.fade) { - // Get current time - const now = Date.now(); - - // Time left for fading? - if (now < this.fade.time.end) { - // Compute current fade progress - const progress - = (now - this.fade.time.start) - / (this.fade.time.end - this.fade.time.start); - - // Compute current level on internal scale - const level - = progress * (this.fade.volume.end - this.fade.volume.start) - + this.fade.volume.start; - - // Map fade level to volume level and apply it to media element - this.media.volume = this.scale.internalToVolume(level); - - // Schedule next update - root.requestAnimationFrame(this.updateVolume.bind(this)); - } else { - // Log end of fade - this.logger - && this.logger( - 'Fade to ' + String(this.fade.volume.end) + ' complete.', - ); - - // Time is up, jump to target volume - this.media.volume = this.scale.internalToVolume(this.fade.volume.end); - - // Set fader to be inactive - this.active = false; - - // Done, call back (if callable) - typeof this.fade.callback === 'function' && this.fade.callback(); - - // Clear fade - this.fade = undefined; - } + // Default dynamic range? + if ( + options.fadeScaling === undefined + || options.fadeScaling === 'logarithmic' + ) { + // Set default of 60 dB + dynamicRange = 3; } - } - - /** - * Internal: Exponential scaler with dynamic range limit. - * - * @param {Number} input - logarithmic input level to be expanded (float, 0…1) - * @param {Number} dynamicRange - expanded output range, in multiples of 10 dB (float, 0…∞) - * @return {Number} - expanded level (float, 0…1) - */ - exponentialScaler(input, dynamicRange) { - // Special case: make zero (or any falsy input) return zero - if (input == 0) { - // Since the dynamic range is limited, - // allow a zero to produce a plain zero instead of a small faction - // (audio would not be recognized as silent otherwise) - return 0; + // Custom dynamic range? + else if ( + !Number.isNaN(options.fadeScaling) + && options.fadeScaling > 0 + ) { + // Turn amplitude dB into a multiple of 10 power dB + dynamicRange = options.fadeScaling / 2 / 10; + } + // Unsupported value + else { + // Abort and throw exception + throw new TypeError( + "Expected 'linear', 'logarithmic' or a positive number as fade scaling preference!", + ); } - // Scale 0…1 to minus something × 10 dB - input = (input - 1) * dynamicRange; + // Use exponential/logarithmic scaler for expansion/compression + this.scale = { + internalToVolume: (level) => + this.exponentialScaler(level, dynamicRange), + volumeToInternal: (level) => + this.logarithmicScaler(level, dynamicRange), + }; - // Compute power of 10 - return 10 ** input; + // Log setting if not default + options.fadeScaling + && this.logger + && this.logger( + 'Using logarithmic fading with ' + + String(10 * dynamicRange) + + ' dB dynamic range.', + ); } - /** - * Internal: Logarithmic scaler with dynamic range limit. - * - * @param {Number} input - exponential input level to be compressed (float, 0…1) - * @param {Number} dynamicRange - coerced input range, in multiples of 10 dB (float, 0…∞) - * @return {Number} - compressed level (float, 0…1) - */ - logarithmicScaler(input, dynamicRange) { - // Special case: make zero (or any falsy input) return zero - if (input == 0) { - // Logarithm of zero would be -∞, which would map to zero anyway - return 0; + // Set initial volume? + if (options.initialVolume !== undefined) { + // Validate volume level and throw if invalid + validateVolumeLevel(options.initialVolume); + + // Set initial volume + this.media.volume = options.initialVolume; + + // Log setting + this.logger + && this.logger( + 'Set initial volume to ' + String(this.media.volume) + '.', + ); + } + + // Fade duration given? + if (options.fadeDuration === undefined) { + // Set default fade duration (1000 ms) + this.fadeDuration = 1000; + } else { + // Try to set given fade duration (will log if successful and throw if not) + this.setFadeDuration(options.fadeDuration); + } + + // Indicate that fader is not active yet + this.active = false; + + // Initialization done + this.logger && this.logger('Initialized for', this.media); + } + + /** + * Re(start) the update cycle. + * (this.active must be truthy for volume updates to take effect) + * + * @return {Object} VolumeFader instance for chaining + */ + start() { + // Set fader to be active + this.active = true; + + // Start by running the update method + this.updateVolume(); + + // Return instance for chaining + return this; + } + + /** + * Stop the update cycle. + * (interrupting any fade) + * + * @return {Object} VolumeFader instance for chaining + */ + stop() { + // Set fader to be inactive + this.active = false; + + // Return instance for chaining + return this; + } + + /** + * Set fade duration. + * (used for future calls to fadeTo) + * + * @param {Number} fadeDuration - fading length in milliseconds + * @throws {TypeError} if fadeDuration is not a number greater than zero + * @return {Object} VolumeFader instance for chaining + */ + setFadeDuration(fadeDuration) { + // If duration is a valid number > 0… + if (!Number.isNaN(fadeDuration) && fadeDuration > 0) { + // Set fade duration + this.fadeDuration = fadeDuration; + + // Log setting + this.logger + && this.logger('Set fade duration to ' + String(fadeDuration) + ' ms.'); + } else { + // Abort and throw an exception + throw new TypeError('Positive number expected as fade duration!'); + } + + // Return instance for chaining + return this; + } + + /** + * Define a new fade and start fading. + * + * @param {Number} targetVolume - level to fade to in the range 0…1 + * @param {Function} callback - (optional) function to be called when fade is complete + * @throws {TypeError} if targetVolume is not in the range 0…1 + * @return {Object} VolumeFader instance for chaining + */ + fadeTo(targetVolume, callback) { + // Validate volume and throw if invalid + validateVolumeLevel(targetVolume); + + // Define new fade + this.fade = { + // Volume start and end point on internal fading scale + volume: { + start: this.scale.volumeToInternal(this.media.volume), + end: this.scale.volumeToInternal(targetVolume), + }, + // Time start and end point + time: { + start: Date.now(), + end: Date.now() + this.fadeDuration, + }, + // Optional callback function + callback, + }; + + // Start fading + this.start(); + + // Log new fade + this.logger && this.logger('New fade started:', this.fade); + + // Return instance for chaining + return this; + } + + // Convenience shorthand methods for common fades + fadeIn(callback) { + this.fadeTo(1, callback); + } + + fadeOut(callback) { + this.fadeTo(0, callback); + } + + /** + * Internal: Update media volume. + * (calls itself through requestAnimationFrame) + */ + updateVolume() { + // Fader active and fade available to process? + if (this.active && this.fade) { + // Get current time + const now = Date.now(); + + // Time left for fading? + if (now < this.fade.time.end) { + // Compute current fade progress + const progress + = (now - this.fade.time.start) + / (this.fade.time.end - this.fade.time.start); + + // Compute current level on internal scale + const level + = (progress * (this.fade.volume.end - this.fade.volume.start)) + this.fade.volume.start; + + // Map fade level to volume level and apply it to media element + this.media.volume = this.scale.internalToVolume(level); + + // Schedule next update + window.requestAnimationFrame(this.updateVolume.bind(this)); + } else { + // Log end of fade + this.logger + && this.logger( + 'Fade to ' + String(this.fade.volume.end) + ' complete.', + ); + + // Time is up, jump to target volume + this.media.volume = this.scale.internalToVolume(this.fade.volume.end); + + // Set fader to be inactive + this.active = false; + + // Done, call back (if callable) + typeof this.fade.callback === 'function' && this.fade.callback(); + + // Clear fade + this.fade = undefined; } - - // Compute base-10 logarithm - input = Math.log10(input); - - // Scale minus something × 10 dB to 0…1 (clipping at 0) - return Math.max(1 + input / dynamicRange, 0); } } - // Export class to root scope - root.VolumeFader = VolumeFader; -})(window); + /** + * Internal: Exponential scaler with dynamic range limit. + * + * @param {Number} input - logarithmic input level to be expanded (float, 0…1) + * @param {Number} dynamicRange - expanded output range, in multiples of 10 dB (float, 0…∞) + * @return {Number} - expanded level (float, 0…1) + */ + exponentialScaler(input, dynamicRange) { + // Special case: make zero (or any falsy input) return zero + if (input === 0) { + // Since the dynamic range is limited, + // allow a zero to produce a plain zero instead of a small faction + // (audio would not be recognized as silent otherwise) + return 0; + } + + // Scale 0…1 to minus something × 10 dB + input = (input - 1) * dynamicRange; + + // Compute power of 10 + return 10 ** input; + } + + /** + * Internal: Logarithmic scaler with dynamic range limit. + * + * @param {Number} input - exponential input level to be compressed (float, 0…1) + * @param {Number} dynamicRange - coerced input range, in multiples of 10 dB (float, 0…∞) + * @return {Number} - compressed level (float, 0…1) + */ + logarithmicScaler(input, dynamicRange) { + // Special case: make zero (or any falsy input) return zero + if (input === 0) { + // Logarithm of zero would be -∞, which would map to zero anyway + return 0; + } + + // Compute base-10 logarithm + input = Math.log10(input); + + // Scale minus something × 10 dB to 0…1 (clipping at 0) + return Math.max(1 + (input / dynamicRange), 0); + } +} + +module.exports = { + VolumeFader +}; diff --git a/plugins/crossfade/front.js b/plugins/crossfade/front.js index 11517e0a..526623f7 100644 --- a/plugins/crossfade/front.js +++ b/plugins/crossfade/front.js @@ -2,23 +2,25 @@ const { ipcRenderer } = require('electron'); const { Howl } = require('howler'); // Extracted from https://github.com/bitfasching/VolumeFader -require('./fader'); +const { VolumeFader } = 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; - +/** + * @type {PluginConfig} + */ const configProvider = require('./config'); +const defaultConfig = require('../../config/defaults').plugins.crossfade; + let config; const configGetNumber = (key) => Number(config[key]) || defaultConfig[key]; const getStreamURL = async (videoID) => { - const url = await ipcRenderer.invoke('audio-url', videoID); - return url; + return await ipcRenderer.invoke('audio-url', videoID); }; const getVideoIDFromURL = (url) => new URLSearchParams(url.split('?')?.at(-1)).get('v'); @@ -26,7 +28,7 @@ const getVideoIDFromURL = (url) => new URLSearchParams(url.split('?')?.at(-1)).g const isReadyToCrossfade = () => transitionAudio && transitionAudio.state() === 'loaded'; const watchVideoIDChanges = (cb) => { - navigation.addEventListener('navigate', (event) => { + window.navigation.addEventListener('navigate', (event) => { const currentVideoID = getVideoIDFromURL( event.currentTarget.currentEntry.url, ); @@ -126,7 +128,7 @@ const crossfade = async (cb) => { } let resolveTransition; - waitForTransition = new Promise((resolve, reject) => { + waitForTransition = new Promise((resolve) => { resolveTransition = resolve; }); diff --git a/plugins/crossfade/menu.js b/plugins/crossfade/menu.js index 8f94135e..c39c14dc 100644 --- a/plugins/crossfade/menu.js +++ b/plugins/crossfade/menu.js @@ -1,10 +1,9 @@ -const config = require('./config'); - -const defaultOptions = require('../../config/defaults').plugins.crossfade; - const prompt = require('custom-electron-prompt'); +const config = require('./config'); + const promptOptions = require('../../providers/prompt-options'); +const defaultOptions = require('../../config/defaults').plugins.crossfade; module.exports = (win) => [ { diff --git a/plugins/discord/back.js b/plugins/discord/back.js index c4eb4bcf..145e20a6 100644 --- a/plugins/discord/back.js +++ b/plugins/discord/back.js @@ -174,10 +174,10 @@ module.exports = (win, { autoReconnect, activityTimoutEnabled, activityTimoutTim } } else if (!hideDurationLeft) { // Add the start and end time of the song - const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000; + const songStartTime = Date.now() - (songInfo.elapsedSeconds * 1000); activityInfo.startTimestamp = songStartTime; activityInfo.endTimestamp - = songStartTime + songInfo.songDuration * 1000; + = songStartTime + (songInfo.songDuration * 1000); } info.rpc.user?.setActivity(activityInfo).catch(console.error); diff --git a/plugins/downloader/back.js b/plugins/downloader/back.js index 09bfd650..9144f83c 100644 --- a/plugins/downloader/back.js +++ b/plugins/downloader/back.js @@ -31,6 +31,8 @@ const { sendFeedback: sendFeedback_, } = require('./utils'); +const config = require('./config'); + const { fetchFromGenius } = require('../lyrics-genius/back'); const { isEnabled } = require('../../config/plugins'); const { getImage, cleanupName } = require('../../providers/song-info'); @@ -39,8 +41,6 @@ const { cache } = require('../../providers/decorators'); const ffmpegMutex = new Mutex(); -const config = require('./config'); - /** @type {Innertube} */ let yt; let win; @@ -187,14 +187,14 @@ async function downloadSongUnsafe( return; } - const download_options = { + const downloadOptions = { type: 'audio', // Audio, video or video+audio quality: 'best', // Best, bestefficiency, 144p, 240p, 480p, 720p and so on. format: 'any', // Media container format }; - const format = info.chooseFormat(download_options); - const stream = await info.download(download_options); + const format = info.chooseFormat(downloadOptions); + const stream = await info.download(downloadOptions); console.info( `Downloading ${metadata.artist} - ${metadata.title} [${metadata.id}]`, @@ -244,14 +244,14 @@ async function downloadSongUnsafe( async function iterableStreamToMP3( stream, metadata, - content_length, + contentLength, sendFeedback, increasePlaylistProgress = () => { }, ) { const chunks = []; let downloaded = 0; - const total = content_length; + const total = contentLength; for await (const chunk of stream) { downloaded += chunk.length; chunks.push(chunk); @@ -281,7 +281,7 @@ async function iterableStreamToMP3( ffmpeg.setProgress(({ ratio }) => { sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio); - increasePlaylistProgress(0.15 + ratio * 0.85); + increasePlaylistProgress(0.15 + (ratio * 0.85)); }); await ffmpeg.run( @@ -377,7 +377,7 @@ async function downloadPlaylist(givenUrl) { }); } catch (error) { sendError( - `Error getting playlist info: make sure it isn\'t a private or "Mixed for you" playlist\n\n${error}`, + `Error getting playlist info: make sure it isn't a private or "Mixed for you" playlist\n\n${error}`, ); return; } @@ -434,7 +434,7 @@ async function downloadPlaylist(givenUrl) { const increaseProgress = (itemPercentage) => { const currentProgress = (counter - 1) / playlist.items.length; - const newProgress = currentProgress + progressStep * itemPercentage; + const newProgress = currentProgress + (progressStep * itemPercentage); win.setProgressBar(newProgress); }; diff --git a/plugins/downloader/front.js b/plugins/downloader/front.js index f04e55f5..ef486a24 100644 --- a/plugins/downloader/front.js +++ b/plugins/downloader/front.js @@ -41,7 +41,7 @@ const menuObserver = new MutationObserver(() => { // TODO: re-enable once contextIsolation is set to true // contextBridge.exposeInMainWorld("downloader", { -// download: () => { +// download: () => { global.download = () => { let videoUrl = getSongMenu() // Selector of first button which is always "Start Radio" diff --git a/plugins/in-app-menu/front.js b/plugins/in-app-menu/front.js index 6533dd84..83d8fe38 100644 --- a/plugins/in-app-menu/front.js +++ b/plugins/in-app-menu/front.js @@ -8,7 +8,7 @@ function $(selector) { return document.querySelector(selector); } -module.exports = (options) => { +module.exports = () => { const visible = () => Boolean($('.cet-menubar').firstChild); const bar = new Titlebar({ icon: 'https://cdn-icons-png.flaticon.com/512/5358/5358672.png', @@ -38,7 +38,7 @@ module.exports = (options) => { }); if (isEnabled('picture-in-picture')) { - ipcRenderer.on('pip-toggle', (_, pipEnabled) => { + ipcRenderer.on('pip-toggle', () => { bar.refreshMenu(); }); } @@ -62,7 +62,7 @@ function setupSearchOpenObserver() { } function setupMenuOpenObserver() { - const menuOpenObserver = new MutationObserver((mutations) => { + const menuOpenObserver = new MutationObserver(() => { $('#nav-bar-background').style.webkitAppRegion = [...$('.cet-menubar').childNodes].some((c) => c.classList.contains('open')) ? 'no-drag' : 'drag'; diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index 8153de81..ae63af27 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -16,10 +16,10 @@ const createFormData = (parameters) => { return formData; }; -const createQueryString = (parameters, api_sig) => { +const createQueryString = (parameters, apiSignature) => { // Creates a querystring const queryData = []; - parameters.api_sig = api_sig; + parameters.api_sig = apiSignature; for (const key in parameters) { queryData.push(`${encodeURIComponent(key)}=${encodeURIComponent(parameters[key])}`); } @@ -49,15 +49,15 @@ const createApiSig = (parameters, secret) => { return sig; }; -const createToken = async ({ api_key, api_root, secret }) => { +const createToken = async ({ apiKey, apiRoot, secret }) => { // Creates and stores the auth token const data = { method: 'auth.gettoken', - api_key, + apiKey, format: 'json', }; - const api_sig = createApiSig(data, secret); - let response = await fetch(`${api_root}${createQueryString(data, api_sig)}`); + const apiSigature = createApiSig(data, secret); + let response = await fetch(`${apiRoot}${createQueryString(data, apiSigature)}`); response = await response.json(); return response?.token; }; @@ -78,8 +78,8 @@ const getAndSetSessionKey = async (config) => { method: 'auth.getsession', token: config.token, }; - const api_sig = createApiSig(data, config.secret); - let res = await fetch(`${config.api_root}${createQueryString(data, api_sig)}`); + const apiSignature = createApiSig(data, config.secret); + let res = await fetch(`${config.api_root}${createQueryString(data, apiSignature)}`); res = await res.json(); if (res.error) { await authenticate(config); @@ -110,7 +110,7 @@ const postSongDataToAPI = async (songInfo, config, data) => { postData.api_sig = createApiSig(postData, config.secret); fetch('https://ws.audioscrobbler.com/2.0/', { method: 'POST', body: createFormData(postData) }) .catch((error) => { - if (error.response.data.error == 9) { + if (error.response.data.error === 9) { // Session key is invalid, so remove it from the config and reauthenticate config.session_key = undefined; setOptions('last-fm', config); diff --git a/plugins/lyrics-genius/back.js b/plugins/lyrics-genius/back.js index 347470f1..b0a19d78 100644 --- a/plugins/lyrics-genius/back.js +++ b/plugins/lyrics-genius/back.js @@ -8,7 +8,7 @@ const fetch = require('node-fetch'); const { cleanupName } = require('../../providers/song-info'); const { injectCSS } = require('../utils'); -const eastAsianChars = /\p{Script=Han}|\p{Script=Katakana}|\p{Script=Hiragana}|\p{Script=Hangul}|\p{Script=Han}/u; +const eastAsianChars = /\p{Script=Katakana}|\p{Script=Hiragana}|\p{Script=Hangul}|\p{Script=Han}/u; let revRomanized = false; module.exports = async (win, options) => { @@ -89,7 +89,7 @@ const getLyricsList = async (queryString) => { * @returns The lyrics of the song URL provided, null if none */ const getLyrics = async (url) => { - response = await fetch(url); + const response = await fetch(url); if (!response.ok) { return null; } diff --git a/plugins/lyrics-genius/front.js b/plugins/lyrics-genius/front.js index 4139b4b1..b7d0bf7b 100644 --- a/plugins/lyrics-genius/front.js +++ b/plugins/lyrics-genius/front.js @@ -65,14 +65,14 @@ module.exports = () => { function setLyrics(lyricsContainer) { lyricsContainer.innerHTML = `
- ${ + ${ hasLyrics ? lyrics.replaceAll(/\r\n|\r|\n/g, '
') : 'Could not retrieve lyrics from genius' } -
- `; + + `; if (hasLyrics) { lyricsContainer.querySelector('.footer').textContent = 'Source: Genius'; enableLyricsTab(); diff --git a/plugins/lyrics-genius/menu.js b/plugins/lyrics-genius/menu.js index e2af715b..1c771cbe 100644 --- a/plugins/lyrics-genius/menu.js +++ b/plugins/lyrics-genius/menu.js @@ -2,7 +2,7 @@ const { toggleRomanized } = require('./back'); const { setOptions } = require('../../config/plugins'); -module.exports = (win, options, refreshMenu) => [ +module.exports = (win, options) => [ { label: 'Romanized Lyrics', type: 'checkbox', diff --git a/plugins/no-google-login/front.js b/plugins/no-google-login/front.js index c51a3255..57ac376d 100644 --- a/plugins/no-google-login/front.js +++ b/plugins/no-google-login/front.js @@ -15,7 +15,7 @@ function removeLoginElements() { const libraryIconPath = 'M16,6v2h-2v5c0,1.1-0.9,2-2,2s-2-0.9-2-2s0.9-2,2-2c0.37,0,0.7,0.11,1,0.28V6H16z M18,20H4V6H3v15h15V20z M21,3H6v15h15V3z M7,4h13v13H7V4z'; const observer = new MutationObserver(() => { - menuEntries = document.querySelectorAll( + const menuEntries = document.querySelectorAll( '#items ytmusic-guide-entry-renderer', ); for (const item of menuEntries) { diff --git a/plugins/notifications/interactive.js b/plugins/notifications/interactive.js index d41b289d..bb0fdc94 100644 --- a/plugins/notifications/interactive.js +++ b/plugins/notifications/interactive.js @@ -2,8 +2,11 @@ const path = require('node:path'); const { Notification, app, ipcMain } = require('electron'); -const { notificationImage, icons, save_temp_icons, secondsToMinutes, ToastStyles } = require('./utils'); +const { notificationImage, icons, saveTempIcon, secondsToMinutes, ToastStyles } = require('./utils'); +/** + * @type {PluginConfig} + */ const config = require('./config'); const getSongControls = require('../../providers/song-controls'); @@ -25,7 +28,7 @@ module.exports = (win) => { ipcMain.on('timeChanged', (_, t) => currentSeconds = t); if (app.isPackaged) { - save_temp_icons(); + saveTempIcon(); } let savedSongInfo; @@ -108,42 +111,42 @@ function sendNotification(songInfo) { // https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-schema // https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xml // https://learn.microsoft.com/en-us/uwp/api/windows.ui.notifications.toasttemplatetype - toastXml: get_xml(songInfo, iconSrc), + toastXml: getXml(songInfo, iconSrc), }); - savedNotification.on('close', (_) => { + savedNotification.on('close', () => { savedNotification = undefined; }); savedNotification.show(); } -const get_xml = (songInfo, iconSrc) => { +const getXml = (songInfo, iconSrc) => { switch (config.get('toastStyle')) { default: case ToastStyles.logo: case ToastStyles.legacy: { - return xml_logo(songInfo, iconSrc); + return xmlLogo(songInfo, iconSrc); } case ToastStyles.banner_top_custom: { - return xml_banner_top_custom(songInfo, iconSrc); + return xmlBannerTopCustom(songInfo, iconSrc); } case ToastStyles.hero: { - return xml_hero(songInfo, iconSrc); + return xmlHero(songInfo, iconSrc); } case ToastStyles.banner_bottom: { - return xml_banner_bottom(songInfo, iconSrc); + return xmlBannerBottom(songInfo, iconSrc); } case ToastStyles.banner_centered_bottom: { - return xml_banner_centered_bottom(songInfo, iconSrc); + return xmlBannerCenteredBottom(songInfo, iconSrc); } case ToastStyles.banner_centered_top: { - return xml_banner_centered_top(songInfo, iconSrc); + return xmlBannerCenteredTop(songInfo, iconSrc); } } }; @@ -186,19 +189,19 @@ const toast = (content, isPaused) => `\ ${getButtons(isPaused)} `; -const xml_image = ({ title, artist, isPaused }, imgSrc, placement) => toast(`\ +const xmlImage = ({ title, artist, isPaused }, imgSrc, placement) => toast(`\ ${title} ${artist}\ `, isPaused); -const xml_logo = (songInfo, imgSrc) => xml_image(songInfo, imgSrc, 'placement="appLogoOverride"'); +const xmlLogo = (songInfo, imgSrc) => xmlImage(songInfo, imgSrc, 'placement="appLogoOverride"'); -const xml_hero = (songInfo, imgSrc) => xml_image(songInfo, imgSrc, 'placement="hero"'); +const xmlHero = (songInfo, imgSrc) => xmlImage(songInfo, imgSrc, 'placement="hero"'); -const xml_banner_bottom = (songInfo, imgSrc) => xml_image(songInfo, imgSrc, ''); +const xmlBannerBottom = (songInfo, imgSrc) => xmlImage(songInfo, imgSrc, ''); -const xml_banner_top_custom = (songInfo, imgSrc) => toast(`\ +const xmlBannerTopCustom = (songInfo, imgSrc) => toast(`\ @@ -206,11 +209,11 @@ const xml_banner_top_custom = (songInfo, imgSrc) => toast(`\ ${songInfo.title} ${songInfo.artist} - ${xml_more_data(songInfo)} + ${xmlMoreData(songInfo)} \ `, songInfo.isPaused); -const xml_more_data = ({ album, elapsedSeconds, songDuration }) => `\ +const xmlMoreData = ({ album, elapsedSeconds, songDuration }) => `\ ${album ? `${album}` : ''} @@ -218,7 +221,7 @@ const xml_more_data = ({ album, elapsedSeconds, songDuration }) => `\ \ `; -const xml_banner_centered_bottom = ({ title, artist, isPaused }, imgSrc) => toast(`\ +const xmlBannerCenteredBottom = ({ title, artist, isPaused }, imgSrc) => toast(`\ @@ -229,7 +232,7 @@ const xml_banner_centered_bottom = ({ title, artist, isPaused }, imgSrc) => toas \ `, isPaused); -const xml_banner_centered_top = ({ title, artist, isPaused }, imgSrc) => toast(`\ +const xmlBannerCenteredTop = ({ title, artist, isPaused }, imgSrc) => toast(`\ diff --git a/plugins/notifications/utils.js b/plugins/notifications/utils.js index 4f992f41..6158bb3f 100644 --- a/plugins/notifications/utils.js +++ b/plugins/notifications/utils.js @@ -69,18 +69,18 @@ module.exports.notificationImage = (songInfo) => { } }; -module.exports.saveImage = cache((img, save_path) => { +module.exports.saveImage = cache((img, savePath) => { try { - fs.writeFileSync(save_path, img.toPNG()); + fs.writeFileSync(savePath, img.toPNG()); } catch (error) { console.log(`Error writing song icon to disk:\n${error.toString()}`); return icon; } - return save_path; + return savePath; }); -module.exports.save_temp_icons = () => { +module.exports.saveTempIcon = () => { for (const kind of Object.keys(module.exports.icons)) { const destinationPath = path.join(userData, 'icons', `${kind}.png`); if (fs.existsSync(destinationPath)) { diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 040c3fe9..85046057 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -1,7 +1,6 @@ const path = require('node:path'); const { app, ipcMain } = require('electron'); -const electronLocalshortcut = require('electron-localshortcut'); const { setOptions } = require('../../config/plugins'); const { injectCSS } = require('../utils'); diff --git a/plugins/picture-in-picture/front.js b/plugins/picture-in-picture/front.js index 0033d6cb..188cc694 100644 --- a/plugins/picture-in-picture/front.js +++ b/plugins/picture-in-picture/front.js @@ -52,7 +52,7 @@ const observer = new MutationObserver(() => { menu.prepend(pipButton); }); -global.togglePictureInPicture = async () => { +const togglePictureInPicture = async () => { if (useNativePiP) { const isInPiP = document.pictureInPictureElement !== null; const video = $('video'); @@ -72,6 +72,7 @@ global.togglePictureInPicture = async () => { ipcRenderer.send('picture-in-picture'); return false; }; +global.togglePictureInPicture = togglePictureInPicture; const listenForToggle = () => { const originalExitButton = $('.exit-fullscreen-button'); @@ -123,7 +124,7 @@ function observeMenu(options) { listenForToggle(); cloneButton('.player-minimize-button').addEventListener('click', async () => { - await global.togglePictureInPicture(); + await togglePictureInPicture(); setTimeout(() => $('#player').click()); }); diff --git a/plugins/playback-speed/front.js b/plugins/playback-speed/front.js index 1f0aaa43..7c01208f 100644 --- a/plugins/playback-speed/front.js +++ b/plugins/playback-speed/front.js @@ -56,8 +56,9 @@ const observePopupContainer = () => { }; const observeVideo = () => { - $('video').addEventListener('ratechange', forcePlaybackRate); - $('video').addEventListener('srcChanged', forcePlaybackRate); + const video = $('video');+ + video.addEventListener('ratechange', forcePlaybackRate); + video.addEventListener('srcChanged', forcePlaybackRate); }; const setupWheelListener = () => { diff --git a/plugins/precise-volume/back.js b/plugins/precise-volume/back.js index 4eab7ca3..e9215973 100644 --- a/plugins/precise-volume/back.js +++ b/plugins/precise-volume/back.js @@ -1,15 +1,15 @@ -const { injectCSS } = require('../utils'); - const path = require('node:path'); +const { globalShortcut } = require('electron'); + +const { injectCSS } = require('../utils'); + /* This is used to determine if plugin is actually active (not if its only enabled in options) */ let enabled = false; -const { globalShortcut } = require('electron'); - module.exports = (win, options) => { enabled = true; injectCSS(win.webContents, path.join(__dirname, 'volume-hud.css')); diff --git a/plugins/precise-volume/front.js b/plugins/precise-volume/front.js index 4c89f326..a8e8f818 100644 --- a/plugins/precise-volume/front.js +++ b/plugins/precise-volume/front.js @@ -26,7 +26,7 @@ const writeOptions = debounce(() => { setOptions('precise-volume', options); }, 1000); -module.exports.moveVolumeHud = debounce((showVideo) => { +const moveVolumeHud = debounce((showVideo) => { const volumeHud = $('#volumeHud'); if (!volumeHud) { return; @@ -36,6 +36,7 @@ module.exports.moveVolumeHud = debounce((showVideo) => { ? `${($('ytmusic-player').clientHeight - $('video').clientHeight) / 2}px` : 0; }, 250); +module.exports.moveVolumeHud = moveVolumeHud; const hideVolumeHud = debounce((volumeHud) => { volumeHud.style.opacity = 0; @@ -215,7 +216,7 @@ const tooltipTargets = [ ]; function setTooltip(volume) { - for (target of tooltipTargets) { + for (const target of tooltipTargets) { $(target).title = `${volume}%`; } } diff --git a/plugins/precise-volume/menu.js b/plugins/precise-volume/menu.js index 8f4316b0..ea4c0ff5 100644 --- a/plugins/precise-volume/menu.js +++ b/plugins/precise-volume/menu.js @@ -6,7 +6,7 @@ const { setMenuOptions } = require('../../config/plugins'); const promptOptions = require('../../providers/prompt-options'); function changeOptions(changedOptions, options, win) { - for (option in changedOptions) { + for (const option in changedOptions) { options[option] = changedOptions[option]; } diff --git a/plugins/quality-changer/front.js b/plugins/quality-changer/front.js index e5f2885f..0ee25ce8 100644 --- a/plugins/quality-changer/front.js +++ b/plugins/quality-changer/front.js @@ -15,6 +15,15 @@ module.exports = () => { }; function setup(event) { + /** + * @type {{ + * getAvailableQualityLevels: () => string[], + * getPlaybackQuality: () => string, + * getAvailableQualityLabels: () => string[], + * setPlaybackQualityRange: (quality: string) => void, + * setPlaybackQuality: (quality: string) => void, + * }} + */ const api = event.detail; $('.top-row-buttons.ytmusic-player').prepend(qualitySettingsButton); diff --git a/plugins/shortcuts/mpris.js b/plugins/shortcuts/mpris.js index f9546b1b..9fe2e4e6 100644 --- a/plugins/shortcuts/mpris.js +++ b/plugins/shortcuts/mpris.js @@ -6,7 +6,7 @@ const getSongControls = require('../../providers/song-controls'); const config = require('../../config'); function setupMPRIS() { - const player = mpris({ + return mpris({ name: 'youtube-music', identity: 'YouTube Music', canRaise: true, @@ -15,8 +15,6 @@ function setupMPRIS() { supportedInterfaces: ['player'], desktopEntry: 'youtube-music', }); - - return player; } /** @param {Electron.BrowserWindow} win */ @@ -108,7 +106,9 @@ function registerMPRIS(win) { player.on('position', seekTo); player.on('shuffle', (enableShuffle) => { - shuffle(); + if (enableShuffle) { + shuffle(); + } }); let mprisVolNewer = false; diff --git a/plugins/tuna-obs/back.js b/plugins/tuna-obs/back.js index 53ab429e..f3f2db87 100644 --- a/plugins/tuna-obs/back.js +++ b/plugins/tuna-obs/back.js @@ -18,7 +18,7 @@ const data = { const post = async (data) => { const port = 1608; - headers = { + const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Access-Control-Allow-Headers': '*', @@ -43,15 +43,6 @@ module.exports = async (win) => { data.progress = secToMilisec(t); post(data); }); - ipcMain.on('playPaused', (_, { isPaused, elapsedSeconds }) => { - if (!data.title) { - return; - } - - data.status = isPaused ? 'stopped' : 'playing'; - data.progress = secToMilisec(elapsedSeconds); - post(data); - }); registerCallback((songInfo) => { if (!songInfo.title && !songInfo.artist) { diff --git a/plugins/visualizer/back.js b/plugins/visualizer/back.js index 3ec1a80e..7a884474 100644 --- a/plugins/visualizer/back.js +++ b/plugins/visualizer/back.js @@ -2,6 +2,6 @@ const path = require('node:path'); const { injectCSS } = require('../utils'); -module.exports = (win, options) => { +module.exports = (win) => { injectCSS(win.webContents, path.join(__dirname, 'empty-player.css')); }; diff --git a/plugins/visualizer/visualizers/wave.js b/plugins/visualizer/visualizers/wave.js index 9088f7dd..f594c785 100644 --- a/plugins/visualizer/visualizers/wave.js +++ b/plugins/visualizer/visualizers/wave.js @@ -17,12 +17,13 @@ class WaveVisualizer { for (const animation of options.animations) { this.visualizer.addAnimation( eval(`new this.visualizer.animations.${animation.type}( - ${JSON.stringify(animation.config)} - )`), + ${JSON.stringify(animation.config)} + )`), ); } } + // eslint-disable-next-line no-unused-vars resize(width, height) { } diff --git a/providers/song-info-front.js b/providers/song-info-front.js index aa63a608..86a419b1 100644 --- a/providers/song-info-front.js +++ b/providers/song-info-front.js @@ -17,19 +17,21 @@ ipcRenderer.on('update-song-info', async (_, extractedSongInfo) => { // Used because 'loadeddata' or 'loadedmetadata' weren't firing on song start for some users (https://github.com/th-ch/youtube-music/issues/473) const srcChangedEvent = new CustomEvent('srcChanged'); -module.exports.setupSeekedListener = singleton(() => { +const setupSeekedListener = singleton(() => { $('video')?.addEventListener('seeked', (v) => ipcRenderer.send('seeked', v.target.currentTime)); }); +module.exports.setupSeekedListener = setupSeekedListener; -module.exports.setupTimeChangedListener = singleton(() => { +const setupTimeChangedListener = singleton(() => { const progressObserver = new MutationObserver((mutations) => { ipcRenderer.send('timeChanged', mutations[0].target.value); global.songInfo.elapsedSeconds = mutations[0].target.value; }); progressObserver.observe($('#progress-bar'), { attributeFilter: ['value'] }); }); +module.exports.setupTimeChangedListener = setupTimeChangedListener; -module.exports.setupRepeatChangedListener = singleton(() => { +const setupRepeatChangedListener = singleton(() => { const repeatObserver = new MutationObserver((mutations) => { ipcRenderer.send('repeatChanged', mutations[0].target.__dataHost.getState().queue.repeatMode); }); @@ -38,53 +40,66 @@ module.exports.setupRepeatChangedListener = singleton(() => { // Emit the initial value as well; as it's persistent between launches. ipcRenderer.send('repeatChanged', $('ytmusic-player-bar').getState().queue.repeatMode); }); +module.exports.setupRepeatChangedListener = setupRepeatChangedListener; -module.exports.setupVolumeChangedListener = singleton((api) => { - $('video').addEventListener('volumechange', (_) => { +const setupVolumeChangedListener = singleton((api) => { + $('video').addEventListener('volumechange', () => { ipcRenderer.send('volumeChanged', api.getVolume()); }); // Emit the initial value as well; as it's persistent between launches. ipcRenderer.send('volumeChanged', api.getVolume()); }); +module.exports.setupVolumeChangedListener = setupVolumeChangedListener; module.exports = () => { document.addEventListener('apiLoaded', (apiEvent) => { ipcRenderer.on('setupTimeChangedListener', async () => { - this.setupTimeChangedListener(); + setupTimeChangedListener(); }); ipcRenderer.on('setupRepeatChangedListener', async () => { - this.setupRepeatChangedListener(); + setupRepeatChangedListener(); }); ipcRenderer.on('setupVolumeChangedListener', async () => { - this.setupVolumeChangedListener(apiEvent.detail); + setupVolumeChangedListener(apiEvent.detail); }); ipcRenderer.on('setupSeekedListener', async () => { - this.setupSeekedListener(); + setupSeekedListener(); }); + const playPausedHandler = (e, status) => { + if (Math.round(e.target.currentTime) > 0) { + ipcRenderer.send('playPaused', { + isPaused: status === 'pause', + elapsedSeconds: Math.floor(e.target.currentTime), + }); + } + }; + + const playPausedHandlers = { + playing: (e) => playPausedHandler(e, 'playing'), + pause: (e) => playPausedHandler(e, 'pause'), + }; + const video = $('video'); + // Name = "dataloaded" and abit later "dataupdated" - apiEvent.detail.addEventListener('videodatachange', (name, _dataEvent) => { + apiEvent.detail.addEventListener('videodatachange', (name) => { if (name !== 'dataloaded') { return; } video.dispatchEvent(srcChangedEvent); + for (const status of ['playing', 'pause']) { // for fix issue that pause event not fired + video.addEventListener(status, playPausedHandlers[status]); + } setTimeout(sendSongInfo, 200); }); for (const status of ['playing', 'pause']) { - video.addEventListener(status, (e) => { - if (Math.round(e.target.currentTime) > 0) { - ipcRenderer.send('playPaused', { - isPaused: status === 'pause', - elapsedSeconds: Math.floor(e.target.currentTime), - }); - } - }); + video.addEventListener(status, playPausedHandlers[status]); } function sendSongInfo() {