@reactoo/watchtogether-sdk-js 2.6.13 → 2.6.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -206,9 +206,7 @@ class RoomSession {
206
206
  this._roomType = 'watchparty';
207
207
  this._isDataChannelOpen = false;
208
208
  this._abortController = null;
209
-
210
- this.isAudioMuted = false;
211
- this.isVideoMuted = false;
209
+ this.isMuted = [];
212
210
  this.isVideoEnabled = false;
213
211
  this.isAudioEnabed = false;
214
212
 
@@ -221,7 +219,6 @@ class RoomSession {
221
219
  if (this.options.debug) {
222
220
  this._enableDebug();
223
221
  }
224
-
225
222
  }
226
223
 
227
224
  _participantShouldSubscribe(userId) {
@@ -242,6 +239,7 @@ class RoomSession {
242
239
  message: 'id non-existent',
243
240
  data: [handleId, 'getParticipantEventName']
244
241
  });
242
+ return;
245
243
  }
246
244
 
247
245
  const participantRole = decodeJanusDisplay(handle.userId)?.role;
@@ -278,6 +276,7 @@ class RoomSession {
278
276
  message: 'id non-existent',
279
277
  data: [handleId, 'getParticipantEventName']
280
278
  });
279
+ return;
281
280
  }
282
281
 
283
282
  const participantRole = decodeJanusDisplay(handle.userId)?.role;
@@ -501,10 +500,10 @@ class RoomSession {
501
500
 
502
501
  if (sender === this.handleId) {
503
502
  if (type === "event") {
503
+
504
504
  var plugindata = json["plugindata"] || {};
505
505
  var msg = plugindata["data"] || {};
506
506
  var jsep = json["jsep"];
507
-
508
507
  let result = msg["result"] || null;
509
508
  let event = msg["videoroom"] || null;
510
509
  let list = msg["publishers"] || {};
@@ -539,9 +538,9 @@ class RoomSession {
539
538
  "request": "join",
540
539
  "room": this.roomId,
541
540
  "ptype": "subscriber",
542
- streams: streams.map(stream => ({feed: stream.id, mid: stream.mid})),
543
- //"feed": id,
544
541
  "private_id": this.privateId,
542
+ streams: streams.filter(s => !s.disabled).map(stream => ({feed: stream.id, mid: stream.mid})),
543
+ //"feed": id,
545
544
  ...(this.webrtcVersion > 1000 ? {id: this.userId} : {}),
546
545
  pin: this.pin
547
546
  }
@@ -560,6 +559,7 @@ class RoomSession {
560
559
  }
561
560
 
562
561
  for (let f in list) {
562
+
563
563
  let userId = list[f]["display"];
564
564
  let streams = list[f]["streams"] || [];
565
565
  let id = list[f]["id"];
@@ -569,24 +569,42 @@ class RoomSession {
569
569
  }
570
570
  this._log('Remote userId: ', userId);
571
571
  if (this._participantShouldSubscribe(userId)) {
572
- this._log('Creating user: ', userId);
573
- this._createParticipant(userId, id)
574
- .then(handle => {
575
- return this.sendMessage(handle.handleId, {
572
+
573
+ let handle = this._getHandle(null, id);
574
+ if(handle) {
575
+ let subscribe = streams.filter(stream => !stream.disabled && handle.webrtcStuff.tracksMap.filter(t => t.active).findIndex(t => t.mid === stream.mid) === -1).map(s => ({feed: s.id, mid: s.mid}));
576
+ let unsubscribe = streams.filter(stream => stream.disabled).map(s => ({feed: s.id, mid: s.mid}));
577
+ this._log('Already subscribed to user: ', userId, 'Update streams', subscribe, unsubscribe);
578
+ if(subscribe.length || unsubscribe.length) {
579
+ this.sendMessage(handle.handleId, {
576
580
  body: {
577
- "request": "join",
578
- "room": this.roomId,
579
- "ptype": "subscriber",
580
- streams: streams.map(stream => ({feed: stream.id, mid: stream.mid})),
581
- "private_id": this.privateId,
582
- ...(this.webrtcVersion > 1000 ? {id: this.userId} : {}),
583
- pin: this.pin
581
+ "request": "update",
582
+ ...(subscribe.length ? {subscribe}: {}),
583
+ ...(unsubscribe.length ? {unsubscribe}: {})
584
584
  }
585
585
  })
586
- })
587
- .catch(err => {
588
- this.emit('error', err);
589
- })
586
+ }
587
+ }
588
+ else {
589
+ this._log('Creating user: ', userId, streams);
590
+ this._createParticipant(userId, id)
591
+ .then(handle => {
592
+ return this.sendMessage(handle.handleId, {
593
+ body: {
594
+ "request": "join",
595
+ "room": this.roomId,
596
+ "ptype": "subscriber",
597
+ "private_id": this.privateId,
598
+ streams: streams.filter(s => !s.disabled).map(stream => ({feed: stream.id, mid: stream.mid})),
599
+ ...(this.webrtcVersion > 1000 ? {id: this.userId} : {}),
600
+ pin: this.pin
601
+ }
602
+ })
603
+ })
604
+ .catch(err => {
605
+ this.emit('error', err);
606
+ })
607
+ }
590
608
  }
591
609
  }
592
610
 
@@ -598,7 +616,7 @@ class RoomSession {
598
616
  this.disconnect().catch(() => {});
599
617
  }
600
618
  } else if (leaving) {
601
- //TODO: shouldnt we detach?
619
+ //TODO: in 1 PeerConnection case we only unsubscribe from streams
602
620
  this._log('leaving', leaving);
603
621
  this._removeParticipant(null, leaving, true);
604
622
  }
@@ -607,6 +625,7 @@ class RoomSession {
607
625
  this._log('unpublished', this.handleId, 'this is us');
608
626
  this._removeParticipant(this.handleId, null, false); // we do just hangup
609
627
  } else if (unpublished) {
628
+ //TODO: in 1 PeerConnection case we only unsubscribe from streams
610
629
  this._log('unpublished', unpublished);
611
630
  this._removeParticipant(null, unpublished, true); // we do hangup and detach
612
631
  }
@@ -684,18 +703,25 @@ class RoomSession {
684
703
  let error = msg["error"];
685
704
  let substream = msg["substream"];
686
705
 
706
+ if (event === "updated") {
707
+ this._log('Remote has updated tracks', msg);
708
+ this._updateParticipantsTrackData(handle.handleId, msg["streams"] || []);
709
+ }
710
+
687
711
  if (event === "attached") {
688
712
  this._log('Remote have successfully joined Room', msg);
713
+ this._updateParticipantsTrackData(handle.handleId, msg["streams"] || []);
689
714
  this.emit(this._getAddParticipantEventName(handle.handleId), {
690
715
  tid: generateUUID(),
691
716
  id: handle.handleId,
692
717
  userId: decodeJanusDisplay(handle.userId)?.userId,
693
718
  role: decodeJanusDisplay(handle.userId)?.role,
694
719
  stream: null,
720
+ streamMap: handle.webrtcStuff?.streamMap,
721
+ source: null,
695
722
  track: null,
696
723
  adding: false,
697
724
  constructId: this.constructId,
698
- metaData: this.options.metaData,
699
725
  hasAudioTrack: false,
700
726
  hasVideoTrack: false
701
727
  });
@@ -783,6 +809,7 @@ class RoomSession {
783
809
  "handle_id": handleId
784
810
  }, true) : Promise.resolve()))
785
811
  .finally(() => {
812
+
786
813
  try {
787
814
  if (handle.webrtcStuff.stream) {
788
815
  if(!this.isRestarting) {
@@ -795,7 +822,11 @@ class RoomSession {
795
822
  } catch (e) {
796
823
  // Do nothing
797
824
  }
825
+
826
+ handle.webrtcStuff.clearTrackRemovedListener?.();
827
+ handle.webrtcStuff.stream.onremovetrack = null;
798
828
  handle.webrtcStuff.stream = null;
829
+
799
830
  if (handle.webrtcStuff.dataChannel) {
800
831
  Object.keys(handle.webrtcStuff.dataChannel).forEach(label => {
801
832
  handle.webrtcStuff.dataChannel[label].onmessage = null;
@@ -818,6 +849,8 @@ class RoomSession {
818
849
  }
819
850
  handle.webrtcStuff = {
820
851
  stream: null,
852
+ streamMap: {},
853
+ tracksMap: [],
821
854
  mySdp: null,
822
855
  mediaConstraints: null,
823
856
  pc: null,
@@ -826,15 +859,24 @@ class RoomSession {
826
859
  dtmfSender: null,
827
860
  trickle: true,
828
861
  iceDone: false,
862
+ clearTrackRemovedListener: null,
829
863
  };
830
864
 
831
865
  if (handleId === this.handleId) {
832
866
  this._isDataChannelOpen = false;
833
867
  this.isPublished = false;
834
868
  this.emit('published', {status: false, hasStream: false});
835
- this.emit('removeLocalParticipant', {id: handleId, userId: decodeJanusDisplay(handle.userId)?.userId, role: decodeJanusDisplay(handle.userId)?.role});
869
+ this.emit('removeLocalParticipant', {
870
+ id: handleId,
871
+ userId: decodeJanusDisplay(handle.userId)?.userId,
872
+ role: decodeJanusDisplay(handle.userId)?.role}
873
+ );
836
874
  } else {
837
- this.emit(this._getRemoveParticipantEventName(handleId), {id: handleId, userId: decodeJanusDisplay(handle.userId)?.userId, role: decodeJanusDisplay(handle.userId)?.role});
875
+ this.emit(this._getRemoveParticipantEventName(handleId), {
876
+ id: handleId,
877
+ userId: decodeJanusDisplay(handle.userId)?.userId,
878
+ role: decodeJanusDisplay(handle.userId)?.role}
879
+ );
838
880
  }
839
881
 
840
882
  if (removeHandle) {
@@ -858,6 +900,8 @@ class RoomSession {
858
900
  rfid, userId,
859
901
  webrtcStuff: {
860
902
  stream: null,
903
+ streamMap: {},
904
+ tracksMap: [],
861
905
  mySdp: null,
862
906
  mediaConstraints: null,
863
907
  pc: null,
@@ -874,6 +918,54 @@ class RoomSession {
874
918
  })
875
919
  }
876
920
 
921
+ _updateParticipantsTrackData(handleId, streams) {
922
+ this._log('Updating participants track data', handleId, streams);
923
+ let handle = this._getHandle(handleId);
924
+ if (!handle) {
925
+ this.emit('error', {
926
+ type: 'warning',
927
+ id: 15,
928
+ message: 'id non-existent',
929
+ data: [handleId, 'updateParticipantsTrackData']
930
+ });
931
+ return;
932
+ }
933
+ let config = handle.webrtcStuff;
934
+ config.tracksMap = structuredClone(streams);
935
+ }
936
+
937
+ _updateRemoteParticipantStreamMap(handleId) {
938
+ this._log('Updating participants stream map', handleId);
939
+ let handle = this._getHandle(handleId);
940
+ if (!handle) {
941
+ this.emit('error', {
942
+ type: 'warning',
943
+ id: 15,
944
+ message: 'id non-existent',
945
+ data: [handleId, 'updateRemoteParticipantStreamMap']
946
+ });
947
+ return;
948
+ }
949
+ let config = handle.webrtcStuff;
950
+ config.streamMap = {};
951
+ config.tracksMap.forEach(tItem => {
952
+ if(tItem.type === 'data') {
953
+ return;
954
+ }
955
+ if(tItem.active === false) {
956
+ return;
957
+ }
958
+ if(!config.streamMap[tItem.feed_description]) {
959
+ config.streamMap[tItem.feed_description] = [];
960
+ }
961
+ let trackId = config.pc.getTransceivers().find(t => t.mid === tItem.mid).receiver.track.id;
962
+ if(trackId) {
963
+ config.streamMap[tItem.feed_description].push(trackId);
964
+ }
965
+ })
966
+ }
967
+
968
+
877
969
  _joinRoom(roomId, pin, userId, display) {
878
970
  return this.sendMessage(this.handleId, {
879
971
  body: {
@@ -1239,11 +1331,12 @@ class RoomSession {
1239
1331
  let handle = this._getHandle(handleId);
1240
1332
  if (!handle) {
1241
1333
  this.emit('error', {
1242
- type: 'warning',
1334
+ type: 'error',
1243
1335
  id: 15,
1244
1336
  message: 'id non-existent',
1245
1337
  data: [handleId, 'create rtc connection']
1246
1338
  });
1339
+ return;
1247
1340
  }
1248
1341
 
1249
1342
  let config = handle.webrtcStuff;
@@ -1266,8 +1359,15 @@ class RoomSession {
1266
1359
  this._log('new RTCPeerConnection', pc_config, pc_constraints);
1267
1360
 
1268
1361
  config.pc = new RTCPeerConnection(pc_config, pc_constraints);
1362
+
1363
+ config.pc.onnegotiationneeded = () => {
1364
+ this._log('onnegotiationneeded');
1365
+ };
1366
+
1269
1367
  config.pc.onconnectionstatechange = () => {
1368
+ //TODO: check if this isnt fired prematurely when we switch internet connection
1270
1369
  if (config.pc.connectionState === 'failed') {
1370
+ this._log('connectionState failed');
1271
1371
  this._iceRestart(handleId);
1272
1372
  }
1273
1373
  this.emit('connectionState', [handleId, handleId === this.handleId, config.pc.connectionState]);
@@ -1279,10 +1379,11 @@ class RoomSession {
1279
1379
  userId: decodeJanusDisplay(handle.userId)?.userId,
1280
1380
  role: decodeJanusDisplay(handle.userId)?.role,
1281
1381
  stream: config.stream,
1382
+ streamMap: config.streamMap,
1282
1383
  track: null,
1384
+ source: null,
1283
1385
  optional: true,
1284
1386
  constructId: this.constructId,
1285
- metaData: this.options.metaData,
1286
1387
  adding: false,
1287
1388
  hasAudioTrack: !!(config.stream && config.stream.getAudioTracks().length),
1288
1389
  hasVideoTrack: !!(config.stream && config.stream.getVideoTracks().length)
@@ -1301,10 +1402,11 @@ class RoomSession {
1301
1402
  userId: decodeJanusDisplay(handle.userId)?.userId,
1302
1403
  role: decodeJanusDisplay(handle.userId)?.role,
1303
1404
  stream: config.stream,
1405
+ streamMap: config.streamMap,
1304
1406
  track: null,
1407
+ source: null,
1305
1408
  optional: true,
1306
1409
  constructId: this.constructId,
1307
- metaData: this.options.metaData,
1308
1410
  adding: false,
1309
1411
  hasAudioTrack: !!(config.stream && config.stream.getAudioTracks().length),
1310
1412
  hasVideoTrack: !!(config.stream && config.stream.getVideoTracks().length)
@@ -1340,14 +1442,44 @@ class RoomSession {
1340
1442
  if(!event.streams)
1341
1443
  return;
1342
1444
 
1343
- //config.stream = event.streams[0];
1344
-
1345
1445
  if (!config.stream) {
1346
1446
  config.stream = new MediaStream();
1347
1447
  }
1448
+
1449
+ if(!event.streams?.[0]?.onremovetrack) {
1450
+ event.streams[0].onremovetrack = (ev) => {
1451
+ this._log('Remote track removed', ev);
1452
+ let transceiver = config.pc?.getTransceivers()?.find(
1453
+ t => t.receiver.track === ev.track);
1454
+
1455
+ let mid = transceiver?.mid || ev.track.id;
1456
+ let source = Object.keys(config.streamMap).find(key => config.streamMap[key].includes(ev.track.id));
1457
+ config.stream?.removeTrack(ev.track);
1458
+ this._updateRemoteParticipantStreamMap(handle.handleId);
1459
+ this.emit(this._getAddParticipantEventName(handle.handleId), {
1460
+ tid: generateUUID(),
1461
+ id: handle.handleId,
1462
+ mid,
1463
+ constructId: this.constructId,
1464
+ userId: decodeJanusDisplay(handle.userId)?.userId,
1465
+ role: decodeJanusDisplay(handle.userId)?.role,
1466
+ stream: config.stream,
1467
+ streamMap: config.streamMap,
1468
+ source,
1469
+ track: ev.track,
1470
+ adding: false,
1471
+ removing: true,
1472
+ hasAudioTrack: !!(config.stream && config.stream.getAudioTracks().length),
1473
+ hasVideoTrack: !!(config.stream && config.stream.getVideoTracks().length)
1474
+ });
1475
+ };
1476
+ }
1477
+
1348
1478
  if (event.track) {
1349
1479
  let mid = event.transceiver ? event.transceiver.mid : event.track.id;
1350
- config.stream.addTrack(event.track);
1480
+ let source = Object.keys(config.streamMap).find(key => config.streamMap[key].includes(event.track.id));
1481
+ config.stream?.addTrack(event.track);
1482
+ this._updateRemoteParticipantStreamMap(handle.handleId);
1351
1483
  this.emit(this._getAddParticipantEventName(handle.handleId), {
1352
1484
  tid: generateUUID(),
1353
1485
  mid,
@@ -1355,9 +1487,10 @@ class RoomSession {
1355
1487
  userId: decodeJanusDisplay(handle.userId)?.userId,
1356
1488
  role: decodeJanusDisplay(handle.userId)?.role,
1357
1489
  stream: config.stream,
1490
+ streamMap: config.streamMap,
1491
+ source,
1358
1492
  track: event.track,
1359
1493
  constructId: this.constructId,
1360
- metaData: this.options.metaData,
1361
1494
  adding: true,
1362
1495
  hasAudioTrack: !!(config.stream && config.stream.getAudioTracks().length),
1363
1496
  hasVideoTrack: !!(config.stream && config.stream.getVideoTracks().length)
@@ -1367,45 +1500,49 @@ class RoomSession {
1367
1500
  return;
1368
1501
 
1369
1502
  event.track.onended = (ev) => {
1503
+ this._log('Remote track ended');
1370
1504
 
1371
1505
  let transceiver = config.pc?.getTransceivers()?.find(
1372
1506
  t => t.receiver.track === ev.target);
1373
1507
 
1374
1508
  let mid = transceiver?.mid || ev.target.id;
1375
-
1376
- if (config.stream) {
1377
- config.stream && config.stream.removeTrack(ev.target);
1378
- this.emit(this._getAddParticipantEventName(handle.handleId), {
1379
- tid: generateUUID(),
1380
- id: handle.handleId,
1381
- mid,
1382
- userId: decodeJanusDisplay(handle.userId)?.userId,
1383
- role: decodeJanusDisplay(handle.userId)?.role,
1384
- stream: config.stream,
1385
- track: ev.target,
1386
- constructId: this.constructId,
1387
- metaData: this.options.metaData,
1388
- adding: false,
1389
- removing: true,
1390
- hasAudioTrack: !!(config.stream && config.stream.getAudioTracks().length),
1391
- hasVideoTrack: !!(config.stream && config.stream.getVideoTracks().length)
1392
- });
1393
- }
1509
+ let source = Object.keys(config.streamMap).find(key => config.streamMap[key].includes(ev.target.id));
1510
+ config.stream?.removeTrack(ev.target);
1511
+ this._updateRemoteParticipantStreamMap(handle.handleId);
1512
+ this.emit(this._getAddParticipantEventName(handle.handleId), {
1513
+ tid: generateUUID(),
1514
+ id: handle.handleId,
1515
+ mid,
1516
+ constructId: this.constructId,
1517
+ userId: decodeJanusDisplay(handle.userId)?.userId,
1518
+ role: decodeJanusDisplay(handle.userId)?.role,
1519
+ stream: config.stream,
1520
+ streamMap: config.streamMap,
1521
+ source,
1522
+ track: ev.target,
1523
+ adding: false,
1524
+ removing: true,
1525
+ hasAudioTrack: !!(config.stream && config.stream.getAudioTracks().length),
1526
+ hasVideoTrack: !!(config.stream && config.stream.getVideoTracks().length)
1527
+ });
1394
1528
  };
1395
1529
 
1396
1530
  event.track.onmute = (ev) => {
1397
- this._log('remoteTrackMuted', 'onmute');
1531
+ this._log('Remote track muted');
1398
1532
 
1399
1533
  let transceiver = config.pc.getTransceivers().find(
1400
1534
  t => t.receiver.track === ev.target);
1401
1535
  let mid = transceiver.mid || ev.target.id;
1402
-
1536
+ let source = Object.keys(config.streamMap).find(key => config.streamMap[key].includes(ev.target.id));
1403
1537
  this.emit('remoteTrackMuted', {
1404
1538
  id: handle.handleId,
1405
1539
  mid,
1540
+ constructId: this.constructId,
1406
1541
  userId: decodeJanusDisplay(handle.userId)?.userId,
1407
1542
  role: decodeJanusDisplay(handle.userId)?.role,
1408
1543
  stream: config.stream,
1544
+ streamMap: config.streamMap,
1545
+ source,
1409
1546
  kind: ev.target.kind,
1410
1547
  track: ev.target,
1411
1548
  muted: true
@@ -1413,26 +1550,27 @@ class RoomSession {
1413
1550
  };
1414
1551
 
1415
1552
  event.track.onunmute = (ev) => {
1416
- this._log('remoteTrackMuted', 'onunmute');
1553
+ this._log('Remote track unmuted');
1417
1554
 
1418
1555
  let transceiver = config.pc.getTransceivers().find(
1419
1556
  t => t.receiver.track === ev.target);
1420
1557
  let mid = transceiver.mid || ev.target.id;
1421
-
1558
+ let source = Object.keys(config.streamMap).find(key => config.streamMap[key].includes(ev.target.id));
1422
1559
  this.emit('remoteTrackMuted', {
1423
1560
  id: handle.handleId,
1424
1561
  mid,
1562
+ constructId: this.constructId,
1425
1563
  userId: decodeJanusDisplay(handle.userId)?.userId,
1426
1564
  role: decodeJanusDisplay(handle.userId)?.role,
1427
1565
  stream: config.stream,
1566
+ streamMap: config.streamMap,
1567
+ source,
1428
1568
  kind: ev.target.kind,
1429
1569
  track: ev.target,
1430
1570
  muted: false
1431
1571
  });
1432
1572
  };
1433
-
1434
1573
  }
1435
-
1436
1574
  };
1437
1575
  }
1438
1576
  }
@@ -1450,7 +1588,6 @@ class RoomSession {
1450
1588
  let state = config.dataChannel[label] ? config.dataChannel[label].readyState : "null";
1451
1589
  this._handleDataEvents(handleId, 'state', {state, label} );
1452
1590
  };
1453
- //TODO: check this
1454
1591
  var onDataChannelError = (error) => {
1455
1592
  this._handleDataEvents(handleId, 'error', {label: error?.channel?.label, error});
1456
1593
  };
@@ -1536,7 +1673,6 @@ class RoomSession {
1536
1673
  config.isIceRestarting = true;
1537
1674
  let hasAudio = !!(config.stream && config.stream.getAudioTracks().length > 0);
1538
1675
  let hasVideo = !!(config.stream && config.stream.getVideoTracks().length > 0);
1539
- this._setupTransceivers(handleId,[hasAudio, false, hasVideo, false]);
1540
1676
  this._createAO('offer', handleId, true )
1541
1677
  .then((jsep) => {
1542
1678
  if (!jsep) {
@@ -1569,118 +1705,91 @@ class RoomSession {
1569
1705
 
1570
1706
  }
1571
1707
 
1572
- _setupTransceivers(handleId, [audioSend, audioRecv, videoSend, videoRecv]) {
1708
+ _setupTransceivers(handleId, [audioSend, audioRecv, videoSend, videoRecv, audioTransceiver = null, videoTransceiver = null]) {
1709
+
1710
+ //TODO: this should be refactored to use handle's trackMap so we dont have to pass any parameters
1573
1711
 
1574
1712
  let handle = this._getHandle(handleId);
1575
1713
  if (!handle) {
1576
1714
  return null;
1577
1715
  }
1578
-
1579
1716
  let config = handle.webrtcStuff;
1580
- let audioTransceiver = null, videoTransceiver = null;
1581
- let transceivers = config.pc.getTransceivers();
1582
- if (transceivers && transceivers.length > 0) {
1583
- for (var i in transceivers) {
1584
- var t = transceivers[i];
1585
- if ((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
1586
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
1587
- if (!audioTransceiver)
1588
- audioTransceiver = t;
1589
- continue;
1590
- }
1591
- if ((t.sender && t.sender.track && t.sender.track.kind === "video") ||
1592
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
1593
- if (!videoTransceiver)
1594
- videoTransceiver = t;
1595
- continue;
1596
- }
1597
- }
1598
- }
1599
1717
 
1600
- // Handle audio (and related changes, if any)
1601
- if (!audioSend && !audioRecv) {
1602
- // Audio disabled: have we removed it?
1603
- if (audioTransceiver) {
1604
- if (audioTransceiver.setDirection) {
1605
- audioTransceiver.setDirection("inactive");
1606
- } else {
1607
- audioTransceiver.direction = "inactive";
1608
- }
1609
- }
1610
- } else {
1611
- // Take care of audio m-line
1612
- if (audioSend && audioRecv) {
1613
- if (audioTransceiver) {
1614
- if (audioTransceiver.setDirection) {
1615
- audioTransceiver.setDirection("sendrecv");
1718
+ const setTransceiver = (transceiver, send, recv) => {
1719
+ if (!send && !recv) {
1720
+ // disabled: have we removed it?
1721
+ if (transceiver) {
1722
+ if (transceiver.setDirection) {
1723
+ transceiver.setDirection("inactive");
1616
1724
  } else {
1617
- audioTransceiver.direction = "sendrecv";
1725
+ transceiver.direction = "inactive";
1618
1726
  }
1619
1727
  }
1620
- } else if (audioSend && !audioRecv) {
1621
- if (audioTransceiver) {
1622
- if (audioTransceiver.setDirection) {
1623
- audioTransceiver.setDirection("sendonly");
1624
- } else {
1625
- audioTransceiver.direction = "sendonly";
1728
+ } else {
1729
+ if (send && recv) {
1730
+ if (transceiver) {
1731
+ if (transceiver.setDirection) {
1732
+ transceiver.setDirection("sendrecv");
1733
+ } else {
1734
+ transceiver.direction = "sendrecv";
1735
+ }
1626
1736
  }
1627
- }
1628
- } else if (!audioSend && audioRecv) {
1629
- if (audioTransceiver) {
1630
- if (audioTransceiver.setDirection) {
1631
- audioTransceiver.setDirection("recvonly");
1737
+ } else if (send && !recv) {
1738
+ if (transceiver) {
1739
+ if (transceiver.setDirection) {
1740
+ transceiver.setDirection("sendonly");
1741
+ } else {
1742
+ transceiver.direction = "sendonly";
1743
+ }
1744
+ }
1745
+ } else if (!send && recv) {
1746
+ if (transceiver) {
1747
+ if (transceiver.setDirection) {
1748
+ transceiver.setDirection("recvonly");
1749
+ } else {
1750
+ transceiver.direction = "recvonly";
1751
+ }
1632
1752
  } else {
1633
- audioTransceiver.direction = "recvonly";
1753
+ // In theory, this is the only case where we might not have a transceiver yet
1754
+ config.pc.addTransceiver("audio", {direction: "recvonly"});
1634
1755
  }
1635
- } else {
1636
- // In theory, this is the only case where we might not have a transceiver yet
1637
- config.pc.addTransceiver("audio", {direction: "recvonly"});
1638
1756
  }
1639
1757
  }
1640
1758
  }
1641
- // Handle video (and related changes, if any)
1642
- if (!videoSend && !videoRecv) {
1643
- if (videoTransceiver) {
1644
- if (videoTransceiver.setDirection) {
1645
- videoTransceiver.setDirection("inactive");
1646
- } else {
1647
- videoTransceiver.direction = "inactive";
1648
- }
1759
+
1760
+ // if we're passing any transceivers, we work only on them, doesn't matter if one of them is null
1761
+ if(audioTransceiver || videoTransceiver) {
1762
+ if(audioTransceiver) {
1763
+ setTransceiver(audioTransceiver, audioSend, audioRecv);
1649
1764
  }
1650
- } else {
1651
- // Take care of video m-line
1652
- if (videoSend && videoRecv) {
1653
- if (videoTransceiver) {
1654
- if (videoTransceiver.setDirection) {
1655
- videoTransceiver.setDirection("sendrecv");
1656
- } else {
1657
- videoTransceiver.direction = "sendrecv";
1658
- }
1659
- }
1660
- } else if (videoSend && !videoRecv) {
1661
- if (videoTransceiver) {
1662
- if (videoTransceiver.setDirection) {
1663
- videoTransceiver.setDirection("sendonly");
1664
- } else {
1665
- videoTransceiver.direction = "sendonly";
1765
+ if(videoTransceiver) {
1766
+ setTransceiver(videoTransceiver, videoSend, videoRecv);
1767
+ }
1768
+ }
1769
+ // else we work on all transceivers
1770
+ else {
1771
+ let transceivers = config.pc.getTransceivers();
1772
+ if (transceivers && transceivers.length > 0) {
1773
+ for (let i in transceivers) {
1774
+ let t = transceivers[i];
1775
+ if (
1776
+ (t.sender && t.sender.track && t.sender.track.kind === "audio") ||
1777
+ (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")
1778
+ ) {
1779
+ setTransceiver(t, audioSend, audioRecv);
1666
1780
  }
1667
- }
1668
- } else if (!videoSend && videoRecv) {
1669
- if (videoTransceiver) {
1670
- if (videoTransceiver.setDirection) {
1671
- videoTransceiver.setDirection("recvonly");
1672
- } else {
1673
- videoTransceiver.direction = "recvonly";
1781
+ if (
1782
+ (t.sender && t.sender.track && t.sender.track.kind === "video") ||
1783
+ (t.receiver && t.receiver.track && t.receiver.track.kind === "video")
1784
+ ) {
1785
+ setTransceiver(t, videoSend, videoRecv);
1674
1786
  }
1675
- } else {
1676
- // In theory, this is the only case where we might not have a transceiver yet
1677
- config.pc.addTransceiver("video", {direction: "recvonly"});
1678
1787
  }
1679
1788
  }
1680
1789
  }
1681
1790
  }
1682
1791
 
1683
- _createAO(type = 'offer', handleId, iceRestart = false) {
1792
+ _createAO(type = 'offer', handleId, iceRestart = false, ) {
1684
1793
 
1685
1794
  let handle = this._getHandle(handleId);
1686
1795
  if (!handle) {
@@ -1707,7 +1816,7 @@ class RoomSession {
1707
1816
  }
1708
1817
 
1709
1818
  return config.pc[methodName](mediaConstraints)
1710
- .then(function (response) {
1819
+ .then( (response) => {
1711
1820
  config.mySdp = response.sdp;
1712
1821
  let _p = config.pc.setLocalDescription(response)
1713
1822
  .catch((e) => {
@@ -1805,8 +1914,35 @@ class RoomSession {
1805
1914
  }
1806
1915
 
1807
1916
  //Public methods
1917
+ _republishOnTrackEnded(source) {
1918
+ let handle = this._getHandle(this.handleId);
1919
+ if (!handle) {
1920
+ return;
1921
+ }
1922
+ let config = handle.webrtcStuff;
1923
+ if (!config.stream) {
1924
+ return;
1925
+ }
1926
+ let sourceTrackIds = (config.streamMap[source] || []);
1927
+ let remainingTracks = [];
1928
+ for(let i = 0; i < sourceTrackIds.length; i++) {
1929
+ let foundTrack = config.stream.getTracks().find(t => t.id === sourceTrackIds[i]);
1930
+ if(foundTrack) {
1931
+ remainingTracks.push(foundTrack);
1932
+ }
1933
+ }
1934
+ if (remainingTracks.length) {
1935
+ let stream = new MediaStream();
1936
+ remainingTracks.forEach(track => stream.addTrack(track));
1937
+ return this.publishLocal(stream, source);
1938
+ }
1939
+ else {
1940
+ return this.publishLocal(null, source);
1941
+ }
1942
+ };
1943
+
1808
1944
 
1809
- publishLocal(stream = null) {
1945
+ publishLocal(stream = null, source = 'camera0') {
1810
1946
 
1811
1947
  if(this.isDisconnecting || !this.isConnected) {
1812
1948
  return Promise.reject({
@@ -1816,7 +1952,23 @@ class RoomSession {
1816
1952
  })
1817
1953
  }
1818
1954
 
1819
- this.emit('publishing', true);
1955
+ if(stream?.getVideoTracks()?.length > 1) {
1956
+ return Promise.reject({
1957
+ type: 'warning',
1958
+ id: 30,
1959
+ message: 'multiple video tracks not supported',
1960
+ data: null
1961
+ })
1962
+ }
1963
+
1964
+ if(stream?.getAudioTracks()?.length > 1) {
1965
+ return Promise.reject({
1966
+ type: 'warning',
1967
+ id: 30,
1968
+ message: 'multiple audio tracks not supported',
1969
+ data: null
1970
+ })
1971
+ }
1820
1972
 
1821
1973
  let handle = this._getHandle(this.handleId);
1822
1974
  if (!handle) {
@@ -1828,26 +1980,105 @@ class RoomSession {
1828
1980
  })
1829
1981
  }
1830
1982
 
1983
+ this.emit('publishing', true);
1984
+
1831
1985
  this._webrtc(this.handleId);
1832
1986
 
1833
1987
  let config = handle.webrtcStuff;
1988
+
1989
+ if (!config.stream) {
1990
+ config.stream = new MediaStream();
1991
+ }
1992
+
1993
+ let needsNegotiation = false;
1994
+ let transceivers = config.pc.getTransceivers();
1995
+ let existingTracks = [...(config.streamMap[source] || [])];
1996
+ config.streamMap[source] = stream?.getTracks()?.map(track => track.id) || [];
1997
+
1998
+ // remove old audio track related to this source
1999
+ let oldAudioStream = config?.stream?.getAudioTracks()?.find(track => existingTracks.includes(track.id));
2000
+ if (oldAudioStream) {
2001
+ try {
2002
+ oldAudioStream.stop();
2003
+
2004
+ } catch (e) {
2005
+ this._log(e);
2006
+ }
2007
+ config.stream.removeTrack(oldAudioStream);
2008
+ config.stream.dispatchEvent(new Event("trackremoved", {detail: oldAudioStream}))
2009
+ }
2010
+
2011
+ // remove old video track related to this source
2012
+ let oldVideoStream = config?.stream?.getVideoTracks()?.find(track => existingTracks.includes(track.id));
2013
+ if (oldVideoStream) {
2014
+ try {
2015
+ oldVideoStream.stop();
2016
+ } catch (e) {
2017
+ this._log(e);
2018
+ }
2019
+ config.stream.removeTrack(oldVideoStream);
2020
+ config.stream.dispatchEvent(new Event("trackremoved", {detail: oldVideoStream}))
2021
+ }
2022
+
1834
2023
  let audioTrackReplacePromise = Promise.resolve();
1835
2024
  let videoTrackReplacePromise = Promise.resolve();
1836
2025
 
1837
- if (!config.stream) {
1838
- config.stream = stream;
1839
- stream?.getTracks()?.forEach( (track) => {
2026
+ let audioTransceiver = null;
2027
+ let videoTransceiver = null;
2028
+ let replaceAudio = stream?.getAudioTracks()?.length;
2029
+ let replaceVideo = stream?.getVideoTracks()?.length;
2030
+
2031
+ for(const transceiver of transceivers) {
2032
+ if(['sendonly', 'sendrecv'].includes(transceiver.currentDirection) && transceiver.sender?.track?.kind === 'audio' && existingTracks.includes(transceiver.sender?.track?.id)) {
2033
+ audioTransceiver = transceiver;
2034
+ }
2035
+ else if(['sendonly', 'sendrecv'].includes(transceiver.currentDirection) && transceiver.sender?.track?.kind === 'video' && existingTracks.includes(transceiver.sender?.track?.id)) {
2036
+ videoTransceiver = transceiver;
2037
+ }
2038
+
2039
+ // Reusing existing transceivers
2040
+ // TODO: if we start using different codecs for different sources, we need to check for that here
2041
+
2042
+ else if(transceiver.currentDirection === 'inactive' && transceiver.sender?.getParameters()?.codecs?.find(c => c.mimeType.indexOf('audio') > -1) && replaceAudio && !audioTransceiver) {
2043
+ audioTransceiver = transceiver;
2044
+ needsNegotiation = true;
2045
+ }
2046
+ else if(transceiver.currentDirection === 'inactive' && transceiver.sender?.getParameters()?.codecs?.find(c => c.mimeType.indexOf('video') > -1) && replaceVideo && !videoTransceiver) {
2047
+ videoTransceiver = transceiver;
2048
+ needsNegotiation = true;
2049
+ }
2050
+ }
2051
+
2052
+ if (replaceAudio) {
2053
+ config.stream.addTrack(stream.getAudioTracks()[0]);
2054
+ if (audioTransceiver && audioTransceiver.sender) {
2055
+ audioTrackReplacePromise = audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]);
2056
+ } else {
2057
+ config.pc.addTrack(stream.getAudioTracks()[0], config.stream);
2058
+ needsNegotiation = true;
2059
+ }
2060
+ }
2061
+ else {
2062
+ if (audioTransceiver && audioTransceiver.sender) {
2063
+ audioTrackReplacePromise = audioTransceiver.sender.replaceTrack(null);
2064
+ needsNegotiation = true;
2065
+ }
2066
+ }
1840
2067
 
1841
- if(track.kind === 'audio' || !this.simulcast) {
1842
- config.pc.addTrack(track, stream);
2068
+ if (replaceVideo) {
2069
+ config.stream.addTrack(stream.getVideoTracks()[0]);
2070
+ if (videoTransceiver && videoTransceiver.sender) {
2071
+ videoTrackReplacePromise = videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]);
2072
+ } else {
2073
+ if(!this.simulcast) {
2074
+ config.pc.addTrack(stream.getVideoTracks()[0], config.stream);
1843
2075
  }
1844
2076
  else {
1845
- // adding simulcast streams
1846
2077
  let bitRates = this.simulcastBitrates;
1847
2078
  if(adapter.browserDetails.browser !== 'firefox') {
1848
2079
  // standard
1849
2080
 
1850
- config.pc.addTransceiver(track, {
2081
+ config.pc.addTransceiver(stream.getVideoTracks()[0], {
1851
2082
  direction: 'sendonly',
1852
2083
  streams: [config.stream],
1853
2084
  sendEncodings: [
@@ -1859,14 +2090,14 @@ class RoomSession {
1859
2090
  }
1860
2091
  else {
1861
2092
  // firefox
1862
- let transceiver = config.pc.addTransceiver(track, {
2093
+ let transceiver = config.pc.addTransceiver(stream.getVideoTracks()[0], {
1863
2094
  direction: 'sendonly',
1864
2095
  streams: [config.stream]
1865
2096
  });
1866
2097
  let sender = transceiver ? transceiver.sender : null;
1867
2098
  if(sender) {
1868
2099
  let parameters = sender.getParameters() || {};
1869
- parameters.encodings = track.sendEncodings || [
2100
+ parameters.encodings = stream.getVideoTracks()[0].sendEncodings || [
1870
2101
  { rid: 'h', active: true, maxBitrate: bitRates.high },
1871
2102
  { rid: 'm', active: true, maxBitrate: bitRates.medium, scaleResolutionDownBy: 2 },
1872
2103
  { rid: 'l', active: true, maxBitrate: bitRates.low, scaleResolutionDownBy: 4 }
@@ -1875,109 +2106,72 @@ class RoomSession {
1875
2106
  }
1876
2107
  }
1877
2108
  }
1878
- });
1879
-
1880
- } else {
1881
-
1882
- let transceivers = config.pc.getTransceivers();
1883
- let audioTransceiver = null;
1884
- let videoTransceiver = null;
1885
- if (transceivers && transceivers.length > 0) {
1886
- for (let i in transceivers) {
1887
- let t = transceivers[i];
1888
- if ((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
1889
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
1890
- audioTransceiver = t;
1891
- break;
1892
- }
1893
- }
1894
- for (let i in transceivers) {
1895
- let t = transceivers[i];
1896
- if ((t.sender && t.sender.track && t.sender.track.kind === "video") ||
1897
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
1898
- videoTransceiver = t;
1899
- break;
1900
- }
1901
- }
2109
+ needsNegotiation = true;
1902
2110
  }
1903
-
1904
- /* UPDATE Audio */
1905
- // stopping existing tracks
1906
-
1907
- let oldAudioStream = config?.stream?.getAudioTracks()?.[0];
1908
- if (oldAudioStream) {
1909
- config.stream.removeTrack(oldAudioStream);
1910
- try {
1911
- oldAudioStream.stop();
1912
- } catch (e) {
1913
- this._log(e);
1914
- }
1915
- }
1916
-
1917
- let replaceAudio = stream?.getAudioTracks()?.length;
1918
- if (replaceAudio) {
1919
- config.stream.addTrack(stream.getAudioTracks()[0]);
1920
- if (audioTransceiver && audioTransceiver.sender) {
1921
- audioTrackReplacePromise = audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]);
1922
- } else {
1923
- config.pc.addTrack(stream.getAudioTracks()[0], stream);
1924
- }
1925
- }
1926
- else {
1927
- if (audioTransceiver && audioTransceiver.sender) {
1928
- audioTrackReplacePromise = audioTransceiver.sender.replaceTrack(null);
1929
- }
2111
+ }
2112
+ else {
2113
+ if (videoTransceiver && videoTransceiver.sender) {
2114
+ videoTrackReplacePromise = videoTransceiver.sender.replaceTrack(null);
2115
+ needsNegotiation = true;
1930
2116
  }
2117
+ }
1931
2118
 
1932
- /* UPDATE Video */
1933
2119
 
1934
- // stopping existing tracks
2120
+ this.isAudioEnabed = !!(config.stream && config.stream.getAudioTracks().length > 0);
2121
+ this.isVideoEnabled = !!(config.stream && config.stream.getVideoTracks().length > 0);
1935
2122
 
1936
- let oldVideoStream = config?.stream?.getVideoTracks()?.[0];
1937
- if (oldVideoStream) {
1938
- config.stream.removeTrack(oldVideoStream);
1939
- try {
1940
- oldVideoStream.stop();
1941
- } catch (e) {
1942
- this._log(e);
1943
- }
1944
- }
2123
+ // we possibly created new transceivers, so we need to get them again
1945
2124
 
1946
- let replaceVideo = stream?.getVideoTracks()?.length;
1947
- if (replaceVideo) {
1948
- config.stream.addTrack(stream.getVideoTracks()[0]);
1949
- if (videoTransceiver && videoTransceiver.sender) {
1950
- videoTrackReplacePromise = videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]);
1951
- } else {
1952
- config.pc.addTrack(stream.getVideoTracks()[0], stream);
1953
- }
1954
- }
1955
- else {
1956
- if (videoTransceiver && videoTransceiver.sender) {
1957
- videoTrackReplacePromise = videoTransceiver.sender.replaceTrack(null);
1958
- }
1959
- }
2125
+ transceivers = config.pc.getTransceivers();
2126
+ existingTracks = [...(config.streamMap[source] || [])];
2127
+ if(!audioTransceiver) {
2128
+ audioTransceiver = transceivers.find(transceiver => transceiver.sender.track && transceiver.sender.track.kind === 'audio' && existingTracks.includes(transceiver.sender.track.id))
2129
+ }
2130
+ if(!videoTransceiver) {
2131
+ videoTransceiver = transceivers.find(transceiver => transceiver.sender.track && transceiver.sender.track.kind === 'video' && existingTracks.includes(transceiver.sender.track.id))
1960
2132
  }
1961
2133
 
1962
2134
  let hasAudio = !!(stream && stream.getAudioTracks().length > 0);
1963
2135
  let hasVideo = !!(stream && stream.getVideoTracks().length > 0);
1964
- let isAudioMuted = !stream || stream.getAudioTracks().length === 0 || !stream.getAudioTracks()[0].enabled;
1965
- let isVideoMuted = !stream || stream.getVideoTracks().length === 0 || !stream.getVideoTracks()[0].enabled;
1966
-
1967
- this.isAudioEnabed = hasAudio;
1968
- this.isVideoEnabled = hasVideo;
1969
- this.isAudioMuted = isAudioMuted;
1970
- this.isVideoMuted = isVideoMuted;
2136
+ this._setupTransceivers(this.handleId, [hasAudio, false, hasVideo, false, audioTransceiver, videoTransceiver]);
1971
2137
 
1972
- this._setupTransceivers(this.handleId, [hasAudio, false, hasVideo, false]);
1973
2138
 
1974
2139
  const emitEvents = () => {
1975
2140
  this.isPublished = true;
1976
- if(config.stream) {
1977
- let tracks = config.stream.getTracks();
2141
+
2142
+ if(!config.stream.onremovetrack) {
2143
+ config.stream.onremovetrack = (ev) => {};
2144
+
2145
+ let trackremoved = (ev) => {
2146
+ this.emit('addLocalParticipant', {
2147
+ tid: generateUUID(),
2148
+ id: handle.handleId,
2149
+ constructId: this.constructId,
2150
+ userId: decodeJanusDisplay(handle.userId)?.userId,
2151
+ role: decodeJanusDisplay(this.display)?.role,
2152
+ track: ev.detail,
2153
+ stream: config.stream,
2154
+ streamMap: config.streamMap,
2155
+ source,
2156
+ adding: false,
2157
+ removing: true,
2158
+ hasAudioTrack: hasAudio,
2159
+ hasVideoTrack: hasVideo,
2160
+ });
2161
+ }
2162
+
2163
+ config.stream.addEventListener('trackremoved', trackremoved);
2164
+ config.clearTrackRemovedListener = () => {
2165
+ config.stream.removeEventListener('trackremoved', trackremoved);
2166
+ }
2167
+ }
2168
+
2169
+ let tracks = config.stream.getTracks();
2170
+ if(tracks.length) {
1978
2171
  tracks.forEach(track => {
1979
2172
  // used as a flag to not emit tracks that been already emitted
1980
2173
  if(!track.onended) {
2174
+
1981
2175
  this.emit('addLocalParticipant', {
1982
2176
  tid: generateUUID(),
1983
2177
  id: handle.handleId,
@@ -1985,31 +2179,24 @@ class RoomSession {
1985
2179
  role: decodeJanusDisplay(this.display)?.role,
1986
2180
  track,
1987
2181
  stream: config.stream,
2182
+ streamMap: config.streamMap,
2183
+ source,
1988
2184
  adding: true,
1989
2185
  constructId: this.constructId,
1990
- metaData: this.options.metaData,
1991
2186
  hasAudioTrack: hasAudio,
1992
2187
  hasVideoTrack: hasVideo
1993
2188
  });
2189
+
1994
2190
  track.onended = (ev) => {
1995
- this.emit('addLocalParticipant', {
1996
- tid:generateUUID(),
1997
- id: handle.handleId,
1998
- userId: decodeJanusDisplay(handle.userId)?.userId,
1999
- role: decodeJanusDisplay(this.display)?.role,
2000
- track: ev.target,
2001
- stream: config.stream,
2002
- adding: false,
2003
- removing: true,
2004
- constructId: this.constructId,
2005
- metaData: this.options.metaData,
2006
- hasAudioTrack: hasAudio,
2007
- hasVideoTrack: hasVideo
2008
- });
2191
+ config.stream.removeTrack(track);
2192
+ config.stream.dispatchEvent(new Event("trackremoved", {detail: track}))
2193
+ this._republishOnTrackEnded(source);
2009
2194
  }
2010
2195
  }
2011
2196
  })
2012
2197
  }
2198
+
2199
+ //TODO: legacy event, remove in future
2013
2200
  else {
2014
2201
  this.emit('addLocalParticipant', {
2015
2202
  tid: generateUUID(),
@@ -2017,24 +2204,34 @@ class RoomSession {
2017
2204
  userId: decodeJanusDisplay(handle.userId)?.userId,
2018
2205
  role: decodeJanusDisplay(this.display)?.role,
2019
2206
  stream: null,
2207
+ streamMap: config.streamMap,
2208
+ source,
2020
2209
  adding: false,
2021
2210
  constructId: this.constructId,
2022
- metaData: this.options.metaData,
2023
2211
  hasAudioTrack: hasAudio,
2024
2212
  hasVideoTrack: hasVideo
2025
2213
  });
2026
2214
  }
2027
- this.emit('published', {status: true, hasStream: !!config.stream});
2215
+
2216
+ this.isMuted = [];
2217
+ for(const source of Object.keys(config.streamMap)) {
2218
+ const audioTrack = config.stream?.getAudioTracks()?.find(track => config.streamMap[source].includes(track.id));
2219
+ const videoTrack = config.stream?.getVideoTracks()?.find(track => config.streamMap[source].includes(track.id));
2220
+ this.isMuted.push({type: 'audio', value: !audioTrack || !audioTrack.enabled, source, mid: transceivers.find(transceiver => transceiver.sender?.track?.kind === 'audio' && transceiver.sender?.track?.id === audioTrack?.id)?.mid});
2221
+ this.isMuted.push({type: 'video', value: !videoTrack || !videoTrack.enabled, source, mid: transceivers.find(transceiver => transceiver.sender?.track?.kind === 'video' && transceiver.sender?.track?.id === videoTrack?.id)?.mid});
2222
+ this.emit('localHasVideo', !!videoTrack, source);
2223
+ this.emit('localHasAudio', !!audioTrack, source);
2224
+ }
2225
+ for(const val of this.isMuted) {
2226
+ this.emit('localMuted', {...val});
2227
+ }
2228
+ this.emit('published', {status: true, hasStream: tracks.length > 0});
2028
2229
  this.emit('publishing', false);
2029
- this.emit('localHasVideo', hasVideo);
2030
- this.emit('localHasAudio', hasAudio);
2031
- this.emit('localMuted', {type: 'video', value: isVideoMuted});
2032
- this.emit('localMuted', {type: 'audio', value: isAudioMuted});
2033
2230
  return this;
2034
2231
  };
2035
2232
 
2036
2233
  // this should be enough
2037
- if(this.isPublished) {
2234
+ if(!needsNegotiation) {
2038
2235
  return Promise
2039
2236
  .all([audioTrackReplacePromise, videoTrackReplacePromise])
2040
2237
  .then(() => emitEvents())
@@ -2051,47 +2248,29 @@ class RoomSession {
2051
2248
  if (jsep.sdp && jsep.sdp.indexOf("\r\na=ice-ufrag") === -1) {
2052
2249
  jsep.sdp = jsep.sdp.replace("\na=ice-ufrag", "\r\na=ice-ufrag");
2053
2250
  }
2054
- return this.sendMessage(this.handleId, {
2055
- body: {"request": "configure", "audio": hasAudio, "video": hasVideo, "data": true, ...(this.recordingFilename ? {filename: this.recordingFilename} : {})},
2056
- jsep
2057
- });
2058
- })
2059
- .then((r) => {
2060
- if (this._isDataChannelOpen) {
2061
- return Promise.resolve(r)
2062
- } else {
2063
- return new Promise((resolve, reject) => {
2064
-
2065
- let dataChannelTimeoutId = null;
2066
-
2067
- let _resolve = (val) => {
2068
- if (val) {
2069
- clearTimeout(dataChannelTimeoutId);
2070
- this._abortController.signal.removeEventListener('abort', _rejectAbort);
2071
- this.off('dataChannel', _resolve, this);
2072
- resolve(this);
2073
- }
2074
- };
2075
2251
 
2076
- let _rejectTimeout = () => {
2077
- this.off('dataChannel', _resolve, this);
2078
- this._abortController.signal.removeEventListener('abort', _rejectAbort);
2079
- reject({type: 'error', id: 27, message: 'Data channel did not open', data: null});
2080
- }
2081
2252
 
2082
- let _rejectAbort = () => {
2083
- this._abortController.signal.removeEventListener('abort', _rejectAbort);
2084
- clearTimeout(dataChannelTimeoutId);
2085
- this.off('dataChannel', _resolve, this);
2086
- reject({type: 'warning', id: 17, message: 'Connection cancelled'})
2253
+ let descriptions = [];
2254
+ Object.keys(config.streamMap).forEach(source => {
2255
+ config.streamMap[source].forEach(trackId => {
2256
+ let t = transceivers.find(transceiver => transceiver.sender.track && transceiver.sender.track.id === trackId)
2257
+ if(t) {
2258
+ descriptions.push({mid: t.mid, description: source});
2087
2259
  }
2260
+ })
2261
+ });
2088
2262
 
2089
- dataChannelTimeoutId = setTimeout(_rejectTimeout, 5000);
2090
- this._abortController.signal.addEventListener('abort', _rejectAbort);
2091
-
2092
- this.on('dataChannel', _resolve, this);
2093
- });
2094
- }
2263
+ return this.sendMessage(this.handleId, {
2264
+ body: {
2265
+ "request": "configure",
2266
+ "audio": this.isAudioEnabed,
2267
+ "video": this.isVideoEnabled,
2268
+ "data": true,
2269
+ ...(this.recordingFilename ? {filename: this.recordingFilename} : {}),
2270
+ descriptions: descriptions
2271
+ },
2272
+ jsep
2273
+ });
2095
2274
  })
2096
2275
  .then(() => emitEvents())
2097
2276
  .catch(e => {
@@ -2111,53 +2290,71 @@ class RoomSession {
2111
2290
  : Promise.resolve()
2112
2291
  }
2113
2292
 
2114
- toggleAudio(value = null, mid) {
2293
+ toggleAudio(value = null, source = 'camera0', mid) {
2115
2294
  let handle = this._getHandle(this.handleId);
2116
2295
  if (!handle) {
2117
2296
  return Promise.reject({type: 'error', id: 21, message: 'no local id, connect first', data: null})
2118
2297
  }
2119
2298
  let config = handle.webrtcStuff;
2120
-
2121
- let transceiver = config.pc.getTransceivers()
2122
- .find(t => t.sender && t.sender.track && t.receiver.track.kind === "audio" && (mid ? t.mid === mid : true));
2123
-
2299
+ let transceivers = config.pc.getTransceivers().filter(t => t.currentDirection !== 'inactive');
2300
+ let transceiver = null;
2301
+ if(source) {
2302
+ transceiver = transceivers
2303
+ .find(t => t.sender && t.sender.track && t.receiver.track.kind === "audio" && (config.streamMap[source] || []).includes(t.sender.track.id));
2304
+ }
2305
+ else {
2306
+ transceiver = transceivers
2307
+ .find(t => t.sender && t.sender.track && t.receiver.track.kind === "audio" && (mid ? t.mid === mid : true));
2308
+ }
2124
2309
  if (transceiver) {
2125
2310
  transceiver.sender.track.enabled = value !== null ? !!value : !transceiver.sender.track.enabled;
2126
2311
  }
2127
2312
 
2128
- this.isAudioMuted = !transceiver || !transceiver.sender.track.enabled;
2129
-
2130
- // if (config.stream && config.stream.getAudioTracks().length) {
2131
- // config.stream.getAudioTracks()[0].enabled = value !== null ? !!value : !config.stream.getAudioTracks()[0].enabled;
2132
- // }
2133
- // this.isAudioMuted = config.stream.getAudioTracks().length === 0 || !config.stream.getAudioTracks()[0].enabled;
2134
- //
2135
-
2136
- this.emit('localMuted', {type: 'audio', value: this.isAudioMuted, mid});
2137
-
2313
+ this.isMuted = [];
2314
+ for(const source of Object.keys(config.streamMap)) {
2315
+ const audioTrack = config.stream?.getAudioTracks()?.find(track => config.streamMap[source].includes(track.id));
2316
+ const audioTransceiver = transceivers.find(transceiver => transceiver.sender.track && transceiver.sender.track.kind === 'audio' && transceiver.sender.track.id === audioTrack?.id);
2317
+ const videoTrack = config.stream?.getVideoTracks()?.find(track => config.streamMap[source].includes(track.id));
2318
+ const videoTransceiver = transceivers.find(transceiver => transceiver.sender.track && transceiver.sender.track.kind === 'video' && transceiver.sender.track.id === videoTrack?.id);
2319
+ this.isMuted.push({type: 'audio', value: !audioTrack || !audioTransceiver || !audioTransceiver?.sender?.track?.enabled , source, mid: audioTransceiver?.mid});
2320
+ this.isMuted.push({type: 'video', value: !videoTrack || !videoTransceiver || !videoTransceiver?.sender?.track?.enabled, source, mid: videoTransceiver?.mid});
2321
+ }
2322
+ for(let val of this.isMuted) {
2323
+ this.emit('localMuted', {...val});
2324
+ }
2138
2325
  }
2139
2326
 
2140
- toggleVideo(value = null, mid) {
2327
+ toggleVideo(value = null, source = 'camera0', mid) {
2141
2328
  let handle = this._getHandle(this.handleId);
2142
2329
  if (!handle) {
2143
2330
  return Promise.reject({type: 'error', id: 21, message: 'no local id, connect first', data: null})
2144
2331
  }
2145
2332
  let config = handle.webrtcStuff;
2146
-
2147
- let transceiver = config.pc.getTransceivers()
2148
- .find(t => t.sender && t.sender.track && t.receiver.track.kind === "video" && (mid ? t.mid === mid : true));
2333
+ let transceivers = config.pc.getTransceivers().filter(t => t.currentDirection !== 'inactive');
2334
+ let transceiver = null;
2335
+ if(source) {
2336
+ transceiver = transceivers
2337
+ .find(t => t.sender && t.sender.track && t.receiver.track.kind === "video" && (config.streamMap[source] || []).includes(t.sender.track.id));
2338
+ }
2339
+ else {
2340
+ transceiver = transceivers
2341
+ .find(t => t.sender && t.sender.track && t.receiver.track.kind === "video" && (mid ? t.mid === mid : true));
2342
+ }
2149
2343
  if (transceiver) {
2150
2344
  transceiver.sender.track.enabled = value !== null ? !!value : !transceiver.sender.track.enabled;
2151
2345
  }
2152
-
2153
- this.isVideoMuted = !transceiver || !transceiver.sender.track.enabled;
2154
-
2155
- // if (config.stream && config.stream.getVideoTracks().length) {
2156
- // config.stream.getVideoTracks()[0].enabled = value !== null ? !!value : !config.stream.getVideoTracks()[0].enabled;
2157
- // }
2158
- // this.isVideoMuted = config.stream.getVideoTracks().length === 0 || !config.stream.getVideoTracks()[0].enabled;
2159
-
2160
- this.emit('localMuted', {type: 'video', value: this.isVideoMuted, mid});
2346
+ this.isMuted = [];
2347
+ for(const source of Object.keys(config.streamMap)) {
2348
+ const audioTrack = config.stream?.getAudioTracks()?.find(track => config.streamMap[source].includes(track.id));
2349
+ const audioTransceiver = transceivers.find(transceiver => transceiver.sender.track && transceiver.sender.track.kind === 'audio' && transceiver.sender.track.id === audioTrack?.id);
2350
+ const videoTrack = config.stream?.getVideoTracks()?.find(track => config.streamMap[source].includes(track.id));
2351
+ const videoTransceiver = transceivers.find(transceiver => transceiver.sender.track && transceiver.sender.track.kind === 'video' && transceiver.sender.track.id === videoTrack?.id);
2352
+ this.isMuted.push({type: 'audio', value: !audioTrack || !audioTransceiver || !audioTransceiver?.sender?.track?.enabled , source, mid: audioTransceiver?.mid});
2353
+ this.isMuted.push({type: 'video', value: !videoTrack || !videoTransceiver || !videoTransceiver?.sender?.track?.enabled, source, mid: videoTransceiver?.mid});
2354
+ }
2355
+ for(let val of this.isMuted) {
2356
+ this.emit('localMuted', {...val});
2357
+ }
2161
2358
  }
2162
2359
 
2163
2360
  selectSubStream(handleId, substream = 2) {