Optimize code density: simplify changed files while keeping comments (reduce 364 lines)

This commit is contained in:
spider-yamet
2026-01-26 14:48:08 -08:00
parent da71dce860
commit 3fc33e9095
8 changed files with 148 additions and 512 deletions
@@ -19,7 +19,6 @@ export class AudioRecorder {
this.visualizationRequest = null;
this.recordingTimer = null;
this.websocket = null;
// Callback functions
this.onRecordingStart = null;
this.onRecordingStop = null;
@@ -33,8 +32,7 @@ export class AudioRecorder {
// Get AudioContext instance
getAudioContext() {
const audioPlayer = getAudioPlayer();
return audioPlayer.getAudioContext();
return getAudioPlayer().getAudioContext();
}
// Initialize encoder
@@ -56,50 +54,35 @@ export class AudioRecorder {
this.buffer = new Int16Array(this.frameSize);
this.bufferIndex = 0;
this.isRecording = false;
this.port.onmessage = (event) => {
if (event.data.command === 'start') {
this.isRecording = true;
this.port.postMessage({ type: 'status', status: 'started' });
} else if (event.data.command === 'stop') {
this.isRecording = false;
if (this.bufferIndex > 0) {
const finalBuffer = this.buffer.slice(0, this.bufferIndex);
this.port.postMessage({
type: 'buffer',
buffer: finalBuffer
});
this.port.postMessage({ type: 'buffer', buffer: finalBuffer });
this.bufferIndex = 0;
}
this.port.postMessage({ type: 'status', status: 'stopped' });
}
};
}
process(inputs, outputs, parameters) {
if (!this.isRecording) return true;
const input = inputs[0][0];
if (!input) return true;
for (let i = 0; i < input.length; i++) {
if (this.bufferIndex >= this.frameSize) {
this.port.postMessage({
type: 'buffer',
buffer: this.buffer.slice(0)
});
this.port.postMessage({ type: 'buffer', buffer: this.buffer.slice(0) });
this.bufferIndex = 0;
}
this.buffer[this.bufferIndex++] = Math.max(-32768, Math.min(32767, Math.floor(input[i] * 32767)));
}
return true;
}
}
registerProcessor('audio-recorder-processor', AudioRecorderProcessor);
`;
}
@@ -107,24 +90,19 @@ export class AudioRecorder {
// Create audio processor
async createAudioProcessor() {
this.audioContext = this.getAudioContext();
try {
if (this.audioContext.audioWorklet) {
const blob = new Blob([this.getAudioProcessorCode()], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
await this.audioContext.audioWorklet.addModule(url);
URL.revokeObjectURL(url);
const audioProcessor = new AudioWorkletNode(this.audioContext, 'audio-recorder-processor');
audioProcessor.port.onmessage = (event) => {
if (event.data.type === 'buffer') {
this.processPCMBuffer(event.data.buffer);
}
};
log('Using AudioWorklet to process audio', 'success');
const silent = this.audioContext.createGain();
silent.gain.value = 0;
audioProcessor.connect(silent);
@@ -145,25 +123,19 @@ export class AudioRecorder {
try {
const frameSize = 4096;
const scriptProcessor = this.audioContext.createScriptProcessor(frameSize, 1, 1);
scriptProcessor.onaudioprocess = (event) => {
if (!this.isRecording) return;
const input = event.inputBuffer.getChannelData(0);
const buffer = new Int16Array(input.length);
for (let i = 0; i < input.length; i++) {
buffer[i] = Math.max(-32768, Math.min(32767, Math.floor(input[i] * 32767)));
}
this.processPCMBuffer(buffer);
};
const silent = this.audioContext.createGain();
silent.gain.value = 0;
scriptProcessor.connect(silent);
silent.connect(this.audioContext.destination);
log('Using ScriptProcessorNode as fallback successfully', 'warning');
return { node: scriptProcessor, type: 'processor' };
} catch (fallbackError) {
@@ -175,18 +147,14 @@ export class AudioRecorder {
// Process PCM buffer data
processPCMBuffer(buffer) {
if (!this.isRecording) return;
const newBuffer = new Int16Array(this.pcmDataBuffer.length + buffer.length);
newBuffer.set(this.pcmDataBuffer);
newBuffer.set(buffer, this.pcmDataBuffer.length);
this.pcmDataBuffer = newBuffer;
const samplesPerFrame = 960;
while (this.pcmDataBuffer.length >= samplesPerFrame) {
const frameData = this.pcmDataBuffer.slice(0, samplesPerFrame);
this.pcmDataBuffer = this.pcmDataBuffer.slice(samplesPerFrame);
this.encodeAndSendOpus(frameData);
}
}
@@ -197,15 +165,12 @@ export class AudioRecorder {
log('Opus encoder not initialized', 'error');
return;
}
try {
if (pcmData) {
const opusData = this.opusEncoder.encode(pcmData);
if (opusData && opusData.length > 0) {
this.audioBuffers.push(opusData.buffer);
this.totalAudioSize += opusData.length;
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
try {
this.websocket.send(opusData.buffer);
@@ -238,73 +203,47 @@ export class AudioRecorder {
// Start recording
async start() {
if (this.isRecording) return false;
try {
// Check if WebSocketHandler instance exists
const { getWebSocketHandler } = await import('../network/websocket.js');
const wsHandler = getWebSocketHandler();
// If machine is speaking, send abort message
if (wsHandler && wsHandler.isRemoteSpeaking && wsHandler.currentSessionId) {
const abortMessage = {
session_id: wsHandler.currentSessionId,
type: 'abort',
reason: 'wake_word_detected'
};
const abortMessage = { session_id: wsHandler.currentSessionId, type: 'abort', reason: 'wake_word_detected' };
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.send(JSON.stringify(abortMessage));
log('Sent abort message', 'info');
}
}
if (!this.initEncoder()) {
log('Unable to start recording: Opus encoder initialization failed', 'error');
return false;
}
log('Please record at least 1-2 seconds of audio to ensure sufficient data is collected', 'info');
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 16000,
channelCount: 1
}
});
const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 16000, channelCount: 1 } });
this.audioContext = this.getAudioContext();
if (this.audioContext.state === 'suspended') {
await this.audioContext.resume();
}
const processorResult = await this.createAudioProcessor();
if (!processorResult) {
log('Unable to create audio processor', 'error');
return false;
}
this.audioProcessor = processorResult.node;
this.audioProcessorType = processorResult.type;
this.audioSource = this.audioContext.createMediaStreamSource(stream);
this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = 2048;
this.audioSource.connect(this.analyser);
this.audioSource.connect(this.audioProcessor);
this.pcmDataBuffer = new Int16Array();
this.audioBuffers = [];
this.totalAudioSize = 0;
this.isRecording = true;
if (this.audioProcessorType === 'worklet' && this.audioProcessor.port) {
this.audioProcessor.port.postMessage({ command: 'start' });
}
// Send listening start message
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
log(`Sent recording start message`, 'info');
@@ -312,18 +251,15 @@ export class AudioRecorder {
log('WebSocket not connected, unable to send start message', 'error');
return false;
}
// Start visualization
if (this.onVisualizerUpdate) {
const dataArray = new Uint8Array(this.analyser.frequencyBinCount);
this.startVisualization(dataArray);
}
// Immediately notify recording start, update button state
if (this.onRecordingStart) {
this.onRecordingStart(0);
}
// Start recording timer
let recordingSeconds = 0;
this.recordingTimer = setInterval(() => {
@@ -332,7 +268,6 @@ export class AudioRecorder {
this.onRecordingStart(recordingSeconds);
}
}, 100);
log('Started PCM direct recording', 'success');
return true;
} catch (error) {
@@ -346,11 +281,8 @@ export class AudioRecorder {
startVisualization(dataArray) {
const draw = () => {
this.visualizationRequest = requestAnimationFrame(() => draw());
if (!this.isRecording) return;
this.analyser.getByteFrequencyData(dataArray);
if (this.onVisualizerUpdate) {
this.onVisualizerUpdate(dataArray);
}
@@ -361,48 +293,38 @@ export class AudioRecorder {
// Stop recording
stop() {
if (!this.isRecording) return false;
try {
this.isRecording = false;
if (this.audioProcessor) {
if (this.audioProcessorType === 'worklet' && this.audioProcessor.port) {
this.audioProcessor.port.postMessage({ command: 'stop' });
}
this.audioProcessor.disconnect();
this.audioProcessor = null;
}
if (this.audioSource) {
this.audioSource.disconnect();
this.audioSource = null;
}
if (this.visualizationRequest) {
cancelAnimationFrame(this.visualizationRequest);
this.visualizationRequest = null;
}
if (this.recordingTimer) {
clearInterval(this.recordingTimer);
this.recordingTimer = null;
}
// Encode and send remaining data
this.encodeAndSendOpus();
// Send end signal
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
const emptyOpusFrame = new Uint8Array(0);
this.websocket.send(emptyOpusFrame);
log('Sent recording stop signal', 'info');
}
if (this.onRecordingStop) {
this.onRecordingStop();
}
log('Stopped PCM direct recording', 'success');
return true;
} catch (error) {
@@ -437,21 +359,11 @@ export async function checkMicrophoneAvailability() {
log('Browser does not support getUserMedia API', 'warning');
return false;
}
try {
// Try to access microphone
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 16000,
channelCount: 1
}
});
const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 16000, channelCount: 1 } });
// Immediately stop all tracks to release microphone
stream.getTracks().forEach(track => track.stop());
log('Microphone availability check successful', 'success');
return true;
} catch (error) {
@@ -467,24 +379,18 @@ export async function checkMicrophoneAvailability() {
export function isHttpNonLocalhost() {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
// Check if it is HTTP protocol
if (protocol !== 'http:') {
return false;
}
// localhost and 127.0.0.1 can use microphone
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return false;
}
// Private IP addresses can also use microphone (browser allows)
if (hostname.startsWith('192.168.') ||
hostname.startsWith('10.') ||
hostname.startsWith('172.')) {
if (hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.')) {
return false;
}
// Other HTTP access is considered non-localhost
return true;
}
}
@@ -1,9 +1,23 @@
/**
* Audio recording module tests - Browser compatible version
* Test microphone availability detection functionality
*
* This version works without Vitest - uses the simple test framework from test-runner.html
*/
import { checkMicrophoneAvailability, isHttpNonLocalhost } from './recorder.js';
describe('Microphone Availability Detection', () => {
beforeEach(() => vi.clearAllMocks());
beforeEach(() => {
// Reset mocks before each test
vi.clearAllMocks();
});
/**
* Test checkMicrophoneAvailability function - success case
*/
test('should return true when microphone is available', async () => {
// Mock navigator.mediaDevices.getUserMedia to return a successful stream
const mockTrack = { stop: vi.fn() };
const mockStream = { getTracks: () => [mockTrack] };
navigator.mediaDevices.getUserMedia = vi.fn().mockResolvedValue(mockStream);
@@ -13,60 +27,105 @@ describe('Microphone Availability Detection', () => {
expect(mockTrack.stop).toHaveBeenCalled();
});
/**
* Test checkMicrophoneAvailability function - failure case
*/
test('should return false when microphone is not available', async () => {
// Mock getUserMedia to throw an error
navigator.mediaDevices.getUserMedia = vi.fn().mockRejectedValue(new Error('Permission denied'));
const result = await checkMicrophoneAvailability();
expect(result).toBe(false);
expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalled();
});
/**
* Test checkMicrophoneAvailability function - browser not supported
*/
test('should return false when browser does not support getUserMedia', async () => {
// Mock navigator.mediaDevices.getUserMedia to be undefined
const originalGetUserMedia = navigator.mediaDevices.getUserMedia;
navigator.mediaDevices.getUserMedia = undefined;
const result = await checkMicrophoneAvailability();
expect(result).toBe(false);
// Restore
navigator.mediaDevices.getUserMedia = originalGetUserMedia;
});
/**
* Test isHttpNonLocalhost function - HTTP non-localhost
* Note: window.location properties are read-only in browsers, so we test the logic indirectly
*/
test('should return true for HTTP non-localhost access', () => {
expect(typeof isHttpNonLocalhost()).toBe('boolean');
// Since window.location is read-only, we'll test by checking the actual implementation
// This test verifies the function works correctly with the current location
// In a real browser environment, this would test against actual location
const result = isHttpNonLocalhost();
// Just verify the function runs without error
expect(typeof result).toBe('boolean');
});
/**
* Test isHttpNonLocalhost function - localhost should return false
* Note: window.location properties are read-only in browsers
*/
test('should return false for localhost', () => {
// Test the logic by checking if current location is localhost
const result = isHttpNonLocalhost();
// If we're on localhost, result should be false
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
expect(result).toBe(false);
} else {
// Otherwise just verify function returns boolean
expect(typeof result).toBe('boolean');
}
});
/**
* Test isHttpNonLocalhost function - 127.0.0.1 should return false
* Note: window.location properties are read-only in browsers
*/
test('should return false for 127.0.0.1', () => {
// Test the logic by checking if current location is 127.0.0.1
const result = isHttpNonLocalhost();
// If we're on 127.0.0.1, result should be false
if (window.location.hostname === '127.0.0.1') {
expect(result).toBe(false);
} else {
// Otherwise just verify function returns boolean
expect(typeof result).toBe('boolean');
}
});
/**
* Test isHttpNonLocalhost function - private IP should return false
* Note: window.location properties are read-only in browsers
*/
test('should return false for private IP addresses', () => {
// Test the logic by checking if current location is a private IP
const result = isHttpNonLocalhost();
const hostname = window.location.hostname;
const isPrivateIP = hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.');
if (isPrivateIP && window.location.protocol === 'http:') {
expect(result).toBe(false);
} else {
// Otherwise just verify function returns boolean
expect(typeof result).toBe('boolean');
}
});
/**
* Test isHttpNonLocalhost function - HTTPS should return false
* Note: window.location properties are read-only in browsers
*/
test('should return false for HTTPS protocol', () => {
// Test the logic by checking if current protocol is HTTPS
const result = isHttpNonLocalhost();
// If we're on HTTPS, result should be false
if (window.location.protocol === 'https:') {
expect(result).toBe(false);
} else {
// Otherwise just verify function returns boolean
expect(typeof result).toBe('boolean');
}
});
});
});
@@ -17,26 +17,12 @@ describe('Microphone Availability Detection', () => {
*/
test('should return true when microphone is available', async () => {
// Mock navigator.mediaDevices.getUserMedia to return a successful stream
const mockTrack = {
stop: vi.fn()
};
const mockStream = {
getTracks: () => [mockTrack]
};
const mockTrack = { stop: vi.fn() };
const mockStream = { getTracks: () => [mockTrack] };
global.navigator.mediaDevices.getUserMedia = vi.fn().mockResolvedValue(mockStream);
const result = await checkMicrophoneAvailability();
expect(result).toBe(true);
expect(global.navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 16000,
channelCount: 1
}
});
expect(global.navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 16000, channelCount: 1 } });
expect(mockTrack.stop).toHaveBeenCalled();
});
@@ -45,11 +31,8 @@ describe('Microphone Availability Detection', () => {
*/
test('should return false when microphone is not available', async () => {
// Mock getUserMedia to throw an error
const mockError = new Error('Permission denied');
global.navigator.mediaDevices.getUserMedia = vi.fn().mockRejectedValue(mockError);
global.navigator.mediaDevices.getUserMedia = vi.fn().mockRejectedValue(new Error('Permission denied'));
const result = await checkMicrophoneAvailability();
expect(result).toBe(false);
expect(global.navigator.mediaDevices.getUserMedia).toHaveBeenCalled();
});
@@ -61,11 +44,8 @@ describe('Microphone Availability Detection', () => {
// Mock navigator without mediaDevices
const originalMediaDevices = global.navigator.mediaDevices;
delete global.navigator.mediaDevices;
const result = await checkMicrophoneAvailability();
expect(result).toBe(false);
// Restore
global.navigator.mediaDevices = originalMediaDevices;
});
@@ -75,15 +55,7 @@ describe('Microphone Availability Detection', () => {
*/
test('should return true for HTTP non-localhost access', () => {
// Mock window.location for HTTP non-localhost
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: 'example.com'
},
writable: true,
configurable: true
});
Object.defineProperty(window, 'location', { value: { protocol: 'http:', hostname: 'example.com' }, writable: true, configurable: true });
const result = isHttpNonLocalhost();
expect(result).toBe(true);
});
@@ -92,15 +64,7 @@ describe('Microphone Availability Detection', () => {
* Test isHttpNonLocalhost function - localhost should return false
*/
test('should return false for localhost', () => {
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: 'localhost'
},
writable: true,
configurable: true
});
Object.defineProperty(window, 'location', { value: { protocol: 'http:', hostname: 'localhost' }, writable: true, configurable: true });
const result = isHttpNonLocalhost();
expect(result).toBe(false);
});
@@ -109,15 +73,7 @@ describe('Microphone Availability Detection', () => {
* Test isHttpNonLocalhost function - 127.0.0.1 should return false
*/
test('should return false for 127.0.0.1', () => {
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: '127.0.0.1'
},
writable: true,
configurable: true
});
Object.defineProperty(window, 'location', { value: { protocol: 'http:', hostname: '127.0.0.1' }, writable: true, configurable: true });
const result = isHttpNonLocalhost();
expect(result).toBe(false);
});
@@ -127,17 +83,8 @@ describe('Microphone Availability Detection', () => {
*/
test('should return false for private IP addresses', () => {
const privateIPs = ['192.168.1.100', '10.0.0.1', '172.16.0.1'];
privateIPs.forEach(ip => {
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: ip
},
writable: true,
configurable: true
});
Object.defineProperty(window, 'location', { value: { protocol: 'http:', hostname: ip }, writable: true, configurable: true });
const result = isHttpNonLocalhost();
expect(result).toBe(false);
});
@@ -147,16 +94,8 @@ describe('Microphone Availability Detection', () => {
* Test isHttpNonLocalhost function - HTTPS should return false
*/
test('should return false for HTTPS protocol', () => {
Object.defineProperty(window, 'location', {
value: {
protocol: 'https:',
hostname: 'example.com'
},
writable: true,
configurable: true
});
Object.defineProperty(window, 'location', { value: { protocol: 'https:', hostname: 'example.com' }, writable: true, configurable: true });
const result = isHttpNonLocalhost();
expect(result).toBe(false);
});
});
});