@webex/plugin-meetings 3.0.0-beta.1 → 3.0.0-beta.2

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.
Files changed (111) hide show
  1. package/dist/common/errors/webex-errors.js +5 -29
  2. package/dist/common/errors/webex-errors.js.map +1 -1
  3. package/dist/constants.js +15 -74
  4. package/dist/constants.js.map +1 -1
  5. package/dist/media/index.js +68 -213
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/media/internal-media-core-wrapper.js +22 -0
  8. package/dist/media/internal-media-core-wrapper.js.map +1 -0
  9. package/dist/media/properties.js +20 -25
  10. package/dist/media/properties.js.map +1 -1
  11. package/dist/media/util.js +0 -27
  12. package/dist/media/util.js.map +1 -1
  13. package/dist/meeting/index.js +694 -432
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +1 -0
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.js +3 -44
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meetings/index.js +64 -5
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/meetings/util.js +24 -1
  22. package/dist/meetings/util.js.map +1 -1
  23. package/dist/members/index.js +68 -0
  24. package/dist/members/index.js.map +1 -1
  25. package/dist/multistream/mediaRequestManager.js +132 -0
  26. package/dist/multistream/mediaRequestManager.js.map +1 -0
  27. package/dist/multistream/multistreamMedia.js +116 -0
  28. package/dist/multistream/multistreamMedia.js.map +1 -0
  29. package/dist/multistream/receiveSlot.js +209 -0
  30. package/dist/multistream/receiveSlot.js.map +1 -0
  31. package/dist/multistream/receiveSlotManager.js +195 -0
  32. package/dist/multistream/receiveSlotManager.js.map +1 -0
  33. package/dist/multistream/remoteMedia.js +284 -0
  34. package/dist/multistream/remoteMedia.js.map +1 -0
  35. package/dist/multistream/remoteMediaGroup.js +243 -0
  36. package/dist/multistream/remoteMediaGroup.js.map +1 -0
  37. package/dist/multistream/remoteMediaManager.js +1113 -0
  38. package/dist/multistream/remoteMediaManager.js.map +1 -0
  39. package/dist/reconnection-manager/index.js +109 -130
  40. package/dist/reconnection-manager/index.js.map +1 -1
  41. package/dist/roap/index.js +57 -240
  42. package/dist/roap/index.js.map +1 -1
  43. package/dist/roap/request.js +2 -114
  44. package/dist/roap/request.js.map +1 -1
  45. package/dist/roap/turnDiscovery.js +11 -5
  46. package/dist/roap/turnDiscovery.js.map +1 -1
  47. package/dist/statsAnalyzer/global.js +2 -0
  48. package/dist/statsAnalyzer/global.js.map +1 -1
  49. package/dist/statsAnalyzer/index.js +39 -36
  50. package/dist/statsAnalyzer/index.js.map +1 -1
  51. package/package.json +20 -19
  52. package/src/common/errors/webex-errors.js +0 -18
  53. package/src/constants.ts +139 -180
  54. package/src/media/index.js +60 -194
  55. package/src/media/internal-media-core-wrapper.ts +9 -0
  56. package/src/media/properties.js +19 -25
  57. package/src/media/util.js +0 -22
  58. package/src/meeting/index.js +565 -320
  59. package/src/meeting/request.js +1 -0
  60. package/src/meeting/util.js +3 -46
  61. package/src/meetings/index.js +30 -1
  62. package/src/meetings/util.js +23 -2
  63. package/src/members/index.js +48 -0
  64. package/src/multistream/mediaRequestManager.ts +164 -0
  65. package/src/multistream/multistreamMedia.ts +92 -0
  66. package/src/multistream/receiveSlot.ts +141 -0
  67. package/src/multistream/receiveSlotManager.ts +142 -0
  68. package/src/multistream/remoteMedia.ts +219 -0
  69. package/src/multistream/remoteMediaGroup.ts +224 -0
  70. package/src/multistream/remoteMediaManager.ts +911 -0
  71. package/src/reconnection-manager/index.js +40 -53
  72. package/src/roap/index.js +47 -207
  73. package/src/roap/request.js +1 -72
  74. package/src/roap/turnDiscovery.ts +12 -6
  75. package/src/statsAnalyzer/global.js +2 -0
  76. package/src/statsAnalyzer/index.js +32 -46
  77. package/test/integration/spec/journey.js +1 -1
  78. package/test/unit/spec/media/index.ts +223 -0
  79. package/test/unit/spec/media/properties.ts +73 -82
  80. package/test/unit/spec/meeting/effectsState.js +1 -3
  81. package/test/unit/spec/meeting/index.js +420 -228
  82. package/test/unit/spec/meeting/muteState.js +7 -0
  83. package/test/unit/spec/meeting/utils.js +61 -2
  84. package/test/unit/spec/meetings/index.js +0 -4
  85. package/test/unit/spec/members/index.js +164 -2
  86. package/test/unit/spec/multistream/mediaRequestManager.ts +511 -0
  87. package/test/unit/spec/multistream/receiveSlot.ts +104 -0
  88. package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
  89. package/test/unit/spec/multistream/remoteMedia.ts +217 -0
  90. package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
  91. package/test/unit/spec/multistream/remoteMediaManager.ts +1251 -0
  92. package/test/unit/spec/roap/index.ts +63 -35
  93. package/test/unit/spec/stats-analyzer/index.js +19 -22
  94. package/dist/peer-connection-manager/index.js +0 -794
  95. package/dist/peer-connection-manager/index.js.map +0 -1
  96. package/dist/roap/collection.js +0 -73
  97. package/dist/roap/collection.js.map +0 -1
  98. package/dist/roap/handler.js +0 -337
  99. package/dist/roap/handler.js.map +0 -1
  100. package/dist/roap/state.js +0 -164
  101. package/dist/roap/state.js.map +0 -1
  102. package/dist/roap/util.js +0 -102
  103. package/dist/roap/util.js.map +0 -1
  104. package/src/peer-connection-manager/index.js +0 -723
  105. package/src/roap/collection.js +0 -63
  106. package/src/roap/handler.js +0 -252
  107. package/src/roap/state.js +0 -149
  108. package/src/roap/util.js +0 -93
  109. package/test/unit/spec/peerconnection-manager/index.js +0 -188
  110. package/test/unit/spec/peerconnection-manager/utils.js +0 -48
  111. package/test/unit/spec/roap/util.js +0 -30
@@ -499,6 +499,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
499
499
  * @param {String} options.deviceUrl Url of a device
500
500
  * @param {String} options.resourceId Populated if you are paired to a device
501
501
  * @param {String} options.localMedias local sdps
502
+ * @param {Boolean} options.preferTranscoding false for multistream (Homer), true for transcoded media (Edonus)
502
503
  * @returns {Promise}
503
504
  */
504
505
  remoteAudioVideoToggle(options) {
@@ -58,7 +58,8 @@ MeetingUtil.remoteUpdateAudioVideo = (audioMuted, videoMuted, meeting) => {
58
58
  selfId: meeting.selfId,
59
59
  localMedias,
60
60
  deviceUrl: meeting.deviceUrl,
61
- correlationId: meeting.correlationId
61
+ correlationId: meeting.correlationId,
62
+ preferTranscoding: !meeting.isMultistream,
62
63
  }).then((response) => {
63
64
  Metrics.postEvent({event: eventType.MEDIA_RESPONSE, meeting});
64
65
 
@@ -95,7 +96,7 @@ MeetingUtil.joinMeeting = (meeting, options) => {
95
96
  moderator: options.moderator,
96
97
  pin: options.pin,
97
98
  moveToResource: options.moveToResource,
98
- preferTranscoding: options.preferTranscoding,
99
+ preferTranscoding: !meeting.isMultistream,
99
100
  asResourceOccupant: options.asResourceOccupant
100
101
  })
101
102
  .then((res) => {
@@ -130,7 +131,6 @@ MeetingUtil.cleanUp = (meeting) => {
130
131
  meeting.unsetPeerConnections();
131
132
  meeting.reconnectionManager.cleanUp();
132
133
  })
133
- .then(() => meeting.roap.stop(meeting.correlationId, meeting.roapSeq))
134
134
  .then(() => meeting.stopKeepAlive());
135
135
  };
136
136
 
@@ -279,49 +279,6 @@ MeetingUtil.joinMeetingOptions = (meeting, options = {}) => {
279
279
  });
280
280
  };
281
281
 
282
- MeetingUtil.updateTransceiver = (options, meetingOptions) => {
283
- const {
284
- type,
285
- sendTrack,
286
- receiveTrack,
287
- track,
288
- transceiver,
289
- peerConnection,
290
- previousMediaDirection
291
- } = options;
292
-
293
- if ((sendTrack !== undefined && sendTrack !== previousMediaDirection.sendTrack) ||
294
- (receiveTrack !== undefined && receiveTrack !== previousMediaDirection.receiveTrack)) {
295
- return Media.updateTransceiver({
296
- meetingId: meetingOptions.meeting.id,
297
- remoteQualityLevel: meetingOptions.mediaProperties.remoteQualityLevel,
298
- enableRtx: meetingOptions.meeting.config.enableRtx,
299
- enableExtmap: meetingOptions.meeting.config.enableExtmap
300
- }, peerConnection, transceiver,
301
- {
302
- track,
303
- type,
304
- receiveTrack,
305
- sendTrack
306
- })
307
- .then(() => meetingOptions.meeting.roap
308
- .sendRoapMediaRequest({
309
- sdp: meetingOptions.mediaProperties.peerConnection.sdp,
310
- roapSeq: meetingOptions.meeting.roapSeq,
311
- meeting: meetingOptions.meeting // or can pass meeting ID
312
- }))
313
- .catch((e) => {
314
- LoggerProxy.logger.error(`Meeting:util#updateTransceiver --> Error updating the ${type} streams with error: ${e}`);
315
- });
316
- } if (track) {
317
- transceiver.sender.replaceTrack(track);
318
-
319
- return Promise.resolve();
320
- }
321
-
322
- return Promise.reject(new ParameterError('update Failed: please pass valid parameter'));
323
- };
324
-
325
282
  MeetingUtil.validateOptions = (options) => {
326
283
  const {
327
284
  sendVideo, sendAudio, sendShare, localStream, localShare
@@ -2,6 +2,7 @@
2
2
  import '@webex/internal-plugin-mercury';
3
3
  import '@webex/internal-plugin-conversation';
4
4
  import {WebexPlugin} from '@webex/webex-core';
5
+ import {MediaConnection as MC} from '@webex/internal-media-core';
5
6
 
6
7
  import 'webrtc-adapter';
7
8
 
@@ -51,7 +52,33 @@ import CaptchaError from '../common/errors/captcha-error';
51
52
  import MeetingCollection from './collection';
52
53
  import MeetingsUtil from './util';
53
54
 
55
+ let mediaLogger;
54
56
 
57
+ class MediaLogger {
58
+ info(...args) {
59
+ LoggerProxy.logger.info(...args);
60
+ }
61
+
62
+ log(...args) {
63
+ LoggerProxy.logger.log(...args);
64
+ }
65
+
66
+ error(...args) {
67
+ LoggerProxy.logger.error(...args);
68
+ }
69
+
70
+ warn(...args) {
71
+ LoggerProxy.logger.warn(...args);
72
+ }
73
+
74
+ trace(...args) {
75
+ LoggerProxy.logger.trace(...args);
76
+ }
77
+
78
+ debug(...args) {
79
+ LoggerProxy.logger.debug(...args);
80
+ }
81
+ }
55
82
  /**
56
83
  * Meetings Ready Event
57
84
  * Emitted when the meetings instance on webex is ready
@@ -374,6 +401,9 @@ export default class Meetings extends WebexPlugin {
374
401
  LoggerConfig.set(this.config.logging);
375
402
  LoggerProxy.set(this.webex.logger);
376
403
 
404
+ mediaLogger = new MediaLogger();
405
+ MC.setLogger(mediaLogger);
406
+
377
407
  /**
378
408
  * The MeetingInfo object to interact with server
379
409
  * @instance
@@ -805,7 +835,6 @@ export default class Meetings extends WebexPlugin {
805
835
  userId: this.webex.internal.device.userId,
806
836
  deviceUrl: this.webex.internal.device.url,
807
837
  orgId: this.webex.internal.device.orgId,
808
- roapSeq: 0,
809
838
  locus: type === _LOCUS_ID_ ? destination : null, // pass the locus object if present
810
839
  meetingInfoProvider: this.meetingInfo,
811
840
  destination,
@@ -6,7 +6,8 @@ import {
6
6
  _CREATED_,
7
7
  LOCUSEVENT,
8
8
  CORRELATION_ID,
9
- EVENT_TRIGGERS
9
+ EVENT_TRIGGERS,
10
+ ROAP
10
11
  } from '../constants';
11
12
  import LoggerProxy from '../common/logs/logger-proxy';
12
13
  import Trigger from '../common/events/trigger-proxy';
@@ -41,7 +42,27 @@ MeetingsUtil.handleRoapMercury = (envelope, meetingCollection) => {
41
42
  const meeting = meetingCollection.getByKey(CORRELATION_ID, data.correlationId);
42
43
 
43
44
  if (meeting) {
44
- meeting.roap.roapEvent(data);
45
+ const {
46
+ seq, messageType, tieBreaker, errorType, errorCause
47
+ } = data.message;
48
+
49
+ if (messageType === ROAP.ROAP_TYPES.TURN_DISCOVERY_RESPONSE) {
50
+ // turn discovery is not part of normal roap protocol and so we are not handling it
51
+ // through the usual roap state machine
52
+ meeting.roap.turnDiscovery.handleTurnDiscoveryResponse(data.message);
53
+ }
54
+ else {
55
+ const roapMessage = {
56
+ seq,
57
+ messageType,
58
+ sdp: data.message.sdps?.length > 0 ? data.message.sdps[0] : undefined,
59
+ tieBreaker,
60
+ errorType,
61
+ errorCause
62
+ };
63
+
64
+ meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
65
+ }
45
66
  }
46
67
  }
47
68
  };
@@ -148,6 +148,18 @@ export default class Members extends StatelessWebexPlugin {
148
148
  * @memberof Members
149
149
  */
150
150
  this.recordingId = null;
151
+
152
+ /**
153
+ * reference to a ReceiveSlotManager instance (for multistream)
154
+ * @private
155
+ */
156
+ this.receiveSlotManager = attrs.receiveSlotManager;
157
+
158
+ /**
159
+ * reference to a MediaRequestManager instance that manages main video requests (for multistream)
160
+ * @private
161
+ */
162
+ this.mediaRequestManagers = attrs.mediaRequestManagers;
151
163
  }
152
164
 
153
165
  /**
@@ -833,4 +845,40 @@ export default class Members extends StatelessWebexPlugin {
833
845
 
834
846
  return Promise.reject(new Error('Members:index#sendDialPadKey --> cannot send DTMF, meeting does not have a connection to the "locus" call control service.'));
835
847
  }
848
+
849
+ /** Finds a member that has any device with a csi matching provided value
850
+ *
851
+ * @param {number} csi
852
+ * @returns {Member}
853
+ */
854
+ findMemberByCsi(csi) {
855
+ return Object.values(this.membersCollection.getAll())
856
+ .find((member) => (
857
+ member.participant?.devices?.find((device) => (
858
+ device.csis?.find((memberCsi) => memberCsi === csi)))));
859
+ }
860
+
861
+ /**
862
+ * Returns an array of a member's CSIs matching the mediaType and mediaContent
863
+ *
864
+ * @param {string} memberId
865
+ * @param {string} mediaType 'audio' or 'video'
866
+ * @param {string} mediaContent 'main' or 'slides'
867
+ * @returns {Member}
868
+ */
869
+ getCsisForMember(memberId, mediaType = 'video', mediaContent = 'main') {
870
+ const csis = [];
871
+
872
+ this.membersCollection.get(memberId)?.participant?.devices?.forEach((device) => {
873
+ if (device.mediaSessions) {
874
+ const deviceCsis = device.mediaSessions
875
+ ?.filter((mediaSession) => mediaSession.mediaType === mediaType && mediaSession.mediaContent === mediaContent)
876
+ .map((mediaSession) => mediaSession.csi);
877
+
878
+ csis.push(...deviceCsis);
879
+ }
880
+ });
881
+
882
+ return csis;
883
+ }
836
884
  }
@@ -0,0 +1,164 @@
1
+ /* eslint-disable require-jsdoc */
2
+ import {MediaConnection as MC} from '@webex/internal-media-core';
3
+
4
+ import LoggerProxy from '../common/logs/logger-proxy';
5
+
6
+ import {ReceiveSlot, ReceiveSlotId} from './receiveSlot';
7
+
8
+ export interface ActiveSpeakerPolicyInfo {
9
+ policy: 'active-speaker';
10
+ priority: number;
11
+ crossPriorityDuplication: boolean;
12
+ crossPolicyDuplication: boolean;
13
+ preferLiveVideo: boolean;
14
+ }
15
+
16
+ export interface ReceiverSelectedPolicyInfo {
17
+ policy: 'receiver-selected';
18
+ csi: number;
19
+ }
20
+
21
+ export type PolicyInfo = ActiveSpeakerPolicyInfo | ReceiverSelectedPolicyInfo;
22
+
23
+ export interface H264CodecInfo {
24
+ codec: 'h264';
25
+ maxFs?: number;
26
+ maxFps?: number;
27
+ maxMbps?: number;
28
+ maxWidth?: number;
29
+ maxHeight?: number;
30
+ }
31
+
32
+ export type CodecInfo = H264CodecInfo; // we'll add AV1 here in the future when it's available
33
+
34
+ export interface MediaRequest {
35
+ policyInfo: PolicyInfo;
36
+ receiveSlots: Array<ReceiveSlot>;
37
+ codecInfo?: CodecInfo;
38
+ }
39
+
40
+ export type MediaRequestId = string;
41
+
42
+ const CODEC_DEFAULTS = {
43
+ h264: {
44
+ maxFs: 8192,
45
+ maxFps: 3000,
46
+ maxMbps: 245760,
47
+ },
48
+ };
49
+
50
+ type SendMediaRequestsCallback = (mediaRequests: MC.MediaRequest[]) => void;
51
+
52
+ export class MediaRequestManager {
53
+ private sendMediaRequestsCallback: SendMediaRequestsCallback;
54
+
55
+ private counter;
56
+
57
+ private clientRequests: {[key: MediaRequestId]: MediaRequest};
58
+
59
+ private slotsActiveInLastMediaRequest: {[key: ReceiveSlotId]: ReceiveSlot};
60
+
61
+ constructor(sendMediaRequestsCallback: SendMediaRequestsCallback) {
62
+ this.sendMediaRequestsCallback = sendMediaRequestsCallback;
63
+ this.counter = 0;
64
+ this.clientRequests = {};
65
+ this.slotsActiveInLastMediaRequest = {};
66
+ }
67
+
68
+ private resetInactiveReceiveSlots() {
69
+ const activeSlots: {[key: ReceiveSlotId]: ReceiveSlot} = {};
70
+
71
+ // create a map of all currently used slot ids
72
+ Object.values(this.clientRequests).forEach((request) =>
73
+ request.receiveSlots.forEach((slot) => {
74
+ activeSlots[slot.id] = slot;
75
+ })
76
+ );
77
+
78
+ // when we stop using some receive slots and they are not included in the new media request,
79
+ // we will never get a 'no source' notification for them, so we reset their state,
80
+ // so that the client doesn't try to display their video anymore
81
+ for (const [slotId, slot] of Object.entries(this.slotsActiveInLastMediaRequest)) {
82
+ if (!(slotId in activeSlots)) {
83
+ LoggerProxy.logger.info(
84
+ `multistream:mediaRequestManager --> resetting sourceState to "no source" for slot ${slot.id}`
85
+ );
86
+ slot.resetSourceState();
87
+ }
88
+ }
89
+
90
+ this.slotsActiveInLastMediaRequest = activeSlots;
91
+ }
92
+
93
+ private sendRequests() {
94
+ const wcmeMediaRequests: MC.MediaRequest[] = [];
95
+
96
+ // todo: check how many streams we're asking for and what resolution and introduce some limits (spark-377701)
97
+
98
+ // map all the client media requests to wcme media requests
99
+ Object.values(this.clientRequests).forEach((mr) => {
100
+ wcmeMediaRequests.push(
101
+ new MC.MediaRequest(
102
+ mr.policyInfo.policy === 'active-speaker'
103
+ ? MC.Policy.ActiveSpeaker
104
+ : MC.Policy.ReceiverSelected,
105
+ mr.policyInfo.policy === 'active-speaker'
106
+ ? new MC.ActiveSpeakerInfo(
107
+ mr.policyInfo.priority,
108
+ mr.policyInfo.crossPriorityDuplication,
109
+ mr.policyInfo.crossPolicyDuplication,
110
+ mr.policyInfo.preferLiveVideo
111
+ )
112
+ : new MC.ReceiverSelectedInfo(mr.policyInfo.csi),
113
+ mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
114
+ mr.codecInfo && [
115
+ new MC.CodecInfo(
116
+ 0x80,
117
+ new MC.H264Codec(
118
+ mr.codecInfo.maxFs || CODEC_DEFAULTS.h264.maxFs,
119
+ mr.codecInfo.maxFps || CODEC_DEFAULTS.h264.maxFps,
120
+ mr.codecInfo.maxMbps || CODEC_DEFAULTS.h264.maxMbps,
121
+ mr.codecInfo.maxWidth,
122
+ mr.codecInfo.maxHeight
123
+ )
124
+ ),
125
+ ]
126
+ )
127
+ );
128
+ });
129
+
130
+ this.sendMediaRequestsCallback(wcmeMediaRequests);
131
+
132
+ this.resetInactiveReceiveSlots();
133
+ }
134
+
135
+ public addRequest(mediaRequest: MediaRequest, commit = true): MediaRequestId {
136
+ // eslint-disable-next-line no-plusplus
137
+ const newId = `${this.counter++}`;
138
+
139
+ this.clientRequests[newId] = mediaRequest;
140
+
141
+ if (commit) {
142
+ this.commit();
143
+ }
144
+
145
+ return newId;
146
+ }
147
+
148
+ public cancelRequest(requestId: MediaRequestId, commit = true) {
149
+ delete this.clientRequests[requestId];
150
+
151
+ if (commit) {
152
+ this.commit();
153
+ }
154
+ }
155
+
156
+ public commit() {
157
+ return this.sendRequests();
158
+ }
159
+
160
+ public reset() {
161
+ this.clientRequests = {};
162
+ this.slotsActiveInLastMediaRequest = {};
163
+ }
164
+ }
@@ -0,0 +1,92 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+
3
+ import {AUDIO, VIDEO} from '../constants';
4
+ import createMuteState from '../meeting/muteState';
5
+ import Meeting from '../meeting';
6
+
7
+ /**
8
+ * Class wrapping all the multistream specific APIs that need to be exposed at meeting level.
9
+ *
10
+ */
11
+ export class MultistreamMedia {
12
+ private meeting: Meeting;
13
+
14
+ /**
15
+ * Constructor
16
+ * @param {Meeting} meeting
17
+ */
18
+ constructor(meeting: Meeting) {
19
+ this.meeting = meeting;
20
+ }
21
+
22
+ /**
23
+ * throws if we don't have a media connection created
24
+ */
25
+ private checkMediaConnection() {
26
+ if (this.meeting?.mediaProperties?.webrtcMediaConnection) {
27
+ return;
28
+ }
29
+ throw new Error('Webrtc media connection is missing');
30
+ }
31
+
32
+ /**
33
+ * Publishes a local track in the meeting
34
+ *
35
+ * @param {MediaStreamTrack} track
36
+ * @returns {Promise}
37
+ */
38
+ publishTrack(track: MediaStreamTrack): Promise<void> {
39
+ this.checkMediaConnection();
40
+
41
+ /* todo: for now we don't support screen share (waiting for server to support bundling, see SPARK-377812)
42
+ for sharing:
43
+ we have to call meeting.stopFloorRequest() before unpublishing a track
44
+ we have to call meeting.share() after publishing a track
45
+ */
46
+
47
+ // todo: depending on how muting is done with Local tracks, this code here might need to change...
48
+
49
+ if (track.kind === 'audio') {
50
+ this.meeting.setLocalAudioTrack(track);
51
+ this.meeting.mediaProperties.mediaDirection.sendAudio = true;
52
+
53
+ // audio state could be undefined if you have not sent audio before
54
+ this.meeting.audio =
55
+ this.meeting.audio ||
56
+ createMuteState(AUDIO, this.meeting, this.meeting.mediaProperties.mediaDirection);
57
+ } else if (track.kind === 'video') {
58
+ this.meeting.setLocalVideoTrack(track);
59
+ this.meeting.mediaProperties.mediaDirection.sendVideo = true;
60
+
61
+ // video state could be undefined if you have not sent video before
62
+ this.meeting.video =
63
+ this.meeting.video ||
64
+ createMuteState(VIDEO, this.meeting, this.meeting.mediaProperties.mediaDirection);
65
+ }
66
+
67
+ return this.meeting.mediaProperties.webrtcMediaConnection.publishTrack(track);
68
+ }
69
+
70
+ /**
71
+ * Unpublishes a local track in the meeting
72
+ *
73
+ * @param {MediaStreamTrack} track
74
+ * @returns {Promise}
75
+ */
76
+ unpublishTrack(track: MediaStreamTrack): Promise<void> {
77
+ // todo: see todos in publishTrack() - they all apply here too:
78
+ // screen sharing - SPARK-377812
79
+ // muting etc
80
+
81
+ if (track.kind === 'audio') {
82
+ this.meeting.setLocalVideoTrack(null);
83
+ this.meeting.mediaProperties.mediaDirection.sendAudio = false;
84
+ } else if (track.kind === 'video') {
85
+ this.meeting.setLocalAudioTrack(null);
86
+ this.meeting.mediaProperties.mediaDirection.sendVideo = false;
87
+ }
88
+ this.checkMediaConnection();
89
+
90
+ return this.meeting.mediaProperties.webrtcMediaConnection.unpublishTrack(track);
91
+ }
92
+ }
@@ -0,0 +1,141 @@
1
+ import {MediaConnection as MC} from '@webex/internal-media-core';
2
+
3
+ import LoggerProxy from '../common/logs/logger-proxy';
4
+ import EventsScope from '../common/events/events-scope';
5
+
6
+ export const ReceiveSlotEvents = {
7
+ SourceUpdate: 'sourceUpdate',
8
+ };
9
+
10
+ export type SourceState = MC.SourceState;
11
+ export type CSI = number;
12
+ export type MemberId = string;
13
+ export type ReceiveSlotId = string;
14
+
15
+ let receiveSlotCounter = 0;
16
+
17
+ export type FindMemberIdCallback = (csi: CSI) => MemberId | undefined;
18
+
19
+ /**
20
+ * Class representing a receive slot. A single receive slot is able to receive a single track
21
+ * for example some participant's main video or audio
22
+ */
23
+ export class ReceiveSlot extends EventsScope {
24
+ private readonly mcReceiveSlot: MC.ReceiveSlot;
25
+
26
+ private readonly findMemberIdCallback: FindMemberIdCallback;
27
+
28
+ public readonly id: ReceiveSlotId;
29
+
30
+ public readonly mediaType: MC.MediaType;
31
+
32
+ #memberId?: MemberId;
33
+
34
+ #csi?: CSI;
35
+
36
+ #sourceState: MC.SourceState;
37
+
38
+ /**
39
+ * constructor - don't use it directly, you should always use meeting.receiveSlotManager.allocateSlot()
40
+ * to create any receive slots
41
+ *
42
+ * @param {MC.MediaType} mediaType
43
+ * @param {MC.ReceiveSlot} mcReceiveSlot
44
+ * @param {FindMemberIdCallback} findMemberIdCallback callback for finding memberId for given CSI
45
+ */
46
+ constructor(
47
+ mediaType: MC.MediaType,
48
+ mcReceiveSlot: MC.ReceiveSlot,
49
+ findMemberIdCallback: FindMemberIdCallback
50
+ ) {
51
+ super();
52
+
53
+ receiveSlotCounter += 1;
54
+
55
+ this.findMemberIdCallback = findMemberIdCallback;
56
+ this.mediaType = mediaType;
57
+ this.mcReceiveSlot = mcReceiveSlot;
58
+ this.#sourceState = 'no source';
59
+ this.id = `r${receiveSlotCounter}`;
60
+
61
+ this.setupEventListeners();
62
+ }
63
+
64
+ /**
65
+ * Getter for memberId
66
+ */
67
+ public get memberId() {
68
+ return this.#memberId;
69
+ }
70
+
71
+ /**
72
+ * Getter for csi
73
+ */
74
+ public get csi() {
75
+ return this.#csi;
76
+ }
77
+
78
+ /**
79
+ * Getter for sourceState
80
+ */
81
+ public get sourceState() {
82
+ return this.#sourceState;
83
+ }
84
+
85
+ /**
86
+ * registers event handlers with the underlying ReceiveSlot
87
+ */
88
+ setupEventListeners() {
89
+ const scope = {
90
+ file: 'meeting/receiveSlot',
91
+ function: 'setupEventListeners',
92
+ };
93
+
94
+ this.mcReceiveSlot.on(
95
+ MC.ReceiveSlotEvents.SourceUpdate,
96
+ (state: MC.SourceState, csi?: number) => {
97
+ LoggerProxy.logger.log(
98
+ `ReceiveSlot#setupEventListeners --> got source update on receive slot ${this.id}, mediaType=${this.mediaType}, csi=${csi}, state=${state}`
99
+ );
100
+ this.#memberId = csi ? this.findMemberIdCallback(csi) : undefined;
101
+ this.#csi = csi;
102
+ this.#sourceState = state;
103
+
104
+ this.emit(scope, ReceiveSlotEvents.SourceUpdate, {
105
+ state: this.#sourceState,
106
+ csi: this.#csi,
107
+ memberId: this.#memberId,
108
+ });
109
+ }
110
+ );
111
+ }
112
+
113
+ /**
114
+ * The MediaStream object associated with this slot.
115
+ *
116
+ * @returns {MediaStream} The MediaStreamTrack.
117
+ */
118
+ get stream(): MediaStream {
119
+ return this.mcReceiveSlot.stream;
120
+ }
121
+
122
+ /**
123
+ * The underlying WCME receive slot
124
+ */
125
+ get wcmeReceiveSlot(): MC.ReceiveSlot {
126
+ return this.mcReceiveSlot;
127
+ }
128
+
129
+ /**
130
+ * Resets the source state to the default 'no source' value.
131
+ * This function should be called on receive slots that are
132
+ * no longer part of a media request. It's needed because WCME
133
+ * does not send any more events on such slots, so the sourceState
134
+ * value would not represent the truth anymore.
135
+ */
136
+ public resetSourceState() {
137
+ this.#sourceState = 'no source';
138
+ this.#csi = undefined;
139
+ this.#memberId = undefined;
140
+ }
141
+ }