[mirotalksfu] - add moderator
هذا الالتزام موجود في:
@@ -16,6 +16,10 @@ module.exports = class Room {
|
||||
this._isLobbyEnabled = false;
|
||||
this._roomPassword = null;
|
||||
this._hostOnlyRecording = false;
|
||||
this._moderator = {
|
||||
start_audio_muted: false,
|
||||
start_video_hidden: false,
|
||||
};
|
||||
this.survey = config.survey;
|
||||
this.redirect = config.redirect;
|
||||
this.peers = new Map();
|
||||
@@ -98,6 +102,23 @@ module.exports = class Room {
|
||||
return this.router.rtpCapabilities;
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// ROOM MODERATOR
|
||||
// ####################################################
|
||||
|
||||
updateRoomModerator(data) {
|
||||
log.debug('Update room moderator', data);
|
||||
switch (data.type) {
|
||||
case 'audio':
|
||||
this._moderator.start_audio_muted = data.status;
|
||||
break;
|
||||
case 'video':
|
||||
this._moderator.start_video_hidden = data.status;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// ROOM INFO
|
||||
// ####################################################
|
||||
@@ -110,6 +131,7 @@ module.exports = class Room {
|
||||
isLobbyEnabled: this._isLobbyEnabled,
|
||||
hostOnlyRecording: this._hostOnlyRecording,
|
||||
},
|
||||
moderator: this._moderator,
|
||||
survey: this.survey,
|
||||
redirect: this.redirect,
|
||||
peers: JSON.stringify([...this.peers]),
|
||||
|
||||
@@ -40,7 +40,7 @@ dependencies: {
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.2.1
|
||||
* @version 1.2.2
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -750,6 +750,16 @@ function startServer() {
|
||||
room.broadCast(socket.id, 'updatePeerInfo', data);
|
||||
});
|
||||
|
||||
socket.on('updateRoomModerator', (dataObject) => {
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
|
||||
const data = checkXSS(dataObject);
|
||||
|
||||
const room = roomList.get(socket.room_id);
|
||||
|
||||
room.updateRoomModerator(data);
|
||||
});
|
||||
|
||||
socket.on('fileInfo', (dataObject) => {
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
|
||||
|
||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalksfu",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "WebRTC SFU browser-based video calls",
|
||||
"main": "Server.js",
|
||||
"scripts": {
|
||||
@@ -34,9 +34,9 @@
|
||||
"author": "Miroslav Pejic",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@sentry/integrations": "7.79.0",
|
||||
"@sentry/node": "7.79.0",
|
||||
"axios": "^1.6.1",
|
||||
"@sentry/integrations": "7.80.1",
|
||||
"@sentry/node": "7.80.1",
|
||||
"axios": "^1.6.2",
|
||||
"body-parser": "1.20.2",
|
||||
"colors": "1.4.0",
|
||||
"compression": "1.7.4",
|
||||
@@ -47,7 +47,7 @@
|
||||
"mediasoup": "3.13.1",
|
||||
"mediasoup-client": "3.7.0",
|
||||
"ngrok": "^4.3.3",
|
||||
"openai": "^4.17.0",
|
||||
"openai": "^4.18.0",
|
||||
"qs": "6.11.2",
|
||||
"socket.io": "4.7.2",
|
||||
"swagger-ui-express": "5.0.0",
|
||||
@@ -58,6 +58,6 @@
|
||||
"devDependencies": {
|
||||
"node-fetch": "^3.3.2",
|
||||
"nodemon": "^3.0.1",
|
||||
"prettier": "3.0.3"
|
||||
"prettier": "3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ th {
|
||||
|
||||
@media screen and (max-width: 540px) {
|
||||
.tab {
|
||||
font-size: 0.5rem;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ class LocalStorage {
|
||||
show_chat_on_msg: true, // show chat on new message
|
||||
show_transcript_on_msg: true, // show transcript on new message
|
||||
speech_in_msg: false, // speech incoming message
|
||||
moderator_audio_muted: false, // Everyone starts muted in the room
|
||||
moderator_video_hidden: false, // Everyone starts hidden in the room
|
||||
mic_auto_gain_control: false,
|
||||
mic_echo_cancellations: true,
|
||||
mic_noise_suppression: true,
|
||||
|
||||
@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.2.1
|
||||
* @version 1.2.2
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1158,6 +1158,9 @@ function handleButtons() {
|
||||
tabAspectBtn.onclick = (e) => {
|
||||
rc.openTab(e, 'tabAspect');
|
||||
};
|
||||
tabModeratorBtn.onclick = (e) => {
|
||||
rc.openTab(e, 'tabModerator');
|
||||
};
|
||||
tabProfileBtn.onclick = (e) => {
|
||||
rc.openTab(e, 'tabProfile');
|
||||
};
|
||||
@@ -1796,6 +1799,21 @@ function handleSelects() {
|
||||
wbIsBgTransparent = !wbIsBgTransparent;
|
||||
wbIsBgTransparent ? wbCanvasBackgroundColor('rgba(0, 0, 0, 0.100)') : setTheme();
|
||||
};
|
||||
// room moderator rules
|
||||
switchEveryoneMute.onchange = (e) => {
|
||||
const startMuted = e.currentTarget.checked;
|
||||
rc.updateRoomModerator({ type: 'audio', status: startMuted });
|
||||
lsSettings.moderator_audio_muted = startMuted;
|
||||
lS.setSettings(lsSettings);
|
||||
e.target.blur();
|
||||
};
|
||||
switchEveryoneHidden.onchange = (e) => {
|
||||
const startHidden = e.currentTarget.checked;
|
||||
rc.updateRoomModerator({ type: 'video', status: startHidden });
|
||||
lsSettings.moderator_video_hidden = startHidden;
|
||||
lS.setSettings(lsSettings);
|
||||
e.target.blur();
|
||||
};
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
@@ -1954,6 +1972,7 @@ function loadSettingsFromLocalStorage() {
|
||||
sampleRateSelect.selectedIndex = lsSettings.mic_sample_rate;
|
||||
sampleSizeSelect.selectedIndex = lsSettings.mic_sample_size;
|
||||
channelCountSelect.selectedIndex = lsSettings.mic_channel_count;
|
||||
|
||||
micLatencyRange.value = lsSettings.mic_latency || 50;
|
||||
micLatencyValue.innerText = lsSettings.mic_latency || 50;
|
||||
micVolumeRange.value = lsSettings.mic_volume || 100;
|
||||
@@ -2050,7 +2069,7 @@ function handleRoomClientEvents() {
|
||||
show(stopVideoButton);
|
||||
setColor(startVideoButton, 'red');
|
||||
setVideoButtonsDisabled(false);
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
// if (isParticipantsListOpen) getRoomParticipants();
|
||||
});
|
||||
rc.on(RoomClient.EVENTS.pauseVideo, () => {
|
||||
console.log('Room event: Client pause video');
|
||||
@@ -2068,13 +2087,13 @@ function handleRoomClientEvents() {
|
||||
show(startVideoButton);
|
||||
setVideoButtonsDisabled(false);
|
||||
isVideoPrivacyActive = false;
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
// if (isParticipantsListOpen) getRoomParticipants();
|
||||
});
|
||||
rc.on(RoomClient.EVENTS.startScreen, () => {
|
||||
console.log('Room event: Client start screen');
|
||||
hide(startScreenButton);
|
||||
show(stopScreenButton);
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
// if (isParticipantsListOpen) getRoomParticipants();
|
||||
});
|
||||
rc.on(RoomClient.EVENTS.pauseScreen, () => {
|
||||
console.log('Room event: Client pause screen');
|
||||
@@ -2086,7 +2105,7 @@ function handleRoomClientEvents() {
|
||||
console.log('Room event: Client stop screen');
|
||||
hide(stopScreenButton);
|
||||
show(startScreenButton);
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
// if (isParticipantsListOpen) getRoomParticipants();
|
||||
});
|
||||
rc.on(RoomClient.EVENTS.roomLock, () => {
|
||||
console.log('Room event: Client lock room');
|
||||
@@ -2867,7 +2886,7 @@ async function saveRoomPeers() {
|
||||
saveObjToJsonFile(peersToSave, 'PARTICIPANTS');
|
||||
}
|
||||
|
||||
async function getRoomParticipants(refresh = false) {
|
||||
async function getRoomParticipants() {
|
||||
const peers = await getRoomPeers();
|
||||
const lists = await getParticipantsList(peers);
|
||||
participantsCount = peers.size;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.2.1
|
||||
* @version 1.2.2
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -158,6 +158,10 @@ class RoomClient {
|
||||
this.peer_uuid = peer_uuid;
|
||||
this.peer_info = peer_info;
|
||||
|
||||
// Moderator
|
||||
this.start_muted = false;
|
||||
this.start_hidden = false;
|
||||
|
||||
this.isAudioAllowed = isAudioAllowed;
|
||||
this.isVideoAllowed = isVideoAllowed;
|
||||
this.isScreenAllowed = isScreenAllowed;
|
||||
@@ -363,6 +367,13 @@ class RoomClient {
|
||||
? (console.log('07.1 ----> WARNING Room Host only recording enabled'),
|
||||
this.event(_EVENTS.hostOnlyRecordingOn))
|
||||
: this.event(_EVENTS.hostOnlyRecordingOff);
|
||||
|
||||
// Handle Room moderator rules
|
||||
if (!isRulesActive || !isPresenter) {
|
||||
console.log('07.2 ----> MODERATOR', room.moderator);
|
||||
this.start_muted = room.moderator && room.moderator.start_audio_muted;
|
||||
this.start_hidden = room.moderator && room.moderator.start_video_hidden;
|
||||
}
|
||||
}
|
||||
adaptAspectRatio(participantsCount);
|
||||
for (let peer of Array.from(peers.keys()).filter((id) => id !== this.peer_id)) {
|
||||
@@ -586,7 +597,7 @@ class RoomClient {
|
||||
this.lobbyRemoveMe(data.peer_id);
|
||||
participantsCount = data.peer_counts;
|
||||
adaptAspectRatio(participantsCount);
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
if (isParticipantsListOpen) getRoomParticipants();
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
@@ -807,21 +818,26 @@ class RoomClient {
|
||||
|
||||
async startLocalMedia() {
|
||||
console.log('08 ----> Start local media');
|
||||
if (this.isAudioAllowed) {
|
||||
if (this.isAudioAllowed && !this.start_muted) {
|
||||
console.log('09 ----> Start audio media');
|
||||
this.produce(mediaType.audio, microphoneSelect.value);
|
||||
} else {
|
||||
setColor(startAudioButton, 'red');
|
||||
console.log('09 ----> Audio is off');
|
||||
setColor(startAudioButton, 'red');
|
||||
this.setIsAudio(this.peer_id, false);
|
||||
this.event(_EVENTS.stopAudio);
|
||||
this.updatePeerInfo(this.peer_name, this.peer_id, 'audio', false);
|
||||
}
|
||||
if (this.isVideoAllowed) {
|
||||
if (this.isVideoAllowed && !this.start_hidden) {
|
||||
console.log('10 ----> Start video media');
|
||||
this.produce(mediaType.video, videoSelect.value);
|
||||
} else {
|
||||
setColor(startVideoButton, 'red');
|
||||
console.log('10 ----> Video is off');
|
||||
setColor(startVideoButton, 'red');
|
||||
this.setVideoOff(this.peer_info, false);
|
||||
this.sendVideoOff();
|
||||
this.event(_EVENTS.stopVideo);
|
||||
this.updatePeerInfo(this.peer_name, this.peer_id, 'video', false);
|
||||
}
|
||||
if (this.joinRoomWithScreen) {
|
||||
console.log('08 ----> Start Screen media');
|
||||
@@ -1504,7 +1520,7 @@ class RoomClient {
|
||||
this.myAudioEl = elem;
|
||||
this.localAudioEl.appendChild(elem);
|
||||
this.attachMediaStream(elem, stream, type, 'Producer');
|
||||
if (this.isAudioAllowed && !speakerSelect.disabled) {
|
||||
if (this.isAudioAllowed && !this.start_muted && !speakerSelect.disabled) {
|
||||
this.attachSinkId(elem, speakerSelect.value);
|
||||
}
|
||||
console.log('[addProducer] audio-element-count', this.localAudioEl.childElementCount);
|
||||
@@ -1634,8 +1650,6 @@ class RoomClient {
|
||||
|
||||
async produceScreenAudio(stream) {
|
||||
try {
|
||||
//this.stopMyAudio();
|
||||
|
||||
if (this.producerLabel.has(mediaType.audioTab)) {
|
||||
return console.log('Producer already exists for this type ' + mediaType.audioTab);
|
||||
}
|
||||
@@ -1658,7 +1672,6 @@ class RoomClient {
|
||||
|
||||
producerSa.on('trackended', () => {
|
||||
this.closeProducer(mediaType.audioTab);
|
||||
// this.startMyAudio();
|
||||
});
|
||||
|
||||
producerSa.on('transportclose', () => {
|
||||
@@ -1687,20 +1700,6 @@ class RoomClient {
|
||||
}
|
||||
}
|
||||
|
||||
startMyAudio() {
|
||||
startAudioButton.click();
|
||||
this.setIsAudio(this.peer_id, true);
|
||||
this.event(_EVENTS.startAudio);
|
||||
setAudioButtonsDisabled(false);
|
||||
}
|
||||
|
||||
stopMyAudio() {
|
||||
stopAudioButton.click();
|
||||
this.setIsAudio(this.peer_id, false);
|
||||
this.event(_EVENTS.stopAudio);
|
||||
setAudioButtonsDisabled(true);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// CONSUMER
|
||||
// ####################################################
|
||||
@@ -1717,7 +1716,7 @@ class RoomClient {
|
||||
this.consumers.set(consumer.id, consumer);
|
||||
|
||||
if (kind === 'video') {
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
if (isParticipantsListOpen) getRoomParticipants();
|
||||
}
|
||||
|
||||
this.handleConsumer(consumer.id, type, stream, peer_name, peer_info);
|
||||
@@ -2056,7 +2055,7 @@ class RoomClient {
|
||||
this.setVideoAvatarImgName(i.id, peer_name);
|
||||
this.getId(i.id).style.display = 'block';
|
||||
handleAspectRatio();
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
if (isParticipantsListOpen) getRoomParticipants();
|
||||
if (!this.isMobileDevice && remotePeer) {
|
||||
this.setTippy(sm.id, 'Send message', 'bottom');
|
||||
this.setTippy(sf.id, 'Send file', 'bottom');
|
||||
@@ -2927,7 +2926,7 @@ class RoomClient {
|
||||
const chatRoom = this.getId('chatRoom');
|
||||
chatRoom.classList.toggle('show');
|
||||
if (!this.isChatOpen) {
|
||||
await getRoomParticipants(true);
|
||||
await getRoomParticipants();
|
||||
hide(chatMinButton);
|
||||
if (!this.isMobileDevice) {
|
||||
show(chatMaxButton);
|
||||
@@ -5118,7 +5117,7 @@ class RoomClient {
|
||||
}).then(() => {
|
||||
switch (action) {
|
||||
case 'refresh':
|
||||
getRoomParticipants(true);
|
||||
getRoomParticipants();
|
||||
break;
|
||||
case 'eject':
|
||||
this.exit();
|
||||
@@ -5369,11 +5368,22 @@ class RoomClient {
|
||||
elemDisplay('chatPrivateMessages', false);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// UPDATE ROOM MODERATOR
|
||||
// ####################################################
|
||||
|
||||
updateRoomModerator(data) {
|
||||
if (!isRulesActive || isPresenter) {
|
||||
this.socket.emit('updateRoomModerator', data);
|
||||
}
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// UPDATE PEER INFO
|
||||
// ####################################################
|
||||
|
||||
updatePeerInfo(peer_name, peer_id, type, status, emit = true) {
|
||||
alert(type);
|
||||
if (emit) {
|
||||
switch (type) {
|
||||
case 'audio':
|
||||
@@ -5437,7 +5447,7 @@ class RoomClient {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isParticipantsListOpen) getRoomParticipants(true);
|
||||
if (isParticipantsListOpen) getRoomParticipants();
|
||||
}
|
||||
|
||||
checkPeerInfoStatus(peer_info) {
|
||||
|
||||
@@ -30,6 +30,7 @@ const BUTTONS = {
|
||||
unlockRoomButton: true, // presenter
|
||||
lobbyButton: true, // presenter
|
||||
micOptionsButton: true, // presenter
|
||||
tabModerator: true, // presenter
|
||||
tabRecording: true,
|
||||
pushToTalk: true,
|
||||
host_only_recording: true, // presenter
|
||||
@@ -87,6 +88,7 @@ function handleRules(isPresenter) {
|
||||
BUTTONS.settings.unlockRoomButton = false;
|
||||
BUTTONS.settings.lobbyButton = false;
|
||||
BUTTONS.settings.micOptionsButton = false;
|
||||
BUTTONS.settings.tabModerator = false;
|
||||
BUTTONS.videoOff.muteAudioButton = false;
|
||||
BUTTONS.videoOff.ejectButton = false;
|
||||
BUTTONS.consumerVideo.ejectButton = false;
|
||||
@@ -100,6 +102,7 @@ function handleRules(isPresenter) {
|
||||
BUTTONS.settings.unlockRoomButton = isRoomLocked;
|
||||
BUTTONS.settings.lobbyButton = true;
|
||||
BUTTONS.settings.micOptionsButton = true;
|
||||
BUTTONS.settings.tabModerator = true;
|
||||
BUTTONS.videoOff.muteAudioButton = true;
|
||||
BUTTONS.videoOff.ejectButton = true;
|
||||
BUTTONS.consumerVideo.ejectButton = true;
|
||||
@@ -120,13 +123,18 @@ function handleRules(isPresenter) {
|
||||
hostOnlyRecording = lsSettings.host_only_recording;
|
||||
switchHostOnlyRecording.checked = hostOnlyRecording;
|
||||
rc.roomAction(hostOnlyRecording ? 'hostOnlyRecordingOn' : 'hostOnlyRecordingOff', true, false);
|
||||
//...
|
||||
// Room moderator
|
||||
switchEveryoneMute.checked = lsSettings.moderator_audio_muted;
|
||||
switchEveryoneHidden.checked = lsSettings.moderator_video_hidden;
|
||||
rc.updateRoomModerator({ type: 'audio', status: switchEveryoneMute.checked });
|
||||
rc.updateRoomModerator({ type: 'video', status: switchEveryoneHidden.checked });
|
||||
}
|
||||
// main. settings...
|
||||
BUTTONS.settings.lockRoomButton ? show(lockRoomButton) : hide(lockRoomButton);
|
||||
BUTTONS.settings.unlockRoomButton ? show(unlockRoomButton) : hide(unlockRoomButton);
|
||||
BUTTONS.settings.lobbyButton ? show(lobbyButton) : hide(lobbyButton);
|
||||
!BUTTONS.settings.micOptionsButton && hide(micOptionsButton);
|
||||
!BUTTONS.settings.tabModerator && hide(tabModeratorBtn);
|
||||
BUTTONS.participantsList.saveInfoButton ? show(participantsSaveBtn) : hide(participantsSaveBtn);
|
||||
BUTTONS.whiteboard.whiteboardLockButton
|
||||
? elemDisplay('whiteboardLockButton', true)
|
||||
|
||||
@@ -182,14 +182,14 @@
|
||||
i
|
||||
? (i.classList.add('is-active'), l.classList.add('is-active'))
|
||||
: 'next' === t
|
||||
? (e.firstElementChild.classList.add('is-active'),
|
||||
e.parentNode
|
||||
.getElementsByClassName('carousel-bullets')[0]
|
||||
.firstElementChild.classList.add('is-active'))
|
||||
: (e.lastElementChild.classList.add('is-active'),
|
||||
e.parentNode
|
||||
.getElementsByClassName('carousel-bullets')[0]
|
||||
.lastElementChild.classList.add('is-active'));
|
||||
? (e.firstElementChild.classList.add('is-active'),
|
||||
e.parentNode
|
||||
.getElementsByClassName('carousel-bullets')[0]
|
||||
.firstElementChild.classList.add('is-active'))
|
||||
: (e.lastElementChild.classList.add('is-active'),
|
||||
e.parentNode
|
||||
.getElementsByClassName('carousel-bullets')[0]
|
||||
.lastElementChild.classList.add('is-active'));
|
||||
}
|
||||
|
||||
function i(e, t) {
|
||||
|
||||
@@ -14084,10 +14084,10 @@
|
||||
typeof global !== 'undefined'
|
||||
? global
|
||||
: typeof self !== 'undefined'
|
||||
? self
|
||||
: typeof window !== 'undefined'
|
||||
? window
|
||||
: {},
|
||||
? self
|
||||
: typeof window !== 'undefined'
|
||||
? window
|
||||
: {},
|
||||
);
|
||||
},
|
||||
{},
|
||||
@@ -14649,8 +14649,8 @@
|
||||
var keyLocation = obj.push
|
||||
? {} // blank object that will be pushed
|
||||
: needsBlank
|
||||
? location[obj.name]
|
||||
: location; // otherwise, named location or root
|
||||
? location[obj.name]
|
||||
: location; // otherwise, named location or root
|
||||
|
||||
attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
|
||||
|
||||
|
||||
@@ -185,6 +185,9 @@ access to use this app.
|
||||
<button id="tabAudioDevicesBtn" class="fas fa-microphone tablinks">
|
||||
<p class="tabButtonText">Audio</p>
|
||||
</button>
|
||||
<button id="tabModeratorBtn" class="fas fa-m tablinks">
|
||||
<p class="tabButtonText">Moderator</p>
|
||||
</button>
|
||||
<button id="tabRecordingBtn" class="fas fa-record-vinyl tablinks">
|
||||
<p class="tabButtonText">Recording</p>
|
||||
</button>
|
||||
@@ -588,6 +591,42 @@ access to use this app.
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<div id="tabModerator" class="tabcontent">
|
||||
<table class="settingsTable">
|
||||
<tr id="everyoneMuteBtn">
|
||||
<td style="width: auto">
|
||||
<div class="title">
|
||||
<i class="fas fa-microphone-slash"></i>
|
||||
<p>Everyone starts muted</p>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check form-switch form-switch-md title">
|
||||
<input id="switchEveryoneMute" class="form-check-input" type="checkbox" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="everyoneHideBtn">
|
||||
<td style="width: auto">
|
||||
<div class="title">
|
||||
<i class="fas fa-video-slash"></i>
|
||||
<p>Everyone starts hidden</p>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check form-switch form-switch-md">
|
||||
<input
|
||||
id="switchEveryoneHidden"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
checked
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="tabRecording" class="tabcontent">
|
||||
<img id="recImage" src="../images/recording.png" />
|
||||
<div id="roomRecording" class="hidden">
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم