mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-12 02:51:46 +00:00
feat: migrate to new plugin api
Co-authored-by: Su-Yong <simssy2205@gmail.com>
This commit is contained in:
132
src/plugins/visualizer/index.ts
Normal file
132
src/plugins/visualizer/index.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import emptyStyle from './empty-player.css?inline';
|
||||
|
||||
import { createPluginBuilder } from '../utils/builder';
|
||||
|
||||
type WaveColor = {
|
||||
gradient: string[];
|
||||
rotate?: number;
|
||||
};
|
||||
|
||||
export type VisualizerPluginConfig = {
|
||||
enabled: boolean;
|
||||
type: 'butterchurn' | 'vudio' | 'wave';
|
||||
butterchurn: {
|
||||
preset: string;
|
||||
renderingFrequencyInMs: number;
|
||||
blendTimeInSeconds: number;
|
||||
},
|
||||
vudio: {
|
||||
effect: string;
|
||||
accuracy: number;
|
||||
lighting: {
|
||||
maxHeight: number;
|
||||
maxSize: number;
|
||||
lineWidth: number;
|
||||
color: string;
|
||||
shadowBlur: number;
|
||||
shadowColor: string;
|
||||
fadeSide: boolean;
|
||||
prettify: boolean;
|
||||
horizontalAlign: string;
|
||||
verticalAlign: string;
|
||||
dottify: boolean;
|
||||
}
|
||||
};
|
||||
wave: {
|
||||
animations: {
|
||||
type: string;
|
||||
config: {
|
||||
bottom?: boolean;
|
||||
top?: boolean;
|
||||
count?: number;
|
||||
cubeHeight?: number;
|
||||
lineWidth?: number;
|
||||
diameter?: number;
|
||||
fillColor?: string | WaveColor;
|
||||
lineColor?: string | WaveColor;
|
||||
radius?: number;
|
||||
frequencyBand?: string;
|
||||
}
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
const builder = createPluginBuilder('visualizer', {
|
||||
name: 'Visualizer',
|
||||
restartNeeded: true,
|
||||
config: {
|
||||
enabled: false,
|
||||
type: 'butterchurn',
|
||||
// Config per visualizer
|
||||
butterchurn: {
|
||||
preset: 'martin [shadow harlequins shape code] - fata morgana',
|
||||
renderingFrequencyInMs: 500,
|
||||
blendTimeInSeconds: 2.7,
|
||||
},
|
||||
vudio: {
|
||||
effect: 'lighting',
|
||||
accuracy: 128,
|
||||
lighting: {
|
||||
maxHeight: 160,
|
||||
maxSize: 12,
|
||||
lineWidth: 1,
|
||||
color: '#49f3f7',
|
||||
shadowBlur: 2,
|
||||
shadowColor: 'rgba(244,244,244,.5)',
|
||||
fadeSide: true,
|
||||
prettify: false,
|
||||
horizontalAlign: 'center',
|
||||
verticalAlign: 'middle',
|
||||
dottify: true,
|
||||
},
|
||||
},
|
||||
wave: {
|
||||
animations: [
|
||||
{
|
||||
type: 'Cubes',
|
||||
config: {
|
||||
bottom: true,
|
||||
count: 30,
|
||||
cubeHeight: 5,
|
||||
fillColor: { gradient: ['#FAD961', '#F76B1C'] },
|
||||
lineColor: 'rgba(0,0,0,0)',
|
||||
radius: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Cubes',
|
||||
config: {
|
||||
top: true,
|
||||
count: 12,
|
||||
cubeHeight: 5,
|
||||
fillColor: { gradient: ['#FAD961', '#F76B1C'] },
|
||||
lineColor: 'rgba(0,0,0,0)',
|
||||
radius: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Circles',
|
||||
config: {
|
||||
lineColor: {
|
||||
gradient: ['#FAD961', '#FAD961', '#F76B1C'],
|
||||
rotate: 90,
|
||||
},
|
||||
lineWidth: 4,
|
||||
diameter: 20,
|
||||
count: 10,
|
||||
frequencyBand: 'base',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as VisualizerPluginConfig,
|
||||
styles: [emptyStyle],
|
||||
});
|
||||
|
||||
export default builder;
|
||||
|
||||
declare global {
|
||||
interface PluginBuilderList {
|
||||
[builder.id]: typeof builder;
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
import emptyPlayerStyle from './empty-player.css';
|
||||
|
||||
import { injectCSS } from '../utils/main';
|
||||
|
||||
export default (win: BrowserWindow) => {
|
||||
injectCSS(win.webContents, emptyPlayerStyle);
|
||||
};
|
||||
@ -1,23 +1,21 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
import builder from './index';
|
||||
|
||||
import { MenuTemplate } from '../../menu';
|
||||
import { setMenuOptions } from '../../config/plugins';
|
||||
const visualizerTypes = ['butterchurn', 'vudio', 'wave'] as const; // For bundling
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
export default builder.createMenu(async ({ getConfig, setConfig }) => {
|
||||
const config = await getConfig();
|
||||
|
||||
const visualizerTypes = ['butterchurn', 'vudio', 'wave']; // For bundling
|
||||
|
||||
export default (win: BrowserWindow, options: ConfigType<'visualizer'>): MenuTemplate => [
|
||||
{
|
||||
label: 'Type',
|
||||
submenu: visualizerTypes.map((visualizerType) => ({
|
||||
label: visualizerType,
|
||||
type: 'radio',
|
||||
checked: options.type === visualizerType,
|
||||
click() {
|
||||
options.type = visualizerType;
|
||||
setMenuOptions('visualizer', options);
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
label: 'Type',
|
||||
submenu: visualizerTypes.map((visualizerType) => ({
|
||||
label: visualizerType,
|
||||
type: 'radio',
|
||||
checked: config.type === visualizerType,
|
||||
click() {
|
||||
setConfig({ type: visualizerType });
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@ -1,83 +1,82 @@
|
||||
import { ButterchurnVisualizer as butterchurn, WaveVisualizer as wave, VudioVisualizer as vudio } from './visualizers';
|
||||
import { Visualizer } from './visualizers/visualizer';
|
||||
|
||||
import defaultConfig from '../../config/defaults';
|
||||
import builder from './index';
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
export default builder.createRenderer(({ getConfig }) => {
|
||||
return {
|
||||
async onLoad() {
|
||||
const config = await getConfig();
|
||||
|
||||
export default (options: ConfigType<'visualizer'>) => {
|
||||
const optionsWithDefaults = {
|
||||
...defaultConfig.plugins.visualizer,
|
||||
...options,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let visualizerType: { new(...args: any[]): Visualizer<unknown> } = vudio;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let visualizerType: { new(...args: any[]): Visualizer<unknown> } = vudio;
|
||||
|
||||
if (optionsWithDefaults.type === 'wave') {
|
||||
visualizerType = wave;
|
||||
} else if (optionsWithDefaults.type === 'butterchurn') {
|
||||
visualizerType = butterchurn;
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
'audioCanPlay',
|
||||
(e) => {
|
||||
const video = document.querySelector<HTMLVideoElement & { captureStream(): MediaStream; }>('video');
|
||||
if (!video) {
|
||||
return;
|
||||
if (config.type === 'wave') {
|
||||
visualizerType = wave;
|
||||
} else if (config.type === 'butterchurn') {
|
||||
visualizerType = butterchurn;
|
||||
}
|
||||
|
||||
const visualizerContainer = document.querySelector<HTMLElement>('#player');
|
||||
if (!visualizerContainer) {
|
||||
return;
|
||||
}
|
||||
document.addEventListener(
|
||||
'audioCanPlay',
|
||||
(e) => {
|
||||
const video = document.querySelector<HTMLVideoElement & { captureStream(): MediaStream; }>('video');
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas = document.querySelector<HTMLCanvasElement>('#visualizer');
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.id = 'visualizer';
|
||||
visualizerContainer?.prepend(canvas);
|
||||
}
|
||||
const visualizerContainer = document.querySelector<HTMLElement>('#player');
|
||||
if (!visualizerContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeCanvas = () => {
|
||||
if (canvas) {
|
||||
canvas.width = visualizerContainer.clientWidth;
|
||||
canvas.height = visualizerContainer.clientHeight;
|
||||
}
|
||||
};
|
||||
let canvas = document.querySelector<HTMLCanvasElement>('#visualizer');
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.id = 'visualizer';
|
||||
visualizerContainer?.prepend(canvas);
|
||||
}
|
||||
|
||||
resizeCanvas();
|
||||
const resizeCanvas = () => {
|
||||
if (canvas) {
|
||||
canvas.width = visualizerContainer.clientWidth;
|
||||
canvas.height = visualizerContainer.clientHeight;
|
||||
}
|
||||
};
|
||||
|
||||
const gainNode = e.detail.audioContext.createGain();
|
||||
gainNode.gain.value = 1.25;
|
||||
e.detail.audioSource.connect(gainNode);
|
||||
resizeCanvas();
|
||||
|
||||
const visualizer = new visualizerType(
|
||||
e.detail.audioContext,
|
||||
e.detail.audioSource,
|
||||
visualizerContainer,
|
||||
canvas,
|
||||
gainNode,
|
||||
video.captureStream(),
|
||||
optionsWithDefaults,
|
||||
const gainNode = e.detail.audioContext.createGain();
|
||||
gainNode.gain.value = 1.25;
|
||||
e.detail.audioSource.connect(gainNode);
|
||||
|
||||
const visualizer = new visualizerType(
|
||||
e.detail.audioContext,
|
||||
e.detail.audioSource,
|
||||
visualizerContainer,
|
||||
canvas,
|
||||
gainNode,
|
||||
video.captureStream(),
|
||||
config,
|
||||
);
|
||||
|
||||
const resizeVisualizer = (width: number, height: number) => {
|
||||
resizeCanvas();
|
||||
visualizer.resize(width, height);
|
||||
};
|
||||
|
||||
resizeVisualizer(canvas.width, canvas.height);
|
||||
const visualizerContainerObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
resizeVisualizer(entry.contentRect.width, entry.contentRect.height);
|
||||
}
|
||||
});
|
||||
visualizerContainerObserver.observe(visualizerContainer);
|
||||
|
||||
visualizer.render();
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
|
||||
const resizeVisualizer = (width: number, height: number) => {
|
||||
resizeCanvas();
|
||||
visualizer.resize(width, height);
|
||||
};
|
||||
|
||||
resizeVisualizer(canvas.width, canvas.height);
|
||||
const visualizerContainerObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
resizeVisualizer(entry.contentRect.width, entry.contentRect.height);
|
||||
}
|
||||
});
|
||||
visualizerContainerObserver.observe(visualizerContainer);
|
||||
|
||||
visualizer.render();
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import ButterchurnPresets from 'butterchurn-presets';
|
||||
|
||||
import { Visualizer } from './visualizer';
|
||||
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
import type { VisualizerPluginConfig } from '../index';
|
||||
|
||||
class ButterchurnVisualizer extends Visualizer<Butterchurn> {
|
||||
name = 'butterchurn';
|
||||
@ -18,7 +18,7 @@ class ButterchurnVisualizer extends Visualizer<Butterchurn> {
|
||||
canvas: HTMLCanvasElement,
|
||||
audioNode: GainNode,
|
||||
stream: MediaStream,
|
||||
options: ConfigType<'visualizer'>,
|
||||
options: VisualizerPluginConfig,
|
||||
) {
|
||||
super(
|
||||
audioContext,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
import type { VisualizerPluginConfig } from '../index';
|
||||
|
||||
export abstract class Visualizer<T> {
|
||||
/**
|
||||
@ -14,7 +14,7 @@ export abstract class Visualizer<T> {
|
||||
_canvas: HTMLCanvasElement,
|
||||
_audioNode: GainNode,
|
||||
_stream: MediaStream,
|
||||
_options: ConfigType<'visualizer'>,
|
||||
_options: VisualizerPluginConfig,
|
||||
) {}
|
||||
|
||||
abstract resize(width: number, height: number): void;
|
||||
|
||||
@ -2,7 +2,7 @@ import Vudio from 'vudio/umd/vudio';
|
||||
|
||||
import { Visualizer } from './visualizer';
|
||||
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
import type { VisualizerPluginConfig } from '../index';
|
||||
|
||||
class VudioVisualizer extends Visualizer<Vudio> {
|
||||
name = 'vudio';
|
||||
@ -16,7 +16,7 @@ class VudioVisualizer extends Visualizer<Vudio> {
|
||||
canvas: HTMLCanvasElement,
|
||||
audioNode: GainNode,
|
||||
stream: MediaStream,
|
||||
options: ConfigType<'visualizer'>,
|
||||
options: VisualizerPluginConfig,
|
||||
) {
|
||||
super(
|
||||
audioContext,
|
||||
|
||||
@ -2,8 +2,7 @@ import { Wave } from '@foobar404/wave';
|
||||
|
||||
import { Visualizer } from './visualizer';
|
||||
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
|
||||
import type { VisualizerPluginConfig } from '../index';
|
||||
class WaveVisualizer extends Visualizer<Wave> {
|
||||
name = 'wave';
|
||||
|
||||
@ -16,7 +15,7 @@ class WaveVisualizer extends Visualizer<Wave> {
|
||||
canvas: HTMLCanvasElement,
|
||||
audioNode: GainNode,
|
||||
stream: MediaStream,
|
||||
options: ConfigType<'visualizer'>,
|
||||
options: VisualizerPluginConfig,
|
||||
) {
|
||||
super(
|
||||
audioContext,
|
||||
|
||||
Reference in New Issue
Block a user