Merge branch 'master' into master

This commit is contained in:
th-ch
2021-10-24 13:34:15 +02:00
committed by GitHub
23 changed files with 481 additions and 173 deletions

View File

@ -30,6 +30,7 @@ const defaultConfig = {
// Disabled plugins // Disabled plugins
shortcuts: { shortcuts: {
enabled: false, enabled: false,
overrideMediaKeys: false,
}, },
downloader: { downloader: {
enabled: false, enabled: false,
@ -59,9 +60,8 @@ const defaultConfig = {
steps: 1, //percentage of volume to change steps: 1, //percentage of volume to change
arrowsShortcut: true, //enable ArrowUp + ArrowDown local shortcuts arrowsShortcut: true, //enable ArrowUp + ArrowDown local shortcuts
globalShortcuts: { globalShortcuts: {
enabled: false, // enable global shortcuts volumeUp: "",
volumeUp: "Shift+PageUp", // Keybind default can be changed volumeDown: ""
volumeDown: "Shift+PageDown"
}, },
savedVolume: undefined //plugin save volume between session here savedVolume: undefined //plugin save volume between session here
}, },

View File

@ -8,6 +8,27 @@ const migrations = {
store.set("plugins.discord.listenAlong", true); store.set("plugins.discord.listenAlong", true);
} }
}, },
">=1.12.0": (store) => {
const options = store.get("plugins.shortcuts")
let updated = false;
for (const optionType of ["global", "local"]) {
if (Array.isArray(options[optionType])) {
const updatedOptions = {};
for (const optionObject of options[optionType]) {
if (optionObject.action && optionObject.shortcut) {
updatedOptions[optionObject.action] = optionObject.shortcut;
}
}
options[optionType] = updatedOptions;
updated = true;
}
}
if (updated) {
store.set("plugins.shortcuts", options);
}
},
">=1.11.0": (store) => { ">=1.11.0": (store) => {
if (store.get("options.resumeOnStart") === undefined) { if (store.get("options.resumeOnStart") === undefined) {
store.set("options.resumeOnStart", true); store.set("options.resumeOnStart", true);

View File

@ -39,7 +39,9 @@ if (config.get("options.proxy")) {
} }
// Adds debug features like hotkeys for triggering dev tools and reload // Adds debug features like hotkeys for triggering dev tools and reload
require("electron-debug")(); require("electron-debug")({
showDevTools: false //disable automatic devTools on new window
});
// Prevent window being garbage collected // Prevent window being garbage collected
let mainWindow; let mainWindow;
@ -60,7 +62,7 @@ function onClosed() {
function loadPlugins(win) { function loadPlugins(win) {
injectCSS(win.webContents, path.join(__dirname, "youtube-music.css")); injectCSS(win.webContents, path.join(__dirname, "youtube-music.css"));
win.webContents.on("did-finish-load", () => { win.webContents.once("did-finish-load", () => {
if (is.dev()) { if (is.dev()) {
console.log("did finish load"); console.log("did finish load");
win.webContents.openDevTools(); win.webContents.openDevTools();

41
menu.js
View File

@ -7,6 +7,9 @@ const is = require("electron-is");
const { getAllPlugins } = require("./plugins/utils"); const { getAllPlugins } = require("./plugins/utils");
const config = require("./config"); const config = require("./config");
const prompt = require("custom-electron-prompt");
const promptOptions = require("./providers/prompt-options");
// true only if in-app-menu was loaded on launch // true only if in-app-menu was loaded on launch
const inAppMenuActive = config.plugins.isEnabled("in-app-menu"); const inAppMenuActive = config.plugins.isEnabled("in-app-menu");
@ -76,6 +79,14 @@ const mainMenuTemplate = (win) => {
config.set("options.resumeOnStart", item.checked); config.set("options.resumeOnStart", item.checked);
}, },
}, },
{
label: "Remove upgrade button",
type: "checkbox",
checked: config.get("options.removeUpgradeButton"),
click: (item) => {
config.set("options.removeUpgradeButton", item.checked);
},
},
...(is.windows() || is.linux() ...(is.windows() || is.linux()
? [ ? [
{ {
@ -149,6 +160,14 @@ const mainMenuTemplate = (win) => {
{ {
label: "Advanced options", label: "Advanced options",
submenu: [ submenu: [
{
label: "Proxy",
type: "checkbox",
checked: !!config.get("options.proxy"),
click: (item) => {
setProxy(item, win);
},
},
{ {
label: "Disable hardware acceleration", label: "Disable hardware acceleration",
type: "checkbox", type: "checkbox",
@ -275,3 +294,25 @@ module.exports.setApplicationMenu = (win) => {
const menu = Menu.buildFromTemplate(menuTemplate); const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
}; };
async function setProxy(item, win) {
const output = await prompt({
title: 'Set Proxy',
label: 'Enter Proxy Address: (leave empty to disable)',
value: config.get("options.proxy"),
type: 'input',
inputAttrs: {
type: 'url',
placeholder: "Example: 'socks5://127.0.0.1:9999"
},
width: 450,
...promptOptions()
}, win);
if (typeof output === "string") {
config.set("options.proxy", output);
item.checked = output !== "";
} else { //user pressed cancel
item.checked = !item.checked; //reset checkbox
}
}

View File

@ -52,9 +52,8 @@
"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", "lint": "xo",
"plugins": "yarn run plugin:adblocker && yarn run plugin:autoconfirm", "plugins": "yarn run plugin:adblocker",
"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",
"release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github", "release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "yarn run clean && electron-builder --mac -p always", "release:mac": "yarn run clean && electron-builder --mac -p always",
"release:win": "yarn run clean && electron-builder --win -p always" "release:win": "yarn run clean && electron-builder --win -p always"
@ -67,9 +66,9 @@
"@cliqz/adblocker-electron": "^1.22.6", "@cliqz/adblocker-electron": "^1.22.6",
"@ffmpeg/core": "^0.10.0", "@ffmpeg/core": "^0.10.0",
"@ffmpeg/ffmpeg": "^0.10.0", "@ffmpeg/ffmpeg": "^0.10.0",
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0",
"async-mutex": "^0.3.2", "async-mutex": "^0.3.2",
"browser-id3-writer": "^4.4.0", "browser-id3-writer": "^4.4.0",
"custom-electron-prompt": "^1.1.0",
"chokidar": "^3.5.2", "chokidar": "^3.5.2",
"custom-electron-titlebar": "^3.2.7", "custom-electron-titlebar": "^3.2.7",
"discord-rpc": "^3.2.0", "discord-rpc": "^3.2.0",

View File

@ -0,0 +1,25 @@
const applyCompressor = () => {
const videoElement = document.querySelector("video");
// If video element is not loaded yet try again
if(videoElement === null) {
setTimeout(applyCompressor, 500);
return;
}
const audioContext = new AudioContext();
let compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.ratio.value = 12;
compressor.knee.value = 40;
compressor.attack.value = 0;
compressor.release.value = 0.25;
const source = audioContext.createMediaElementSource(videoElement);
source.connect(compressor);
compressor.connect(audioContext.destination);
};
module.exports = applyCompressor;

View File

@ -1,12 +0,0 @@
// Define global chrome object to be compliant with the extension code
global.chrome = {
runtime: {
getManifest: () => ({
version: 1
})
}
};
module.exports = () => {
require("YoutubeNonStop/autoconfirm.js");
};

View File

@ -145,7 +145,4 @@ module.exports.clear = () => {
}; };
module.exports.connect = connect; module.exports.connect = connect;
module.exports.registerRefresh = (cb) => refreshCallbacks.push(cb); module.exports.registerRefresh = (cb) => refreshCallbacks.push(cb);
/** module.exports.isConnected = () => info.rpc !== null;
* @type {Info}
*/
module.exports.info = Object.defineProperties({}, Object.keys(info).reduce((o, k) => ({ ...o, [k]: { enumerable: true, get: () => info[k] } }), {}));

View File

@ -1,6 +1,6 @@
const { setOptions } = require("../../config/plugins"); const { setOptions } = require("../../config/plugins");
const { edit } = require("../../config"); const { edit } = require("../../config");
const { clear, info, connect, registerRefresh } = require("./back"); const { clear, connect, registerRefresh, isConnected } = require("./back");
let hasRegisterred = false; let hasRegisterred = false;
@ -12,8 +12,8 @@ module.exports = (win, options, refreshMenu) => {
return [ return [
{ {
label: info.rpc !== null ? "Connected" : "Reconnect", label: isConnected() ? "Connected" : "Reconnect",
enabled: info.rpc === null, enabled: !isConnected(),
click: connect, click: connect,
}, },
{ {

View File

@ -1,23 +1,9 @@
const { isEnabled } = require("../../config/plugins");
/* /*
This is used to determine if plugin is actually active This is used to determine if plugin is actually active
(not if its only enabled in options) (not if its only enabled in options)
*/ */
let enabled = false; let enabled = false;
module.exports = (win) => { module.exports = () => enabled = true;
enabled = true;
// youtube-music register some of the target listeners after DOMContentLoaded module.exports.enabled = () => enabled;
// did-finish-load is called after all elements finished loading, including said listeners
// Thats the reason the timing is controlled from main
win.webContents.once("did-finish-load", () => {
win.webContents.send("restoreAddEventListener");
win.webContents.send("setupVideoPlayerVolumeMousewheel", !isEnabled("hide-video-player"));
});
};
module.exports.enabled = () => {
return enabled;
};

View File

@ -3,27 +3,73 @@ const { ipcRenderer, remote } = require("electron");
const { setOptions } = require("../../config/plugins"); const { setOptions } = require("../../config/plugins");
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }
let api;
module.exports = (options) => { module.exports = (options) => {
document.addEventListener('apiLoaded', e => {
api = e.detail;
firstRun(options);
})
};
/** Restore saved volume and setup tooltip */
function firstRun(options) {
if (typeof options.savedVolume === "number") {
// Set saved volume as tooltip
setTooltip(options.savedVolume);
if (api.getVolume() !== options.savedVolume) {
api.setVolume(options.savedVolume);
}
}
setupPlaybar(options); setupPlaybar(options);
setupSliderObserver(options);
setupLocalArrowShortcuts(options); setupLocalArrowShortcuts(options);
if (options.globalShortcuts?.enabled) { setupGlobalShortcuts(options);
setupGlobalShortcuts(options);
const noVid = $("#main-panel")?.computedStyleMap().get("display").value === "none";
injectVolumeHud(noVid);
if (!noVid) {
setupVideoPlayerOnwheel(options);
}
}
function injectVolumeHud(noVid) {
if (noVid) {
const position = "top: 18px; right: 60px; z-index: 999; position: absolute;";
const mainStyle = "font-size: xx-large; padding: 10px; transition: opacity 1s";
$(".center-content.ytmusic-nav-bar").insertAdjacentHTML("beforeend",
`<span id="volumeHud" style="${position + mainStyle}"></span>`)
} else {
const position = `top: 10px; left: 10px; z-index: 999; position: absolute;`;
const mainStyle = "font-size: xxx-large; padding: 10px; transition: opacity 0.6s; webkit-text-stroke: 1px black; font-weight: 600;";
$("#song-video").insertAdjacentHTML('afterend',
`<span id="volumeHud" style="${position + mainStyle}"></span>`)
}
}
let hudFadeTimeout;
function showVolumeHud(volume) {
let volumeHud = $("#volumeHud");
if (!volumeHud) return;
volumeHud.textContent = volume + '%';
volumeHud.style.opacity = 1;
if (hudFadeTimeout) {
clearTimeout(hudFadeTimeout);
} }
firstRun(options); hudFadeTimeout = setTimeout(() => {
volumeHud.style.opacity = 0;
// This way the ipc listener gets cleared either way hudFadeTimeout = null;
ipcRenderer.once("setupVideoPlayerVolumeMousewheel", (_event, toEnable) => { }, 2000);
if (toEnable) }
setupVideoPlayerOnwheel(options);
});
};
/** Add onwheel event to video player */ /** Add onwheel event to video player */
function setupVideoPlayerOnwheel(options) { function setupVideoPlayerOnwheel(options) {
@ -34,35 +80,20 @@ function setupVideoPlayerOnwheel(options) {
}); });
} }
function toPercent(volume) {
return Math.round(Number.parseFloat(volume) * 100);
}
function saveVolume(volume, options) { function saveVolume(volume, options) {
options.savedVolume = volume; options.savedVolume = volume;
setOptions("precise-volume", options); writeOptions(options);
} }
/** Restore saved volume and setup tooltip */ //without this function it would rewrite config 20 time when volume change by 20
function firstRun(options) { let writeTimeout;
const videoStream = $(".video-stream"); function writeOptions(options) {
const slider = $("#volume-slider"); if (writeTimeout) clearTimeout(writeTimeout);
// Those elements load abit after DOMContentLoaded
if (videoStream && slider) { writeTimeout = setTimeout(() => {
// Set saved volume IF it pass checks setOptions("precise-volume", options);
if (options.savedVolume writeTimeout = null;
&& options.savedVolume >= 0 && options.savedVolume <= 100 }, 1500)
&& Math.abs(slider.value - options.savedVolume) < 5
// If plugin was disabled and volume changed then diff>4
) {
videoStream.volume = options.savedVolume / 100;
slider.value = options.savedVolume;
}
// Set current volume as tooltip
setTooltip(toPercent(videoStream.volume));
} else {
setTimeout(firstRun, 500, options); // Try again in 500 milliseconds
}
} }
/** Add onwheel event to play bar and also track if play bar is hovered*/ /** Add onwheel event to play bar and also track if play bar is hovered*/
@ -83,32 +114,63 @@ function setupPlaybar(options) {
playerbar.addEventListener("mouseleave", () => { playerbar.addEventListener("mouseleave", () => {
playerbar.classList.remove("on-hover"); playerbar.classList.remove("on-hover");
}); });
setupSliderObserver(options);
}
/** Save volume + Update the volume tooltip when volume-slider is manually changed */
function setupSliderObserver(options) {
const sliderObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
// This checks that volume-slider was manually set
if (mutation.oldValue !== mutation.target.value &&
(typeof options.savedVolume !== "number" || Math.abs(options.savedVolume - mutation.target.value) > 4)) {
// Diff>4 means it was manually set
setTooltip(mutation.target.value);
saveVolume(mutation.target.value, options);
}
}
});
// Observing only changes in 'value' of volume-slider
sliderObserver.observe($("#volume-slider"), {
attributeFilter: ["value"],
attributeOldValue: true
});
} }
/** if (toIncrease = false) then volume decrease */ /** if (toIncrease = false) then volume decrease */
function changeVolume(toIncrease, options) { function changeVolume(toIncrease, options) {
// Need to change both the actual volume and the slider
const videoStream = $(".video-stream");
const slider = $("#volume-slider");
// Apply volume change if valid // Apply volume change if valid
const steps = (options.steps || 1) / 100; const steps = (options.steps || 1);
videoStream.volume = toIncrease ? api.setVolume(toIncrease ?
Math.min(videoStream.volume + steps, 1) : Math.min(api.getVolume() + steps, 100) :
Math.max(videoStream.volume - steps, 0); Math.max(api.getVolume() - steps, 0));
// Save the new volume // Save the new volume
saveVolume(toPercent(videoStream.volume), options); saveVolume(api.getVolume(), options);
// Slider value automatically rounds to multiples of 5
slider.value = options.savedVolume; // change slider position (important)
updateVolumeSlider(options);
// Change tooltips to new value // Change tooltips to new value
setTooltip(options.savedVolume); setTooltip(options.savedVolume);
// Show volume slider on volume change // Show volume slider
showVolumeSlider(slider); showVolumeSlider();
// Show volume HUD
showVolumeHud(options.savedVolume);
}
function updateVolumeSlider(options) {
// Slider value automatically rounds to multiples of 5
$("#volume-slider").value = options.savedVolume > 0 && options.savedVolume < 5 ?
5 : options.savedVolume;
} }
let volumeHoverTimeoutID; let volumeHoverTimeoutID;
function showVolumeSlider(slider) { function showVolumeSlider() {
const slider = $("#volume-slider");
// This class display the volume slider if not in minimized mode // This class display the volume slider if not in minimized mode
slider.classList.add("on-hover"); slider.classList.add("on-hover");
// Reset timeout if previous one hasn't completed // Reset timeout if previous one hasn't completed
@ -124,27 +186,6 @@ function showVolumeSlider(slider) {
}, 3000); }, 3000);
} }
/** Save volume + Update the volume tooltip when volume-slider is manually changed */
function setupSliderObserver(options) {
const sliderObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
// This checks that volume-slider was manually set
if (mutation.oldValue !== mutation.target.value &&
(!options.savedVolume || Math.abs(options.savedVolume - mutation.target.value) > 4)) {
// Diff>4 means it was manually set
setTooltip(mutation.target.value);
saveVolume(mutation.target.value, options);
}
}
});
// Observing only changes in 'value' of volume-slider
sliderObserver.observe($("#volume-slider"), {
attributeFilter: ["value"],
attributeOldValue: true
});
}
// Set new volume as tooltip for volume slider and icon + expanding slider (appears when window size is small) // Set new volume as tooltip for volume slider and icon + expanding slider (appears when window size is small)
const tooltipTargets = [ const tooltipTargets = [
"#volume-slider", "#volume-slider",

View File

@ -1,13 +1,16 @@
const { enabled } = require("./back"); const { enabled } = require("./back");
const { setOptions } = require("../../config/plugins"); const { setOptions } = require("../../config/plugins");
const prompt = require("custom-electron-prompt");
const promptOptions = require("../../providers/prompt-options");
module.exports = (win, options) => [ module.exports = (win, options) => [
{ {
label: "Arrowkeys controls", label: "Local Arrowkeys Controls",
type: "checkbox", type: "checkbox",
checked: !!options.arrowsShortcut, checked: !!options.arrowsShortcut,
click: (item) => { click: item => {
// Dynamically change setting if plugin enabled // Dynamically change setting if plugin is enabled
if (enabled()) { if (enabled()) {
win.webContents.send("setArrowsShortcut", item.checked); win.webContents.send("setArrowsShortcut", item.checked);
} else { // Fallback to usual method if disabled } else { // Fallback to usual method if disabled
@ -15,5 +18,61 @@ module.exports = (win, options) => [
setOptions("precise-volume", options); setOptions("precise-volume", options);
} }
} }
},
{
label: "Global Hotkeys",
type: "checkbox",
checked: !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown,
click: item => promptGlobalShortcuts(win, options, item)
},
{
label: "Set Custom Volume Steps",
click: () => promptVolumeSteps(win, options)
} }
]; ];
// Helper function for globalShortcuts prompt
const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ || undefined }; };
async function promptVolumeSteps(win, options) {
const output = await prompt({
title: "Volume Steps",
label: "Choose Volume Increase/Decrease Steps",
value: options.steps || 1,
type: "counter",
counterOptions: { minimum: 0, maximum: 100, multiFire: true },
width: 380,
...promptOptions()
}, win)
if (output || output === 0) { // 0 is somewhat valid
options.steps = output;
setOptions("precise-volume", options);
}
}
async function promptGlobalShortcuts(win, options, item) {
const output = await prompt({
title: "Global Volume Keybinds",
label: "Choose Global Volume Keybinds:",
type: "keybind",
keybindOptions: [
kb("Increase Volume", "volumeUp", options.globalShortcuts?.volumeUp),
kb("Decrease Volume", "volumeDown", options.globalShortcuts?.volumeDown)
],
...promptOptions()
}, win)
if (output) {
for (const { value, accelerator } of output) {
options.globalShortcuts[value] = accelerator;
}
setOptions("precise-volume", options);
item.checked = !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown;
} else {
// Reset checkbox if prompt was canceled
item.checked = !item.checked;
}
}

View File

@ -24,10 +24,10 @@ function overrideAddEventListener() {
module.exports = () => { module.exports = () => {
overrideAddEventListener(); overrideAddEventListener();
// Restore original function after did-finish-load to avoid keeping Element.prototype altered // Restore original function after finished loading to avoid keeping Element.prototype altered
ipcRenderer.once("restoreAddEventListener", () => { // Called from main to make sure page is completly loaded window.addEventListener('load', () => {
Element.prototype.addEventListener = Element.prototype._addEventListener; Element.prototype.addEventListener = Element.prototype._addEventListener;
Element.prototype._addEventListener = undefined; Element.prototype._addEventListener = undefined;
ignored = undefined; ignored = undefined;
}); }, { once: true });
}; };

View File

@ -1,9 +1,11 @@
const { globalShortcut } = require("electron"); const { globalShortcut } = require("electron");
const is = require("electron-is"); const is = require("electron-is");
const electronLocalshortcut = require("electron-localshortcut"); const electronLocalshortcut = require("electron-localshortcut");
const getSongControls = require("../../providers/song-controls"); const getSongControls = require("../../providers/song-controls");
const { setupMPRIS } = require("./mpris"); const { setupMPRIS } = require("./mpris");
const registerCallback = require("../../providers/song-info");
let player;
function _registerGlobalShortcut(webContents, shortcut, action) { function _registerGlobalShortcut(webContents, shortcut, action) {
globalShortcut.register(shortcut, () => { globalShortcut.register(shortcut, () => {
@ -21,47 +23,89 @@ function registerShortcuts(win, options) {
const songControls = getSongControls(win); const songControls = getSongControls(win);
const { playPause, next, previous, search } = songControls; const { playPause, next, previous, search } = songControls;
_registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause); if (options.overrideMediaKeys) {
_registerGlobalShortcut(win.webContents, "MediaNextTrack", next); _registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause);
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous); _registerGlobalShortcut(win.webContents, "MediaNextTrack", next);
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous);
}
_registerLocalShortcut(win, "CommandOrControl+F", search); _registerLocalShortcut(win, "CommandOrControl+F", search);
_registerLocalShortcut(win, "CommandOrControl+L", search); _registerLocalShortcut(win, "CommandOrControl+L", search);
registerCallback(songInfo => {
if (player) {
player.metadata = {
'mpris:length': songInfo.songDuration * 60 * 1000 * 1000, // In microseconds
'mpris:artUrl': songInfo.imageSrc,
'xesam:title': songInfo.title,
'xesam:artist': songInfo.artist
};
if (!songInfo.isPaused) {
player.playbackStatus = "Playing"
}
}
}
)
if (is.linux()) { if (is.linux()) {
try { try {
const player = setupMPRIS(); const MPRISPlayer = setupMPRIS();
player.on("raise", () => { MPRISPlayer.on("raise", () => {
win.setSkipTaskbar(false); win.setSkipTaskbar(false);
win.show(); win.show();
}); });
player.on("playpause", playPause); MPRISPlayer.on("play", () => {
player.on("next", next); if (MPRISPlayer.playbackStatus !== 'Playing') {
player.on("previous", previous); MPRISPlayer.playbackStatus = 'Playing';
playPause()
}
});
MPRISPlayer.on("pause", () => {
if (MPRISPlayer.playbackStatus !== 'Paused') {
MPRISPlayer.playbackStatus = 'Paused';
playPause()
}
});
MPRISPlayer.on("next", () => {
next()
});
MPRISPlayer.on("previous", () => {
previous()
});
player = MPRISPlayer
} catch (e) { } catch (e) {
console.warn("Error in MPRIS", e); console.warn("Error in MPRIS", e);
} }
} }
const { global, local } = options; const { global, local } = options;
(global || []).forEach(({ shortcut, action }) => { const shortcutOptions = { global, local };
console.debug("Registering global shortcut", shortcut, ":", action);
if (!action || !songControls[action]) {
console.warn("Invalid action", action);
return;
}
_registerGlobalShortcut(win.webContents, shortcut, songControls[action]); for (const optionType in shortcutOptions) {
}); registerAllShortcuts(shortcutOptions[optionType], optionType);
(local || []).forEach(({ shortcut, action }) => { }
console.debug("Registering local shortcut", shortcut, ":", action);
if (!action || !songControls[action]) {
console.warn("Invalid action", action);
return;
}
_registerLocalShortcut(win, shortcut, songControls[action]); function registerAllShortcuts(container, type) {
}); for (const action in container) {
if (!container[action]) {
continue; // Action accelerator is empty
}
console.debug(`Registering ${type} shortcut`, container[action], ":", action);
if (!songControls[action]) {
console.warn("Invalid action", action);
continue;
}
if (type === "global") {
_registerGlobalShortcut(win.webContents, container[action], songControls[action]);
} else { // type === "local"
_registerLocalShortcut(win, local[action], songControls[action]);
}
}
}
} }
module.exports = registerShortcuts; module.exports = registerShortcuts;

53
plugins/shortcuts/menu.js Normal file
View File

@ -0,0 +1,53 @@
const { setOptions } = require("../../config/plugins");
const prompt = require("custom-electron-prompt");
const promptOptions = require("../../providers/prompt-options");
module.exports = (win, options) => [
{
label: "Set Global Song Controls",
click: () => promptKeybind(options, win)
},
{
label: "Override MediaKeys",
type: "checkbox",
checked: options.overrideMediaKeys,
click: item => setOption(options, "overrideMediaKeys", item.checked)
}
];
function setOption(options, key = null, newValue = null) {
if (key && newValue !== null) {
options[key] = newValue;
}
setOptions("shortcuts", options);
}
// Helper function for keybind prompt
const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ }; };
async function promptKeybind(options, win) {
const output = await prompt({
title: "Global Keybinds",
label: "Choose Global Keybinds for Songs Control:",
type: "keybind",
keybindOptions: [ // If default=undefined then no default is used
kb("Previous", "previous", options.global?.previous),
kb("Play / Pause", "playPause", options.global?.playPause),
kb("Next", "next", options.global?.next)
],
height: 270,
...promptOptions()
}, win);
if (output) {
if (!options.global) {
options.global = {};
}
for (const { value, accelerator } of output) {
options.global[value] = accelerator;
}
setOption(options);
}
// else -> pressed cancel
}

View File

@ -5,11 +5,12 @@ const { remote } = require("electron");
const config = require("./config"); const config = require("./config");
const { fileExists } = require("./plugins/utils"); const { fileExists } = require("./plugins/utils");
const setupFrontLogger = require("./providers/front-logger"); const setupFrontLogger = require("./providers/front-logger");
const setupSongControl = require("./providers/song-controls-front");
const setupSongInfo = require("./providers/song-info-front"); const setupSongInfo = require("./providers/song-info-front");
const plugins = config.plugins.getEnabled(); const plugins = config.plugins.getEnabled();
let api;
plugins.forEach(([plugin, options]) => { plugins.forEach(([plugin, options]) => {
const preloadPath = path.join(__dirname, "plugins", plugin, "preload.js"); const preloadPath = path.join(__dirname, "plugins", plugin, "preload.js");
fileExists(preloadPath, () => { fileExists(preloadPath, () => {
@ -38,16 +39,46 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
}); });
// wait for complete load of youtube api
listenForApiLoad();
// inject song-info provider // inject song-info provider
setupSongInfo(); setupSongInfo();
// inject song-control provider
setupSongControl();
// inject front logger // inject front logger
setupFrontLogger(); setupFrontLogger();
// Add action for reloading // Add action for reloading
global.reload = () => global.reload = () =>
remote.getCurrentWindow().webContents.loadURL(config.get("url")); remote.getCurrentWindow().webContents.loadURL(config.get("url"));
// Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min
setInterval(() => window._lact = Date.now(), 900000);
}); });
function listenForApiLoad() {
api = document.querySelector('#movie_player');
if (api) {
onApiLoaded();
return;
}
const observer = new MutationObserver(() => {
api = document.querySelector('#movie_player');
if (api) {
observer.disconnect();
onApiLoaded();
}
})
observer.observe(document.documentElement, { childList: true, subtree: true });
}
function onApiLoaded() {
document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api }));
// Remove upgrade button
if (config.get("options.removeUpgradeButton")) {
document.querySelector('ytmusic-pivot-bar-item-renderer[tab-id="SPunlimited"]')?.style.display = "none";
}
}

View File

@ -0,0 +1,14 @@
const customTitlebar = require("custom-electron-titlebar");
module.exports = () => {
new customTitlebar.Titlebar({
backgroundColor: customTitlebar.Color.fromHex("#050505"),
minimizable: false,
maximizable: false,
menu: null
});
const mainStyle = document.querySelector("#container").style;
mainStyle.width = "100%";
mainStyle.position = "fixed";
mainStyle.border = "unset";
};

View File

@ -0,0 +1,18 @@
const path = require("path");
const is = require("electron-is");
const iconPath = path.join(__dirname, "..", "assets", "youtube-music-tray.png");
const customTitlebarPath = path.join(__dirname, "prompt-custom-titlebar.js");
const promptOptions = is.macOS() ? {
customStylesheet: "dark",
icon: iconPath
} : {
customStylesheet: "dark",
// The following are used for custom titlebar
frame: false,
customScript: customTitlebarPath,
enableRemoteModule: true
};
module.exports = () => promptOptions;

View File

@ -1,18 +0,0 @@
const { ipcRenderer } = require("electron");
let videoStream = document.querySelector(".video-stream");
module.exports = () => {
ipcRenderer.on("playPause", () => {
if (!videoStream) {
videoStream = document.querySelector(".video-stream");
}
if (videoStream.paused) {
videoStream.play();
} else {
videoStream.yns_pause ?
videoStream.yns_pause() :
videoStream.pause();
}
});
};

View File

@ -12,7 +12,7 @@ module.exports = (win) => {
// Playback // Playback
previous: () => pressKey(win, "k"), previous: () => pressKey(win, "k"),
next: () => pressKey(win, "j"), next: () => pressKey(win, "j"),
playPause: () => win.webContents.send("playPause"), playPause: () => pressKey(win, "space"),
like: () => pressKey(win, "_"), like: () => pressKey(win, "_"),
dislike: () => pressKey(win, "+"), dislike: () => pressKey(win, "+"),
go10sBack: () => pressKey(win, "h"), go10sBack: () => pressKey(win, "h"),

View File

@ -35,7 +35,6 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
## Available plugins: ## Available plugins:
- **Ad Blocker**: block all ads and tracking out of the box - **Ad Blocker**: block all ads and tracking out of the box
- **Auto confirm when paused**: when the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) modal appears, automatically click "Yes"
- **Blur navigation bar**: makes navigation bar transparent and blurry - **Blur navigation bar**: makes navigation bar transparent and blurry
- **Disable autoplay**: makes every song start in "paused" mode - **Disable autoplay**: makes every song start in "paused" mode
- [**Discord**](https://discord.com/): show your friends what you listen to with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png) - [**Discord**](https://discord.com/): show your friends what you listen to with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
@ -52,6 +51,7 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): skips non-music parts - [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): skips non-music parts
- **Taskbar media control**: control app from your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png) - **Taskbar media control**: control app from your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **Touchbar**: custom TouchBar layout for macOS - **Touchbar**: custom TouchBar layout for macOS
- **Auto confirm when paused** (Always Enabled): disable the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) popup that pause music after a certain time
## Dev ## Dev

View File

@ -1518,10 +1518,6 @@
dependencies: dependencies:
"@wdio/logger" "6.10.10" "@wdio/logger" "6.10.10"
"YoutubeNonStop@git://github.com/lawfx/YoutubeNonStop.git#v0.9.0":
version "0.0.0"
resolved "git://github.com/lawfx/YoutubeNonStop.git#7b6b97b31bb3fd2078179660db0fd3fcc7062259"
abab@^2.0.3: abab@^2.0.3:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
@ -2960,6 +2956,11 @@ cssstyle@^2.2.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
custom-electron-prompt@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/custom-electron-prompt/-/custom-electron-prompt-1.1.0.tgz#611b790047c91f6b532c7861355a0e1f9a81aef2"
integrity sha512-YZYmwZnMOdoWROUlJ+rEMHYsp4XJNNqLj6sZnx5aKBJ8cprEjKP4L5wfo6U+yyX/L9fxVOtvYD0Mp8ki5I9Kow==
custom-electron-titlebar@^3.2.7: custom-electron-titlebar@^3.2.7:
version "3.2.7" version "3.2.7"
resolved "https://registry.yarnpkg.com/custom-electron-titlebar/-/custom-electron-titlebar-3.2.7.tgz#fb249d6180cbda074b1d392bea755fa0743012a8" resolved "https://registry.yarnpkg.com/custom-electron-titlebar/-/custom-electron-titlebar-3.2.7.tgz#fb249d6180cbda074b1d392bea755fa0743012a8"

View File

@ -28,3 +28,9 @@ ytmusic-search-box.ytmusic-nav-bar {
ytmusic-mealbar-promo-renderer { ytmusic-mealbar-promo-renderer {
display: none !important; display: none !important;
} }
/* Disable Image Selection */
img {
-webkit-user-select: none;
user-select: none;
}