Skip to content

Commit

Permalink
Attempt #2 to fix Permission Denied in mobile when turning on the mic
Browse files Browse the repository at this point in the history
  • Loading branch information
ViaAnthroposBenevolentia committed Dec 23, 2024
1 parent 97029a5 commit 2e2a522
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 34 deletions.
30 changes: 12 additions & 18 deletions js/audio/audio-recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,20 @@ export class AudioRecorder {
* @async
*/
async start(onAudioData) {
if (this.isRecording) {
Logger.warn('Attempting to start recording when already recording');
return;
}

this.onAudioData = onAudioData;
try {
Logger.info('Starting audio recorder...');

// First check if we have audio permission
const permissionResult = await navigator.permissions.query({ name: 'microphone' })
.catch(() => ({ state: 'prompt' })); // Fallback for browsers that don't support permission query
.catch(() => ({ state: 'prompt' }));

Logger.info('Permission state:', permissionResult.state);

if (permissionResult.state === 'denied') {
throw new ApplicationError(
Expand Down Expand Up @@ -88,23 +97,8 @@ export class AudioRecorder {
this.processor.connect(this.audioContext.destination);
this.isRecording = true;
} catch (error) {
Logger.error('Microphone access error:', error);

// Provide more specific error messages
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
throw new ApplicationError(
'Microphone access was denied. Please allow microphone access and try again.',
ErrorCodes.AUDIO_PERMISSION_DENIED,
{ originalError: error }
);
} else if (error.name === 'NotFoundError') {
throw new ApplicationError(
'No microphone found. Please ensure your device has a working microphone.',
ErrorCodes.AUDIO_DEVICE_NOT_FOUND,
{ originalError: error }
);
}

Logger.error('Audio recorder start error:', error);
console.error('Full audio recorder error:', error);
throw error;
}
}
Expand Down
70 changes: 54 additions & 16 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,16 +472,37 @@ handleApiKey(); // Call this when the page loads
* @returns {Promise<AudioStreamer>} The audio streamer instance.
*/
async function ensureAudioInitialized() {
if (!audioCtx) {
audioCtx = new AudioContext();
}
if (!audioStreamer) {
audioStreamer = new AudioStreamer(audioCtx);
await audioStreamer.addWorklet('vumeter-out', 'js/audio/worklets/vol-meter.js', (ev) => {
updateAudioVisualizer(ev.data.volume);
});
try {
Logger.info('Ensuring audio is initialized...');

if (!audioCtx) {
Logger.info('Creating new AudioContext...');
audioCtx = new (window.AudioContext || window.webkitAudioContext)({
sampleRate: 16000 // Match the expected sample rate
});
}

if (audioCtx.state === 'suspended') {
Logger.info('Resuming AudioContext...');
await audioCtx.resume();
}

if (!audioStreamer) {
Logger.info('Creating new AudioStreamer...');
audioStreamer = new AudioStreamer(audioCtx);
Logger.info('Adding VU meter worklet...');
await audioStreamer.addWorklet('vumeter-out', 'js/audio/worklets/vol-meter.js', (ev) => {
updateAudioVisualizer(ev.data.volume);
});
}

Logger.info('Audio initialization complete');
return audioStreamer;
} catch (error) {
Logger.error('Failed to initialize audio:', error);
console.error('Full audio init error:', error);
throw new Error(`Failed to initialize audio: ${error.message}`);
}
return audioStreamer;
}

/**
Expand All @@ -491,22 +512,28 @@ async function ensureAudioInitialized() {
async function handleMicToggle() {
if (!isRecording) {
try {
// Add debug logging
Logger.info('Starting microphone...');
micButton.disabled = true;
micButton.classList.add('loading');

// First check if the context is in suspended state
if (audioCtx && audioCtx.state === 'suspended') {
Logger.info('Resuming audio context...');
await audioCtx.resume();
}

Logger.info('Initializing audio...');
await ensureAudioInitialized();
audioRecorder = new AudioRecorder();

// Add loading state to mic button
micButton.disabled = true;
micButton.classList.add('loading');
Logger.info('Creating audio recorder...');
audioRecorder = new AudioRecorder();

const inputAnalyser = audioCtx.createAnalyser();
inputAnalyser.fftSize = 256;
const inputDataArray = new Uint8Array(inputAnalyser.frequencyBinCount);

Logger.info('Starting audio recorder...');
await audioRecorder.start((base64Data) => {
if (isUsingTool) {
client.sendRealtimeInput([{
Expand All @@ -526,6 +553,7 @@ async function handleMicToggle() {
updateAudioVisualizer(inputVolume, true);
});

Logger.info('Getting user media...');
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
Expand All @@ -537,13 +565,22 @@ async function handleMicToggle() {
const source = audioCtx.createMediaStreamSource(stream);
source.connect(inputAnalyser);

await audioStreamer.resume();
if (audioStreamer) {
Logger.info('Resuming audio streamer...');
await audioStreamer.resume();
} else {
Logger.error('Audio streamer not initialized');
throw new Error('Audio streamer not initialized');
}

isRecording = true;
Logger.info('Microphone started');
Logger.info('Microphone started successfully');
logMessage('Microphone started', 'system');
updateMicIcon();

} catch (error) {
Logger.error('Microphone error:', error);
console.error('Full error:', error); // Add full error logging

// Provide user-friendly error messages
let errorMessage = 'Could not access microphone. ';
Expand All @@ -552,12 +589,13 @@ async function handleMicToggle() {
} else if (error.code === ErrorCodes.AUDIO_DEVICE_NOT_FOUND) {
errorMessage += 'Please ensure your device has a working microphone.';
} else {
errorMessage += error.message;
errorMessage += error.message || 'Unknown error occurred';
}

logMessage(`Error: ${errorMessage}`, 'system');
isRecording = false;
updateMicIcon();

} finally {
// Always remove loading state
micButton.disabled = false;
Expand Down

0 comments on commit 2e2a522

Please sign in to comment.