diff --git a/app/src/Room.js b/app/src/Room.js
index 5cde5d31..c5f91f22 100644
--- a/app/src/Room.js
+++ b/app/src/Room.js
@@ -547,7 +547,7 @@ module.exports = class Room {
enableUdp: true,
enableTcp: true,
preferUdp: true,
- iceConsentTimeout: 20,
+ iceConsentTimeout: 30,
initialAvailableOutgoingBitrate,
};
diff --git a/app/src/Server.js b/app/src/Server.js
index 73208068..95a1b98e 100644
--- a/app/src/Server.js
+++ b/app/src/Server.js
@@ -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 CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @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);
- 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);
});
diff --git a/package.json b/package.json
index d723f60c..1fcbbd7e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mirotalksfu",
- "version": "1.7.63",
+ "version": "1.7.64",
"description": "WebRTC SFU browser-based video calls",
"main": "Server.js",
"scripts": {
@@ -90,7 +90,7 @@
"mocha": "^11.1.0",
"node-fetch": "^3.3.2",
"nodemon": "^3.1.9",
- "prettier": "3.5.2",
+ "prettier": "3.5.3",
"proxyquire": "^2.1.3",
"should": "^13.2.3",
"sinon": "^19.0.2"
diff --git a/public/js/Brand.js b/public/js/Brand.js
index de73dcc1..ab035b69 100644
--- a/public/js/Brand.js
+++ b/public/js/Brand.js
@@ -64,7 +64,7 @@ let BRAND = {
},
about: {
imageUrl: '../images/mirotalk-logo.gif',
- title: 'WebRTC SFU v1.7.63',
+ title: 'WebRTC SFU v1.7.64',
html: `
diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js
index a8cdae20..1270b0c3 100644
--- a/public/js/RoomClient.js
+++ b/public/js/RoomClient.js
@@ -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.7.63
+ * @version 1.7.64
*
*/
@@ -236,6 +236,12 @@ class RoomClient {
this.reconnectInterval = 3000;
this.maxReconnectInterval = 15000;
+ // Handle ICE
+ this.iceMaxRetries = 3;
+ this.iceRestarting = false;
+ this.iceProducerRestarting = false;
+ this.iceConsumerRestarting = false;
+
this.room_id = room_id;
this.peer_id = socket.id;
this.peer_name = peer_name;
@@ -688,29 +694,30 @@ class RoomClient {
// ####################################################
async initTransports(device) {
- // ####################################################
- // PRODUCER TRANSPORT
- // ####################################################
+ await this.initProducerTransport(device);
+ await this.initConsumerTransport(device);
+ }
+ // ####################################################
+ // PRODUCER TRANSPORT
+ // ####################################################
+
+ async initProducerTransport(device) {
const producerTransportData = await this.socket.request('createWebRtcTransport', {
forceTcp: false,
rtpCapabilities: device.rtpCapabilities,
});
if (producerTransportData.error) {
- console.error(producerTransportData.error);
+ console.error('Producer Transport creation failed', producerTransportData.error);
return;
}
- producerTransportData['proprietaryConstraints'] = { optional: [{ googDscp: true }] };
-
this.producerTransport = device.createSendTransport(producerTransportData);
+ this.setupProducerTransportHandlers();
+ }
- console.info('07.4 producerTransportData ---->', {
- producerTransportId: this.producerTransport.id,
- producerTransportData: producerTransportData,
- });
-
+ setupProducerTransportHandlers() {
this.producerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await this.socket.request('connectTransport', {
@@ -725,7 +732,6 @@ class RoomClient {
});
this.producerTransport.on('produce', async ({ kind, appData, rtpParameters }, callback, errback) => {
- console.log('Going to produce', { kind, appData, rtpParameters });
try {
const { producer_id } = await this.socket.request('produce', {
producerTransportId: this.producerTransport.id,
@@ -733,40 +739,42 @@ class RoomClient {
appData,
rtpParameters,
});
- callback({
- id: producer_id,
- });
+ callback({ id: producer_id });
} catch (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) {
case 'connecting':
console.log('Producer Transport connecting...');
break;
case 'connected':
- console.log('Producer Transport connected', { id: this.producerTransport.id });
+ console.log('✅ Producer Transport connected', { id: this.producerTransport.id });
break;
case 'disconnected':
- console.log('Producer Transport disconnected', { id: this.producerTransport.id });
+ console.warn('Producer Transport disconnected', { id: this.producerTransport.id });
+
+ await this.restartProducerIce();
+
break;
case 'failed':
- console.warn('Producer Transport failed', { id: this.producerTransport.id });
+ console.warn('❌ Producer Transport failed', { id: this.producerTransport.id });
this.producerTransport.close();
popupHtmlMessage(
null,
image.network,
- 'Producer Transport failed',
- 'Check Your Network Configuration',
+ 'Producer Transport',
+ 'Unable to connect. Please check your network.',
'center',
false,
true,
);
-
break;
default:
console.log('Producer transport connection state changes', {
@@ -776,34 +784,27 @@ class RoomClient {
break;
}
});
+ }
- this.producerTransport.on('icegatheringstatechange', (state) => {
- console.log('Producer icegatheringstatechange', {
- state: state,
- id: this.producerTransport.id,
- });
- });
-
- // ####################################################
- // CONSUMER TRANSPORT
- // ####################################################
+ // ####################################################
+ // CONSUMER TRANSPORT
+ // ####################################################
+ async initConsumerTransport(device) {
const consumerTransportData = await this.socket.request('createWebRtcTransport', {
forceTcp: false,
});
if (consumerTransportData.error) {
- console.error(consumerTransportData.error);
+ console.error('Consumer Transport creation failed', consumerTransportData.error);
return;
}
this.consumerTransport = device.createRecvTransport(consumerTransportData);
+ this.setupConsumerTransportHandlers();
+ }
- console.info('07.5 consumerTransportData ---->', {
- consumerTransportId: this.consumerTransport.id,
- consumerTransportData: consumerTransportData,
- });
-
+ setupConsumerTransportHandlers() {
this.consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
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) {
case 'connecting':
console.log('Consumer Transport connecting...');
break;
case 'connected':
- console.log('Consumer Transport connected', { id: this.consumerTransport.id });
+ console.log('✅ Consumer Transport connected', { id: this.consumerTransport.id });
break;
case 'disconnected':
- console.log('Consumer Transport disconnected', { id: this.consumerTransport.id });
+ console.warn('Consumer Transport disconnected', { id: this.consumerTransport.id });
+
+ await this.restartConsumerIce();
+
break;
case 'failed':
- console.warn('Consumer Transport failed', { id: this.consumerTransport.id });
+ console.warn('❌ Consumer Transport failed', { id: this.consumerTransport.id });
this.consumerTransport.close();
popupHtmlMessage(
null,
image.network,
- 'Consumer Transport failed',
- 'Check Your Network Configuration',
+ 'Consumer Transport',
+ 'Unable to connect. Please check your network.',
'center',
false,
true,
);
-
break;
default:
console.log('Consumer transport connection state changes', {
@@ -852,50 +857,111 @@ class RoomClient {
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 {
- if (this.producerTransport) {
- const iceParameters = await this.socket.request('restartIce', {
- transport_id: this.producerTransport.id,
- });
+ this[`ice${type}Restarting`] = true;
- 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) {
- 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;
}
}