I am not able to access the microphone of browsers like Safari and Chrome on iPhone using jQuery code.
When I open the page and click the start button, I am stuck on listening mode. Instead of that, I want to process the audio for Ajax. The same code works fine on Windows.
jQuery(document).ready(function() {
var baseURL = window.location.origin + "/";
let mediaRecorder;
let audio;
let typingInterval; // Declare this variable outside so it is accessible globally
const startButton = $("#startRecording");
const stopButton = $("#stopButton");
const resetButton = $("#resetButton");
const bottomroundbtns = $(".bottomroundbtns, .bottomroundbtns1");
startButton.on('click', function() {
$(".index1").hide();
$(".index2").show();
$("#index3").hide();
$("#index4").hide();
startRecording();
});
resetButton.on('click', function() {
startRecording();
$(".index2").show();
$('.listn').html('Listening...');
$(".index3").hide();
$(".index4").hide();
});
// start recording without silence detection
async function startRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true
});
const audioContext = new(window.AudioContext || window.webkitAudioContext)();
const microphone = audioContext.createMediaStreamSource(stream);
const mimeType = 'audio/webm;codecs=opus';
mediaRecorder = new MediaRecorder(stream, {
mimeType
});
mediaRecorder.ondataavailable = async (event) => {
if (event.data.size > 0) {
await sendAudioChunk(event.data);
}
};
mediaRecorder.start();
// Wait for 10 seconds before calling the ajax
setTimeout(() => {
mediaRecorder.stop();
}, 10000);
} catch (error) {
console.log("Microphone error:", error);
$('.listn').html('Microphone access denied.');
}
}
async function sendAudioChunk(audioBlob) {
const formData = new FormData();
formData.append("audio_chunk", audioBlob);
try {
$('.listn').html('Processing...');
const response = await fetch(baseURL + "process_audio", {
method: "POST",
body: formData
});
const result = await response.json();
if (result.audio_content && result.transcription) {
playAudioFromBase64(result.audio_content, result.chat_response);
} else {
console.log("Unable to generate response.");
}
} catch (error) {
console.log("Error sending audio chunk to server:", error);
}
}
function playAudioFromBase64(base64Audio, transcription) {
$(".index2").hide();
$(".index3").show();
audio = new Audio("data:audio/mp3;base64," + base64Audio);
audio.play();
// Visualizer initialization before audio starts
startVisualizer();
audio.addEventListener('loadedmetadata', () => {
const duration = audio.duration;
typewriterEffect(transcription, duration);
});
audio.addEventListener('ended', () => {
$(".index3").show();
$("#index3").hide();
$(".index4").show();
});
}
function typewriterEffect(text, audioDuration) {
$('#index3').show();
$('#txtResponse').text('');
const words = text.split(' ');
let index = 0;
const numWords = words.length;
// Calculate interval based on the audio duration
let interval = audioDuration / numWords;
// Set a moderate minimum interval (500ms between words)
interval = Math.max(interval, 500); // Minimum interval of 500ms per word
// Set a moderately fast typing speed with a minimum interval of 250ms
const minInterval = 250; // Moderate typing effect with a minimum interval of 250ms
typingInterval = setInterval(() => {
if (index < numWords) {
$('#txtResponse').append(words[index] + " ");
index++;
} else {
clearInterval(typingInterval); // Stop typing effect once all words are typed
}
}, Math.max(minInterval, interval));
}
// Stop button to stop audio recording
stopButton.on("click", () => {
stopAudioAndReset();
});
// Stop and reset the audio recording process
function stopAudioAndReset() {
$('#index3').hide();
$('.index4').show();
if (audio) {
audio.pause();
audio.currentTime = 0;
}
if (typingInterval) {
clearInterval(typingInterval); // Clear the typing effect interval here
}
// $('#txtResponse').text(''); // Optionally, clear the text on reset
}
// Handle click on individual <p> tags inside .bottomroundbtns
bottomroundbtns.on("click", "p", async function() {
$(".index1").hide();
$(".index2").show();
$(".listn").html('Processing...');
const text = $(this).text();
$.ajax({
url: baseURL + "process_audio",
type: "POST",
dataType: "json",
data: {
transcription_text: text
},
success: function(result) {
if (result.audio_content && result.transcription) {
playAudioFromBase64(result.audio_content, result.chat_response);
$('#index2').hide();
$('#index3').show();
} else {
console.log("Unable to generate response.");
}
},
error: function(err) {
console.log("AJAX error:", err);
}
});
});
// visualizer function
navigator.mediaDevices.getUserMedia({
audio: true
})
.then(function(stream) {
const audioContext = new(window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser();
const microphone = audioContext.createMediaStreamSource(stream);
microphone.connect(analyser);
analyser.fftSize = 512;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const canvas = document.getElementById('visualizer');
const canvasCtx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const globeRadius = Math.min(canvas.width, canvas.height) / 3.5; // Increase radius for full-screen
const points = []; // Points on the globe
const numPoints = 800; // Increase number of points for density
// Create 3D points on the globe
for (let i = 0; i < numPoints; i++) {
const theta = Math.random() * Math.PI * 2; // Random angle around the z-axis
const phi = Math.acos((Math.random() * 2) - 1); // Random angle from z-axis
const x = globeRadius * Math.sin(phi) * Math.cos(theta);
const y = globeRadius * Math.sin(phi) * Math.sin(theta);
const z = globeRadius * Math.cos(phi);
points.push({
x,
y,
z
});
}
function animateVisualizer() {
requestAnimationFrame(animateVisualizer);
// Get audio data
analyser.getByteFrequencyData(dataArray);
// Clear the canvas
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
// Rotate points and draw the globe
for (let i = 0; i < points.length; i++) {
const point = points[i];
// Rotate around the Y-axis
const angleY = 0.005; // Slower rotation for smoother effect
const sinY = Math.sin(angleY);
const cosY = Math.cos(angleY);
const xRot = point.x * cosY - point.z * sinY;
const zRot = point.x * sinY + point.z * cosY;
point.x = xRot;
point.z = zRot;
// Project 3D point to 2D space
const scale = globeRadius / (globeRadius + point.z);
const x2d = point.x * scale + canvas.width / 2;
const y2d = point.y * scale + canvas.height / 2;
// Use audio data to modulate the brightness and size of the points
const barHeight = dataArray[i % bufferLength];
const size = Math.max(2, barHeight / 40); // Size changes based on amplitude
const alpha = Math.max(0.2, barHeight / 200); // Modulate opacity for a pulse effect
// Add glow effect for brightness
canvasCtx.shadowBlur = barHeight > 20 ? size * 4 : size * 2; // Brighten on sound
canvasCtx.shadowColor = `rgba(255, 50, 50, ${alpha})`; // Bright red glow color
// Draw the point
canvasCtx.beginPath();
canvasCtx.arc(x2d, y2d, size, 0, Math.PI * 2);
canvasCtx.fillStyle = `rgba(255, 50, 50, ${alpha})`; // Bright red color
canvasCtx.fill();
}
}
animateVisualizer();
})
.catch(function(err) {
console.log('Error accessing microphone: ', err);
});
/**
* start the visualizer once the audio starts playing
*/
function startVisualizer() {
if (!audio) return;
const audioContext = new(window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser();
const source = audioContext.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 512;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const canvas = document.getElementById('visualizer');
const canvasCtx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const globeRadius = Math.min(canvas.width, canvas.height) / 3.5;
const points = [];
const numPoints = 800;
// Create 3D points
for (let i = 0; i < numPoints; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos((Math.random() * 2) - 1);
const x = globeRadius * Math.sin(phi) * Math.cos(theta);
const y = globeRadius * Math.sin(phi) * Math.sin(theta);
const z = globeRadius * Math.cos(phi);
points.push({
x,
y,
z
});
}
// Enhanced animation function with real-time frequency analysis
function animateVisualizer() {
requestAnimationFrame(animateVisualizer);
analyser.getByteFrequencyData(dataArray); // Get real-time frequency data
canvasCtx.clearRect(0, 0, canvas.width, canvas.height); // Clear previous frame
// Rotate and project points with dynamic effects based on frequency data
for (let i = 0; i < points.length; i++) {
const point = points[i];
const angleY = 0.005;
const sinY = Math.sin(angleY);
const cosY = Math.cos(angleY);
const xRot = point.x * cosY - point.z * sinY;
const zRot = point.x * sinY + point.z * cosY;
point.x = xRot;
point.z = zRot;
const scale = globeRadius / (globeRadius + point.z);
const x2d = point.x * scale + canvas.width / 2;
const y2d = point.y * scale + canvas.height / 2;
const barHeight = dataArray[i % bufferLength];
const size = Math.max(2, barHeight / 40);
const alpha = Math.max(0.2, barHeight / 200);
canvasCtx.shadowBlur = barHeight > 20 ? size * 4 : size * 2;
canvasCtx.shadowColor = `rgba(255, 50, 50, ${alpha})`;
canvasCtx.beginPath();
canvasCtx.arc(x2d, y2d, size, 0, Math.PI * 2);
canvasCtx.fillStyle = `rgba(255, 50, 50, ${alpha})`;
canvasCtx.fill();
}
}
animateVisualizer(); // Start the animation loop
}
});