[mirotalk] - #150 allow Join room without audio/video

This commit is contained in:
Miroslav Pejic
2023-10-10 08:09:55 +02:00
parent a7eddddb16
commit 803e10565d
3 changed files with 174 additions and 76 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ dependencies: {
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.0.8
* @version 1.0.9
*
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mirotalk",
"version": "1.0.8",
"version": "1.0.9",
"description": "A free WebRTC browser-based video call",
"main": "server.js",
"scripts": {
+172 -74
View File
@@ -15,7 +15,7 @@
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.0.8
* @version 1.0.9
*
*/
@@ -830,8 +830,8 @@ function refreshMainButtonsToolTipPlacement() {
placement = btnsBarSelect.options[btnsBarSelect.selectedIndex].value == 'vertical' ? 'right' : 'top';
setTippy(shareRoomBtn, 'Invite others to join', placement);
setTippy(hideMeBtn, 'Toggle hide myself from the room view', placement);
setTippy(audioBtn, 'Stop the audio', placement);
setTippy(videoBtn, 'Stop the video', placement);
setTippy(audioBtn, useAudio ? 'Stop the audio' : 'My audio is disabled', placement);
setTippy(videoBtn, useVideo ? 'Stop the video' : 'My video is disabled', placement);
setTippy(screenShareBtn, 'Start screen sharing', placement);
setTippy(recordStreamBtn, 'Start recording', placement);
setTippy(fullScreenBtn, 'View full screen', placement);
@@ -1132,6 +1132,9 @@ async function handleConnect() {
await initEnumerateDevices();
await setupLocalVideoMedia();
await setupLocalAudioMedia();
if (!useVideo || (!useVideo && !useAudio)) {
await loadLocalMedia(new MediaStream(), 'video');
}
getHtmlElementsById();
setButtonsToolTip();
manageLeftButtons();
@@ -1305,14 +1308,14 @@ async function whoAreYou() {
await loadLocalStorage();
if (!buttons.main.showVideoBtn) {
if (!useVideo || !buttons.main.showVideoBtn) {
useVideo = false;
elemDisplay(document.getElementById('initVideo'), false);
elemDisplay(document.getElementById('initVideoBtn'), false);
elemDisplay(document.getElementById('initVideoSelect'), false);
elemDisplay(document.getElementById('tabVideoBtn'), false);
}
if (!buttons.main.showAudioBtn) {
if (!useAudio || !buttons.main.showAudioBtn) {
//useAudio = false;
elemDisplay(document.getElementById('initAudioBtn'), false);
elemDisplay(document.getElementById('initMicrophoneSelect'), false);
@@ -1552,14 +1555,14 @@ function checkPeerAudioVideo() {
let video = filterXSS(qs.get('video'));
if (audio) {
audio = audio.toLowerCase();
let queryPeerAudio = audio === '1' || audio === 'true';
let queryPeerAudio = useAudio ? audio === '1' || audio === 'true' : false;
if (queryPeerAudio != null) handleAudio(audioBtn, false, queryPeerAudio);
elemDisplay(document.getElementById('tabAudioBtn'), queryPeerAudio);
console.log('Direct join', { audio: queryPeerAudio });
}
if (video) {
video = video.toLowerCase();
let queryPeerVideo = video === '1' || video === 'true';
let queryPeerVideo = useVideo ? video === '1' || video === 'true' : false;
if (queryPeerVideo != null) handleVideo(videoBtn, false, queryPeerVideo);
elemDisplay(document.getElementById('tabVideoBtn'), queryPeerVideo);
console.log('Direct join', { video: queryPeerVideo });
@@ -1662,6 +1665,10 @@ async function handleAddPeer(config) {
});
}
if (!peer_video) {
await loadRemoteMediaStream(new MediaStream(), peers, peer_id, 'video');
}
await wbUpdate();
playSound('addPeer');
}
@@ -1714,8 +1721,10 @@ async function handleOnTrack(peer_id, peers) {
peerConnections[peer_id].ontrack = (event) => {
const remoteVideoStream = getId(`${peer_id}_video`);
const remoteAudioStream = getId(`${peer_id}_audio`);
const remoteAvatarImage = getId(`${peer_id}_avatar`);
const peerInfo = peers[peer_id];
const { peer_name } = peerInfo;
const { peer_name, peer_video } = peerInfo;
const { kind } = event.track;
console.log('[ON TRACK] - info', { peer_id, peer_name, kind });
@@ -1734,12 +1743,16 @@ async function handleOnTrack(peer_id, peers) {
? attachMediaStream(remoteAudioStream, event.streams[0])
: loadRemoteMediaStream(event.streams[0], peers, peer_id, kind);
break;
case 'screen':
// Currently replace the video track and vice versa
break;
default:
break;
}
} else {
console.log('[ON TRACK] - SCREEN SHARING', { peer_id, peer_name, kind });
// Create a new screen share video stream from track video (refreshMyStreamToPeers)
const inboundStream = new MediaStream([event.track]);
attachMediaStream(remoteVideoStream, inboundStream);
remoteAvatarImage.style.display = 'none';
remoteVideoStream.style.display = 'block';
}
};
}
@@ -2178,27 +2191,6 @@ async function initEnumerateDevices() {
console.log('05. init Enumerate Devices');
await initEnumerateVideoDevices();
await initEnumerateAudioDevices();
if (!useAudio && !useVideo) {
initEnumerateDevicesFailed = true;
playSound('alert');
await Swal.fire({
allowOutsideClick: false,
allowEscapeKey: false,
background: '#000000',
position: 'center',
imageUrl: camMicOff,
title: 'Camera and microphone not allowed',
text: "Meet needs access to the camera and microphone. Click the locked camera and microphone icon in your browser's address bar, before to join room.",
showDenyButton: false,
confirmButtonText: `OK`,
showClass: { popup: 'animate__animated animate__fadeInDown' },
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
}).then((result) => {
if (result.isConfirmed) {
openURL('/'); // back to homepage
}
});
}
}
/**
@@ -2343,7 +2335,7 @@ function addChild(device, els) {
* https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
*/
async function setupLocalVideoMedia() {
if (localVideoMediaStream || initEnumerateDevicesFailed) {
if (!useVideo || localVideoMediaStream || initEnumerateDevicesFailed) {
return;
}
@@ -2355,6 +2347,7 @@ async function setupLocalVideoMedia() {
const stream = await navigator.mediaDevices.getUserMedia({ video: videoConstraints });
if (stream) {
await loadLocalMedia(stream, 'video');
console.log('10. Access granted to video device');
}
} catch (err) {
handleMediaError('video', err);
@@ -2367,7 +2360,7 @@ async function setupLocalVideoMedia() {
* https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
*/
async function setupLocalAudioMedia() {
if (localAudioMediaStream || initEnumerateDevicesFailed) {
if (!useAudio || localAudioMediaStream || initEnumerateDevicesFailed) {
return;
}
@@ -2381,6 +2374,7 @@ async function setupLocalAudioMedia() {
await loadLocalMedia(stream, 'audio');
if (useAudio) {
await getMicrophoneVolumeIndicator(stream);
console.log('10. Access granted to audio device');
}
}
} catch (err) {
@@ -2395,10 +2389,6 @@ async function setupLocalAudioMedia() {
*/
function handleMediaError(mediaType, err) {
console.error(`[Error] - Access denied for ${mediaType} device`, err);
playSound('alert');
openURL(
`/permission?roomId=${roomId}&getUserMediaError=${err.toString()} <br/> Check the common getusermedia errors <a href="https://blog.addpipe.com/common-getusermedia-errors" target="_blank">here</a>`,
);
}
/**
@@ -2406,9 +2396,7 @@ function handleMediaError(mediaType, err) {
* @param {object} stream media stream audio - video
*/
async function loadLocalMedia(stream, kind) {
console.log(`10. Access granted to ${kind} device`);
console.log('LOAD LOCAL MEDIA STREAM TRACKS', stream.getTracks());
if (stream) console.log('LOAD LOCAL MEDIA STREAM TRACKS', stream.getTracks());
switch (kind) {
case 'video':
@@ -2604,6 +2592,18 @@ async function loadLocalMedia(stream, kind) {
myVideoAvatarImage.style.display = 'block';
myVideoStatusIcon.className = className.videoOff;
videoBtn.className = className.videoOff;
if (!isMobileDevice) {
setTippy(myVideoStatusIcon, 'My video is disabled', 'bottom');
}
}
if (!useAudio) {
const audioBtn = getId('audioBtn');
myAudioStatusIcon.className = className.audioOff;
audioBtn.className = className.audioOff;
if (!isMobileDevice) {
setTippy(myAudioStatusIcon, 'My audio is disabled', 'bottom');
}
}
break;
case 'audio':
@@ -2667,6 +2667,7 @@ async function loadRemoteMediaStream(stream, peers, peer_id, kind) {
console.log('REMOTE PEER INFO', peers[peer_id]);
const peer_name = peers[peer_id]['peer_name'];
const peer_audio = peers[peer_id]['peer_audio'];
const peer_video = peers[peer_id]['peer_video'];
const peer_video_status = peers[peer_id]['peer_video_status'];
const peer_audio_status = peers[peer_id]['peer_audio_status'];
@@ -2675,7 +2676,7 @@ async function loadRemoteMediaStream(stream, peers, peer_id, kind) {
const peer_rec_status = peers[peer_id]['peer_rec_status'];
const peer_privacy_status = peers[peer_id]['peer_privacy_status'];
console.log('LOAD REMOTE MEDIA STREAM TRACKS - PeerName:[' + peer_name + ']', stream.getTracks());
if (stream) console.log('LOAD REMOTE MEDIA STREAM TRACKS - PeerName:[' + peer_name + ']', stream.getTracks());
switch (kind) {
case 'video':
@@ -2831,7 +2832,7 @@ async function loadRemoteMediaStream(stream, peers, peer_id, kind) {
remoteVideoNavBar.appendChild(remoteVideoStatusIcon);
remoteVideoNavBar.appendChild(remoteAudioStatusIcon);
if (buttons.remote.showAudioVolume) {
if (peer_audio && buttons.remote.showAudioVolume) {
remoteVideoNavBar.appendChild(remoteAudioVolume);
}
remoteVideoNavBar.appendChild(remoteHandStatusIcon);
@@ -3616,6 +3617,7 @@ function getTimeToString(time) {
* @param {MediaStream} localVideoMediaStream
*/
function refreshMyVideoStatus(localVideoMediaStream) {
if (!localVideoMediaStream) return;
// check Track video status
localVideoMediaStream.getTracks().forEach((track) => {
if (track.kind === 'video') {
@@ -3629,6 +3631,7 @@ function refreshMyVideoStatus(localVideoMediaStream) {
* @param {MediaStream} localAudioMediaStream
*/
function refreshMyAudioStatus(localAudioMediaStream) {
if (!localAudioMediaStream) return;
// check Track audio status
localAudioMediaStream.getTracks().forEach((track) => {
if (track.kind === 'audio') {
@@ -4822,6 +4825,7 @@ function handleError(err) {
* @param {object} stream media stream audio - video
*/
function attachMediaStream(element, stream) {
if (!element || !stream) return;
//console.log("DEPRECATED, attachMediaStream will soon be removed.");
element.srcObject = stream;
console.log('Success, media stream attached', stream.getTracks());
@@ -5111,28 +5115,33 @@ async function stopLocalAudioTrack() {
}
/**
* Enable - disable screen sharing
* https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
* @param {boolean} init screen before join
* Toggle screen sharing and handle related actions
* @param {boolean} init - Indicates if it's the initial screen share state
*/
async function toggleScreenSharing(init = false) {
screenMaxFrameRate = parseInt(screenFpsSelect.value);
const constraints = {
audio: false, // enable/disable tab audio
video: { frameRate: { max: screenMaxFrameRate } },
}; // true | { frameRate: { max: screenMaxFrameRate } }
let screenMediaPromise = null;
try {
// Going to save webcam video status before to screen share
// Set screen frame rate
screenMaxFrameRate = parseInt(screenFpsSelect.value);
const constraints = {
audio: false,
video: { frameRate: { max: screenMaxFrameRate } },
};
// Store webcam video status before screen sharing
if (!isScreenStreaming) {
myVideoStatusBefore = myVideoStatus;
console.log('My video status before screen sharing: ' + myVideoStatusBefore);
} else {
if (!useVideo && !useAudio) {
return handleToggleScreenException('Audio and Video are disabled', init);
}
}
screenMediaPromise = isScreenStreaming
// Get screen or webcam media stream based on current state
const screenMediaPromise = isScreenStreaming
? await navigator.mediaDevices.getUserMedia(await getAudioVideoConstraints())
: await navigator.mediaDevices.getDisplayMedia(constraints);
if (screenMediaPromise) {
isVideoPrivacyActive = false;
emitPeerStatus('privacy', isVideoPrivacyActive);
@@ -5148,13 +5157,14 @@ async function toggleScreenSharing(init = false) {
adaptAspectRatio();
await refreshConstraints(screenMediaPromise, videoMaxFrameRate);
}
await emitPeerStatus('screen', myScreenStatus);
await stopLocalVideoTrack();
await refreshMyLocalStream(screenMediaPromise);
await refreshMyStreamToPeers(screenMediaPromise);
if (init) {
// handle init media stream
// Handle init media stream
if (initStream) stopTracks(initStream);
initStream = screenMediaPromise;
if (hasVideoTrack(initStream)) {
@@ -5171,23 +5181,82 @@ async function toggleScreenSharing(init = false) {
}
}
// Disable cam video when screen sharing stop
// Disable cam video when screen sharing stops
if (!init && !isScreenStreaming && !myVideoStatusBefore) setMyVideoOff(myPeerName);
// Enable cam video when screen sharing stop
// Enable cam video when screen sharing stops
if (!init && !isScreenStreaming && myVideoStatusBefore) setMyVideoStatusTrue();
myVideo.classList.toggle('mirror');
setScreenSharingStatus(isScreenStreaming);
if (myVideoAvatarImage && !useVideo)
if (myVideoAvatarImage && !useVideo) {
myVideoAvatarImage.style.display = isScreenStreaming ? 'none' : 'block';
let myPrivacyBtn = getId('myPrivacyBtn');
}
const myPrivacyBtn = getId('myPrivacyBtn');
if (myPrivacyBtn) myPrivacyBtn.style.display = isScreenStreaming ? 'none' : 'inline';
if (isScreenStreaming || isVideoPinned) getId('myVideoPinBtn').click();
}
} catch (err) {
console.error('[Error] Unable to share the screen', err);
await handleToggleScreenException(`[Warning] Unable to share the screen: ${err}`, init);
if (init) return;
// userLog('error', 'Unable to share the screen ' + err);
}
}
/**
* Handle exception and actions when toggling screen sharing
* @param {string} reason - The reason message
* @param {boolean} init - Indicates whether it's an initial state
*/
async function handleToggleScreenException(reason, init) {
try {
console.warn('handleToggleScreenException', reason);
// Update video privacy status
isVideoPrivacyActive = false;
emitPeerStatus('privacy', isVideoPrivacyActive);
// Inform peers about screen sharing stop
emitPeersAction('screenStop');
// Turn off your video
setMyVideoOff(myPeerName);
// Toggle screen streaming status
isScreenStreaming = !isScreenStreaming;
myScreenStatus = isScreenStreaming;
// Update screen sharing status
setScreenSharingStatus(isScreenStreaming);
// Emit screen status to peers
await emitPeerStatus('screen', myScreenStatus);
// Stop the local video track
await stopLocalVideoTrack();
// Handle video status based on conditions
if (!init && !isScreenStreaming && !myVideoStatusBefore) {
setMyVideoOff(myPeerName);
} else if (!init && !isScreenStreaming && myVideoStatusBefore) {
setMyVideoStatusTrue();
}
// Toggle the 'mirror' class on myVideo
myVideo.classList.toggle('mirror');
// Handle video avatar image and privacy button visibility
if (myVideoAvatarImage && !useVideo) {
myVideoAvatarImage.style.display = isScreenStreaming ? 'none' : 'block';
}
// Automatically pin the video if screen sharing or video is pinned
if (isScreenStreaming || isVideoPinned) {
getId('myVideoPinBtn').click();
}
} catch (error) {
console.error('[Error] An unexpected error occurred', error);
}
}
@@ -5196,6 +5265,7 @@ async function toggleScreenSharing(init = false) {
* @param {boolean} status of screen sharing
*/
function setScreenSharingStatus(status) {
if (!useVideo) myVideo.style.display = status ? 'block' : 'none';
initScreenShareBtn.className = status ? className.screenOff : className.screenOn;
screenShareBtn.className = status ? className.screenOff : className.screenOn;
setTippy(screenShareBtn, status ? 'Stop screen sharing' : 'Start screen sharing', placement);
@@ -5261,7 +5331,7 @@ async function refreshMyStreamToPeers(stream, localAudioTrackChange = false) {
const myAudioTrack =
streamHasAudioTrack && (localAudioTrackChange || isScreenStreaming)
? stream.getAudioTracks()[0]
: localAudioMediaStream.getAudioTracks()[0];
: localAudioMediaStream && localAudioMediaStream.getAudioTracks()[0];
// Refresh my stream to connected peers except myself
for (const peer_id in peerConnections) {
@@ -5270,7 +5340,7 @@ async function refreshMyStreamToPeers(stream, localAudioTrackChange = false) {
// Replace video track
const videoSender = peerConnections[peer_id].getSenders().find((s) => s.track && s.track.kind === 'video');
if (videoSender) {
if (useVideo && videoSender) {
videoSender.replaceTrack(stream.getVideoTracks()[0]);
console.log('REPLACE VIDEO TRACK TO', { peer_id, peer_name });
} else {
@@ -5325,11 +5395,13 @@ async function refreshMyLocalStream(stream, localAudioTrackChange = false) {
}
const tracksToInclude = [];
const videoTrack = hasVideoTrack(stream) ? stream.getVideoTracks()[0] : localVideoMediaStream.getVideoTracks()[0];
const videoTrack = hasVideoTrack(stream)
? stream.getVideoTracks()[0]
: localVideoMediaStream && localVideoMediaStream.getVideoTracks()[0];
const audioTrack =
hasAudioTrack(stream) && localAudioTrackChange
? stream.getAudioTracks()[0]
: localAudioMediaStream.getAudioTracks()[0];
: localAudioMediaStream && localAudioMediaStream.getAudioTracks()[0];
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStream
if (useVideo || isScreenStreaming) {
@@ -5349,7 +5421,7 @@ async function refreshMyLocalStream(stream, localAudioTrackChange = false) {
}
} else {
console.log('Refresh my local media stream AUDIO');
if (audioTrack) {
if (useAudio && audioTrack) {
tracksToInclude.push(audioTrack);
localAudioMediaStream = new MediaStream([audioTrack]);
getMicrophoneVolumeIndicator(localAudioMediaStream);
@@ -5380,6 +5452,7 @@ async function refreshMyLocalStream(stream, localAudioTrackChange = false) {
* @returns boolean
*/
function hasAudioTrack(mediaStream) {
if (!mediaStream) return false;
const audioTracks = mediaStream.getAudioTracks();
return audioTracks.length > 0;
}
@@ -5390,6 +5463,7 @@ function hasAudioTrack(mediaStream) {
* @returns boolean
*/
function hasVideoTrack(mediaStream) {
if (!mediaStream) return false;
const videoTracks = mediaStream.getVideoTracks();
return videoTracks.length > 0;
}
@@ -5462,7 +5536,7 @@ function startStreamRecording() {
try {
audioRecorder = new MixedAudioRecorder();
const audioStreams = getAudioStreamFromVideoElements();
const audioStreams = getAudioStreamFromAudioElements();
console.log('Audio streams tracks --->', audioStreams.getTracks());
const audioMixerStreams = audioRecorder.getMixedAudioStream([audioStreams, localAudioMediaStream]);
@@ -5551,7 +5625,7 @@ function startDesktopRecording(options, audioMixerTracks) {
}
// Add audio mixer tracks to combinedTracks if available
if (Array.isArray(audioMixerTracks)) {
if (useAudio && Array.isArray(audioMixerTracks)) {
combinedTracks.push(...audioMixerTracks);
}
@@ -5575,6 +5649,22 @@ function startDesktopRecording(options, audioMixerTracks) {
});
}
/**
* Get a MediaStream containing audio tracks from audio elements on the page.
* @returns {MediaStream} A MediaStream containing audio tracks.
*/
function getAudioStreamFromAudioElements() {
const audioElements = document.querySelectorAll('audio');
const audioStream = new MediaStream();
audioElements.forEach((audio) => {
const audioTrack = audio.srcObject.getAudioTracks()[0];
if (audioTrack) {
audioStream.addTrack(audioTrack);
}
});
return audioStream;
}
/**
* Get a MediaStream containing audio tracks from video elements on the page.
* @returns {MediaStream} A MediaStream containing audio tracks.
@@ -6115,6 +6205,8 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) {
// check if i receive a private message
let msgBubble = getPrivateMsg ? 'private-msg-bubble' : 'msg-bubble';
const isValidPrivateMessage = getPrivateMsg && getMsgId != null && getMsgId != myPeerId;
let msgHTML = `
<div id="msg-${chatMessagesId}" class="msg ${getSide}-msg">
<img class="msg-img" src="${getImg}" />
@@ -6127,7 +6219,7 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) {
<hr/>
`;
// add btn direct reply to private message
if (getPrivateMsg && getMsgId != null && getMsgId != myPeerId) {
if (isValidPrivateMessage) {
msgHTML += `
<button
class="${className.msgPrivate}"
@@ -6169,7 +6261,9 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) {
setTippy(getId('msg-delete-' + chatMessagesId), 'Delete', 'top');
setTippy(getId('msg-copy-' + chatMessagesId), 'Copy', 'top');
setTippy(getId('msg-speech-' + chatMessagesId), 'Speech', 'top');
setTippy(getId('msg-private-reply-' + chatMessagesId), 'Reply', 'top');
if (isValidPrivateMessage) {
setTippy(getId('msg-private-reply-' + chatMessagesId), 'Reply', 'top');
}
}
chatMessagesId++;
}
@@ -6810,7 +6904,9 @@ function setMyHandStatus() {
* @param {boolean} status of my audio
*/
function setMyAudioStatus(status) {
myAudioStatusIcon.className = status ? className.audioOn : className.audioOff;
const audioClassName = status ? className.audioOn : className.audioOff;
audioBtn.className = audioClassName;
myAudioStatusIcon.className = audioClassName;
// send my audio status to all peers in the room
emitPeerStatus('audio', status);
setTippy(myAudioStatusIcon, status ? 'My audio is on' : 'My audio is off', 'bottom');
@@ -7138,8 +7234,10 @@ function handleScreenStop(peer_id, peer_use_video) {
if (remoteVideoAvatarImage && remoteVideoStream && !peer_use_video) {
remoteVideoAvatarImage.style.display = 'block';
remoteVideoStream.srcObject.getVideoTracks().forEach((track) => {
track.enabled = false;
track.stop();
// track.enabled = false;
});
remoteVideoStream.style.display = 'none';
} else {
if (remoteVideoAvatarImage) {
remoteVideoAvatarImage.style.display = 'none';