mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-07-09 02:57:31 +08:00
✨ feat: 添加EQ音效调节功能 实时调节以及多个预设提供
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
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 howlInstance: Howl | 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);
|
||||
this.howlInstance = howl;
|
||||
|
||||
// 创建/复用源节点
|
||||
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;
|
||||
this.howlInstance = null;
|
||||
} catch (error) {
|
||||
console.error('资源清理失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const eqService = new EQService();
|
||||
Reference in New Issue
Block a user