mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-13 19:31:46 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cb6f56feb | |||
| 46285a5ed0 | |||
| 496836b33b | |||
| af127879a5 | |||
| 38ef452801 | |||
| a9a5d99676 | |||
| e5ab50cebd | |||
| 49194f8141 | |||
| 641ae27efd | |||
| 47a5dec465 | |||
| c93eabb400 | |||
| 492a47321d | |||
| c89f6af8c6 | |||
| 9687c6c8e4 | |||
| ef0a89126a | |||
| 8ce71d628d | |||
| ca95d105c8 | |||
| 12568c2b09 | |||
| 82abb4d4d3 | |||
| 3c0a5dbbe5 | |||
| 0b98eef06f | |||
| 18e69c9f2a | |||
| 8f5d06d420 | |||
| 8a299461a0 | |||
| 0c58bec921 | |||
| e0cb132686 | |||
| 2a192f39f9 | |||
| b7ebb7d499 | |||
| fffeac21b7 | |||
| 4387cb485d | |||
| 2a58dc823a | |||
| 8eb38271ff | |||
| 1987ad1d4f | |||
| cc4dae60ef | |||
| 1943116aa1 | |||
| 3485d26b11 | |||
| 4a60aa9f20 | |||
| cda07c9675 | |||
| ca64a77ed0 | |||
| 30e94d1d6f | |||
| b8c6ebfa53 | |||
| b26748ded8 | |||
| f186da0834 | |||
| 33855f17dd | |||
| 541c7f34b7 | |||
| 090ca828c0 | |||
| 78974c02e5 | |||
| 4508464fd1 | |||
| dd6455a559 |
@ -63,7 +63,19 @@ const defaultConfig = {
|
|||||||
volumeDown: "Shift+PageDown"
|
volumeDown: "Shift+PageDown"
|
||||||
},
|
},
|
||||||
savedVolume: undefined //plugin save volume between session here
|
savedVolume: undefined //plugin save volume between session here
|
||||||
}
|
},
|
||||||
|
sponsorblock: {
|
||||||
|
enabled: false,
|
||||||
|
apiURL: "https://sponsor.ajay.app",
|
||||||
|
categories: [
|
||||||
|
"sponsor",
|
||||||
|
"intro",
|
||||||
|
"outro",
|
||||||
|
"interaction",
|
||||||
|
"selfpromo",
|
||||||
|
"music_offtopic",
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
41
index.js
41
index.js
@ -2,6 +2,7 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const electron = require("electron");
|
const electron = require("electron");
|
||||||
|
const enhanceWebRequest = require("electron-better-web-request").default;
|
||||||
const is = require("electron-is");
|
const is = require("electron-is");
|
||||||
const unhandled = require("electron-unhandled");
|
const unhandled = require("electron-unhandled");
|
||||||
const { autoUpdater } = require("electron-updater");
|
const { autoUpdater } = require("electron-updater");
|
||||||
@ -154,6 +155,8 @@ function createMainWindow() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
removeContentSecurityPolicy();
|
||||||
|
|
||||||
return win;
|
return win;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,3 +380,41 @@ function showUnresponsiveDialog(win, details) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeContentSecurityPolicy(
|
||||||
|
session = electron.session.defaultSession
|
||||||
|
) {
|
||||||
|
// Allows defining multiple "onHeadersReceived" listeners
|
||||||
|
// by enhancing the session.
|
||||||
|
// Some plugins (e.g. adblocker) also define a "onHeadersReceived" listener
|
||||||
|
enhanceWebRequest(session);
|
||||||
|
|
||||||
|
// Custom listener to tweak the content security policy
|
||||||
|
session.webRequest.onHeadersReceived(function (details, callback) {
|
||||||
|
if (
|
||||||
|
!details.responseHeaders["content-security-policy-report-only"] &&
|
||||||
|
!details.responseHeaders["content-security-policy"]
|
||||||
|
)
|
||||||
|
return callback({ cancel: false });
|
||||||
|
delete details.responseHeaders["content-security-policy-report-only"];
|
||||||
|
delete details.responseHeaders["content-security-policy"];
|
||||||
|
callback({ cancel: false, responseHeaders: details.responseHeaders });
|
||||||
|
});
|
||||||
|
|
||||||
|
// When multiple listeners are defined, apply them all
|
||||||
|
session.webRequest.setResolver("onHeadersReceived", (listeners) => {
|
||||||
|
const response = listeners.reduce(
|
||||||
|
async (accumulator, listener) => {
|
||||||
|
if (accumulator.cancel) {
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await listener.apply();
|
||||||
|
return { ...accumulator, ...result };
|
||||||
|
},
|
||||||
|
{ cancel: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,5 @@ module.exports = {
|
|||||||
globals: {
|
globals: {
|
||||||
__APP__: undefined, // A different app will be launched in each test environment
|
__APP__: undefined, // A different app will be launched in each test environment
|
||||||
},
|
},
|
||||||
testEnvironment: "./tests/environment",
|
|
||||||
testTimeout: 30000, // 30s
|
testTimeout: 30000, // 30s
|
||||||
};
|
};
|
||||||
|
|||||||
445
menu.js
445
menu.js
@ -7,12 +7,13 @@ const is = require("electron-is");
|
|||||||
const { getAllPlugins } = require("./plugins/utils");
|
const { getAllPlugins } = require("./plugins/utils");
|
||||||
const config = require("./config");
|
const config = require("./config");
|
||||||
|
|
||||||
const pluginEnabledMenu = (win, plugin, label = "", hasSubmenu = false) => ({
|
// true only if in-app-menu was loaded on launch
|
||||||
|
const inAppMenuActive = config.plugins.isEnabled("in-app-menu");
|
||||||
|
|
||||||
|
const pluginEnabledMenu = (plugin, label = "", hasSubmenu = false, refreshMenu = undefined) => ({
|
||||||
label: label || plugin,
|
label: label || plugin,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
checked: config.plugins.isEnabled(plugin),
|
checked: config.plugins.isEnabled(plugin),
|
||||||
//Submenu check used in in-app-menu
|
|
||||||
hasSubmenu: hasSubmenu || undefined,
|
|
||||||
click: (item) => {
|
click: (item) => {
|
||||||
if (item.checked) {
|
if (item.checked) {
|
||||||
config.plugins.enable(plugin);
|
config.plugins.enable(plugin);
|
||||||
@ -20,252 +21,226 @@ const pluginEnabledMenu = (win, plugin, label = "", hasSubmenu = false) => ({
|
|||||||
config.plugins.disable(plugin);
|
config.plugins.disable(plugin);
|
||||||
}
|
}
|
||||||
if (hasSubmenu) {
|
if (hasSubmenu) {
|
||||||
this.setApplicationMenu(win);
|
refreshMenu();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const mainMenuTemplate = (win) => [
|
const mainMenuTemplate = (win) => {
|
||||||
{
|
const refreshMenu = () => {
|
||||||
label: "Plugins",
|
this.setApplicationMenu(win);
|
||||||
submenu: [
|
if (inAppMenuActive) {
|
||||||
...getAllPlugins().map((plugin) => {
|
win.webContents.send("updateMenu", true);
|
||||||
const pluginPath = path.join(__dirname, "plugins", plugin, "menu.js")
|
}
|
||||||
if (existsSync(pluginPath)) {
|
}
|
||||||
if (!config.plugins.isEnabled(plugin)) {
|
return [
|
||||||
return pluginEnabledMenu(win, plugin, "", true);
|
{
|
||||||
|
label: "Plugins",
|
||||||
|
submenu: [
|
||||||
|
...getAllPlugins().map((plugin) => {
|
||||||
|
const pluginPath = path.join(__dirname, "plugins", plugin, "menu.js")
|
||||||
|
if (existsSync(pluginPath)) {
|
||||||
|
if (!config.plugins.isEnabled(plugin)) {
|
||||||
|
return pluginEnabledMenu(plugin, "", true, refreshMenu);
|
||||||
|
}
|
||||||
|
const getPluginMenu = require(pluginPath);
|
||||||
|
return {
|
||||||
|
label: plugin,
|
||||||
|
submenu: [
|
||||||
|
pluginEnabledMenu(plugin, "Enabled", true, refreshMenu),
|
||||||
|
...getPluginMenu(win, config.plugins.getOptions(plugin), refreshMenu),
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const getPluginMenu = require(pluginPath);
|
|
||||||
return {
|
|
||||||
label: plugin,
|
|
||||||
submenu: [
|
|
||||||
pluginEnabledMenu(win, plugin, "Enabled", true),
|
|
||||||
...getPluginMenu(win, config.plugins.getOptions(plugin), () =>
|
|
||||||
module.exports.setApplicationMenu(win)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluginEnabledMenu(win, plugin);
|
return pluginEnabledMenu(plugin);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Options",
|
label: "Options",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: "Auto-update",
|
label: "Auto-update",
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
checked: config.get("options.autoUpdates"),
|
checked: config.get("options.autoUpdates"),
|
||||||
click: (item) => {
|
click: (item) => {
|
||||||
config.set("options.autoUpdates", item.checked);
|
config.set("options.autoUpdates", item.checked);
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Resume last song when app starts",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: config.get("options.resumeOnStart"),
|
|
||||||
click: (item) => {
|
|
||||||
config.set("options.resumeOnStart", item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...(is.windows() || is.linux()
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: "Hide menu",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: config.get("options.hideMenu"),
|
|
||||||
click: (item) => {
|
|
||||||
config.set("options.hideMenu", item.checked);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(is.windows() || is.macOS()
|
|
||||||
? // Only works on Win/Mac
|
|
||||||
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: "Start at login",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: config.get("options.startAtLogin"),
|
|
||||||
click: (item) => {
|
|
||||||
config.set("options.startAtLogin", item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
label: "Tray",
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: "Disabled",
|
|
||||||
type: "radio",
|
|
||||||
checked: !config.get("options.tray"),
|
|
||||||
click: () => {
|
|
||||||
config.set("options.tray", false);
|
|
||||||
config.set("options.appVisible", true);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Enabled + app visible",
|
|
||||||
type: "radio",
|
|
||||||
checked:
|
|
||||||
config.get("options.tray") && config.get("options.appVisible"),
|
|
||||||
click: () => {
|
|
||||||
config.set("options.tray", true);
|
|
||||||
config.set("options.appVisible", true);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Enabled + app hidden",
|
|
||||||
type: "radio",
|
|
||||||
checked:
|
|
||||||
config.get("options.tray") && !config.get("options.appVisible"),
|
|
||||||
click: () => {
|
|
||||||
config.set("options.tray", true);
|
|
||||||
config.set("options.appVisible", false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ type: "separator" },
|
|
||||||
{
|
|
||||||
label: "Play/Pause on click",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: config.get("options.trayClickPlayPause"),
|
|
||||||
click: (item) => {
|
|
||||||
config.set("options.trayClickPlayPause", item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ type: "separator" },
|
|
||||||
{
|
|
||||||
label: "Advanced options",
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: "Disable hardware acceleration",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: config.get("options.disableHardwareAcceleration"),
|
|
||||||
click: (item) => {
|
|
||||||
config.set("options.disableHardwareAcceleration", item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Restart on config changes",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: config.get("options.restartOnConfigChanges"),
|
|
||||||
click: (item) => {
|
|
||||||
config.set("options.restartOnConfigChanges", item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Reset App cache when app starts",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: config.get("options.autoResetAppCache"),
|
|
||||||
click: (item) => {
|
|
||||||
config.set("options.autoResetAppCache", item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ type: "separator" },
|
|
||||||
{
|
|
||||||
label: "Toggle DevTools",
|
|
||||||
// Cannot use "toggleDevTools" role in MacOS
|
|
||||||
click: () => {
|
|
||||||
const { webContents } = win;
|
|
||||||
if (webContents.isDevToolsOpened()) {
|
|
||||||
webContents.closeDevTools();
|
|
||||||
} else {
|
|
||||||
const devToolsOptions = {};
|
|
||||||
webContents.openDevTools(devToolsOptions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Edit config.json",
|
|
||||||
click: () => {
|
|
||||||
config.edit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "View",
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: "Reload",
|
|
||||||
click: () => {
|
|
||||||
win.webContents.reload();
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Resume last song when app starts",
|
||||||
label: "Force Reload",
|
type: "checkbox",
|
||||||
click: () => {
|
checked: config.get("options.resumeOnStart"),
|
||||||
win.webContents.reloadIgnoringCache();
|
click: (item) => {
|
||||||
|
config.set("options.resumeOnStart", item.checked);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
...(is.windows() || is.linux()
|
||||||
{ type: "separator" },
|
? [
|
||||||
{
|
{
|
||||||
label: "Zoom In",
|
label: "Hide menu",
|
||||||
click: () => {
|
type: "checkbox",
|
||||||
win.webContents.setZoomLevel(
|
checked: config.get("options.hideMenu"),
|
||||||
win.webContents.getZoomLevel() + 1
|
click: (item) => {
|
||||||
);
|
config.set("options.hideMenu", item.checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(is.windows() || is.macOS()
|
||||||
|
? // Only works on Win/Mac
|
||||||
|
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: "Start at login",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: config.get("options.startAtLogin"),
|
||||||
|
click: (item) => {
|
||||||
|
config.set("options.startAtLogin", item.checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
label: "Tray",
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: "Disabled",
|
||||||
|
type: "radio",
|
||||||
|
checked: !config.get("options.tray"),
|
||||||
|
click: () => {
|
||||||
|
config.set("options.tray", false);
|
||||||
|
config.set("options.appVisible", true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Enabled + app visible",
|
||||||
|
type: "radio",
|
||||||
|
checked:
|
||||||
|
config.get("options.tray") && config.get("options.appVisible"),
|
||||||
|
click: () => {
|
||||||
|
config.set("options.tray", true);
|
||||||
|
config.set("options.appVisible", true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Enabled + app hidden",
|
||||||
|
type: "radio",
|
||||||
|
checked:
|
||||||
|
config.get("options.tray") && !config.get("options.appVisible"),
|
||||||
|
click: () => {
|
||||||
|
config.set("options.tray", true);
|
||||||
|
config.set("options.appVisible", false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "separator" },
|
||||||
|
{
|
||||||
|
label: "Play/Pause on click",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: config.get("options.trayClickPlayPause"),
|
||||||
|
click: (item) => {
|
||||||
|
config.set("options.trayClickPlayPause", item.checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: "Zoom Out",
|
label: "Advanced options",
|
||||||
click: () => {
|
submenu: [
|
||||||
win.webContents.setZoomLevel(
|
{
|
||||||
win.webContents.getZoomLevel() - 1
|
label: "Disable hardware acceleration",
|
||||||
);
|
type: "checkbox",
|
||||||
|
checked: config.get("options.disableHardwareAcceleration"),
|
||||||
|
click: (item) => {
|
||||||
|
config.set("options.disableHardwareAcceleration", item.checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Restart on config changes",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: config.get("options.restartOnConfigChanges"),
|
||||||
|
click: (item) => {
|
||||||
|
config.set("options.restartOnConfigChanges", item.checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Reset App cache when app starts",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: config.get("options.autoResetAppCache"),
|
||||||
|
click: (item) => {
|
||||||
|
config.set("options.autoResetAppCache", item.checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "separator" },
|
||||||
|
is.macOS() ?
|
||||||
|
{
|
||||||
|
label: "Toggle DevTools",
|
||||||
|
// Cannot use "toggleDevTools" role in MacOS
|
||||||
|
click: () => {
|
||||||
|
const { webContents } = win;
|
||||||
|
if (webContents.isDevToolsOpened()) {
|
||||||
|
webContents.closeDevTools();
|
||||||
|
} else {
|
||||||
|
const devToolsOptions = {};
|
||||||
|
webContents.openDevTools(devToolsOptions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} :
|
||||||
|
{ role: "toggleDevTools" },
|
||||||
|
{
|
||||||
|
label: "Edit config.json",
|
||||||
|
click: () => {
|
||||||
|
config.edit();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
{
|
},
|
||||||
label: "Reset Zoom",
|
{
|
||||||
click: () => {
|
label: "View",
|
||||||
win.webContents.setZoomLevel(0);
|
submenu: [
|
||||||
|
{ role: "reload" },
|
||||||
|
{ role: "forceReload" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "zoomIn" },
|
||||||
|
{ role: "zoomOut" },
|
||||||
|
{ role: "resetZoom" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Navigation",
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: "Go back",
|
||||||
|
click: () => {
|
||||||
|
if (win.webContents.canGoBack()) {
|
||||||
|
win.webContents.goBack();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
],
|
label: "Go forward",
|
||||||
},
|
click: () => {
|
||||||
{
|
if (win.webContents.canGoForward()) {
|
||||||
label: "Navigation",
|
win.webContents.goForward();
|
||||||
submenu: [
|
}
|
||||||
{
|
},
|
||||||
label: "Go back",
|
|
||||||
click: () => {
|
|
||||||
if (win.webContents.canGoBack()) {
|
|
||||||
win.webContents.goBack();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Restart App",
|
||||||
label: "Go forward",
|
click: () => {
|
||||||
click: () => {
|
app.relaunch();
|
||||||
if (win.webContents.canGoForward()) {
|
app.quit();
|
||||||
win.webContents.goForward();
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
{ role: "quit" },
|
||||||
{
|
],
|
||||||
label: "Restart App",
|
},
|
||||||
click: () => {
|
];
|
||||||
app.relaunch();
|
}
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Quit App",
|
|
||||||
click: () => {
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
module.exports.mainMenuTemplate = mainMenuTemplate;
|
module.exports.mainMenuTemplate = mainMenuTemplate;
|
||||||
module.exports.setApplicationMenu = (win) => {
|
module.exports.setApplicationMenu = (win) => {
|
||||||
|
|||||||
24
package.json
24
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-music",
|
"name": "youtube-music",
|
||||||
"productName": "YouTube Music",
|
"productName": "YouTube Music",
|
||||||
"version": "1.12.1",
|
"version": "1.12.2",
|
||||||
"description": "YouTube Music Desktop App - including custom plugins",
|
"description": "YouTube Music Desktop App - including custom plugins",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "th-ch/youtube-music",
|
"repository": "th-ch/youtube-music",
|
||||||
@ -51,6 +51,7 @@
|
|||||||
"build:linux": "yarn run clean && electron-builder --linux",
|
"build:linux": "yarn run clean && electron-builder --linux",
|
||||||
"build:mac": "yarn run clean && electron-builder --mac",
|
"build:mac": "yarn run clean && electron-builder --mac",
|
||||||
"build:win": "yarn run clean && electron-builder --win",
|
"build:win": "yarn run clean && electron-builder --win",
|
||||||
|
"lint": "xo",
|
||||||
"plugins": "yarn run plugin:adblocker && yarn run plugin:autoconfirm",
|
"plugins": "yarn run plugin:adblocker && yarn run plugin:autoconfirm",
|
||||||
"plugin:adblocker": "rimraf plugins/adblocker/ad-blocker-engine.bin && node plugins/adblocker/blocker.js",
|
"plugin:adblocker": "rimraf plugins/adblocker/ad-blocker-engine.bin && node plugins/adblocker/blocker.js",
|
||||||
"plugin:autoconfirm": "yarn run generate:package YoutubeNonStop",
|
"plugin:autoconfirm": "yarn run generate:package YoutubeNonStop",
|
||||||
@ -59,31 +60,32 @@
|
|||||||
"release:win": "yarn run clean && electron-builder --win -p always"
|
"release:win": "yarn run clean && electron-builder --win -p always"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.16.1",
|
"node": ">=12.20",
|
||||||
"npm": "Please use yarn and not npm"
|
"npm": "Please use yarn and not npm"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cliqz/adblocker-electron": "^1.20.5",
|
"@cliqz/adblocker-electron": "^1.22.1",
|
||||||
"@ffmpeg/core": "^0.9.0",
|
"@ffmpeg/core": "^0.10.0",
|
||||||
"@ffmpeg/ffmpeg": "^0.9.7",
|
"@ffmpeg/ffmpeg": "^0.10.0",
|
||||||
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0",
|
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0",
|
||||||
"async-mutex": "^0.3.1",
|
"async-mutex": "^0.3.1",
|
||||||
"browser-id3-writer": "^4.4.0",
|
"browser-id3-writer": "^4.4.0",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
"custom-electron-titlebar": "^3.2.6",
|
"custom-electron-titlebar": "^3.2.7",
|
||||||
"discord-rpc": "^3.2.0",
|
"discord-rpc": "^3.2.0",
|
||||||
|
"electron-better-web-request": "^1.0.1",
|
||||||
"electron-debug": "^3.2.0",
|
"electron-debug": "^3.2.0",
|
||||||
"electron-is": "^3.0.0",
|
"electron-is": "^3.0.0",
|
||||||
"electron-localshortcut": "^3.2.1",
|
"electron-localshortcut": "^3.2.1",
|
||||||
"electron-store": "^7.0.3",
|
"electron-store": "^7.0.3",
|
||||||
"electron-unhandled": "^3.0.2",
|
"electron-unhandled": "^3.0.2",
|
||||||
"electron-updater": "^4.3.8",
|
"electron-updater": "^4.3.9",
|
||||||
"filenamify": "^4.3.0",
|
"filenamify": "^4.3.0",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
"node-notifier": "^9.0.1",
|
"node-notifier": "^9.0.1",
|
||||||
"ytdl-core": "^4.7.0",
|
"ytdl-core": "^4.8.3",
|
||||||
"ytpl": "^2.2.0"
|
"ytpl": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^12.0.8",
|
"electron": "^12.0.8",
|
||||||
@ -94,9 +96,11 @@
|
|||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"spectron": "^14.0.0",
|
"spectron": "^14.0.0",
|
||||||
"xo": "^0.38.2"
|
"xo": "^0.40.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
"glob-parent": "5.1.2",
|
||||||
|
"minimist": "1.2.5",
|
||||||
"yargs-parser": "18.1.3"
|
"yargs-parser": "18.1.3"
|
||||||
},
|
},
|
||||||
"xo": {
|
"xo": {
|
||||||
|
|||||||
@ -8,7 +8,9 @@ const SOURCES = [
|
|||||||
"https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt",
|
"https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt",
|
||||||
// uBlock Origin
|
// uBlock Origin
|
||||||
"https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt",
|
"https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt",
|
||||||
"https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2020.txt",
|
"https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2021.txt",
|
||||||
|
// Fanboy Annoyances
|
||||||
|
"https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt",
|
||||||
];
|
];
|
||||||
|
|
||||||
const loadAdBlockerEngine = (
|
const loadAdBlockerEngine = (
|
||||||
@ -31,7 +33,17 @@ const loadAdBlockerEngine = (
|
|||||||
...additionalBlockLists,
|
...additionalBlockLists,
|
||||||
];
|
];
|
||||||
|
|
||||||
ElectronBlocker.fromLists(fetch, lists, {}, cachingOptions)
|
ElectronBlocker.fromLists(
|
||||||
|
fetch,
|
||||||
|
lists,
|
||||||
|
{
|
||||||
|
// when generating the engine for caching, do not load network filters
|
||||||
|
// So that enhancing the session works as expected
|
||||||
|
// Allowing to define multiple webRequest listeners
|
||||||
|
loadNetworkFilters: session !== undefined,
|
||||||
|
},
|
||||||
|
cachingOptions
|
||||||
|
)
|
||||||
.then((blocker) => {
|
.then((blocker) => {
|
||||||
if (session) {
|
if (session) {
|
||||||
blocker.enableBlockingInSession(session);
|
blocker.enableBlockingInSession(session);
|
||||||
|
|||||||
@ -1,25 +1,10 @@
|
|||||||
let videoElement = null;
|
const { ontimeupdate } = require("../../providers/video-element");
|
||||||
|
|
||||||
const observer = new MutationObserver((mutations, observer) => {
|
module.exports = () => {
|
||||||
if (!videoElement) {
|
ontimeupdate((videoElement) => {
|
||||||
videoElement = document.querySelector("video");
|
if (videoElement.currentTime === 0 && videoElement.duration !== NaN) {
|
||||||
}
|
// auto-confirm-when-paused plugin can interfere here if not disabled!
|
||||||
|
videoElement.pause();
|
||||||
if (videoElement) {
|
}
|
||||||
videoElement.ontimeupdate = () => {
|
|
||||||
if (videoElement.currentTime === 0 && videoElement.duration !== NaN) {
|
|
||||||
// auto-confirm-when-paused plugin can interfere here if not disabled!
|
|
||||||
videoElement.pause();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function observeVideoElement() {
|
|
||||||
observer.observe(document, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = observeVideoElement;
|
|
||||||
|
|||||||
@ -55,7 +55,9 @@ function handle(win) {
|
|||||||
{ ...nowPlayingMetadata, ...currentMetadata };
|
{ ...nowPlayingMetadata, ...currentMetadata };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const coverBuffer = songMetadata.image ? songMetadata.image.toPNG() : null;
|
const coverBuffer = songMetadata.image && !songMetadata.image.isEmpty() ?
|
||||||
|
songMetadata.image.toPNG() : null;
|
||||||
|
|
||||||
const writer = new ID3Writer(songBuffer);
|
const writer = new ID3Writer(songBuffer);
|
||||||
|
|
||||||
// Create the metadata tags
|
// Create the metadata tags
|
||||||
|
|||||||
@ -1,83 +1,33 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const { Menu } = require("electron");
|
|
||||||
const electronLocalshortcut = require("electron-localshortcut");
|
const electronLocalshortcut = require("electron-localshortcut");
|
||||||
|
|
||||||
const config = require("../../config");
|
const config = require("../../config");
|
||||||
const { setApplicationMenu } = require("../../menu");
|
|
||||||
const { injectCSS } = require("../utils");
|
const { injectCSS } = require("../utils");
|
||||||
|
|
||||||
//tracks menu visibility
|
//tracks menu visibility
|
||||||
let visible = true;
|
let visible = true;
|
||||||
// win hook for fixing menu
|
|
||||||
let win;
|
|
||||||
|
|
||||||
const originalBuildMenu = Menu.buildFromTemplate;
|
|
||||||
// This function natively gets called on all submenu so no more reason to use recursion
|
|
||||||
Menu.buildFromTemplate = (template) => {
|
|
||||||
// Fix checkboxes and radio buttons
|
|
||||||
updateTemplate(template);
|
|
||||||
|
|
||||||
// return as normal
|
|
||||||
return originalBuildMenu(template);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = (winImport) => {
|
|
||||||
win = winImport;
|
|
||||||
|
|
||||||
|
module.exports = (win) => {
|
||||||
// css for custom scrollbar + disable drag area(was causing bugs)
|
// css for custom scrollbar + disable drag area(was causing bugs)
|
||||||
injectCSS(win.webContents, path.join(__dirname, "style.css"));
|
injectCSS(win.webContents, path.join(__dirname, "style.css"));
|
||||||
|
|
||||||
win.once("ready-to-show", () => {
|
win.once("ready-to-show", () => {
|
||||||
|
|
||||||
setApplicationMenu(win);
|
|
||||||
|
|
||||||
//register keyboard shortcut && hide menu if hideMenu is enabled
|
//register keyboard shortcut && hide menu if hideMenu is enabled
|
||||||
if (config.get("options.hideMenu")) {
|
if (config.get("options.hideMenu")) {
|
||||||
electronLocalshortcut.register(win, "Esc", () => {
|
electronLocalshortcut.register(win, "Esc", () => {
|
||||||
switchMenuVisibility();
|
setMenuVisibility(!visible);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//set menu visibility on load
|
|
||||||
win.webContents.once("did-finish-load", () => {
|
win.webContents.once("did-finish-load", () => {
|
||||||
// fix bug with menu not applying on start when no internet connection available
|
// fix bug with menu not applying on start when no internet connection available
|
||||||
setMenuVisibility(!config.get("options.hideMenu"));
|
setMenuVisibility(!config.get("options.hideMenu"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setMenuVisibility(value) {
|
||||||
|
visible = value;
|
||||||
|
win.webContents.send("updateMenu", visible);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function switchMenuVisibility() {
|
|
||||||
setMenuVisibility(!visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMenuVisibility(value) {
|
|
||||||
visible = value;
|
|
||||||
win.webContents.send("updateMenu", visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCheckboxesAndRadioButtons(item, isRadio, hasSubmenu) {
|
|
||||||
if (!isRadio) {
|
|
||||||
//fix checkbox
|
|
||||||
item.checked = !item.checked;
|
|
||||||
}
|
|
||||||
//update menu if radio / hasSubmenu
|
|
||||||
if (isRadio || hasSubmenu) {
|
|
||||||
win.webContents.send("updateMenu", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update checkboxes/radio buttons
|
|
||||||
function updateTemplate(template) {
|
|
||||||
for (let item of template) {
|
|
||||||
// Change onClick of checkbox+radio
|
|
||||||
if ((item.type === "checkbox" || item.type === "radio") && !item.fixed) {
|
|
||||||
const originalOnclick = item.click;
|
|
||||||
item.click = (itemClicked) => {
|
|
||||||
originalOnclick(itemClicked);
|
|
||||||
updateCheckboxesAndRadioButtons(itemClicked, item.type === "radio", item.hasSubmenu);
|
|
||||||
};
|
|
||||||
item.fixed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -10,15 +10,7 @@ module.exports = () => {
|
|||||||
bar.updateTitle(" ");
|
bar.updateTitle(" ");
|
||||||
document.title = "Youtube Music";
|
document.title = "Youtube Music";
|
||||||
|
|
||||||
ipcRenderer.on("updateMenu", function (event, menu) {
|
ipcRenderer.on("updateMenu", function (_event, showMenu) {
|
||||||
if (menu) {
|
bar.updateMenu(showMenu ? remote.Menu.getApplicationMenu() : null);
|
||||||
bar.updateMenu(remote.Menu.getApplicationMenu());
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
bar.updateMenu(null);
|
|
||||||
} catch (e) {
|
|
||||||
//will always throw type error - null isn't menu, but it works
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,11 +4,6 @@
|
|||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* allow submenu's to show correctly */
|
|
||||||
.menubar-menu-container {
|
|
||||||
overflow-y: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fixes scrollbar positioning relative to nav bar */
|
/* fixes scrollbar positioning relative to nav bar */
|
||||||
#nav-bar-background.ytmusic-app-layout {
|
#nav-bar-background.ytmusic-app-layout {
|
||||||
right: 15px !important;
|
right: 15px !important;
|
||||||
|
|||||||
51
plugins/sponsorblock/back.js
Normal file
51
plugins/sponsorblock/back.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const fetch = require("node-fetch");
|
||||||
|
|
||||||
|
const defaultConfig = require("../../config/defaults");
|
||||||
|
const registerCallback = require("../../providers/song-info");
|
||||||
|
const { sortSegments } = require("./segments");
|
||||||
|
|
||||||
|
let videoID;
|
||||||
|
|
||||||
|
module.exports = (win, options) => {
|
||||||
|
const { apiURL, categories } = {
|
||||||
|
...defaultConfig.plugins.sponsorblock,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
registerCallback(async (info) => {
|
||||||
|
const newURL = info.url || win.webContents.getURL();
|
||||||
|
const newVideoID = new URL(newURL).searchParams.get("v");
|
||||||
|
|
||||||
|
if (videoID !== newVideoID) {
|
||||||
|
videoID = newVideoID;
|
||||||
|
const segments = await fetchSegments(apiURL, categories);
|
||||||
|
win.webContents.send("sponsorblock-skip", segments);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchSegments = async (apiURL, categories) => {
|
||||||
|
const sponsorBlockURL = `${apiURL}/api/skipSegments?videoID=${videoID}&categories=${JSON.stringify(
|
||||||
|
categories
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const resp = await fetch(sponsorBlockURL, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
redirect: "follow",
|
||||||
|
});
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const segments = await resp.json();
|
||||||
|
const sortedSegments = sortSegments(
|
||||||
|
segments.map((submission) => submission.segment)
|
||||||
|
);
|
||||||
|
|
||||||
|
return sortedSegments;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
27
plugins/sponsorblock/front.js
Normal file
27
plugins/sponsorblock/front.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
const { ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
const is = require("electron-is");
|
||||||
|
|
||||||
|
const { ontimeupdate } = require("../../providers/video-element");
|
||||||
|
|
||||||
|
let currentSegments = [];
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
ipcRenderer.on("sponsorblock-skip", (_, segments) => {
|
||||||
|
currentSegments = segments;
|
||||||
|
});
|
||||||
|
|
||||||
|
ontimeupdate((videoElement) => {
|
||||||
|
currentSegments.forEach((segment) => {
|
||||||
|
if (
|
||||||
|
videoElement.currentTime >= segment[0] &&
|
||||||
|
videoElement.currentTime <= segment[1]
|
||||||
|
) {
|
||||||
|
videoElement.currentTime = segment[1];
|
||||||
|
if (is.dev()) {
|
||||||
|
console.log("SponsorBlock: skipping segment", segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
29
plugins/sponsorblock/segments.js
Normal file
29
plugins/sponsorblock/segments.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Segments are an array [ [start, end], … ]
|
||||||
|
module.exports.sortSegments = (segments) => {
|
||||||
|
segments.sort((segment1, segment2) =>
|
||||||
|
segment1[0] === segment2[0]
|
||||||
|
? segment1[1] - segment2[1]
|
||||||
|
: segment1[0] - segment2[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
const compiledSegments = [];
|
||||||
|
let currentSegment;
|
||||||
|
|
||||||
|
segments.forEach((segment) => {
|
||||||
|
if (!currentSegment) {
|
||||||
|
currentSegment = segment;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentSegment[1] < segment[0]) {
|
||||||
|
compiledSegments.push(currentSegment);
|
||||||
|
currentSegment = segment;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSegment[1] = Math.max(currentSegment[1], segment[1]);
|
||||||
|
});
|
||||||
|
compiledSegments.push(currentSegment);
|
||||||
|
|
||||||
|
return compiledSegments;
|
||||||
|
};
|
||||||
34
plugins/sponsorblock/tests/segments.test.js
Normal file
34
plugins/sponsorblock/tests/segments.test.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const { sortSegments } = require("../segments");
|
||||||
|
|
||||||
|
test("Segment sorting", () => {
|
||||||
|
expect(
|
||||||
|
sortSegments([
|
||||||
|
[0, 3],
|
||||||
|
[7, 8],
|
||||||
|
[5, 6],
|
||||||
|
])
|
||||||
|
).toEqual([
|
||||||
|
[0, 3],
|
||||||
|
[5, 6],
|
||||||
|
[7, 8],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
sortSegments([
|
||||||
|
[0, 5],
|
||||||
|
[6, 8],
|
||||||
|
[4, 6],
|
||||||
|
])
|
||||||
|
).toEqual([[0, 8]]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
sortSegments([
|
||||||
|
[0, 6],
|
||||||
|
[7, 8],
|
||||||
|
[4, 6],
|
||||||
|
])
|
||||||
|
).toEqual([
|
||||||
|
[0, 6],
|
||||||
|
[7, 8],
|
||||||
|
]);
|
||||||
|
});
|
||||||
@ -9,18 +9,21 @@ const progressSelector = "#progress-bar";
|
|||||||
// Grab the progress using the selector
|
// Grab the progress using the selector
|
||||||
const getProgress = async (win) => {
|
const getProgress = async (win) => {
|
||||||
// Get current value of the progressbar element
|
// Get current value of the progressbar element
|
||||||
const elapsedSeconds = await win.webContents.executeJavaScript(
|
return win.webContents.executeJavaScript(
|
||||||
'document.querySelector("' + progressSelector + '").value'
|
'document.querySelector("' + progressSelector + '").value'
|
||||||
);
|
);
|
||||||
|
|
||||||
return elapsedSeconds;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Grab the native image using the src
|
// Grab the native image using the src
|
||||||
const getImage = async (src) => {
|
const getImage = async (src) => {
|
||||||
const result = await fetch(src);
|
const result = await fetch(src);
|
||||||
const buffer = await result.buffer();
|
const buffer = await result.buffer();
|
||||||
return nativeImage.createFromBuffer(buffer);
|
const output = nativeImage.createFromBuffer(buffer);
|
||||||
|
if (output.isEmpty() && !src.endsWith(".jpg") && src.includes(".jpg")) { // fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
|
||||||
|
return getImage(src.slice(0, src.lastIndexOf(".jpg")+4));
|
||||||
|
} else {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// To find the paused status, we check if the title contains `-`
|
// To find the paused status, we check if the title contains `-`
|
||||||
@ -30,7 +33,7 @@ const getPausedStatus = async (win) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getArtist = async (win) => {
|
const getArtist = async (win) => {
|
||||||
return await win.webContents.executeJavaScript(`
|
return win.webContents.executeJavaScript(`
|
||||||
document.querySelector(".subtitle.ytmusic-player-bar .yt-formatted-string")
|
document.querySelector(".subtitle.ytmusic-player-bar .yt-formatted-string")
|
||||||
?.textContent
|
?.textContent
|
||||||
`);
|
`);
|
||||||
@ -112,4 +115,3 @@ module.exports = registerCallback;
|
|||||||
module.exports.setupSongInfo = registerProvider;
|
module.exports.setupSongInfo = registerProvider;
|
||||||
module.exports.getImage = getImage;
|
module.exports.getImage = getImage;
|
||||||
module.exports.cleanupArtistName = cleanupArtistName;
|
module.exports.cleanupArtistName = cleanupArtistName;
|
||||||
|
|
||||||
|
|||||||
22
providers/video-element.js
Normal file
22
providers/video-element.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
let videoElement = null;
|
||||||
|
|
||||||
|
module.exports.ontimeupdate = (cb) => {
|
||||||
|
const observer = new MutationObserver((mutations, observer) => {
|
||||||
|
if (!videoElement) {
|
||||||
|
videoElement = document.querySelector("video");
|
||||||
|
if (videoElement) {
|
||||||
|
observer.disconnect();
|
||||||
|
videoElement.ontimeupdate = () => cb(videoElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!videoElement) {
|
||||||
|
observer.observe(document, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
videoElement.ontimeupdate = () => cb(videoElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* @jest-environment ./tests/environment
|
||||||
|
*/
|
||||||
|
|
||||||
describe("YouTube Music App", () => {
|
describe("YouTube Music App", () => {
|
||||||
const app = global.__APP__;
|
const app = global.__APP__;
|
||||||
|
|
||||||
|
|||||||
7
tray.js
7
tray.js
@ -63,12 +63,7 @@ module.exports.setUpTray = (app, win) => {
|
|||||||
app.quit();
|
app.quit();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{ role: "quit" }
|
||||||
label: "Quit",
|
|
||||||
click: () => {
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const trayMenu = Menu.buildFromTemplate(template);
|
const trayMenu = Menu.buildFromTemplate(template);
|
||||||
|
|||||||
Reference in New Issue
Block a user