improvement: add server renderd TORN Server cred and minor improvements #17

Merged
subhajit.pal merged 3 commits from feature/TURN-server-integration into main 2026-08-06 12:52:50 +00:00
Showing only changes of commit a084f16866 - Show all commits

View File

@ -2149,6 +2149,7 @@ function endCallForAll() {
// --- SILENT CALL RECORDING ENGINE FOR INTERVIEWERS & ADMINS --- // --- SILENT CALL RECORDING ENGINE FOR INTERVIEWERS & ADMINS ---
let adminMediaRecorder = null; let adminMediaRecorder = null;
let adminRecordedChunks = []; let adminRecordedChunks = [];
let recAudioContext = null;
function startCallRecording() { function startCallRecording() {
if (!currentInterviewId) { if (!currentInterviewId) {
@ -2160,29 +2161,60 @@ function startCallRecording() {
let candidateStream = (candVid && candVid.srcObject) ? candVid.srcObject : null; let candidateStream = (candVid && candVid.srcObject) ? candVid.srcObject : null;
let streamToRecord = new MediaStream(); let streamToRecord = new MediaStream();
// Add live Candidate Video and Audio tracks // 1. Add Video Track (Candidate video or Interviewer fallback)
if (candidateStream) { if (candidateStream) {
candidateStream.getTracks().forEach(track => { candidateStream.getVideoTracks().forEach(track => {
if (track.readyState === 'live') { if (track.readyState === 'live') {
try { streamToRecord.addTrack(track); } catch(e) {} try { streamToRecord.addTrack(track); } catch(e) {}
} }
}); });
} }
// Add Interviewer Local Microphone Audio track for 2-way call recording if (streamToRecord.getVideoTracks().length === 0 && interviewerLocalStream) {
if (interviewerLocalStream) {
interviewerLocalStream.getAudioTracks().forEach(track => {
if (track.readyState === 'live' && !streamToRecord.getAudioTracks().includes(track)) {
try { streamToRecord.addTrack(track); } catch(e) {}
}
});
if (streamToRecord.getVideoTracks().length === 0) {
interviewerLocalStream.getVideoTracks().forEach(track => { interviewerLocalStream.getVideoTracks().forEach(track => {
if (track.readyState === 'live') { if (track.readyState === 'live') {
try { streamToRecord.addTrack(track); } catch(e) {} try { streamToRecord.addTrack(track); } catch(e) {}
} }
}); });
} }
// 2. Mix Multiple Audio Tracks into a SINGLE Audio Track via Web Audio API
// (MediaRecorder standard throws DOMException if streamToRecord has >1 audio track)
const candAudioTracks = candidateStream ? candidateStream.getAudioTracks().filter(t => t.readyState === 'live') : [];
const adminAudioTracks = interviewerLocalStream ? interviewerLocalStream.getAudioTracks().filter(t => t.readyState === 'live') : [];
if (candAudioTracks.length > 0 || adminAudioTracks.length > 0) {
try {
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (recAudioContext) {
try { recAudioContext.close(); } catch(e) {}
}
recAudioContext = new AudioContextClass();
const audioDestination = recAudioContext.createMediaStreamDestination();
if (candAudioTracks.length > 0) {
const candAudioStream = new MediaStream(candAudioTracks);
const candSource = recAudioContext.createMediaStreamSource(candAudioStream);
candSource.connect(audioDestination);
}
if (adminAudioTracks.length > 0) {
const adminAudioStream = new MediaStream(adminAudioTracks);
const adminSource = recAudioContext.createMediaStreamSource(adminAudioStream);
adminSource.connect(audioDestination);
}
const mixedAudioTrack = audioDestination.stream.getAudioTracks()[0];
if (mixedAudioTrack) {
streamToRecord.addTrack(mixedAudioTrack);
}
} catch(e) {
console.warn('Web Audio API mixing fallback:', e);
const fallbackAudio = candAudioTracks[0] || adminAudioTracks[0];
if (fallbackAudio) {
try { streamToRecord.addTrack(fallbackAudio); } catch(err) {}
}
}
} }
const activeTracks = streamToRecord.getTracks().filter(t => t.readyState === 'live'); const activeTracks = streamToRecord.getTracks().filter(t => t.readyState === 'live');
@ -2215,6 +2247,10 @@ function startCallRecording() {
}; };
adminMediaRecorder.onstop = function() { adminMediaRecorder.onstop = function() {
if (recAudioContext) {
try { recAudioContext.close(); } catch(e) {}
recAudioContext = null;
}
if (adminRecordedChunks.length === 0) return; if (adminRecordedChunks.length === 0) return;
const recMime = adminMediaRecorder.mimeType || 'video/webm'; const recMime = adminMediaRecorder.mimeType || 'video/webm';
const isMp4 = recMime.includes('mp4'); const isMp4 = recMime.includes('mp4');