@webex/plugin-meetings 3.0.0-beta.111 → 3.0.0-beta.112
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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/media/index.js +0 -21
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/index.js +19 -0
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +288 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -0
- package/dist/meeting/muteState.js +49 -34
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +0 -39
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +17 -18
- package/dist/meeting/util.js.map +1 -1
- package/dist/roap/index.js +4 -19
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +23 -39
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +2 -10
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/types/meeting/index.d.ts +3 -0
- package/dist/types/meeting/locusMediaRequest.d.ts +68 -0
- package/dist/types/meeting/muteState.d.ts +3 -2
- package/dist/types/meeting/request.d.ts +0 -18
- package/dist/types/roap/request.d.ts +6 -8
- package/dist/types/roap/turnDiscovery.d.ts +4 -1
- package/package.json +19 -19
- package/src/media/index.ts +0 -23
- package/src/meeting/index.ts +22 -0
- package/src/meeting/locusMediaRequest.ts +303 -0
- package/src/meeting/muteState.ts +24 -9
- package/src/meeting/request.ts +0 -46
- package/src/meeting/util.ts +15 -13
- package/src/roap/index.ts +4 -16
- package/src/roap/request.ts +22 -42
- package/src/roap/turnDiscovery.ts +2 -8
- package/test/unit/spec/meeting/locusMediaRequest.ts +414 -0
- package/test/unit/spec/meeting/muteState.js +97 -71
- package/test/unit/spec/meeting/utils.js +11 -37
- package/test/unit/spec/roap/index.ts +2 -37
- package/test/unit/spec/roap/request.ts +27 -57
- package/test/unit/spec/roap/turnDiscovery.ts +3 -5
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/* eslint-disable valid-jsdoc */
|
|
2
|
+
import {defer} from 'lodash';
|
|
3
|
+
import {Defer} from '@webex/common';
|
|
4
|
+
import {WebexPlugin} from '@webex/webex-core';
|
|
5
|
+
import {MEDIA, HTTP_VERBS, ROAP} from '../constants';
|
|
6
|
+
import LoggerProxy from '../common/logs/logger-proxy';
|
|
7
|
+
|
|
8
|
+
export type MediaRequestType = 'RoapMessage' | 'LocalMute';
|
|
9
|
+
export type RequestResult = any;
|
|
10
|
+
|
|
11
|
+
export type RoapRequest = {
|
|
12
|
+
type: 'RoapMessage';
|
|
13
|
+
selfUrl: string;
|
|
14
|
+
mediaId: string;
|
|
15
|
+
roapMessage: any;
|
|
16
|
+
reachability: any;
|
|
17
|
+
joinCookie: any; // any, because this is opaque to the client, we pass whatever object we got from one backend component (Orpheus) to the other (Locus)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type LocalMuteRequest = {
|
|
21
|
+
type: 'LocalMute';
|
|
22
|
+
selfUrl: string;
|
|
23
|
+
mediaId: string;
|
|
24
|
+
muteOptions: {
|
|
25
|
+
audioMuted?: boolean;
|
|
26
|
+
videoMuted?: boolean;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type Request = RoapRequest | LocalMuteRequest;
|
|
31
|
+
|
|
32
|
+
/** Class representing a single /media request being sent to Locus */
|
|
33
|
+
class InternalRequestInfo {
|
|
34
|
+
public readonly request: Request;
|
|
35
|
+
private pendingPromises: Defer[];
|
|
36
|
+
private sendRequestFn: (request: Request) => Promise<RequestResult>;
|
|
37
|
+
|
|
38
|
+
/** Constructor */
|
|
39
|
+
constructor(
|
|
40
|
+
request: Request,
|
|
41
|
+
pendingPromise: Defer,
|
|
42
|
+
sendRequestFn: (request: Request) => Promise<RequestResult>
|
|
43
|
+
) {
|
|
44
|
+
this.request = request;
|
|
45
|
+
this.pendingPromises = [pendingPromise];
|
|
46
|
+
this.sendRequestFn = sendRequestFn;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns the list of pending promises associated with this request
|
|
51
|
+
*/
|
|
52
|
+
public getPendingPromises() {
|
|
53
|
+
return this.pendingPromises;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Adds promises to the list of pending promises associated with this request
|
|
58
|
+
*/
|
|
59
|
+
public addPendingPromises(pendingPromises: Defer[]) {
|
|
60
|
+
this.pendingPromises.push(...pendingPromises);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Executes the request. Returned promise is resolved once the request
|
|
65
|
+
* is completed (no matter if it succeeded or failed).
|
|
66
|
+
*/
|
|
67
|
+
public execute(): Promise<void> {
|
|
68
|
+
return this.sendRequestFn(this.request)
|
|
69
|
+
.then((result) => {
|
|
70
|
+
// resolve all the pending promises associated with this request
|
|
71
|
+
this.pendingPromises.forEach((d) => d.resolve(result));
|
|
72
|
+
})
|
|
73
|
+
.catch((e) => {
|
|
74
|
+
// reject all the pending promises associated with this request
|
|
75
|
+
this.pendingPromises.forEach((d) => d.reject(e));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type Config = {
|
|
81
|
+
device: {
|
|
82
|
+
url: string;
|
|
83
|
+
deviceType: string;
|
|
84
|
+
};
|
|
85
|
+
correlationId: string;
|
|
86
|
+
preferTranscoding: boolean;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns true if the request is triggering confluence creation in the server
|
|
91
|
+
*/
|
|
92
|
+
function isRequestAffectingConfluenceState(request: Request): boolean {
|
|
93
|
+
return (
|
|
94
|
+
request.type === 'RoapMessage' && request.roapMessage.messageType === ROAP.ROAP_TYPES.OFFER
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* This class manages all /media API requests to Locus. Every call to that
|
|
100
|
+
* Locus API has to go through this class.
|
|
101
|
+
*/
|
|
102
|
+
export class LocusMediaRequest extends WebexPlugin {
|
|
103
|
+
private config: Config;
|
|
104
|
+
private latestAudioMuted?: boolean;
|
|
105
|
+
private latestVideoMuted?: boolean;
|
|
106
|
+
private isRequestInProgress: boolean;
|
|
107
|
+
private queuedRequests: InternalRequestInfo[];
|
|
108
|
+
private confluenceState: 'not created' | 'creation in progress' | 'created';
|
|
109
|
+
/**
|
|
110
|
+
* Constructor
|
|
111
|
+
*/
|
|
112
|
+
constructor(config: Config, options: any) {
|
|
113
|
+
super({}, options);
|
|
114
|
+
this.isRequestInProgress = false;
|
|
115
|
+
this.queuedRequests = [];
|
|
116
|
+
this.config = config;
|
|
117
|
+
this.confluenceState = 'not created';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Add a request to the internal queue.
|
|
122
|
+
*/
|
|
123
|
+
private addToQueue(info: InternalRequestInfo) {
|
|
124
|
+
if (info.request.type === 'LocalMute' && this.queuedRequests.length > 0) {
|
|
125
|
+
// We don't need additional local mute requests in the queue.
|
|
126
|
+
// We only need at most 1 local mute or 1 roap request, because
|
|
127
|
+
// roap requests also include mute state, so whatever request
|
|
128
|
+
// is sent out, it will send the latest local mute state.
|
|
129
|
+
// We only need to store the pendingPromises so that they get resolved
|
|
130
|
+
// when the roap request is sent out.
|
|
131
|
+
this.queuedRequests[0].addPendingPromises(info.getPendingPromises());
|
|
132
|
+
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (info.request.type === 'RoapMessage' && this.queuedRequests.length > 0) {
|
|
137
|
+
// remove any LocalMute requests from the queue, because this Roap message
|
|
138
|
+
// will also update the mute status in Locus, so they are redundant
|
|
139
|
+
this.queuedRequests = this.queuedRequests.filter((r) => {
|
|
140
|
+
if (r.request.type === 'LocalMute') {
|
|
141
|
+
// we need to keep the pending promises from the local mute request
|
|
142
|
+
// that we're removing from the queue
|
|
143
|
+
info.addPendingPromises(r.getPendingPromises());
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return true;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.queuedRequests.push(info);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Takes the next request from the queue and executes it. Once that
|
|
157
|
+
* request is completed, the next one will be taken from the queue
|
|
158
|
+
* and executed and this is repeated until the queue is empty.
|
|
159
|
+
*/
|
|
160
|
+
private executeNextQueuedRequest(): void {
|
|
161
|
+
if (this.isRequestInProgress) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const nextRequest = this.queuedRequests.shift();
|
|
166
|
+
|
|
167
|
+
if (nextRequest) {
|
|
168
|
+
this.isRequestInProgress = true;
|
|
169
|
+
nextRequest.execute().then(() => {
|
|
170
|
+
this.isRequestInProgress = false;
|
|
171
|
+
this.executeNextQueuedRequest();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Returns latest requested audio and video mute values. If they have never been
|
|
178
|
+
* requested, we assume audio/video to be muted.
|
|
179
|
+
*/
|
|
180
|
+
private getLatestMuteState() {
|
|
181
|
+
const audioMuted = this.latestAudioMuted !== undefined ? this.latestAudioMuted : true;
|
|
182
|
+
const videoMuted = this.latestVideoMuted !== undefined ? this.latestVideoMuted : true;
|
|
183
|
+
|
|
184
|
+
return {audioMuted, videoMuted};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Prepares the uri and body for the media request to be sent to Locus
|
|
189
|
+
*/
|
|
190
|
+
private sendHttpRequest(request: Request) {
|
|
191
|
+
const uri = `${request.selfUrl}/${MEDIA}`;
|
|
192
|
+
|
|
193
|
+
const {audioMuted, videoMuted} = this.getLatestMuteState();
|
|
194
|
+
|
|
195
|
+
// first setup things common to all requests
|
|
196
|
+
const body: any = {
|
|
197
|
+
device: this.config.device,
|
|
198
|
+
correlationId: this.config.correlationId,
|
|
199
|
+
clientMediaPreferences: {
|
|
200
|
+
preferTranscoding: this.config.preferTranscoding,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const localMedias: any = {
|
|
205
|
+
audioMuted,
|
|
206
|
+
videoMuted,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// now add things specific to request type
|
|
210
|
+
switch (request.type) {
|
|
211
|
+
case 'LocalMute':
|
|
212
|
+
body.respOnlySdp = true;
|
|
213
|
+
body.usingResource = null;
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
case 'RoapMessage':
|
|
217
|
+
localMedias.roapMessage = request.roapMessage;
|
|
218
|
+
localMedias.reachability = request.reachability;
|
|
219
|
+
body.clientMediaPreferences.joinCookie = request.joinCookie;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
body.localMedias = [
|
|
224
|
+
{
|
|
225
|
+
localSdp: JSON.stringify(localMedias), // this part must be JSON stringified, Locus requires this
|
|
226
|
+
mediaId: request.mediaId,
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
LoggerProxy.logger.info(
|
|
231
|
+
`Meeting:LocusMediaRequest#sendHttpRequest --> ${request.type} audioMuted=${audioMuted} videoMuted=${videoMuted}`
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (isRequestAffectingConfluenceState(request) && this.confluenceState === 'not created') {
|
|
235
|
+
this.confluenceState = 'creation in progress';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
return this.request({
|
|
240
|
+
method: HTTP_VERBS.PUT,
|
|
241
|
+
uri,
|
|
242
|
+
body,
|
|
243
|
+
})
|
|
244
|
+
.then((result) => {
|
|
245
|
+
if (isRequestAffectingConfluenceState(request)) {
|
|
246
|
+
this.confluenceState = 'created';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return result;
|
|
250
|
+
})
|
|
251
|
+
.catch((e) => {
|
|
252
|
+
if (
|
|
253
|
+
isRequestAffectingConfluenceState(request) &&
|
|
254
|
+
this.confluenceState === 'creation in progress'
|
|
255
|
+
) {
|
|
256
|
+
this.confluenceState = 'not created';
|
|
257
|
+
}
|
|
258
|
+
throw e;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Sends a media request to Locus
|
|
264
|
+
*/
|
|
265
|
+
public send(request: Request): Promise<RequestResult> {
|
|
266
|
+
if (request.type === 'LocalMute') {
|
|
267
|
+
const {audioMuted, videoMuted} = request.muteOptions;
|
|
268
|
+
|
|
269
|
+
if (audioMuted !== undefined) {
|
|
270
|
+
this.latestAudioMuted = audioMuted;
|
|
271
|
+
}
|
|
272
|
+
if (videoMuted !== undefined) {
|
|
273
|
+
this.latestVideoMuted = videoMuted;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (this.confluenceState === 'not created') {
|
|
277
|
+
// if there is no confluence, there is no point sending out local mute request
|
|
278
|
+
// as it will fail so we just store the latest audio/video muted values
|
|
279
|
+
// and resolve immediately, so that higher layer (MuteState class) doesn't get blocked
|
|
280
|
+
// and can call us again if user mutes/unmutes again before confluence is created
|
|
281
|
+
LoggerProxy.logger.info(
|
|
282
|
+
'Meeting:LocusMediaRequest#send --> called with LocalMute request before confluence creation'
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return Promise.resolve({});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const pendingPromise = new Defer();
|
|
290
|
+
|
|
291
|
+
const newRequest = new InternalRequestInfo(
|
|
292
|
+
request,
|
|
293
|
+
pendingPromise,
|
|
294
|
+
this.sendHttpRequest.bind(this)
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
this.addToQueue(newRequest);
|
|
298
|
+
|
|
299
|
+
defer(() => this.executeNextQueuedRequest());
|
|
300
|
+
|
|
301
|
+
return pendingPromise.promise;
|
|
302
|
+
}
|
|
303
|
+
}
|
package/src/meeting/muteState.ts
CHANGED
|
@@ -34,8 +34,10 @@ export const createMuteState = (type, meeting, mediaDirection, sdkOwnsLocalTrack
|
|
|
34
34
|
the last requested state by the client.
|
|
35
35
|
|
|
36
36
|
More info about Locus muting API: https://sqbu-github.cisco.com/pages/WebExSquared/locus/guides/mute.html#
|
|
37
|
+
|
|
38
|
+
This class is exported only for unit tests. It should never be instantiated directly with new MuteState(), instead createMuteState() should be called
|
|
37
39
|
*/
|
|
38
|
-
class MuteState {
|
|
40
|
+
export class MuteState {
|
|
39
41
|
pendingPromiseReject: any;
|
|
40
42
|
pendingPromiseResolve: any;
|
|
41
43
|
state: any;
|
|
@@ -62,7 +64,7 @@ class MuteState {
|
|
|
62
64
|
localMute: false,
|
|
63
65
|
},
|
|
64
66
|
server: {
|
|
65
|
-
localMute:
|
|
67
|
+
localMute: true,
|
|
66
68
|
// because remoteVideoMuted and unmuteVideoAllowed are updated seperately, they might be undefined
|
|
67
69
|
remoteMute: type === AUDIO ? meeting.remoteMuted : meeting.remoteVideoMuted ?? false,
|
|
68
70
|
unmuteAllowed: type === AUDIO ? meeting.unmuteAllowed : meeting.unmuteVideoAllowed ?? true,
|
|
@@ -95,7 +97,7 @@ class MuteState {
|
|
|
95
97
|
: meeting.mediaProperties.videoTrack?.muted;
|
|
96
98
|
|
|
97
99
|
LoggerProxy.logger.info(
|
|
98
|
-
`Meeting:muteState#
|
|
100
|
+
`Meeting:muteState#init --> ${this.type}: local track initial mute state: ${initialMute}`
|
|
99
101
|
);
|
|
100
102
|
|
|
101
103
|
if (initialMute !== undefined) {
|
|
@@ -103,6 +105,19 @@ class MuteState {
|
|
|
103
105
|
|
|
104
106
|
this.applyClientStateToServer(meeting);
|
|
105
107
|
}
|
|
108
|
+
} else {
|
|
109
|
+
// in the mode where sdkOwnsLocalTrack is false (transcoded meetings),
|
|
110
|
+
// SDK API currently doesn't allow to start with audio/video muted,
|
|
111
|
+
// so we need to apply the initial local mute state (false) to server
|
|
112
|
+
this.state.syncToServerInProgress = true;
|
|
113
|
+
this.sendLocalMuteRequestToServer(meeting)
|
|
114
|
+
.then(() => {
|
|
115
|
+
this.state.syncToServerInProgress = false;
|
|
116
|
+
})
|
|
117
|
+
.catch(() => {
|
|
118
|
+
this.state.syncToServerInProgress = false;
|
|
119
|
+
// not much we can do here...
|
|
120
|
+
});
|
|
106
121
|
}
|
|
107
122
|
}
|
|
108
123
|
|
|
@@ -312,16 +327,14 @@ class MuteState {
|
|
|
312
327
|
* @returns {Promise}
|
|
313
328
|
*/
|
|
314
329
|
private sendLocalMuteRequestToServer(meeting?: any) {
|
|
315
|
-
const audioMuted =
|
|
316
|
-
|
|
317
|
-
const videoMuted =
|
|
318
|
-
this.type === VIDEO ? this.state.client.localMute : meeting.video?.state.client.localMute;
|
|
330
|
+
const audioMuted = this.type === AUDIO ? this.state.client.localMute : undefined;
|
|
331
|
+
const videoMuted = this.type === VIDEO ? this.state.client.localMute : undefined;
|
|
319
332
|
|
|
320
333
|
LoggerProxy.logger.info(
|
|
321
334
|
`Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: sending local mute (audio=${audioMuted}, video=${videoMuted}) to server`
|
|
322
335
|
);
|
|
323
336
|
|
|
324
|
-
return MeetingUtil.remoteUpdateAudioVideo(audioMuted, videoMuted
|
|
337
|
+
return MeetingUtil.remoteUpdateAudioVideo(meeting, audioMuted, videoMuted)
|
|
325
338
|
.then((locus) => {
|
|
326
339
|
LoggerProxy.logger.info(
|
|
327
340
|
`Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: local mute (audio=${audioMuted}, video=${videoMuted}) applied to server`
|
|
@@ -329,7 +342,9 @@ class MuteState {
|
|
|
329
342
|
|
|
330
343
|
this.state.server.localMute = this.type === AUDIO ? audioMuted : videoMuted;
|
|
331
344
|
|
|
332
|
-
|
|
345
|
+
if (locus) {
|
|
346
|
+
meeting.locusInfo.onFullLocus(locus);
|
|
347
|
+
}
|
|
333
348
|
|
|
334
349
|
return locus;
|
|
335
350
|
})
|
package/src/meeting/request.ts
CHANGED
|
@@ -593,52 +593,6 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
593
593
|
});
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
-
/**
|
|
597
|
-
* Toggle remote audio and/or video
|
|
598
|
-
* @param {Object} options options for toggling
|
|
599
|
-
* @param {String} options.selfId Locus self id??
|
|
600
|
-
* @param {String} options.locusUrl Locus url
|
|
601
|
-
* @param {String} options.deviceUrl Url of a device
|
|
602
|
-
* @param {String} options.resourceId Populated if you are paired to a device
|
|
603
|
-
* @param {String} options.localMedias local sdps
|
|
604
|
-
* @param {Boolean} options.preferTranscoding false for multistream (Homer), true for transcoded media (Edonus)
|
|
605
|
-
* @returns {Promise}
|
|
606
|
-
*/
|
|
607
|
-
remoteAudioVideoToggle(
|
|
608
|
-
options:
|
|
609
|
-
| {
|
|
610
|
-
selfId: string;
|
|
611
|
-
locusUrl: string;
|
|
612
|
-
deviceUrl: string;
|
|
613
|
-
resourceId: string;
|
|
614
|
-
localMedias: string;
|
|
615
|
-
}
|
|
616
|
-
| any
|
|
617
|
-
) {
|
|
618
|
-
const uri = `${options.locusUrl}/${PARTICIPANT}/${options.selfId}/${MEDIA}`;
|
|
619
|
-
const body = {
|
|
620
|
-
device: {
|
|
621
|
-
// @ts-ignore
|
|
622
|
-
deviceType: this.config.meetings.deviceType,
|
|
623
|
-
url: options.deviceUrl,
|
|
624
|
-
},
|
|
625
|
-
usingResource: options.resourceId || null,
|
|
626
|
-
correlationId: options.correlationId,
|
|
627
|
-
respOnlySdp: true,
|
|
628
|
-
localMedias: options.localMedias,
|
|
629
|
-
clientMediaPreferences: {
|
|
630
|
-
preferTranscoding: options.preferTranscoding ?? true,
|
|
631
|
-
},
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
// @ts-ignore
|
|
635
|
-
return this.request({
|
|
636
|
-
method: HTTP_VERBS.PUT,
|
|
637
|
-
uri,
|
|
638
|
-
body,
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
|
|
642
596
|
/**
|
|
643
597
|
* change the content floor grant
|
|
644
598
|
* @param {Object} options options for floor grant
|
package/src/meeting/util.ts
CHANGED
|
@@ -44,33 +44,35 @@ MeetingUtil.parseLocusJoin = (response) => {
|
|
|
44
44
|
return parsed;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
MeetingUtil.remoteUpdateAudioVideo = (audioMuted, videoMuted
|
|
47
|
+
MeetingUtil.remoteUpdateAudioVideo = (meeting, audioMuted?: boolean, videoMuted?: boolean) => {
|
|
48
48
|
if (!meeting) {
|
|
49
49
|
return Promise.reject(new ParameterError('You need a meeting object.'));
|
|
50
50
|
}
|
|
51
|
-
const localMedias = Media.generateLocalMedias(meeting.mediaId, audioMuted, videoMuted);
|
|
52
51
|
|
|
53
|
-
if (
|
|
52
|
+
if (!meeting.locusMediaRequest) {
|
|
54
53
|
return Promise.reject(
|
|
55
|
-
new ParameterError(
|
|
54
|
+
new ParameterError(
|
|
55
|
+
'You need a meeting with a media connection, call Meeting.addMedia() first.'
|
|
56
|
+
)
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
Metrics.postEvent({event: eventType.MEDIA_REQUEST, meeting});
|
|
60
61
|
|
|
61
|
-
return meeting.
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
return meeting.locusMediaRequest
|
|
63
|
+
.send({
|
|
64
|
+
type: 'LocalMute',
|
|
65
|
+
selfUrl: meeting.selfUrl,
|
|
66
|
+
mediaId: meeting.mediaId,
|
|
67
|
+
muteOptions: {
|
|
68
|
+
audioMuted,
|
|
69
|
+
videoMuted,
|
|
70
|
+
},
|
|
69
71
|
})
|
|
70
72
|
.then((response) => {
|
|
71
73
|
Metrics.postEvent({event: eventType.MEDIA_RESPONSE, meeting});
|
|
72
74
|
|
|
73
|
-
return response
|
|
75
|
+
return response?.body?.locus;
|
|
74
76
|
});
|
|
75
77
|
};
|
|
76
78
|
|
package/src/roap/index.ts
CHANGED
|
@@ -98,11 +98,8 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
98
98
|
roapMessage,
|
|
99
99
|
locusSelfUrl: meeting.selfUrl,
|
|
100
100
|
mediaId: options.mediaId,
|
|
101
|
-
correlationId: options.correlationId,
|
|
102
|
-
audioMuted: meeting.audio?.isLocallyMuted(),
|
|
103
|
-
videoMuted: meeting.video?.isLocallyMuted(),
|
|
104
101
|
meetingId: meeting.id,
|
|
105
|
-
|
|
102
|
+
locusMediaRequest: meeting.locusMediaRequest,
|
|
106
103
|
})
|
|
107
104
|
.then(() => {
|
|
108
105
|
LoggerProxy.logger.log(`Roap:index#sendRoapOK --> ROAP OK sent with seq ${options.seq}`);
|
|
@@ -135,11 +132,8 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
135
132
|
roapMessage,
|
|
136
133
|
locusSelfUrl: meeting.selfUrl,
|
|
137
134
|
mediaId: options.mediaId,
|
|
138
|
-
correlationId: options.correlationId,
|
|
139
|
-
audioMuted: meeting.isAudioMuted(),
|
|
140
|
-
videoMuted: meeting.isVideoMuted(),
|
|
141
135
|
meetingId: meeting.id,
|
|
142
|
-
|
|
136
|
+
locusMediaRequest: meeting.locusMediaRequest,
|
|
143
137
|
});
|
|
144
138
|
}
|
|
145
139
|
|
|
@@ -167,11 +161,8 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
167
161
|
roapMessage,
|
|
168
162
|
locusSelfUrl: meeting.selfUrl,
|
|
169
163
|
mediaId: options.mediaId,
|
|
170
|
-
correlationId: options.correlationId,
|
|
171
|
-
audioMuted: meeting.audio?.isLocallyMuted(),
|
|
172
|
-
videoMuted: meeting.video?.isLocallyMuted(),
|
|
173
164
|
meetingId: meeting.id,
|
|
174
|
-
|
|
165
|
+
locusMediaRequest: meeting.locusMediaRequest,
|
|
175
166
|
})
|
|
176
167
|
.then(() => {
|
|
177
168
|
LoggerProxy.logger.log(
|
|
@@ -206,13 +197,10 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
206
197
|
|
|
207
198
|
return this.roapRequest.sendRoap({
|
|
208
199
|
roapMessage,
|
|
209
|
-
correlationId: meeting.correlationId,
|
|
210
200
|
locusSelfUrl: meeting.selfUrl,
|
|
211
201
|
mediaId: sendEmptyMediaId ? '' : meeting.mediaId,
|
|
212
|
-
audioMuted: meeting.audio?.isLocallyMuted(),
|
|
213
|
-
videoMuted: meeting.video?.isLocallyMuted(),
|
|
214
202
|
meetingId: meeting.id,
|
|
215
|
-
|
|
203
|
+
locusMediaRequest: meeting.locusMediaRequest,
|
|
216
204
|
});
|
|
217
205
|
})
|
|
218
206
|
|
package/src/roap/request.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import {StatelessWebexPlugin} from '@webex/webex-core';
|
|
4
4
|
|
|
5
5
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
6
|
-
import {
|
|
6
|
+
import {REACHABILITY} from '../constants';
|
|
7
7
|
import Metrics from '../metrics';
|
|
8
8
|
import {eventType} from '../metrics/config';
|
|
9
|
+
import {LocusMediaRequest} from '../meeting/locusMediaRequest';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @class RoapRequest
|
|
@@ -64,69 +65,48 @@ export default class RoapRequest extends StatelessWebexPlugin {
|
|
|
64
65
|
* @param {String} options.locusSelfUrl
|
|
65
66
|
* @param {String} options.mediaId
|
|
66
67
|
* @param {String} options.correlationId
|
|
67
|
-
* @param {Boolean} options.audioMuted
|
|
68
|
-
* @param {Boolean} options.videoMuted
|
|
69
68
|
* @param {String} options.meetingId
|
|
70
|
-
* @param {Boolean} options.preferTranscoding
|
|
71
69
|
* @returns {Promise} returns the response/failure of the request
|
|
72
70
|
*/
|
|
73
71
|
async sendRoap(options: {
|
|
74
72
|
roapMessage: any;
|
|
75
73
|
locusSelfUrl: string;
|
|
76
74
|
mediaId: string;
|
|
77
|
-
correlationId: string;
|
|
78
|
-
audioMuted: boolean;
|
|
79
|
-
videoMuted: boolean;
|
|
80
75
|
meetingId: string;
|
|
81
|
-
|
|
76
|
+
locusMediaRequest?: LocusMediaRequest;
|
|
82
77
|
}) {
|
|
83
|
-
const {roapMessage, locusSelfUrl, mediaId,
|
|
78
|
+
const {roapMessage, locusSelfUrl, mediaId, meetingId, locusMediaRequest} = options;
|
|
84
79
|
|
|
85
80
|
if (!mediaId) {
|
|
86
|
-
LoggerProxy.logger.info('Roap:request#sendRoap -->
|
|
81
|
+
LoggerProxy.logger.info('Roap:request#sendRoap --> sending empty mediaID');
|
|
87
82
|
}
|
|
88
83
|
|
|
84
|
+
if (!locusMediaRequest) {
|
|
85
|
+
LoggerProxy.logger.warn(
|
|
86
|
+
'Roap:request#sendRoap --> locusMediaRequest unavailable, not sending roap'
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return Promise.reject(new Error('sendRoap called when locusMediaRequest is undefined'));
|
|
90
|
+
}
|
|
89
91
|
const {localSdp: localSdpWithReachabilityData, joinCookie} = await this.attachReachabilityData({
|
|
90
92
|
roapMessage,
|
|
91
|
-
// eslint-disable-next-line no-warning-comments
|
|
92
|
-
// TODO: check whats the need for video and audiomute
|
|
93
|
-
audioMuted: !!options.audioMuted,
|
|
94
|
-
videoMuted: !!options.videoMuted,
|
|
95
93
|
});
|
|
96
94
|
|
|
97
|
-
const mediaUrl = `${locusSelfUrl}/${MEDIA}`;
|
|
98
|
-
// @ts-ignore
|
|
99
|
-
const deviceUrl = this.webex.internal.device.url;
|
|
100
|
-
|
|
101
95
|
LoggerProxy.logger.info(
|
|
102
|
-
`Roap:request#sendRoap --> ${
|
|
96
|
+
`Roap:request#sendRoap --> ${locusSelfUrl} \n ${roapMessage.messageType} \n seq:${roapMessage.seq}`
|
|
103
97
|
);
|
|
104
98
|
|
|
105
99
|
Metrics.postEvent({event: eventType.MEDIA_REQUEST, meetingId});
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
},
|
|
117
|
-
correlationId,
|
|
118
|
-
localMedias: [
|
|
119
|
-
{
|
|
120
|
-
localSdp: JSON.stringify(localSdpWithReachabilityData),
|
|
121
|
-
mediaId: options.mediaId,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
clientMediaPreferences: {
|
|
125
|
-
preferTranscoding: options.preferTranscoding ?? true,
|
|
126
|
-
joinCookie,
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
})
|
|
101
|
+
return locusMediaRequest
|
|
102
|
+
.send({
|
|
103
|
+
type: 'RoapMessage',
|
|
104
|
+
selfUrl: locusSelfUrl,
|
|
105
|
+
joinCookie,
|
|
106
|
+
mediaId,
|
|
107
|
+
roapMessage,
|
|
108
|
+
reachability: localSdpWithReachabilityData.reachability,
|
|
109
|
+
})
|
|
130
110
|
.then((res) => {
|
|
131
111
|
Metrics.postEvent({event: eventType.MEDIA_RESPONSE, meetingId});
|
|
132
112
|
|
|
@@ -176,15 +176,12 @@ export default class TurnDiscovery {
|
|
|
176
176
|
return this.roapRequest
|
|
177
177
|
.sendRoap({
|
|
178
178
|
roapMessage,
|
|
179
|
-
correlationId: meeting.correlationId,
|
|
180
179
|
// @ts-ignore - Fix missing type
|
|
181
180
|
locusSelfUrl: meeting.selfUrl,
|
|
182
181
|
// @ts-ignore - Fix missing type
|
|
183
182
|
mediaId: isReconnecting ? '' : meeting.mediaId,
|
|
184
|
-
audioMuted: meeting.audio?.isLocallyMuted(),
|
|
185
|
-
videoMuted: meeting.video?.isLocallyMuted(),
|
|
186
183
|
meetingId: meeting.id,
|
|
187
|
-
|
|
184
|
+
locusMediaRequest: meeting.locusMediaRequest,
|
|
188
185
|
})
|
|
189
186
|
.then(({mediaConnections}) => {
|
|
190
187
|
if (mediaConnections) {
|
|
@@ -213,11 +210,8 @@ export default class TurnDiscovery {
|
|
|
213
210
|
locusSelfUrl: meeting.selfUrl,
|
|
214
211
|
// @ts-ignore - fix type
|
|
215
212
|
mediaId: meeting.mediaId,
|
|
216
|
-
correlationId: meeting.correlationId,
|
|
217
|
-
audioMuted: meeting.audio?.isLocallyMuted(),
|
|
218
|
-
videoMuted: meeting.video?.isLocallyMuted(),
|
|
219
213
|
meetingId: meeting.id,
|
|
220
|
-
|
|
214
|
+
locusMediaRequest: meeting.locusMediaRequest,
|
|
221
215
|
});
|
|
222
216
|
}
|
|
223
217
|
|