mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
feat(ambient-mode): add config for ambient-mode plugin (#1349)
This commit is contained in:
@ -81,7 +81,16 @@ const defaultConfig = {
|
||||
disableDefaultLists: false,
|
||||
},
|
||||
'album-color-theme': {},
|
||||
'ambient-mode': {},
|
||||
'ambient-mode': {
|
||||
enabled: false,
|
||||
quality: 50,
|
||||
buffer: 30,
|
||||
interpolationTime: 1500,
|
||||
blur: 100,
|
||||
size: 100,
|
||||
opacity: 1,
|
||||
fullscreen: false,
|
||||
},
|
||||
'audio-compressor': {},
|
||||
'blur-nav-bar': {},
|
||||
'bypass-age-restrictions': {},
|
||||
|
||||
@ -8,6 +8,7 @@ import { startingPages } from './providers/extracted-data';
|
||||
import promptOptions from './providers/prompt-options';
|
||||
|
||||
import adblockerMenu from './plugins/adblocker/menu';
|
||||
import ambientModeMenu from './plugins/ambient-mode/menu';
|
||||
import captionsSelectorMenu from './plugins/captions-selector/menu';
|
||||
import crossfadeMenu from './plugins/crossfade/menu';
|
||||
import disableAutoplayMenu from './plugins/disable-autoplay/menu';
|
||||
@ -32,6 +33,7 @@ const betaPlugins = ['crossfade', 'lumiastream'];
|
||||
|
||||
const pluginMenus = {
|
||||
'adblocker': adblockerMenu,
|
||||
'ambient-mode': ambientModeMenu,
|
||||
'disable-autoplay': disableAutoplayMenu,
|
||||
'captions-selector': captionsSelectorMenu,
|
||||
'crossfade': crossfadeMenu,
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
import config from './config';
|
||||
import style from './style.css';
|
||||
|
||||
import { injectCSS } from '../utils';
|
||||
|
||||
export default (win: BrowserWindow) => {
|
||||
config.subscribeAll((newConfig) => {
|
||||
win.webContents.send('ambient-mode:config-change', newConfig);
|
||||
});
|
||||
|
||||
export default (win: BrowserWindow) => {
|
||||
injectCSS(win.webContents, style);
|
||||
};
|
||||
|
||||
4
src/plugins/ambient-mode/config.ts
Normal file
4
src/plugins/ambient-mode/config.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { PluginConfig } from '../../config/dynamic';
|
||||
|
||||
const config = new PluginConfig('ambient-mode');
|
||||
export default config;
|
||||
@ -1,9 +1,15 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import { ConfigType } from '../../config/dynamic';
|
||||
|
||||
export default (_: ConfigType<'ambient-mode'>) => {
|
||||
const interpolationTime = 3000; // interpolation time (ms)
|
||||
const framerate = 30; // frame
|
||||
const qualityRatio = 50; // width size (pixel)
|
||||
export default (config: ConfigType<'ambient-mode'>) => {
|
||||
let interpolationTime = config.interpolationTime; // interpolation time (ms)
|
||||
let buffer = config.buffer; // frame
|
||||
let qualityRatio = config.quality; // width size (pixel)
|
||||
let sizeRatio = config.size / 100; // size ratio (percent)
|
||||
let blur = config.blur; // blur (pixel)
|
||||
let opacity = config.opacity; // opacity (percent)
|
||||
let isFullscreen = config.fullscreen; // fullscreen (boolean)
|
||||
|
||||
let unregister: (() => void) | null = null;
|
||||
|
||||
@ -37,7 +43,7 @@ export default (_: ConfigType<'ambient-mode'>) => {
|
||||
|
||||
context.globalAlpha = 1;
|
||||
if (lastImageData) {
|
||||
const frameOffset = (1 / framerate) * (1000 / interpolationTime);
|
||||
const frameOffset = (1 / buffer) * (1000 / interpolationTime);
|
||||
context.globalAlpha = 1 - (frameOffset * 2); // because of alpha value must be < 1
|
||||
context.putImageData(lastImageData, 0, 0);
|
||||
context.globalAlpha = frameOffset;
|
||||
@ -61,8 +67,18 @@ export default (_: ConfigType<'ambient-mode'>) => {
|
||||
|
||||
blurCanvas.width = qualityRatio;
|
||||
blurCanvas.height = Math.floor(newHeight / newWidth * qualityRatio);
|
||||
blurCanvas.style.width = `${newWidth}px`;
|
||||
blurCanvas.style.height = `${newHeight}px`;
|
||||
blurCanvas.style.width = `${newWidth * sizeRatio}px`;
|
||||
blurCanvas.style.height = `${newHeight * sizeRatio}px`;
|
||||
|
||||
if (isFullscreen) blurCanvas.classList.add('fullscreen');
|
||||
else blurCanvas.classList.remove('fullscreen');
|
||||
|
||||
const leftOffset = newWidth * (sizeRatio - 1) / 2;
|
||||
const topOffset = newHeight * (sizeRatio - 1) / 2;
|
||||
blurCanvas.style.setProperty('--left', `${-1 * leftOffset}px`);
|
||||
blurCanvas.style.setProperty('--top', `${-1 * topOffset}px`);
|
||||
blurCanvas.style.setProperty('--blur', `${blur}px`);
|
||||
blurCanvas.style.setProperty('--opacity', `${opacity}`);
|
||||
};
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
@ -75,10 +91,22 @@ export default (_: ConfigType<'ambient-mode'>) => {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
applyVideoAttributes();
|
||||
});
|
||||
const onConfigSync = (_: Electron.IpcRendererEvent, newConfig: ConfigType<'ambient-mode'>) => {
|
||||
if (typeof newConfig.interpolationTime === 'number') interpolationTime = newConfig.interpolationTime;
|
||||
if (typeof newConfig.buffer === 'number') buffer = newConfig.buffer;
|
||||
if (typeof newConfig.quality === 'number') qualityRatio = newConfig.quality;
|
||||
if (typeof newConfig.size === 'number') sizeRatio = newConfig.size / 100;
|
||||
if (typeof newConfig.blur === 'number') blur = newConfig.blur;
|
||||
if (typeof newConfig.opacity === 'number') opacity = newConfig.opacity;
|
||||
if (typeof newConfig.fullscreen === 'boolean') isFullscreen = newConfig.fullscreen;
|
||||
|
||||
applyVideoAttributes();
|
||||
};
|
||||
ipcRenderer.on('ambient-mode:config-change', onConfigSync);
|
||||
|
||||
/* hooking */
|
||||
let canvasInterval: NodeJS.Timeout | null = null;
|
||||
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / framerate)));
|
||||
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / buffer)));
|
||||
applyVideoAttributes();
|
||||
observer.observe(songVideo, { attributes: true });
|
||||
resizeObserver.observe(songVideo);
|
||||
@ -90,7 +118,7 @@ export default (_: ConfigType<'ambient-mode'>) => {
|
||||
};
|
||||
const onPlay = () => {
|
||||
if (canvasInterval) clearInterval(canvasInterval);
|
||||
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / framerate)));
|
||||
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / buffer)));
|
||||
};
|
||||
songVideo.addEventListener('pause', onPause);
|
||||
songVideo.addEventListener('play', onPlay);
|
||||
@ -107,6 +135,7 @@ export default (_: ConfigType<'ambient-mode'>) => {
|
||||
|
||||
observer.disconnect();
|
||||
resizeObserver.disconnect();
|
||||
ipcRenderer.off('ambient-mode:config-change', onConfigSync);
|
||||
window.removeEventListener('resize', applyVideoAttributes);
|
||||
|
||||
wrapper.removeChild(blurCanvas);
|
||||
|
||||
87
src/plugins/ambient-mode/menu.ts
Normal file
87
src/plugins/ambient-mode/menu.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import config from './config';
|
||||
|
||||
import { MenuTemplate } from '../../menu';
|
||||
|
||||
const interpolationTimeList = [0, 500, 1000, 1500, 2000, 3000, 4000, 5000];
|
||||
const qualityList = [10, 25, 50, 100, 200, 500, 1000];
|
||||
const sizeList = [100, 110, 125, 150, 175, 200, 300];
|
||||
const bufferList = [1, 5, 10, 20, 30];
|
||||
const blurAmountList = [0, 5, 10, 25, 50, 100, 150, 200, 500];
|
||||
const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
|
||||
|
||||
export default (): MenuTemplate => [
|
||||
{
|
||||
label: 'Smoothness transition',
|
||||
submenu: interpolationTimeList.map((interpolationTime) => ({
|
||||
label: `During ${interpolationTime / 1000}s`,
|
||||
type: 'radio',
|
||||
checked: config.get('interpolationTime') === interpolationTime,
|
||||
click() {
|
||||
config.set('interpolationTime', interpolationTime);
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Quality',
|
||||
submenu: qualityList.map((quality) => ({
|
||||
label: `${quality} pixels`,
|
||||
type: 'radio',
|
||||
checked: config.get('quality') === quality,
|
||||
click() {
|
||||
config.set('quality', quality);
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Size',
|
||||
submenu: sizeList.map((size) => ({
|
||||
label: `${size}%`,
|
||||
type: 'radio',
|
||||
checked: config.get('size') === size,
|
||||
click() {
|
||||
config.set('size', size);
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Buffer',
|
||||
submenu: bufferList.map((buffer) => ({
|
||||
label: `${buffer}`,
|
||||
type: 'radio',
|
||||
checked: config.get('buffer') === buffer,
|
||||
click() {
|
||||
config.set('buffer', buffer);
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Opacity',
|
||||
submenu: opacityList.map((opacity) => ({
|
||||
label: `${opacity * 100}%`,
|
||||
type: 'radio',
|
||||
checked: config.get('opacity') === opacity,
|
||||
click() {
|
||||
config.set('opacity', opacity);
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Blur amount',
|
||||
submenu: blurAmountList.map((blur) => ({
|
||||
label: `${blur} pixels`,
|
||||
type: 'radio',
|
||||
checked: config.get('blur') === blur,
|
||||
click() {
|
||||
config.set('blur', blur);
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Using fullscreen',
|
||||
type: 'checkbox',
|
||||
checked: config.get('fullscreen'),
|
||||
click(item) {
|
||||
config.set('fullscreen', item.checked);
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -1,7 +1,26 @@
|
||||
#song-video canvas.html5-blur-canvas{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
#song-video canvas.html5-blur-canvas {
|
||||
filter: blur(var(--blur, 100px));
|
||||
opacity: var(--opacity, 1);
|
||||
|
||||
filter: blur(100px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#song-video canvas.html5-blur-canvas:not(.fullscreen) {
|
||||
position: absolute;
|
||||
|
||||
left: var(--left, 0px);
|
||||
top: var(--top, 0px);
|
||||
}
|
||||
|
||||
#song-video canvas.html5-blur-canvas.fullscreen {
|
||||
position: fixed;
|
||||
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
#song-video .html5-video-container > video {
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user