import { Injectable } from '@angular/core'; import { Capacitor } from '@capacitor/core'; import { Filesystem, Directory } from '@capacitor/filesystem'; @Injectable({ providedIn: 'root' }) export class AudioService { private audioContext!: AudioContext; private mediaRecorder!: MediaRecorder; private audioChunks: Blob[] = []; private audioAnalyser!: AnalyserNode; private microphone!: MediaStreamAudioSourceNode; private scriptProcessor!: ScriptProcessorNode; private volumeCallback!: (volume: number) => void; private isRecording = false; constructor() {} initAudioContext() { if (!this.audioContext) { this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); } } async startRecording(volumeCallback: (volume: number) => void): Promise { this.volumeCallback = volumeCallback; this.audioChunks = []; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); this.mediaRecorder = new MediaRecorder(stream); // 设置音频分析 this.setupAudioAnalysis(stream); this.mediaRecorder.ondataavailable = (event) => { this.audioChunks.push(event.data); }; this.mediaRecorder.start(100); // 每100ms收集一次数据 this.isRecording = true; } catch (error) { console.error('无法访问麦克风:', error); throw error; } } private setupAudioAnalysis(stream: MediaStream) { this.microphone = this.audioContext.createMediaStreamSource(stream); this.audioAnalyser = this.audioContext.createAnalyser(); this.audioAnalyser.fftSize = 256; this.scriptProcessor = this.audioContext.createScriptProcessor(2048, 1, 1); this.microphone.connect(this.audioAnalyser); this.audioAnalyser.connect(this.scriptProcessor); this.scriptProcessor.connect(this.audioContext.destination); const that = this; this.scriptProcessor.onaudioprocess = function() { if (!that.isRecording) return; const array = new Uint8Array(that.audioAnalyser.frequencyBinCount); that.audioAnalyser.getByteFrequencyData(array); let values = 0; const length = array.length; for (let i = 0; i < length; i++) { values += array[i]; } const average = values / length; that.volumeCallback(average); }; } async stopRecording(): Promise { return new Promise((resolve) => { this.mediaRecorder.onstop = () => { const audioBlob = new Blob(this.audioChunks, { type: 'audio/wav' }); resolve(audioBlob); }; this.mediaRecorder.stop(); this.cleanupAudioNodes(); this.isRecording = false; }); } getRecordedChunks(): Blob[] { return this.audioChunks; } analyzeAudio(audioChunks: Blob[], callback: (dataArray: Uint8Array) => void) { const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); const fileReader = new FileReader(); fileReader.onload = () => { const arrayBuffer = fileReader.result as ArrayBuffer; this.audioContext.decodeAudioData(arrayBuffer).then((audioBuffer) => { const offlineContext = new OfflineAudioContext( audioBuffer.numberOfChannels, audioBuffer.length, audioBuffer.sampleRate ); const source = offlineContext.createBufferSource(); source.buffer = audioBuffer; const analyser = offlineContext.createAnalyser(); analyser.fftSize = 2048; source.connect(analyser); analyser.connect(offlineContext.destination); source.start(0); offlineContext.startRendering().then(() => { const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); analyser.getByteTimeDomainData(dataArray); callback(dataArray); }); }); }; fileReader.readAsArrayBuffer(audioBlob); } private cleanupAudioNodes() { if (this.scriptProcessor) { this.scriptProcessor.disconnect(); this.scriptProcessor.onaudioprocess = null; } if (this.microphone) { this.microphone.disconnect(); } if (this.mediaRecorder && this.mediaRecorder.stream) { this.mediaRecorder.stream.getTracks().forEach(track => track.stop()); } } }