[mirotalksfu] - #33 recording all audioTracks
هذا الالتزام موجود في:
82
public/js/Helpers.js
Normal file
82
public/js/Helpers.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
class MixedAudioRecorder {
|
||||||
|
constructor(useGainNode = true) {
|
||||||
|
this.useGainNode = useGainNode;
|
||||||
|
this.gainNode = null;
|
||||||
|
this.audioSources = [];
|
||||||
|
this.audioSource = null;
|
||||||
|
this.audioDestination = null;
|
||||||
|
this.audioContext = this.createAudioContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
createAudioContext() {
|
||||||
|
if (window.AudioContext) {
|
||||||
|
return new AudioContext();
|
||||||
|
} else if (window.webkitAudioContext) {
|
||||||
|
return new webkitAudioContext();
|
||||||
|
} else if (window.mozAudioContext) {
|
||||||
|
return new mozAudioContext();
|
||||||
|
} else {
|
||||||
|
throw new Error('Web Audio API is not supported in this browser');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMixedAudioStream(audioStreams) {
|
||||||
|
this.audioSources = [];
|
||||||
|
|
||||||
|
if (this.useGainNode) {
|
||||||
|
this.gainNode = this.audioContext.createGain();
|
||||||
|
this.gainNode.connect(this.audioContext.destination);
|
||||||
|
this.gainNode.gain.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioStreams.forEach((stream) => {
|
||||||
|
if (!stream.getTracks().filter((t) => t.kind === 'audio').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let audioSource = this.audioContext.createMediaStreamSource(stream);
|
||||||
|
|
||||||
|
if (this.useGainNode) {
|
||||||
|
audioSource.connect(this.gainNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.audioSources.push(audioSource);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.audioDestination = this.audioContext.createMediaStreamDestination();
|
||||||
|
this.audioSources.forEach((source) => {
|
||||||
|
source.connect(this.audioDestination);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.audioDestination.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopMixedAudioStream() {
|
||||||
|
if (this.useGainNode) {
|
||||||
|
this.gainNode.disconnect();
|
||||||
|
this.gainNode = null;
|
||||||
|
}
|
||||||
|
if (this.audioSources.length) {
|
||||||
|
this.audioSources.forEach((source) => {
|
||||||
|
source.disconnect();
|
||||||
|
});
|
||||||
|
this.audioSources = [];
|
||||||
|
}
|
||||||
|
if (this.audioDestination) {
|
||||||
|
this.audioDestination.disconnect();
|
||||||
|
this.audioDestination = null;
|
||||||
|
}
|
||||||
|
if (this.audioContext) {
|
||||||
|
this.audioContext.close();
|
||||||
|
this.audioContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
// const audioRecorder = new MixedAudioRecorder();
|
||||||
|
// To start recording, call audioRecorder.getMixedAudioStream(audioStreams);
|
||||||
|
// To stop recording, call audioRecorder.stopMixedAudioStream();
|
||||||
|
// Credits: https://github.com/muaz-khan/MultiStreamsMixer
|
||||||
@@ -116,7 +116,9 @@ const _EVENTS = {
|
|||||||
hostOnlyRecordingOff: 'hostOnlyRecordingOff',
|
hostOnlyRecordingOff: 'hostOnlyRecordingOff',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Recording
|
||||||
let recordedBlobs;
|
let recordedBlobs;
|
||||||
|
|
||||||
class RoomClient {
|
class RoomClient {
|
||||||
constructor(
|
constructor(
|
||||||
localAudioEl,
|
localAudioEl,
|
||||||
@@ -204,6 +206,8 @@ class RoomClient {
|
|||||||
this.fileSharingInput = '*';
|
this.fileSharingInput = '*';
|
||||||
this.chunkSize = 1024 * 16; // 16kb/s
|
this.chunkSize = 1024 * 16; // 16kb/s
|
||||||
|
|
||||||
|
this.audioRecorder = null;
|
||||||
|
|
||||||
// Encodings
|
// Encodings
|
||||||
this.forceVP8 = false; // Force VP8 codec for webcam and screen sharing
|
this.forceVP8 = false; // Force VP8 codec for webcam and screen sharing
|
||||||
this.forceVP9 = false; // Force VP9 codec for webcam and screen sharing
|
this.forceVP9 = false; // Force VP9 codec for webcam and screen sharing
|
||||||
@@ -373,10 +377,10 @@ class RoomClient {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name === 'UnsupportedError') {
|
if (error.name === 'UnsupportedError') {
|
||||||
console.error('Browser not supported');
|
console.error('Browser not supported');
|
||||||
this.userLog('error', 'Browser not supported', 'center');
|
this.userLog('error', 'Browser not supported', 'center', 6000);
|
||||||
}
|
}
|
||||||
console.error('Browser not supported: ', error);
|
console.error('Browser not supported: ', error);
|
||||||
this.userLog('error', 'Browser not supported: ' + error, 'center');
|
this.userLog('error', 'Browser not supported: ' + error, 'center', 6000);
|
||||||
}
|
}
|
||||||
await device.load({
|
await device.load({
|
||||||
routerRtpCapabilities,
|
routerRtpCapabilities,
|
||||||
@@ -2139,14 +2143,14 @@ class RoomClient {
|
|||||||
if (err.name === 'SecurityError')
|
if (err.name === 'SecurityError')
|
||||||
errorMessage = `You need to use HTTPS for selecting audio output device: ${err}`;
|
errorMessage = `You need to use HTTPS for selecting audio output device: ${err}`;
|
||||||
console.error('Attach SinkId error: ', errorMessage);
|
console.error('Attach SinkId error: ', errorMessage);
|
||||||
this.userLog('error', errorMessage, 'top-end');
|
this.userLog('error', errorMessage, 'top-end', 6000);
|
||||||
speakerSelect.selectedIndex = 0;
|
speakerSelect.selectedIndex = 0;
|
||||||
lS.setLocalStorageDevices(lS.MEDIA_TYPE.speaker, 0, speakerSelect.value);
|
lS.setLocalStorageDevices(lS.MEDIA_TYPE.speaker, 0, speakerSelect.value);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let error = `Browser seems doesn't support output device selection.`;
|
let error = `Browser seems doesn't support output device selection.`;
|
||||||
console.warn(error);
|
console.warn(error);
|
||||||
this.userLog('error', error, 'top-end');
|
this.userLog('error', error, 'top-end', 6000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3093,7 +3097,7 @@ class RoomClient {
|
|||||||
this.userLog('success', 'Message copied!', 'top-end', 1000);
|
this.userLog('success', 'Message copied!', 'top-end', 1000);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.userLog('error', err, 'top-end', 2000);
|
this.userLog('error', err, 'top-end', 6000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3299,20 +3303,21 @@ class RoomClient {
|
|||||||
console.log('MediaRecorder supported options', options);
|
console.log('MediaRecorder supported options', options);
|
||||||
options = { mimeType: options[0] };
|
options = { mimeType: options[0] };
|
||||||
try {
|
try {
|
||||||
// get all participants audio tracks
|
this.audioRecorder = new MixedAudioRecorder();
|
||||||
const audioTracks = this.getAudioTracksFromAudioElements();
|
const audioStreams = this.getAudioStreamFromAudioElements();
|
||||||
//
|
const audioMixerStreams = this.audioRecorder.getMixedAudioStream([audioStreams, this.localAudioStream]);
|
||||||
|
const audioMixerTracks = audioMixerStreams.getTracks();
|
||||||
|
|
||||||
if (this.isMobileDevice) {
|
if (this.isMobileDevice) {
|
||||||
const videoTracks = this.localVideoStream.getTracks();
|
const videoTracks = this.localVideoStream.getTracks();
|
||||||
console.log('INIT CAM RECORDING', {
|
console.log('INIT CAM RECORDING', {
|
||||||
localAudioStream: this.localAudioStream,
|
localAudioStream: this.localAudioStream,
|
||||||
localVideoStream: this.localVideoStream,
|
localVideoStream: this.localVideoStream,
|
||||||
videoTracks: videoTracks,
|
videoTracks: videoTracks,
|
||||||
audioTracks: audioTracks,
|
audioMixerTracks: audioMixerTracks,
|
||||||
});
|
});
|
||||||
// on mobile devices recording camera + all audio tracks
|
// on mobile devices recording camera + all audio tracks
|
||||||
let newStream = new MediaStream([...videoTracks, ...audioTracks]);
|
let newStream = new MediaStream([...videoTracks, ...audioMixerTracks]);
|
||||||
// TODO not recording all audio tracks
|
|
||||||
console.log('New Cam Media Stream ---> ', newStream.getTracks());
|
console.log('New Cam Media Stream ---> ', newStream.getTracks());
|
||||||
this.mediaRecorder = new MediaRecorder(newStream, options);
|
this.mediaRecorder = new MediaRecorder(newStream, options);
|
||||||
console.log('Created MediaRecorder', this.mediaRecorder, 'with options', options);
|
console.log('Created MediaRecorder', this.mediaRecorder, 'with options', options);
|
||||||
@@ -3332,10 +3337,9 @@ class RoomClient {
|
|||||||
localAudioStream: this.localAudioStream,
|
localAudioStream: this.localAudioStream,
|
||||||
localVideoStream: this.localVideoStream,
|
localVideoStream: this.localVideoStream,
|
||||||
screenTracks: screenTracks,
|
screenTracks: screenTracks,
|
||||||
audioTracks: audioTracks,
|
audioMixerTracks: audioMixerTracks,
|
||||||
});
|
});
|
||||||
this.recScreenStream = new MediaStream([...screenTracks, ...audioTracks]);
|
this.recScreenStream = new MediaStream([...screenTracks, ...audioMixerTracks]);
|
||||||
// TODO not recording all audio tracks
|
|
||||||
console.log('New Screen/Window Media Stream ---> ', this.recScreenStream.getTracks());
|
console.log('New Screen/Window Media Stream ---> ', this.recScreenStream.getTracks());
|
||||||
this.mediaRecorder = new MediaRecorder(this.recScreenStream, options);
|
this.mediaRecorder = new MediaRecorder(this.recScreenStream, options);
|
||||||
console.log('Created MediaRecorder', this.mediaRecorder, 'with options', options);
|
console.log('Created MediaRecorder', this.mediaRecorder, 'with options', options);
|
||||||
@@ -3347,15 +3351,25 @@ class RoomClient {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('Error Unable to recording the screen + audio', err);
|
console.error('Error Unable to recording the screen + audio', err);
|
||||||
this.userLog('error', 'Unable to recording the screen + audio reason: ' + err, 'top-end');
|
this.userLog('error', 'Unable to recording the screen + audio reason: ' + err, 'top-end', 6000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Exception while creating MediaRecorder: ', err);
|
console.error('Exception while creating MediaRecorder: ', err);
|
||||||
return this.userLog('error', "Can't start stream recording reason: " + err, 'top-end');
|
return this.userLog('error', "Can't start stream recording reason: " + err, 'top-end', 6000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAudioTrack(mediaStream) {
|
||||||
|
const audioTracks = mediaStream.getAudioTracks();
|
||||||
|
return audioTracks.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVideoTrack(mediaStream) {
|
||||||
|
const videoTracks = mediaStream.getVideoTracks();
|
||||||
|
return videoTracks.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
getAudioTracksFromAudioElements() {
|
getAudioTracksFromAudioElements() {
|
||||||
const audioElements = document.querySelectorAll('audio');
|
const audioElements = document.querySelectorAll('audio');
|
||||||
const audioTracks = [];
|
const audioTracks = [];
|
||||||
@@ -3482,6 +3496,7 @@ class RoomClient {
|
|||||||
}
|
}
|
||||||
if (this.isMobileDevice) this.getId('swapCameraButton').className = '';
|
if (this.isMobileDevice) this.getId('swapCameraButton').className = '';
|
||||||
this.event(_EVENTS.stopRec);
|
this.event(_EVENTS.stopRec);
|
||||||
|
this.audioRecorder.stopMixedAudioStream();
|
||||||
this.recordingAction('Stop recording');
|
this.recordingAction('Stop recording');
|
||||||
this.sound('recStop');
|
this.sound('recStop');
|
||||||
}
|
}
|
||||||
@@ -3898,7 +3913,7 @@ class RoomClient {
|
|||||||
this.socket.emit('shareVideoAction', data);
|
this.socket.emit('shareVideoAction', data);
|
||||||
this.openVideo(data);
|
this.openVideo(data);
|
||||||
} else {
|
} else {
|
||||||
this.userLog('error', 'Not valid video URL', 'top-end');
|
this.userLog('error', 'Not valid video URL', 'top-end', 6000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
<script defer src="https://rawgit.com/leizongmin/js-xss/master/dist/xss.js"></script>
|
<script defer src="https://rawgit.com/leizongmin/js-xss/master/dist/xss.js"></script>
|
||||||
<script defer src="../js/LocalStorage.js"></script>
|
<script defer src="../js/LocalStorage.js"></script>
|
||||||
<script defer src="../js/Rules.js"></script>
|
<script defer src="../js/Rules.js"></script>
|
||||||
|
<script defer src="../js/Helpers.js"></script>
|
||||||
<script defer src="../js/Room.js"></script>
|
<script defer src="../js/Room.js"></script>
|
||||||
<script defer src="../js/RoomClient.js"></script>
|
<script defer src="../js/RoomClient.js"></script>
|
||||||
<script defer src="../js/SpeechRec.js"></script>
|
<script defer src="../js/SpeechRec.js"></script>
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم