apply fix from eslint

This commit is contained in:
JellyBrick
2023-08-29 19:14:51 +09:00
parent c722896a73
commit 897cfd3c7d
33 changed files with 471 additions and 524 deletions

View File

@ -46,6 +46,7 @@ module.exports = {
allowTemplateLiterals: false,
}],
'quote-props': ['error', 'consistent'],
'semi': ['error', 'always'],
},
env: {
browser: true,

View File

@ -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) {

View File

@ -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();
};

View File

@ -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;
});
}

80
package-lock.json generated
View File

@ -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"
}

View File

@ -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": {

View File

@ -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) {

View File

@ -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
};

View File

@ -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;
});

View File

@ -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) => [
{

View File

@ -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);

View File

@ -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);
};

View File

@ -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"

View File

@ -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';

View File

@ -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);

View File

@ -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;
}

View File

@ -65,14 +65,14 @@ module.exports = () => {
function setLyrics(lyricsContainer) {
lyricsContainer.innerHTML = `<div id="contents" class="style-scope ytmusic-section-list-renderer description ytmusic-description-shelf-renderer genius-lyrics">
${
${
hasLyrics
? lyrics.replaceAll(/\r\n|\r|\n/g, '<br/>')
: 'Could not retrieve lyrics from genius'
}
</div>
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline"></yt-formatted-string>`;
</div>
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline"></yt-formatted-string>`;
if (hasLyrics) {
lyricsContainer.querySelector('.footer').textContent = 'Source: Genius';
enableLyricsTab();

View File

@ -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',

View File

@ -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) {

View File

@ -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)}
</toast>`;
const xml_image = ({ title, artist, isPaused }, imgSrc, placement) => toast(`\
const xmlImage = ({ title, artist, isPaused }, imgSrc, placement) => toast(`\
<image id="1" src="${imgSrc}" name="Image" ${placement}/>
<text id="1">${title}</text>
<text id="2">${artist}</text>\
`, 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(`\
<image id="1" src="${imgSrc}" name="Image" />
<text></text>
<group>
@ -206,11 +209,11 @@ const xml_banner_top_custom = (songInfo, imgSrc) => toast(`\
<text hint-style="body">${songInfo.title}</text>
<text hint-style="captionSubtle">${songInfo.artist}</text>
</subgroup>
${xml_more_data(songInfo)}
${xmlMoreData(songInfo)}
</group>\
`, songInfo.isPaused);
const xml_more_data = ({ album, elapsedSeconds, songDuration }) => `\
const xmlMoreData = ({ album, elapsedSeconds, songDuration }) => `\
<subgroup hint-textStacking="bottom">
${album
? `<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${album}</text>` : ''}
@ -218,7 +221,7 @@ const xml_more_data = ({ album, elapsedSeconds, songDuration }) => `\
</subgroup>\
`;
const xml_banner_centered_bottom = ({ title, artist, isPaused }, imgSrc) => toast(`\
const xmlBannerCenteredBottom = ({ title, artist, isPaused }, imgSrc) => toast(`\
<text></text>
<group>
<subgroup hint-weight="1" hint-textStacking="center">
@ -229,7 +232,7 @@ const xml_banner_centered_bottom = ({ title, artist, isPaused }, imgSrc) => toas
<image id="1" src="${imgSrc}" name="Image" hint-removeMargin="true" />\
`, isPaused);
const xml_banner_centered_top = ({ title, artist, isPaused }, imgSrc) => toast(`\
const xmlBannerCenteredTop = ({ title, artist, isPaused }, imgSrc) => toast(`\
<image id="1" src="${imgSrc}" name="Image" />
<text></text>
<group>

View File

@ -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)) {

View File

@ -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');

View File

@ -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());
});

View File

@ -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 = () => {

View File

@ -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'));

View File

@ -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}%`;
}
}

View File

@ -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];
}

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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'));
};

View File

@ -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) {
}

View File

@ -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() {