mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 10:11:46 +00:00
apply fix from eslint
This commit is contained in:
@ -46,6 +46,7 @@ module.exports = {
|
||||
allowTemplateLiterals: false,
|
||||
}],
|
||||
'quote-props': ['error', 'consistent'],
|
||||
'semi': ['error', 'always'],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
11
index.js
11
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;
|
||||
});
|
||||
}
|
||||
|
||||
80
package-lock.json
generated
80
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
|
||||
@ -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) => [
|
||||
{
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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());
|
||||
});
|
||||
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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'));
|
||||
|
||||
@ -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}%`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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'));
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user