[mirotalksfu] - improve Transport layer
هذا الالتزام موجود في:
@@ -547,7 +547,7 @@ module.exports = class Room {
|
|||||||
enableUdp: true,
|
enableUdp: true,
|
||||||
enableTcp: true,
|
enableTcp: true,
|
||||||
preferUdp: true,
|
preferUdp: true,
|
||||||
iceConsentTimeout: 20,
|
iceConsentTimeout: 30,
|
||||||
initialAvailableOutgoingBitrate,
|
initialAvailableOutgoingBitrate,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,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.7.63
|
* @version 1.7.64
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -1823,9 +1823,22 @@ function startServer() {
|
|||||||
|
|
||||||
const { room, peer } = getRoomAndPeer(socket);
|
const { room, peer } = getRoomAndPeer(socket);
|
||||||
|
|
||||||
if (!peer) return;
|
if (!peer) {
|
||||||
|
console.error('Peer not found for socket:', socket.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
peer.updatePeerInfo(data); // peer_info.audio OR video OFF
|
if (typeof peer.updatePeerInfo !== 'function') {
|
||||||
|
console.error('updatePeerInfo is not a function on peer:', peer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.updatePeerInfo(data); // peer_info.audio OR video OFF //*
|
||||||
|
|
||||||
|
if (typeof room.closeProducer !== 'function') {
|
||||||
|
console.error('closeProducer is not a function on room:', room);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
room.closeProducer(socket.id, data.producer_id);
|
room.closeProducer(socket.id, data.producer_id);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.7.63",
|
"version": "1.7.64",
|
||||||
"description": "WebRTC SFU browser-based video calls",
|
"description": "WebRTC SFU browser-based video calls",
|
||||||
"main": "Server.js",
|
"main": "Server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"mocha": "^11.1.0",
|
"mocha": "^11.1.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"nodemon": "^3.1.9",
|
"nodemon": "^3.1.9",
|
||||||
"prettier": "3.5.2",
|
"prettier": "3.5.3",
|
||||||
"proxyquire": "^2.1.3",
|
"proxyquire": "^2.1.3",
|
||||||
"should": "^13.2.3",
|
"should": "^13.2.3",
|
||||||
"sinon": "^19.0.2"
|
"sinon": "^19.0.2"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ let BRAND = {
|
|||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
imageUrl: '../images/mirotalk-logo.gif',
|
imageUrl: '../images/mirotalk-logo.gif',
|
||||||
title: '<strong>WebRTC SFU v1.7.63</strong>',
|
title: '<strong>WebRTC SFU v1.7.64</strong>',
|
||||||
html: `
|
html: `
|
||||||
<button
|
<button
|
||||||
id="support-button"
|
id="support-button"
|
||||||
|
|||||||
@@ -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.7.63
|
* @version 1.7.64
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -5272,7 +5272,7 @@ function showAbout() {
|
|||||||
position: 'center',
|
position: 'center',
|
||||||
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
|
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
|
||||||
customClass: { image: 'img-about' },
|
customClass: { image: 'img-about' },
|
||||||
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.7.63',
|
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.7.64',
|
||||||
html: `
|
html: `
|
||||||
<br />
|
<br />
|
||||||
<div id="about">
|
<div id="about">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* @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.7.63
|
* @version 1.7.64
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -236,6 +236,12 @@ class RoomClient {
|
|||||||
this.reconnectInterval = 3000;
|
this.reconnectInterval = 3000;
|
||||||
this.maxReconnectInterval = 15000;
|
this.maxReconnectInterval = 15000;
|
||||||
|
|
||||||
|
// Handle ICE
|
||||||
|
this.iceMaxRetries = 3;
|
||||||
|
this.iceRestarting = false;
|
||||||
|
this.iceProducerRestarting = false;
|
||||||
|
this.iceConsumerRestarting = false;
|
||||||
|
|
||||||
this.room_id = room_id;
|
this.room_id = room_id;
|
||||||
this.peer_id = socket.id;
|
this.peer_id = socket.id;
|
||||||
this.peer_name = peer_name;
|
this.peer_name = peer_name;
|
||||||
@@ -688,29 +694,30 @@ class RoomClient {
|
|||||||
// ####################################################
|
// ####################################################
|
||||||
|
|
||||||
async initTransports(device) {
|
async initTransports(device) {
|
||||||
// ####################################################
|
await this.initProducerTransport(device);
|
||||||
// PRODUCER TRANSPORT
|
await this.initConsumerTransport(device);
|
||||||
// ####################################################
|
}
|
||||||
|
|
||||||
|
// ####################################################
|
||||||
|
// PRODUCER TRANSPORT
|
||||||
|
// ####################################################
|
||||||
|
|
||||||
|
async initProducerTransport(device) {
|
||||||
const producerTransportData = await this.socket.request('createWebRtcTransport', {
|
const producerTransportData = await this.socket.request('createWebRtcTransport', {
|
||||||
forceTcp: false,
|
forceTcp: false,
|
||||||
rtpCapabilities: device.rtpCapabilities,
|
rtpCapabilities: device.rtpCapabilities,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (producerTransportData.error) {
|
if (producerTransportData.error) {
|
||||||
console.error(producerTransportData.error);
|
console.error('Producer Transport creation failed', producerTransportData.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
producerTransportData['proprietaryConstraints'] = { optional: [{ googDscp: true }] };
|
|
||||||
|
|
||||||
this.producerTransport = device.createSendTransport(producerTransportData);
|
this.producerTransport = device.createSendTransport(producerTransportData);
|
||||||
|
this.setupProducerTransportHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
console.info('07.4 producerTransportData ---->', {
|
setupProducerTransportHandlers() {
|
||||||
producerTransportId: this.producerTransport.id,
|
|
||||||
producerTransportData: producerTransportData,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.producerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
this.producerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||||
try {
|
try {
|
||||||
await this.socket.request('connectTransport', {
|
await this.socket.request('connectTransport', {
|
||||||
@@ -725,7 +732,6 @@ class RoomClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.producerTransport.on('produce', async ({ kind, appData, rtpParameters }, callback, errback) => {
|
this.producerTransport.on('produce', async ({ kind, appData, rtpParameters }, callback, errback) => {
|
||||||
console.log('Going to produce', { kind, appData, rtpParameters });
|
|
||||||
try {
|
try {
|
||||||
const { producer_id } = await this.socket.request('produce', {
|
const { producer_id } = await this.socket.request('produce', {
|
||||||
producerTransportId: this.producerTransport.id,
|
producerTransportId: this.producerTransport.id,
|
||||||
@@ -733,40 +739,42 @@ class RoomClient {
|
|||||||
appData,
|
appData,
|
||||||
rtpParameters,
|
rtpParameters,
|
||||||
});
|
});
|
||||||
callback({
|
callback({ id: producer_id });
|
||||||
id: producer_id,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errback(err);
|
errback(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.producerTransport.on('connectionstatechange', (state) => {
|
this.producerTransport.on('connectionstatechange', async (state) => {
|
||||||
|
console.log(`Producer Transport state changed to: ${state}`, { id: this.producerTransport.id });
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'connecting':
|
case 'connecting':
|
||||||
console.log('Producer Transport connecting...');
|
console.log('Producer Transport connecting...');
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
console.log('Producer Transport connected', { id: this.producerTransport.id });
|
console.log('✅ Producer Transport connected', { id: this.producerTransport.id });
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
console.log('Producer Transport disconnected', { id: this.producerTransport.id });
|
console.warn('Producer Transport disconnected', { id: this.producerTransport.id });
|
||||||
|
|
||||||
|
await this.restartProducerIce();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'failed':
|
case 'failed':
|
||||||
console.warn('Producer Transport failed', { id: this.producerTransport.id });
|
console.warn('❌ Producer Transport failed', { id: this.producerTransport.id });
|
||||||
|
|
||||||
this.producerTransport.close();
|
this.producerTransport.close();
|
||||||
|
|
||||||
popupHtmlMessage(
|
popupHtmlMessage(
|
||||||
null,
|
null,
|
||||||
image.network,
|
image.network,
|
||||||
'Producer Transport failed',
|
'Producer Transport',
|
||||||
'Check Your Network Configuration',
|
'Unable to connect. Please check your network.',
|
||||||
'center',
|
'center',
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Producer transport connection state changes', {
|
console.log('Producer transport connection state changes', {
|
||||||
@@ -776,34 +784,27 @@ class RoomClient {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.producerTransport.on('icegatheringstatechange', (state) => {
|
// ####################################################
|
||||||
console.log('Producer icegatheringstatechange', {
|
// CONSUMER TRANSPORT
|
||||||
state: state,
|
// ####################################################
|
||||||
id: this.producerTransport.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ####################################################
|
|
||||||
// CONSUMER TRANSPORT
|
|
||||||
// ####################################################
|
|
||||||
|
|
||||||
|
async initConsumerTransport(device) {
|
||||||
const consumerTransportData = await this.socket.request('createWebRtcTransport', {
|
const consumerTransportData = await this.socket.request('createWebRtcTransport', {
|
||||||
forceTcp: false,
|
forceTcp: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (consumerTransportData.error) {
|
if (consumerTransportData.error) {
|
||||||
console.error(consumerTransportData.error);
|
console.error('Consumer Transport creation failed', consumerTransportData.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.consumerTransport = device.createRecvTransport(consumerTransportData);
|
this.consumerTransport = device.createRecvTransport(consumerTransportData);
|
||||||
|
this.setupConsumerTransportHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
console.info('07.5 consumerTransportData ---->', {
|
setupConsumerTransportHandlers() {
|
||||||
consumerTransportId: this.consumerTransport.id,
|
|
||||||
consumerTransportData: consumerTransportData,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
this.consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||||
try {
|
try {
|
||||||
await this.socket.request('connectTransport', {
|
await this.socket.request('connectTransport', {
|
||||||
@@ -817,32 +818,36 @@ class RoomClient {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.consumerTransport.on('connectionstatechange', (state) => {
|
this.consumerTransport.on('connectionstatechange', async (state) => {
|
||||||
|
console.log(`Consumer Transport state changed to: ${state}`, { id: this.consumerTransport.id });
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'connecting':
|
case 'connecting':
|
||||||
console.log('Consumer Transport connecting...');
|
console.log('Consumer Transport connecting...');
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
console.log('Consumer Transport connected', { id: this.consumerTransport.id });
|
console.log('✅ Consumer Transport connected', { id: this.consumerTransport.id });
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
console.log('Consumer Transport disconnected', { id: this.consumerTransport.id });
|
console.warn('Consumer Transport disconnected', { id: this.consumerTransport.id });
|
||||||
|
|
||||||
|
await this.restartConsumerIce();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'failed':
|
case 'failed':
|
||||||
console.warn('Consumer Transport failed', { id: this.consumerTransport.id });
|
console.warn('❌ Consumer Transport failed', { id: this.consumerTransport.id });
|
||||||
|
|
||||||
this.consumerTransport.close();
|
this.consumerTransport.close();
|
||||||
|
|
||||||
popupHtmlMessage(
|
popupHtmlMessage(
|
||||||
null,
|
null,
|
||||||
image.network,
|
image.network,
|
||||||
'Consumer Transport failed',
|
'Consumer Transport',
|
||||||
'Check Your Network Configuration',
|
'Unable to connect. Please check your network.',
|
||||||
'center',
|
'center',
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Consumer transport connection state changes', {
|
console.log('Consumer transport connection state changes', {
|
||||||
@@ -852,50 +857,111 @@ class RoomClient {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.consumerTransport.on('icegatheringstatechange', (state) => {
|
|
||||||
console.log('Consumer icegatheringstatechange', {
|
|
||||||
state: state,
|
|
||||||
id: this.consumerTransport.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ####################################################
|
|
||||||
// TODO: DATA TRANSPORT
|
|
||||||
// ####################################################
|
|
||||||
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ####################################################
|
// ####################################################
|
||||||
// RESTART ICE
|
// TODO: DATA TRANSPORT
|
||||||
// ####################################################
|
// ####################################################
|
||||||
|
|
||||||
async restartIce() {
|
// ####################################################
|
||||||
console.log('Restart ICE...');
|
// HANDLE ICE
|
||||||
|
// ####################################################
|
||||||
|
|
||||||
|
async restartTransportIce(transport, type) {
|
||||||
|
if (!transport || typeof transport !== 'object' || transport.closed || this[`ice${type}Restarting`]) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.producerTransport) {
|
this[`ice${type}Restarting`] = true;
|
||||||
const iceParameters = await this.socket.request('restartIce', {
|
|
||||||
transport_id: this.producerTransport.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Restarting producer transport ICE', iceParameters);
|
if (transport.connectionState !== 'disconnected') return;
|
||||||
|
|
||||||
await this.producerTransport.restartIce({ iceParameters });
|
console.warn(`🔄 Restarting ${type} ICE...`, {
|
||||||
|
id: transport.id,
|
||||||
|
state: transport.connectionState,
|
||||||
|
});
|
||||||
|
|
||||||
|
let retryCount = 0;
|
||||||
|
while (retryCount < this.iceMaxRetries) {
|
||||||
|
try {
|
||||||
|
const iceParameters = await Promise.race([
|
||||||
|
this.socket.request('restartIce', { transport_id: transport.id }),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('ICE restart timeout')), 5000 + retryCount * 5000),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!iceParameters) {
|
||||||
|
console.warn(`⚠️ No ${type} ICE Parameters received.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(`🚀 Restarting ${type} transport ICE`, iceParameters);
|
||||||
|
await transport.restartIce({ iceParameters });
|
||||||
|
|
||||||
|
console.info(`✅ Successfully restarted ${type} ICE`);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
retryCount++;
|
||||||
|
console.error(`❌ ${type} ICE restart failed (attempt ${retryCount})`, {
|
||||||
|
id: transport.id,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (retryCount >= this.iceMaxRetries) {
|
||||||
|
console.error(`🚨 Max retries reached for ${type} ICE restart.`);
|
||||||
|
|
||||||
|
transport.close();
|
||||||
|
|
||||||
|
popupHtmlMessage(
|
||||||
|
null,
|
||||||
|
image.network,
|
||||||
|
'Transport closed',
|
||||||
|
'Unable to reconnect. Please check your network.',
|
||||||
|
'center',
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 5000 * retryCount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.consumerTransport) {
|
|
||||||
const iceParameters = await this.socket.request('restartIce', {
|
|
||||||
transport_id: this.consumerTransport.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Restarting consumer transport ICE', iceParameters);
|
|
||||||
|
|
||||||
await this.consumerTransport.restartIce({ iceParameters });
|
|
||||||
}
|
|
||||||
console.log('Restart ICE done');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Restart ICE error', error);
|
console.error(`🔥 Restart ${type} ICE error`, {
|
||||||
|
id: transport?.id,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this[`ice${type}Restarting`] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async restartProducerIce() {
|
||||||
|
await this.restartTransportIce(this.producerTransport, 'Producer');
|
||||||
|
}
|
||||||
|
|
||||||
|
async restartConsumerIce() {
|
||||||
|
await this.restartTransportIce(this.consumerTransport, 'Consumer');
|
||||||
|
}
|
||||||
|
|
||||||
|
async restartIce() {
|
||||||
|
if (this.iceRestarting) return;
|
||||||
|
|
||||||
|
console.warn('Restart ICE...', {
|
||||||
|
producerTransportConnectionState: this.producerTransport.connectionState,
|
||||||
|
consumerTransportConnectionState: this.consumerTransport.connectionState,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.iceRestarting = true;
|
||||||
|
await this.restartProducerIce();
|
||||||
|
await this.restartConsumerIce();
|
||||||
|
console.log('✅ Restart ICE done');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Restart ICE error', error);
|
||||||
|
} finally {
|
||||||
|
this.iceRestarting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم