[mirotalksfu] - #171 add complete volume control
هذا الالتزام موجود في:
@@ -55,7 +55,7 @@ dev dependencies: {
|
|||||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
* @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
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.6.18
|
* @version 1.6.19
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ module.exports = {
|
|||||||
snapShotButton: true,
|
snapShotButton: true,
|
||||||
muteAudioButton: true,
|
muteAudioButton: true,
|
||||||
videoPrivacyButton: true,
|
videoPrivacyButton: true,
|
||||||
|
audioVolumeInput: true,
|
||||||
},
|
},
|
||||||
consumerVideo: {
|
consumerVideo: {
|
||||||
videoPictureInPicture: true,
|
videoPictureInPicture: true,
|
||||||
@@ -466,7 +467,7 @@ module.exports = {
|
|||||||
sendVideoButton: true,
|
sendVideoButton: true,
|
||||||
muteVideoButton: true,
|
muteVideoButton: true,
|
||||||
muteAudioButton: true,
|
muteAudioButton: true,
|
||||||
audioVolumeInput: true, // Disabled for mobile
|
audioVolumeInput: true,
|
||||||
geolocationButton: true, // Presenter
|
geolocationButton: true, // Presenter
|
||||||
banButton: true, // presenter
|
banButton: true, // presenter
|
||||||
ejectButton: true, // presenter
|
ejectButton: true, // presenter
|
||||||
@@ -476,7 +477,7 @@ module.exports = {
|
|||||||
sendFileButton: true,
|
sendFileButton: true,
|
||||||
sendVideoButton: true,
|
sendVideoButton: true,
|
||||||
muteAudioButton: true,
|
muteAudioButton: true,
|
||||||
audioVolumeInput: true, // Disabled for mobile
|
audioVolumeInput: true,
|
||||||
geolocationButton: true, // Presenter
|
geolocationButton: true, // Presenter
|
||||||
banButton: true, // presenter
|
banButton: true, // presenter
|
||||||
ejectButton: true, // presenter
|
ejectButton: true, // presenter
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.6.18",
|
"version": "1.6.19",
|
||||||
"description": "WebRTC SFU browser-based video calls",
|
"description": "WebRTC SFU browser-based video calls",
|
||||||
"main": "Server.js",
|
"main": "Server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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 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
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.6.18
|
* @version 1.6.19
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -4563,7 +4563,7 @@ function showAbout() {
|
|||||||
imageUrl: image.about,
|
imageUrl: image.about,
|
||||||
customClass: { image: 'img-about' },
|
customClass: { image: 'img-about' },
|
||||||
position: 'center',
|
position: 'center',
|
||||||
title: 'WebRTC SFU v1.6.18',
|
title: 'WebRTC SFU v1.6.19',
|
||||||
html: `
|
html: `
|
||||||
<br />
|
<br />
|
||||||
<div id="about">
|
<div id="about">
|
||||||
|
|||||||
@@ -1404,6 +1404,9 @@ class RoomClient {
|
|||||||
throw new Error('Producer not found!');
|
throw new Error('Producer not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('PRODUCER MEDIA TYPE ----> ' + type);
|
||||||
|
console.log('PRODUCER', producer);
|
||||||
|
|
||||||
this.producers.set(producer.id, producer);
|
this.producers.set(producer.id, producer);
|
||||||
this.producerLabel.set(type, producer.id);
|
this.producerLabel.set(type, producer.id);
|
||||||
|
|
||||||
@@ -1812,6 +1815,23 @@ class RoomClient {
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getConsumerIdByProducerId(producerId) {
|
||||||
|
for (let [consumerId, consumer] of this.consumers.entries()) {
|
||||||
|
if (consumer._producerId === producerId) {
|
||||||
|
return consumerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProducerIdByConsumerId(consumerId) {
|
||||||
|
const consumer = this.consumers.get(consumerId);
|
||||||
|
if (consumer) {
|
||||||
|
return consumer._producerId;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ####################################################
|
// ####################################################
|
||||||
// PRODUCER
|
// PRODUCER
|
||||||
// ####################################################
|
// ####################################################
|
||||||
@@ -1853,7 +1873,7 @@ class RoomClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleProducer(id, type, stream) {
|
async handleProducer(id, type, stream) {
|
||||||
let elem, vb, vp, ts, d, p, i, au, pip, fs, pm, pb, pn, mv;
|
let elem, vb, vp, ts, d, p, i, au, pip, fs, pm, pb, pn, pv, mv;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case mediaType.video:
|
case mediaType.video:
|
||||||
case mediaType.screen:
|
case mediaType.screen:
|
||||||
@@ -1902,6 +1922,15 @@ class RoomClient {
|
|||||||
pb.className = 'bar';
|
pb.className = 'bar';
|
||||||
pb.style.height = '1%';
|
pb.style.height = '1%';
|
||||||
pm.appendChild(pb);
|
pm.appendChild(pb);
|
||||||
|
|
||||||
|
pv = document.createElement('input');
|
||||||
|
pv.id = this.peer_id + '___pVolume';
|
||||||
|
pv.type = 'range';
|
||||||
|
pv.min = 0;
|
||||||
|
pv.max = 100;
|
||||||
|
pv.value = 100;
|
||||||
|
|
||||||
|
BUTTONS.producerVideo.audioVolumeInput && vb.appendChild(pv);
|
||||||
BUTTONS.producerVideo.muteAudioButton && vb.appendChild(au);
|
BUTTONS.producerVideo.muteAudioButton && vb.appendChild(au);
|
||||||
BUTTONS.producerVideo.videoPrivacyButton && !isScreen && vb.appendChild(vp);
|
BUTTONS.producerVideo.videoPrivacyButton && !isScreen && vb.appendChild(vp);
|
||||||
BUTTONS.producerVideo.snapShotButton && vb.appendChild(ts);
|
BUTTONS.producerVideo.snapShotButton && vb.appendChild(ts);
|
||||||
@@ -1931,6 +1960,7 @@ class RoomClient {
|
|||||||
this.handleMV(elem.id, mv.id);
|
this.handleMV(elem.id, mv.id);
|
||||||
this.handlePN(elem.id, pn.id, d.id, isScreen);
|
this.handlePN(elem.id, pn.id, d.id, isScreen);
|
||||||
this.handleZV(elem.id, d.id, this.peer_id);
|
this.handleZV(elem.id, d.id, this.peer_id);
|
||||||
|
this.handlePV(id + '___' + pv.id);
|
||||||
|
|
||||||
if (!isScreen) this.handleVP(elem.id, vp.id);
|
if (!isScreen) this.handleVP(elem.id, vp.id);
|
||||||
|
|
||||||
@@ -1962,6 +1992,7 @@ class RoomClient {
|
|||||||
this.myAudioEl = elem;
|
this.myAudioEl = elem;
|
||||||
this.localAudioEl.appendChild(elem);
|
this.localAudioEl.appendChild(elem);
|
||||||
await this.attachMediaStream(elem, stream, type, 'Producer');
|
await this.attachMediaStream(elem, stream, type, 'Producer');
|
||||||
|
this.handlePV(elem.id + '___' + this.peer_id + '___pVolume');
|
||||||
console.log('[addProducer] audio-element-count', this.localAudioEl.childElementCount);
|
console.log('[addProducer] audio-element-count', this.localAudioEl.childElementCount);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -2380,7 +2411,7 @@ class RoomClient {
|
|||||||
this.handleSV(sv.id);
|
this.handleSV(sv.id);
|
||||||
BUTTONS.consumerVideo.muteVideoButton && this.handleCM(cm.id);
|
BUTTONS.consumerVideo.muteVideoButton && this.handleCM(cm.id);
|
||||||
BUTTONS.consumerVideo.muteAudioButton && this.handleAU(au.id);
|
BUTTONS.consumerVideo.muteAudioButton && this.handleAU(au.id);
|
||||||
this.handlePV(id + '___' + pv.id);
|
this.handleCV(id + '___' + pv.id);
|
||||||
this.handleGL(gl.id);
|
this.handleGL(gl.id);
|
||||||
this.handleBAN(ban.id);
|
this.handleBAN(ban.id);
|
||||||
this.handleKO(ko.id);
|
this.handleKO(ko.id);
|
||||||
@@ -2440,7 +2471,7 @@ class RoomClient {
|
|||||||
let inputPv = this.getId(audioConsumerId);
|
let inputPv = this.getId(audioConsumerId);
|
||||||
|
|
||||||
if (inputPv) {
|
if (inputPv) {
|
||||||
this.handlePV(id + '___' + audioConsumerId);
|
this.handleCV(id + '___' + audioConsumerId);
|
||||||
this.setPeerAudio(remotePeerId, remotePeerAudio);
|
this.setPeerAudio(remotePeerId, remotePeerAudio);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2539,12 +2570,14 @@ class RoomClient {
|
|||||||
au = this.createButton(peer_id + '__audio', peer_audio ? html.audioOn : html.audioOff);
|
au = this.createButton(peer_id + '__audio', peer_audio ? html.audioOn : html.audioOff);
|
||||||
|
|
||||||
if (remotePeer) {
|
if (remotePeer) {
|
||||||
|
//
|
||||||
pv = document.createElement('input');
|
pv = document.createElement('input');
|
||||||
pv.id = peer_id + '___pVolume';
|
pv.id = peer_id + '___pVolume';
|
||||||
pv.type = 'range';
|
pv.type = 'range';
|
||||||
pv.min = 0;
|
pv.min = 0;
|
||||||
pv.max = 100;
|
pv.max = 100;
|
||||||
pv.value = 100;
|
pv.value = 100;
|
||||||
|
|
||||||
sf = this.createButton('remotePeer___' + peer_id + '___sendFile', html.sendFile);
|
sf = this.createButton('remotePeer___' + peer_id + '___sendFile', html.sendFile);
|
||||||
sm = this.createButton('remotePeer___' + peer_id + '___sendMsg', html.sendMsg);
|
sm = this.createButton('remotePeer___' + peer_id + '___sendMsg', html.sendMsg);
|
||||||
sv = this.createButton('remotePeer___' + peer_id + '___sendVideo', html.sendVideo);
|
sv = this.createButton('remotePeer___' + peer_id + '___sendVideo', html.sendVideo);
|
||||||
@@ -2579,8 +2612,9 @@ class RoomClient {
|
|||||||
BUTTONS.videoOff.sendVideoButton && vb.appendChild(sv);
|
BUTTONS.videoOff.sendVideoButton && vb.appendChild(sv);
|
||||||
BUTTONS.videoOff.sendFileButton && vb.appendChild(sf);
|
BUTTONS.videoOff.sendFileButton && vb.appendChild(sf);
|
||||||
BUTTONS.videoOff.sendMessageButton && vb.appendChild(sm);
|
BUTTONS.videoOff.sendMessageButton && vb.appendChild(sm);
|
||||||
BUTTONS.videoOff.audioVolumeInput && !this.isMobileDevice && vb.appendChild(pv);
|
BUTTONS.videoOff.audioVolumeInput && vb.appendChild(pv);
|
||||||
}
|
}
|
||||||
|
|
||||||
vb.appendChild(au);
|
vb.appendChild(au);
|
||||||
d.appendChild(i);
|
d.appendChild(i);
|
||||||
d.appendChild(p);
|
d.appendChild(p);
|
||||||
@@ -2592,7 +2626,7 @@ class RoomClient {
|
|||||||
BUTTONS.videoOff.muteAudioButton && this.handleAU(au.id);
|
BUTTONS.videoOff.muteAudioButton && this.handleAU(au.id);
|
||||||
|
|
||||||
if (remotePeer) {
|
if (remotePeer) {
|
||||||
this.handlePV('remotePeer___' + pv.id);
|
this.handleCV(peer_id + '___' + pv.id);
|
||||||
this.handleSM(sm.id);
|
this.handleSM(sm.id);
|
||||||
this.handleSF(sf.id);
|
this.handleSF(sf.id);
|
||||||
this.handleSV(sv.id);
|
this.handleSV(sv.id);
|
||||||
@@ -6840,44 +6874,129 @@ class RoomClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ####################################################
|
// ####################################################
|
||||||
// HANDLE PEER VOLUME
|
// HANDLE PEERS AUDIO VOLUME
|
||||||
// ###################################################
|
// ####################################################
|
||||||
|
|
||||||
|
handleCV(uid) {
|
||||||
|
this.handleVolumeControl(uid, true);
|
||||||
|
}
|
||||||
|
|
||||||
handlePV(uid) {
|
handlePV(uid) {
|
||||||
|
this.handleVolumeControl(uid, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVolumeControl(uid, isConsumer = true) {
|
||||||
const words = uid.split('___');
|
const words = uid.split('___');
|
||||||
let peer_id = words[1] + '___pVolume';
|
const peer_id = `${words[1]}___pVolume`;
|
||||||
let audioConsumerId = this.audioConsumers.get(peer_id);
|
const audioPlayer = this.getId(isConsumer ? this.audioConsumers.get(peer_id) : words[0]);
|
||||||
let audioConsumerPlayer = this.getId(audioConsumerId);
|
const inputElement = this.getId(peer_id);
|
||||||
let inputPv = this.getId(peer_id);
|
|
||||||
if (inputPv && audioConsumerPlayer) {
|
if (inputElement && audioPlayer) {
|
||||||
inputPv.style.display = 'inline';
|
inputElement.style.display = 'inline';
|
||||||
inputPv.value = 100;
|
inputElement.value = 100;
|
||||||
|
|
||||||
|
let volumeUpdateTimeout;
|
||||||
|
|
||||||
const updateVolume = () => {
|
const updateVolume = () => {
|
||||||
const volume = inputPv.value / 100;
|
const volume = inputElement.value / 100;
|
||||||
this.setAudioVolume(audioConsumerPlayer, volume);
|
this.setAudioVolume(audioPlayer, volume);
|
||||||
|
|
||||||
|
// Clear any existing timeout to prevent sending too frequently
|
||||||
|
if (volumeUpdateTimeout) {
|
||||||
|
clearTimeout(volumeUpdateTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a timeout to send the update after 0.5 second
|
||||||
|
volumeUpdateTimeout = setTimeout(() => {
|
||||||
|
// Prepare the command to update peer volume
|
||||||
|
const cmd = {
|
||||||
|
type: 'peerAudio',
|
||||||
|
peer_name: this.peer_name,
|
||||||
|
[isConsumer ? 'audioConsumerId' : 'audioProducerId']: isConsumer
|
||||||
|
? this.audioConsumers.get(peer_id)
|
||||||
|
: this.audioProducerId,
|
||||||
|
peer_id: peer_id,
|
||||||
|
volume: volume,
|
||||||
|
broadcast: true,
|
||||||
|
};
|
||||||
|
this.emitCmd(cmd);
|
||||||
|
}, 500); // 0.5 second delay
|
||||||
};
|
};
|
||||||
inputPv.addEventListener('input', updateVolume);
|
|
||||||
inputPv.addEventListener('change', updateVolume);
|
this.addVolumeEventListeners(inputElement, updateVolume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAudioVolume(audioPlayer, volume) {
|
||||||
|
if (audioPlayer) {
|
||||||
if (this.isMobileDevice) {
|
if (this.isMobileDevice) {
|
||||||
inputPv.addEventListener('touchstart', updateVolume);
|
audioPlayer.muted = volume === 0;
|
||||||
inputPv.addEventListener('touchmove', updateVolume);
|
if (!audioPlayer.muted) {
|
||||||
|
// Adjust playback rate as volume on mobile devices
|
||||||
|
audioPlayer.playbackRate = Math.max(0.1, volume);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set volume directly on desktop devices
|
||||||
|
audioPlayer.volume = volume;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setAudioVolume(audioConsumerPlayer, volume) {
|
handlePeerAudio(cmd) {
|
||||||
if (audioConsumerPlayer) {
|
console.log('handlePeerAudio', { cmd });
|
||||||
if (this.isMobileDevice) {
|
|
||||||
// On mobile, we'll use a different approach
|
const { peer_id, audioProducerId, audioConsumerId, volume } = cmd;
|
||||||
audioConsumerPlayer.muted = volume === 0;
|
|
||||||
if (!audioConsumerPlayer.muted) {
|
const inputPV = this.getId(peer_id);
|
||||||
// We can only set volume to 1 on mobile, so we'll adjust playback rate instead
|
|
||||||
audioConsumerPlayer.playbackRate = Math.max(0.1, volume);
|
if (!inputPV) return;
|
||||||
}
|
|
||||||
} else {
|
inputPV.value = volume * 100;
|
||||||
// On desktop, we can directly set the volume
|
|
||||||
audioConsumerPlayer.volume = volume;
|
if (audioProducerId) {
|
||||||
}
|
this.handleConsumerAudio(audioProducerId, volume);
|
||||||
|
/*
|
||||||
|
If the producer has changed the volume from the default value of 100,
|
||||||
|
disable the volume input control on the consumer side to prevent further adjustments.
|
||||||
|
Otherwise, keep the input enabled if the volume is still at 100.
|
||||||
|
*/
|
||||||
|
inputPV.disabled = inputPV.value < 100 ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioConsumerId) this.handleProducerAudio(audioConsumerId, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConsumerAudio(audioProducerId, volume) {
|
||||||
|
const consumerAudioId = this.getConsumerIdByProducerId(audioProducerId);
|
||||||
|
if (!consumerAudioId) return;
|
||||||
|
|
||||||
|
const consumerAudioPlayer = this.getId(consumerAudioId);
|
||||||
|
if (!consumerAudioPlayer) return;
|
||||||
|
|
||||||
|
this.setAudioVolume(consumerAudioPlayer, volume);
|
||||||
|
|
||||||
|
console.log('handleConsumerPeerAudio', { consumerAudioId, consumerAudioPlayer });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleProducerAudio(audioConsumerId, volume) {
|
||||||
|
const producerAudioId = this.getProducerIdByConsumerId(audioConsumerId);
|
||||||
|
if (!producerAudioId) return;
|
||||||
|
|
||||||
|
const producerAudioPlayer = this.getId(producerAudioId);
|
||||||
|
if (!producerAudioPlayer) return;
|
||||||
|
|
||||||
|
this.setAudioVolume(producerAudioPlayer, volume);
|
||||||
|
|
||||||
|
console.log('handleProducerPeerAudio', { producerAudioId, producerAudioPlayer });
|
||||||
|
}
|
||||||
|
|
||||||
|
addVolumeEventListeners(inputElement, updateVolumeCallback) {
|
||||||
|
inputElement.addEventListener('input', updateVolumeCallback);
|
||||||
|
inputElement.addEventListener('change', updateVolumeCallback);
|
||||||
|
|
||||||
|
if (this.isMobileDevice) {
|
||||||
|
inputElement.addEventListener('touchstart', updateVolumeCallback);
|
||||||
|
inputElement.addEventListener('touchmove', updateVolumeCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7060,6 +7179,9 @@ class RoomClient {
|
|||||||
case 'ejectAll':
|
case 'ejectAll':
|
||||||
this.exit();
|
this.exit();
|
||||||
break;
|
break;
|
||||||
|
case 'peerAudio':
|
||||||
|
this.handlePeerAudio(cmd);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
//...
|
//...
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ let BUTTONS = {
|
|||||||
snapShotButton: true,
|
snapShotButton: true,
|
||||||
muteAudioButton: true,
|
muteAudioButton: true,
|
||||||
videoPrivacyButton: true,
|
videoPrivacyButton: true,
|
||||||
|
audioVolumeInput: true,
|
||||||
},
|
},
|
||||||
consumerVideo: {
|
consumerVideo: {
|
||||||
videoPictureInPicture: true,
|
videoPictureInPicture: true,
|
||||||
@@ -65,7 +66,7 @@ let BUTTONS = {
|
|||||||
sendVideoButton: true,
|
sendVideoButton: true,
|
||||||
muteVideoButton: true,
|
muteVideoButton: true,
|
||||||
muteAudioButton: true,
|
muteAudioButton: true,
|
||||||
audioVolumeInput: true, // Disabled for mobile
|
audioVolumeInput: true,
|
||||||
geolocationButton: true, // Presenter
|
geolocationButton: true, // Presenter
|
||||||
banButton: true, // presenter
|
banButton: true, // presenter
|
||||||
ejectButton: true, // presenter
|
ejectButton: true, // presenter
|
||||||
@@ -75,7 +76,7 @@ let BUTTONS = {
|
|||||||
sendFileButton: true,
|
sendFileButton: true,
|
||||||
sendVideoButton: true,
|
sendVideoButton: true,
|
||||||
muteAudioButton: true,
|
muteAudioButton: true,
|
||||||
audioVolumeInput: true, // Disabled for mobile
|
audioVolumeInput: true,
|
||||||
geolocationButton: true, // Presenter
|
geolocationButton: true, // Presenter
|
||||||
banButton: true, // presenter
|
banButton: true, // presenter
|
||||||
ejectButton: true, // presenter
|
ejectButton: true, // presenter
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم