mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-12 11:01:45 +00:00
QOL: Move source code under the src directory. (#1318)
This commit is contained in:
9
src/plugins/visualizer/back.ts
Normal file
9
src/plugins/visualizer/back.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
import emptyPlayerStyle from './empty-player.css';
|
||||
|
||||
import { injectCSS } from '../utils';
|
||||
|
||||
export default (win: BrowserWindow) => {
|
||||
injectCSS(win.webContents, emptyPlayerStyle);
|
||||
};
|
||||
55
src/plugins/visualizer/butterchurn.d.ts
vendored
Normal file
55
src/plugins/visualizer/butterchurn.d.ts
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
declare module 'butterchurn' {
|
||||
interface VisualizerOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
meshWidth?: number;
|
||||
meshHeight?: number;
|
||||
pixelRatio?: number;
|
||||
textureRatio?: number;
|
||||
outputFXAA?: boolean;
|
||||
}
|
||||
|
||||
class Visualizer {
|
||||
constructor(audioContext: AudioContext, canvas: HTMLCanvasElement, opts: ButterchurnOptions);
|
||||
loseGLContext(): void;
|
||||
connectAudio(audioNode: AudioNode): void;
|
||||
disconnectAudio(audioNode: AudioNode): void;
|
||||
static overrideDefaultVars(baseValsDefaults: unknown, baseVals: unknown): unknown;
|
||||
createQVars(): Record<string, WebAssembly.Global>;
|
||||
createTVars(): Record<string, WebAssembly.Global>;
|
||||
createPerFramePool(baseVals: unknown): Record<string, WebAssembly.Global>;
|
||||
createPerPixelPool(baseVals: unknown): Record<string, WebAssembly.Global>;
|
||||
createCustomShapePerFramePool(baseVals: unknown): Record<string, WebAssembly.Global>;
|
||||
createCustomWavePerFramePool(baseVals: unknown): Record<string, WebAssembly.Global>;
|
||||
static makeShapeResetPool(pool: Record<string, WebAssembly.Global>, variables: string[], idx: number): Record<string, WebAssembly.Global>;
|
||||
static base64ToArrayBuffer(base64: string): ArrayBuffer;
|
||||
loadPreset(presetMap: unknown, blendTime?: number): Promise<void>;
|
||||
async loadWASMPreset(preset: unknown, blendTime: number): Promise<void>;
|
||||
loadJSPreset(preset: unknown, blendTime: number): void;
|
||||
loadExtraImages(imageData: unknown): void;
|
||||
setRendererSize(width: number, height: number, opts?: VisualizerOptions): void;
|
||||
setInternalMeshSize(width: number, height: number): void;
|
||||
setOutputAA(useAA: boolean): void;
|
||||
setCanvas(canvas: HTMLCanvasElement): void;
|
||||
render(opts?: VisualizerOptions): unknown;
|
||||
launchSongTitleAnim(text: string): void;
|
||||
toDataURL(): string;
|
||||
warpBufferToDataURL(): string;
|
||||
}
|
||||
|
||||
interface ButterchurnOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
onlyUseWASM?: boolean;
|
||||
}
|
||||
|
||||
export default class Butterchurn {
|
||||
static createVisualizer(audioContext: AudioContext, canvas: HTMLCanvasElement, options?: ButterchurnOptions): Visualizer;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'butterchurn-presets' {
|
||||
const presets: Record<string, unknown>;
|
||||
|
||||
export default presets;
|
||||
}
|
||||
5
src/plugins/visualizer/empty-player.css
Normal file
5
src/plugins/visualizer/empty-player.css
Normal file
@ -0,0 +1,5 @@
|
||||
#visualizer {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background-color: black;
|
||||
}
|
||||
83
src/plugins/visualizer/front.ts
Normal file
83
src/plugins/visualizer/front.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { ButterchurnVisualizer as butterchurn, WaveVisualizer as wave, VudioVisualizer as vudio } from './visualizers';
|
||||
import { Visualizer } from './visualizers/visualizer';
|
||||
|
||||
import defaultConfig from '../../config/defaults';
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const visualizerContainer = document.querySelector<HTMLElement>('#player');
|
||||
if (!visualizerContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas = document.querySelector<HTMLCanvasElement>('#visualizer');
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.id = 'visualizer';
|
||||
visualizerContainer?.prepend(canvas);
|
||||
}
|
||||
|
||||
const resizeCanvas = () => {
|
||||
if (canvas) {
|
||||
canvas.width = visualizerContainer.clientWidth;
|
||||
canvas.height = visualizerContainer.clientHeight;
|
||||
}
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
|
||||
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(),
|
||||
optionsWithDefaults,
|
||||
);
|
||||
|
||||
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 },
|
||||
);
|
||||
};
|
||||
23
src/plugins/visualizer/menu.ts
Normal file
23
src/plugins/visualizer/menu.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
import { MenuTemplate } from '../../menu';
|
||||
import { setMenuOptions } from '../../config/plugins';
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
|
||||
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);
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
63
src/plugins/visualizer/visualizers/butterchurn.ts
Normal file
63
src/plugins/visualizer/visualizers/butterchurn.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import Butterchurn from 'butterchurn';
|
||||
import ButterchurnPresets from 'butterchurn-presets';
|
||||
|
||||
import { Visualizer } from './visualizer';
|
||||
|
||||
import { ConfigType } from '../../../config/dynamic';
|
||||
|
||||
class ButterchurnVisualizer extends Visualizer<Butterchurn> {
|
||||
name = 'butterchurn';
|
||||
|
||||
visualizer: ReturnType<typeof Butterchurn.createVisualizer>;
|
||||
private readonly renderingFrequencyInMs: number;
|
||||
|
||||
constructor(
|
||||
audioContext: AudioContext,
|
||||
audioSource: MediaElementAudioSourceNode,
|
||||
visualizerContainer: HTMLElement,
|
||||
canvas: HTMLCanvasElement,
|
||||
audioNode: GainNode,
|
||||
stream: MediaStream,
|
||||
options: ConfigType<'visualizer'>,
|
||||
) {
|
||||
super(
|
||||
audioContext,
|
||||
audioSource,
|
||||
visualizerContainer,
|
||||
canvas,
|
||||
audioNode,
|
||||
stream,
|
||||
options,
|
||||
);
|
||||
|
||||
this.visualizer = Butterchurn.createVisualizer(
|
||||
audioContext,
|
||||
canvas,
|
||||
{
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
}
|
||||
);
|
||||
|
||||
const preset = ButterchurnPresets[options.butterchurn.preset];
|
||||
this.visualizer.loadPreset(preset, options.butterchurn.blendTimeInSeconds);
|
||||
|
||||
this.visualizer.connectAudio(audioNode);
|
||||
|
||||
this.renderingFrequencyInMs = options.butterchurn.renderingFrequencyInMs;
|
||||
}
|
||||
|
||||
resize(width: number, height: number) {
|
||||
this.visualizer.setRendererSize(width, height);
|
||||
}
|
||||
|
||||
render() {
|
||||
const renderVisualizer = () => {
|
||||
requestAnimationFrame(renderVisualizer);
|
||||
this.visualizer.render();
|
||||
};
|
||||
setTimeout(renderVisualizer, this.renderingFrequencyInMs);
|
||||
}
|
||||
}
|
||||
|
||||
export default ButterchurnVisualizer;
|
||||
5
src/plugins/visualizer/visualizers/index.ts
Normal file
5
src/plugins/visualizer/visualizers/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import ButterchurnVisualizer from './butterchurn';
|
||||
import VudioVisualizer from './vudio';
|
||||
import WaveVisualizer from './wave';
|
||||
|
||||
export { ButterchurnVisualizer, VudioVisualizer, WaveVisualizer };
|
||||
22
src/plugins/visualizer/visualizers/visualizer.ts
Normal file
22
src/plugins/visualizer/visualizers/visualizer.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
|
||||
export abstract class Visualizer<T> {
|
||||
/**
|
||||
* The name must be the same as the file name.
|
||||
*/
|
||||
abstract name: string;
|
||||
abstract visualizer: T;
|
||||
|
||||
protected constructor(
|
||||
audioContext: AudioContext,
|
||||
audioSource: MediaElementAudioSourceNode,
|
||||
visualizerContainer: HTMLElement,
|
||||
canvas: HTMLCanvasElement,
|
||||
audioNode: GainNode,
|
||||
stream: MediaStream,
|
||||
options: ConfigType<'visualizer'>,
|
||||
) {}
|
||||
|
||||
abstract resize(width: number, height: number): void;
|
||||
abstract render(): void;
|
||||
}
|
||||
52
src/plugins/visualizer/visualizers/vudio.ts
Normal file
52
src/plugins/visualizer/visualizers/vudio.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import Vudio from 'vudio/umd/vudio';
|
||||
|
||||
import { Visualizer } from './visualizer';
|
||||
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
|
||||
class VudioVisualizer extends Visualizer<Vudio> {
|
||||
name = 'vudio';
|
||||
|
||||
visualizer: Vudio;
|
||||
|
||||
constructor(
|
||||
audioContext: AudioContext,
|
||||
audioSource: MediaElementAudioSourceNode,
|
||||
visualizerContainer: HTMLElement,
|
||||
canvas: HTMLCanvasElement,
|
||||
audioNode: GainNode,
|
||||
stream: MediaStream,
|
||||
options: ConfigType<'visualizer'>,
|
||||
) {
|
||||
super(
|
||||
audioContext,
|
||||
audioSource,
|
||||
visualizerContainer,
|
||||
canvas,
|
||||
audioNode,
|
||||
stream,
|
||||
options,
|
||||
);
|
||||
|
||||
this.visualizer = new Vudio(stream, canvas, {
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
// Visualizer config
|
||||
...options,
|
||||
});
|
||||
|
||||
this.visualizer.dance();
|
||||
}
|
||||
|
||||
resize(width: number, height: number) {
|
||||
this.visualizer.setOption({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
}
|
||||
}
|
||||
|
||||
export default VudioVisualizer;
|
||||
51
src/plugins/visualizer/visualizers/wave.ts
Normal file
51
src/plugins/visualizer/visualizers/wave.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Wave } from '@foobar404/wave';
|
||||
|
||||
import { Visualizer } from './visualizer';
|
||||
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
|
||||
class WaveVisualizer extends Visualizer<Wave> {
|
||||
name = 'wave';
|
||||
|
||||
visualizer: Wave;
|
||||
|
||||
constructor(
|
||||
audioContext: AudioContext,
|
||||
audioSource: MediaElementAudioSourceNode,
|
||||
visualizerContainer: HTMLElement,
|
||||
canvas: HTMLCanvasElement,
|
||||
audioNode: GainNode,
|
||||
stream: MediaStream,
|
||||
options: ConfigType<'visualizer'>,
|
||||
) {
|
||||
super(
|
||||
audioContext,
|
||||
audioSource,
|
||||
visualizerContainer,
|
||||
canvas,
|
||||
audioNode,
|
||||
stream,
|
||||
options,
|
||||
);
|
||||
|
||||
this.visualizer = new Wave(
|
||||
{ context: audioContext, source: audioSource },
|
||||
canvas,
|
||||
);
|
||||
for (const animation of options.wave.animations) {
|
||||
const TargetVisualizer = this.visualizer.animations[animation.type as keyof typeof this.visualizer.animations];
|
||||
|
||||
this.visualizer.addAnimation(
|
||||
new TargetVisualizer(animation.config as never), // Magic of Typescript
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
resize(_: number, __: number) {
|
||||
}
|
||||
|
||||
render() {
|
||||
}
|
||||
}
|
||||
|
||||
export default WaveVisualizer;
|
||||
34
src/plugins/visualizer/vudio.d.ts
vendored
Normal file
34
src/plugins/visualizer/vudio.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
declare module 'vudio/umd/vudio' {
|
||||
interface NoneWaveformOptions {
|
||||
maxHeight?: number;
|
||||
minHeight?: number;
|
||||
spacing?: number;
|
||||
color?: string | string[];
|
||||
shadowBlur?: number;
|
||||
shadowColor?: string;
|
||||
fadeSide?: boolean;
|
||||
}
|
||||
|
||||
interface WaveformOptions extends NoneWaveformOptions{
|
||||
horizontalAlign: 'left' | 'center' | 'right';
|
||||
verticalAlign: 'top' | 'middle' | 'bottom';
|
||||
}
|
||||
|
||||
interface VudioOptions {
|
||||
effect?: 'waveform' | 'circlewave' | 'circlebar' | 'lighting';
|
||||
accuracy?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
waveform?: WaveformOptions
|
||||
}
|
||||
|
||||
class Vudio {
|
||||
constructor(audio: HTMLAudioElement | MediaStream, canvas: HTMLCanvasElement, options: VudioOptions = {});
|
||||
|
||||
dance(): void;
|
||||
pause(): void;
|
||||
setOption(options: VudioOptions): void;
|
||||
}
|
||||
|
||||
export default Vudio;
|
||||
}
|
||||
Reference in New Issue
Block a user