improvement: add server renderd TORN Server cred and minor improvements #17
@ -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,28 +2161,59 @@ 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.getVideoTracks().forEach(track => {
|
||||||
interviewerLocalStream.getAudioTracks().forEach(track => {
|
if (track.readyState === 'live') {
|
||||||
if (track.readyState === 'live' && !streamToRecord.getAudioTracks().includes(track)) {
|
|
||||||
try { streamToRecord.addTrack(track); } catch(e) {}
|
try { streamToRecord.addTrack(track); } catch(e) {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (streamToRecord.getVideoTracks().length === 0) {
|
}
|
||||||
interviewerLocalStream.getVideoTracks().forEach(track => {
|
|
||||||
if (track.readyState === 'live') {
|
// 2. Mix Multiple Audio Tracks into a SINGLE Audio Track via Web Audio API
|
||||||
try { streamToRecord.addTrack(track); } catch(e) {}
|
// (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) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user