mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-21 22:07:23 +08:00
187 lines
5.0 KiB
TypeScript
187 lines
5.0 KiB
TypeScript
import { Howl, Howler } from 'howler';
|
|
import Tuna from 'tunajs';
|
|
|
|
// 类型定义扩展
|
|
interface HowlSound {
|
|
_sounds: Array<{
|
|
_node: HTMLMediaElement & {
|
|
destination?: MediaElementAudioSourceNode;
|
|
};
|
|
}>;
|
|
}
|
|
|
|
export interface EQSettings {
|
|
[key: string]: number;
|
|
}
|
|
|
|
export class EQService {
|
|
private context: AudioContext | null = null;
|
|
|
|
private tuna: any = null;
|
|
|
|
private equalizer: any = null;
|
|
|
|
private source: MediaElementAudioSourceNode | null = null;
|
|
|
|
private gainNode: GainNode | null = null;
|
|
|
|
private bypass = false;
|
|
|
|
// 预设频率
|
|
private readonly frequencies = [31, 62, 125, 250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
|
|
// 默认EQ设置
|
|
private defaultEQSettings: EQSettings = Object.fromEntries(
|
|
this.frequencies.map((f) => [f.toString(), 0])
|
|
);
|
|
|
|
constructor() {
|
|
this.loadSavedSettings();
|
|
this.bypass = localStorage.getItem('eqBypass') === 'true';
|
|
this.initializeUserGestureHandler();
|
|
}
|
|
|
|
// 初始化用户手势处理
|
|
private initializeUserGestureHandler() {
|
|
const handler = async () => {
|
|
if (this.context?.state === 'suspended') {
|
|
await this.context.resume();
|
|
}
|
|
document.removeEventListener('click', handler);
|
|
};
|
|
document.addEventListener('click', handler);
|
|
}
|
|
|
|
// 初始化音频上下文
|
|
public async setupAudioContext(howl: Howl) {
|
|
try {
|
|
// 使用Howler的现有上下文
|
|
this.context = (Howler.ctx as AudioContext) || new AudioContext();
|
|
|
|
// 初始化Howler的音频系统(如果需要)
|
|
if (!Howler.ctx) {
|
|
Howler.ctx = this.context;
|
|
Howler.masterGain = this.context.createGain();
|
|
Howler.masterGain.connect(this.context.destination);
|
|
}
|
|
|
|
// 确保上下文处于运行状态
|
|
if (this.context.state === 'suspended') {
|
|
await this.context.resume();
|
|
}
|
|
|
|
const sound = (howl as unknown as HowlSound)._sounds[0];
|
|
if (!sound?._node) throw new Error('无法获取音频节点');
|
|
|
|
// 清理现有资源
|
|
await this.dispose();
|
|
|
|
// 创建新的处理链
|
|
this.tuna = new Tuna(this.context);
|
|
|
|
// 创建/复用源节点
|
|
if (!sound._node.destination) {
|
|
this.source = this.context.createMediaElementSource(sound._node);
|
|
sound._node.destination = this.source;
|
|
} else {
|
|
this.source = sound._node.destination;
|
|
}
|
|
|
|
// 创建效果节点
|
|
this.gainNode = this.context.createGain();
|
|
this.equalizer = new this.tuna.Equalizer({
|
|
frequencies: this.frequencies,
|
|
gains: this.frequencies.map((f) => this.getSavedGain(f.toString())),
|
|
bypass: this.bypass
|
|
});
|
|
|
|
// 连接节点链
|
|
this.source!.connect(this.equalizer.input).connect(this.gainNode).connect(Howler.masterGain);
|
|
|
|
// 恢复音量设置
|
|
const volume = localStorage.getItem('volume');
|
|
this.gainNode.gain.value = volume ? parseFloat(volume) : 1;
|
|
} catch (error) {
|
|
console.error('音频上下文初始化失败:', error);
|
|
await this.dispose();
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// EQ功能开关
|
|
public setEnabled(enabled: boolean) {
|
|
this.bypass = !enabled;
|
|
localStorage.setItem('eqBypass', JSON.stringify(this.bypass));
|
|
if (this.equalizer) this.equalizer.bypass = this.bypass;
|
|
}
|
|
|
|
public isEnabled(): boolean {
|
|
return !this.bypass;
|
|
}
|
|
|
|
// 调整频率增益
|
|
public setFrequencyGain(frequency: string, gain: number) {
|
|
const index = this.frequencies.findIndex((f) => f.toString() === frequency);
|
|
if (index !== -1 && this.equalizer) {
|
|
this.equalizer.setGain(index, gain);
|
|
this.saveSettings(frequency, gain);
|
|
}
|
|
}
|
|
|
|
// 重置EQ设置
|
|
public resetEQ() {
|
|
this.frequencies.forEach((f) => {
|
|
this.setFrequencyGain(f.toString(), 0);
|
|
});
|
|
localStorage.removeItem('eqSettings');
|
|
}
|
|
|
|
// 获取当前设置
|
|
public getAllSettings(): EQSettings {
|
|
return this.loadSavedSettings();
|
|
}
|
|
|
|
// 保存/加载设置
|
|
private saveSettings(frequency: string, gain: number) {
|
|
const settings = this.loadSavedSettings();
|
|
settings[frequency] = gain;
|
|
localStorage.setItem('eqSettings', JSON.stringify(settings));
|
|
}
|
|
|
|
private loadSavedSettings(): EQSettings {
|
|
const saved = localStorage.getItem('eqSettings');
|
|
return saved ? JSON.parse(saved) : { ...this.defaultEQSettings };
|
|
}
|
|
|
|
private getSavedGain(frequency: string): number {
|
|
return this.loadSavedSettings()[frequency] || 0;
|
|
}
|
|
|
|
// 清理资源
|
|
public async dispose() {
|
|
try {
|
|
[this.source, this.equalizer, this.gainNode].forEach((node) => {
|
|
if (node) {
|
|
node.disconnect();
|
|
// 特殊清理Tuna节点
|
|
if (node instanceof Tuna.Equalizer) node.destroy();
|
|
}
|
|
});
|
|
|
|
if (this.context && this.context !== Howler.ctx) {
|
|
await this.context.close();
|
|
}
|
|
|
|
this.context = null;
|
|
this.tuna = null;
|
|
this.source = null;
|
|
this.equalizer = null;
|
|
this.gainNode = null;
|
|
} catch (error) {
|
|
console.error('资源清理失败:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const eqService = new EQService();
|