mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
Merge branch 'master' into master
This commit is contained in:
@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
6
index.js
6
index.js
@ -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
41
menu.js
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
25
plugins/audio-compressor/front.js
Normal file
25
plugins/audio-compressor/front.js
Normal 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;
|
||||||
@ -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");
|
|
||||||
};
|
|
||||||
@ -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] } }), {}));
|
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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;
|
|
||||||
};
|
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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 });
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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
53
plugins/shortcuts/menu.js
Normal 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
|
||||||
|
}
|
||||||
39
preload.js
39
preload.js
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
providers/prompt-custom-titlebar.js
Normal file
14
providers/prompt-custom-titlebar.js
Normal 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";
|
||||||
|
};
|
||||||
18
providers/prompt-options.js
Normal file
18
providers/prompt-options.js
Normal 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;
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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"),
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user