@webex/plugin-meetings 3.0.0-bnr.5 → 3.0.0-stream-classes.1

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 (303) hide show
  1. package/README.md +46 -8
  2. package/dist/annotation/annotation.types.js +7 -0
  3. package/dist/annotation/annotation.types.js.map +1 -0
  4. package/dist/annotation/constants.js +49 -0
  5. package/dist/annotation/constants.js.map +1 -0
  6. package/dist/annotation/index.js +342 -0
  7. package/dist/annotation/index.js.map +1 -0
  8. package/dist/breakouts/breakout.js +70 -32
  9. package/dist/breakouts/breakout.js.map +1 -1
  10. package/dist/breakouts/events.js +45 -0
  11. package/dist/breakouts/events.js.map +1 -0
  12. package/dist/breakouts/index.js +422 -217
  13. package/dist/breakouts/index.js.map +1 -1
  14. package/dist/breakouts/utils.js +12 -1
  15. package/dist/breakouts/utils.js.map +1 -1
  16. package/dist/common/errors/webex-errors.js +3 -2
  17. package/dist/common/errors/webex-errors.js.map +1 -1
  18. package/dist/common/logs/logger-proxy.js +1 -1
  19. package/dist/common/logs/logger-proxy.js.map +1 -1
  20. package/dist/common/logs/request.d.ts +1 -1
  21. package/dist/common/queue.js +24 -9
  22. package/dist/common/queue.js.map +1 -1
  23. package/dist/config.js +1 -7
  24. package/dist/config.js.map +1 -1
  25. package/dist/constants.js +118 -24
  26. package/dist/constants.js.map +1 -1
  27. package/dist/controls-options-manager/enums.js +2 -0
  28. package/dist/controls-options-manager/enums.js.map +1 -1
  29. package/dist/controls-options-manager/index.js +19 -14
  30. package/dist/controls-options-manager/index.js.map +1 -1
  31. package/dist/controls-options-manager/types.js.map +1 -1
  32. package/dist/controls-options-manager/util.js +80 -11
  33. package/dist/controls-options-manager/util.js.map +1 -1
  34. package/dist/index.js +62 -20
  35. package/dist/index.js.map +1 -1
  36. package/dist/interpretation/collection.js +23 -0
  37. package/dist/interpretation/collection.js.map +1 -0
  38. package/dist/interpretation/index.js +366 -0
  39. package/dist/interpretation/index.js.map +1 -0
  40. package/dist/interpretation/siLanguage.js +25 -0
  41. package/dist/interpretation/siLanguage.js.map +1 -0
  42. package/dist/locus-info/controlsUtils.js +71 -1
  43. package/dist/locus-info/controlsUtils.js.map +1 -1
  44. package/dist/locus-info/index.js +305 -57
  45. package/dist/locus-info/index.js.map +1 -1
  46. package/dist/locus-info/infoUtils.js +7 -1
  47. package/dist/locus-info/infoUtils.js.map +1 -1
  48. package/dist/locus-info/mediaSharesUtils.js +43 -1
  49. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  50. package/dist/locus-info/parser.js +219 -63
  51. package/dist/locus-info/parser.js.map +1 -1
  52. package/dist/locus-info/selfUtils.js +44 -22
  53. package/dist/locus-info/selfUtils.js.map +1 -1
  54. package/dist/media/index.js +57 -104
  55. package/dist/media/index.js.map +1 -1
  56. package/dist/media/properties.js +60 -121
  57. package/dist/media/properties.js.map +1 -1
  58. package/dist/meeting/in-meeting-actions.js +61 -3
  59. package/dist/meeting/in-meeting-actions.js.map +1 -1
  60. package/dist/meeting/index.js +2530 -2534
  61. package/dist/meeting/index.js.map +1 -1
  62. package/dist/meeting/locusMediaRequest.js +292 -0
  63. package/dist/meeting/locusMediaRequest.js.map +1 -0
  64. package/dist/meeting/muteState.js +125 -205
  65. package/dist/meeting/muteState.js.map +1 -1
  66. package/dist/meeting/request.js +150 -150
  67. package/dist/meeting/request.js.map +1 -1
  68. package/dist/meeting/util.js +568 -438
  69. package/dist/meeting/util.js.map +1 -1
  70. package/dist/meeting-info/index.js +48 -7
  71. package/dist/meeting-info/index.js.map +1 -1
  72. package/dist/meeting-info/meeting-info-v2.js +94 -38
  73. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  74. package/dist/meeting-info/utilv2.js +4 -2
  75. package/dist/meeting-info/utilv2.js.map +1 -1
  76. package/dist/meetings/index.d.ts +0 -2
  77. package/dist/meetings/index.js +260 -85
  78. package/dist/meetings/index.js.map +1 -1
  79. package/dist/meetings/meetings.types.js +7 -0
  80. package/dist/meetings/meetings.types.js.map +1 -0
  81. package/dist/meetings/util.js +42 -7
  82. package/dist/meetings/util.js.map +1 -1
  83. package/dist/member/index.d.ts +2 -0
  84. package/dist/member/index.js +26 -0
  85. package/dist/member/index.js.map +1 -1
  86. package/dist/member/member.types.d.ts +11 -0
  87. package/dist/member/member.types.js +18 -0
  88. package/dist/member/member.types.js.map +1 -0
  89. package/dist/member/types.js +11 -1
  90. package/dist/member/types.js.map +1 -1
  91. package/dist/member/util.js +60 -23
  92. package/dist/member/util.js.map +1 -1
  93. package/dist/members/index.js +4 -1
  94. package/dist/members/index.js.map +1 -1
  95. package/dist/members/request.js +75 -45
  96. package/dist/members/request.js.map +1 -1
  97. package/dist/members/util.js +308 -317
  98. package/dist/members/util.js.map +1 -1
  99. package/dist/metrics/config.js +1 -3
  100. package/dist/metrics/config.js.map +1 -1
  101. package/dist/metrics/constants.js +1 -0
  102. package/dist/metrics/constants.js.map +1 -1
  103. package/dist/metrics/index.d.ts +1 -1
  104. package/dist/metrics/index.js +1 -451
  105. package/dist/metrics/index.js.map +1 -1
  106. package/dist/multistream/mediaRequestManager.js +136 -40
  107. package/dist/multistream/mediaRequestManager.js.map +1 -1
  108. package/dist/multistream/receiveSlot.js.map +1 -1
  109. package/dist/multistream/receiveSlotManager.js +4 -4
  110. package/dist/multistream/receiveSlotManager.js.map +1 -1
  111. package/dist/multistream/remoteMedia.js.map +1 -1
  112. package/dist/multistream/remoteMediaGroup.js +60 -3
  113. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  114. package/dist/multistream/remoteMediaManager.js +36 -0
  115. package/dist/multistream/remoteMediaManager.js.map +1 -1
  116. package/dist/multistream/sendSlotManager.js +233 -0
  117. package/dist/multistream/sendSlotManager.js.map +1 -0
  118. package/dist/reachability/index.js +18 -3
  119. package/dist/reachability/index.js.map +1 -1
  120. package/dist/reachability/request.js +5 -3
  121. package/dist/reachability/request.js.map +1 -1
  122. package/dist/reconnection-manager/index.js +181 -153
  123. package/dist/reconnection-manager/index.js.map +1 -1
  124. package/dist/recording-controller/index.js +21 -2
  125. package/dist/recording-controller/index.js.map +1 -1
  126. package/dist/recording-controller/util.js +9 -8
  127. package/dist/recording-controller/util.js.map +1 -1
  128. package/dist/roap/index.js +25 -32
  129. package/dist/roap/index.js.map +1 -1
  130. package/dist/roap/request.js +42 -51
  131. package/dist/roap/request.js.map +1 -1
  132. package/dist/roap/turnDiscovery.js +97 -38
  133. package/dist/roap/turnDiscovery.js.map +1 -1
  134. package/dist/rtcMetrics/constants.js +12 -0
  135. package/dist/rtcMetrics/constants.js.map +1 -0
  136. package/dist/rtcMetrics/index.js +117 -0
  137. package/dist/rtcMetrics/index.js.map +1 -0
  138. package/dist/statsAnalyzer/index.js +0 -1
  139. package/dist/statsAnalyzer/index.js.map +1 -1
  140. package/dist/types/annotation/annotation.types.d.ts +43 -0
  141. package/dist/types/annotation/constants.d.ts +31 -0
  142. package/dist/types/annotation/index.d.ts +124 -0
  143. package/dist/types/breakouts/events.d.ts +2 -0
  144. package/dist/types/breakouts/utils.d.ts +7 -0
  145. package/dist/types/common/errors/webex-errors.d.ts +1 -1
  146. package/dist/types/config.d.ts +0 -6
  147. package/dist/types/constants.d.ts +51 -21
  148. package/dist/types/controls-options-manager/enums.d.ts +2 -0
  149. package/dist/types/controls-options-manager/index.d.ts +1 -1
  150. package/dist/types/controls-options-manager/types.d.ts +7 -1
  151. package/dist/types/index.d.ts +1 -1
  152. package/dist/types/interpretation/collection.d.ts +5 -0
  153. package/dist/types/interpretation/index.d.ts +5 -0
  154. package/dist/types/interpretation/siLanguage.d.ts +5 -0
  155. package/dist/types/locus-info/index.d.ts +39 -1
  156. package/dist/types/media/index.d.ts +2 -0
  157. package/dist/types/media/properties.d.ts +16 -38
  158. package/dist/types/meeting/in-meeting-actions.d.ts +46 -2
  159. package/dist/types/meeting/index.d.ts +179 -379
  160. package/dist/types/meeting/locusMediaRequest.d.ts +70 -0
  161. package/dist/types/meeting/muteState.d.ts +39 -40
  162. package/dist/types/meeting/request.d.ts +25 -26
  163. package/dist/types/meeting/util.d.ts +74 -1
  164. package/dist/types/meeting-info/meeting-info-v2.d.ts +14 -3
  165. package/dist/types/meetings/index.d.ts +49 -1
  166. package/dist/types/meetings/meetings.types.d.ts +4 -0
  167. package/dist/types/member/index.d.ts +2 -0
  168. package/dist/types/members/request.d.ts +56 -11
  169. package/dist/types/members/util.d.ts +209 -1
  170. package/dist/types/metrics/config.d.ts +26 -2
  171. package/dist/types/metrics/constants.d.ts +1 -0
  172. package/dist/types/metrics/index.d.ts +17 -0
  173. package/dist/types/multistream/mediaRequestManager.d.ts +27 -10
  174. package/dist/types/multistream/receiveSlot.d.ts +3 -3
  175. package/dist/types/multistream/remoteMedia.d.ts +2 -2
  176. package/dist/types/multistream/remoteMediaManager.d.ts +14 -0
  177. package/dist/types/roap/request.d.ts +6 -8
  178. package/dist/types/roap/turnDiscovery.d.ts +18 -1
  179. package/package.json +21 -20
  180. package/src/annotation/annotation.types.ts +50 -0
  181. package/src/annotation/constants.ts +36 -0
  182. package/src/annotation/index.ts +328 -0
  183. package/src/breakouts/README.md +3 -2
  184. package/src/breakouts/breakout.ts +62 -27
  185. package/src/breakouts/events.ts +56 -0
  186. package/src/breakouts/index.ts +244 -64
  187. package/src/breakouts/utils.ts +13 -0
  188. package/src/common/errors/webex-errors.ts +6 -2
  189. package/src/common/logs/logger-proxy.ts +1 -1
  190. package/src/common/queue.ts +22 -8
  191. package/src/config.ts +0 -6
  192. package/src/constants.ts +111 -19
  193. package/src/controls-options-manager/enums.ts +2 -0
  194. package/src/controls-options-manager/index.ts +13 -10
  195. package/src/controls-options-manager/types.ts +10 -0
  196. package/src/controls-options-manager/util.ts +82 -11
  197. package/src/index.ts +18 -11
  198. package/src/interpretation/README.md +60 -0
  199. package/src/interpretation/collection.ts +19 -0
  200. package/src/interpretation/index.ts +332 -0
  201. package/src/interpretation/siLanguage.ts +18 -0
  202. package/src/locus-info/controlsUtils.ts +81 -0
  203. package/src/locus-info/index.ts +318 -57
  204. package/src/locus-info/infoUtils.ts +10 -2
  205. package/src/locus-info/mediaSharesUtils.ts +48 -0
  206. package/src/locus-info/parser.ts +224 -39
  207. package/src/locus-info/selfUtils.ts +32 -20
  208. package/src/media/index.ts +94 -108
  209. package/src/media/properties.ts +69 -109
  210. package/src/meeting/in-meeting-actions.ts +120 -4
  211. package/src/meeting/index.ts +1967 -2120
  212. package/src/meeting/locusMediaRequest.ts +314 -0
  213. package/src/meeting/muteState.ts +119 -194
  214. package/src/meeting/request.ts +122 -115
  215. package/src/meeting/util.ts +549 -413
  216. package/src/meeting-info/index.ts +54 -8
  217. package/src/meeting-info/meeting-info-v2.ts +89 -24
  218. package/src/meeting-info/utilv2.ts +6 -2
  219. package/src/meetings/index.ts +247 -87
  220. package/src/meetings/meetings.types.ts +12 -0
  221. package/src/meetings/util.ts +47 -12
  222. package/src/member/index.ts +28 -1
  223. package/src/member/types.ts +14 -0
  224. package/src/member/util.ts +75 -26
  225. package/src/members/index.ts +7 -1
  226. package/src/members/request.ts +61 -21
  227. package/src/members/util.ts +316 -326
  228. package/src/metrics/constants.ts +1 -0
  229. package/src/metrics/index.ts +1 -474
  230. package/src/multistream/mediaRequestManager.ts +183 -67
  231. package/src/multistream/receiveSlot.ts +4 -4
  232. package/src/multistream/receiveSlotManager.ts +4 -4
  233. package/src/multistream/remoteMedia.ts +2 -2
  234. package/src/multistream/remoteMediaGroup.ts +59 -0
  235. package/src/multistream/remoteMediaManager.ts +33 -0
  236. package/src/multistream/sendSlotManager.ts +170 -0
  237. package/src/reachability/index.ts +15 -4
  238. package/src/reachability/request.ts +7 -3
  239. package/src/reconnection-manager/index.ts +36 -29
  240. package/src/recording-controller/index.ts +20 -3
  241. package/src/recording-controller/util.ts +26 -9
  242. package/src/roap/index.ts +25 -30
  243. package/src/roap/request.ts +44 -51
  244. package/src/roap/turnDiscovery.ts +51 -25
  245. package/src/rtcMetrics/constants.ts +3 -0
  246. package/src/rtcMetrics/index.ts +100 -0
  247. package/src/statsAnalyzer/index.ts +0 -1
  248. package/test/integration/spec/converged-space-meetings.js +60 -3
  249. package/test/integration/spec/journey.js +336 -259
  250. package/test/integration/spec/space-meeting.js +76 -3
  251. package/test/unit/spec/annotation/index.ts +418 -0
  252. package/test/unit/spec/breakouts/breakout.ts +85 -26
  253. package/test/unit/spec/breakouts/events.ts +89 -0
  254. package/test/unit/spec/breakouts/index.ts +636 -98
  255. package/test/unit/spec/breakouts/utils.js +19 -1
  256. package/test/unit/spec/common/queue.js +31 -2
  257. package/test/unit/spec/controls-options-manager/index.js +8 -1
  258. package/test/unit/spec/controls-options-manager/util.js +576 -397
  259. package/test/unit/spec/fixture/locus.js +1 -0
  260. package/test/unit/spec/interpretation/collection.ts +15 -0
  261. package/test/unit/spec/interpretation/index.ts +589 -0
  262. package/test/unit/spec/interpretation/siLanguage.ts +28 -0
  263. package/test/unit/spec/locus-info/controlsUtils.js +195 -1
  264. package/test/unit/spec/locus-info/index.js +950 -45
  265. package/test/unit/spec/locus-info/infoUtils.js +37 -15
  266. package/test/unit/spec/locus-info/mediaSharesUtils.ts +22 -0
  267. package/test/unit/spec/locus-info/parser.js +62 -22
  268. package/test/unit/spec/locus-info/selfConstant.js +19 -0
  269. package/test/unit/spec/locus-info/selfUtils.js +131 -26
  270. package/test/unit/spec/media/index.ts +82 -79
  271. package/test/unit/spec/meeting/in-meeting-actions.ts +60 -2
  272. package/test/unit/spec/meeting/index.js +3208 -1734
  273. package/test/unit/spec/meeting/locusMediaRequest.ts +443 -0
  274. package/test/unit/spec/meeting/muteState.js +328 -417
  275. package/test/unit/spec/meeting/request.js +393 -48
  276. package/test/unit/spec/meeting/utils.js +552 -76
  277. package/test/unit/spec/meeting-info/index.js +181 -0
  278. package/test/unit/spec/meeting-info/meetinginfov2.js +258 -20
  279. package/test/unit/spec/meeting-info/utilv2.js +21 -0
  280. package/test/unit/spec/meetings/index.js +631 -145
  281. package/test/unit/spec/meetings/utils.js +164 -9
  282. package/test/unit/spec/member/index.js +44 -14
  283. package/test/unit/spec/member/util.js +296 -155
  284. package/test/unit/spec/members/index.js +23 -3
  285. package/test/unit/spec/members/request.js +167 -35
  286. package/test/unit/spec/metrics/index.js +1 -50
  287. package/test/unit/spec/multistream/mediaRequestManager.ts +366 -8
  288. package/test/unit/spec/multistream/receiveSlot.ts +1 -1
  289. package/test/unit/spec/multistream/remoteMediaGroup.ts +266 -0
  290. package/test/unit/spec/multistream/remoteMediaManager.ts +123 -0
  291. package/test/unit/spec/multistream/sendSlotManager.ts +242 -0
  292. package/test/unit/spec/reachability/index.ts +66 -5
  293. package/test/unit/spec/reachability/request.js +3 -1
  294. package/test/unit/spec/reconnection-manager/index.js +55 -5
  295. package/test/unit/spec/recording-controller/index.js +294 -218
  296. package/test/unit/spec/recording-controller/util.js +223 -96
  297. package/test/unit/spec/roap/index.ts +21 -48
  298. package/test/unit/spec/roap/request.ts +74 -60
  299. package/test/unit/spec/roap/turnDiscovery.ts +30 -6
  300. package/test/unit/spec/rtcMetrics/index.ts +68 -0
  301. package/test/utils/integrationTestUtils.js +46 -0
  302. package/test/utils/testUtils.js +0 -60
  303. package/src/metrics/config.ts +0 -487
@@ -1,8 +1,26 @@
1
1
  import {difference} from 'lodash';
2
2
 
3
- import SimpleQueue from '../common/queue';
3
+ import SortedQueue from '../common/queue';
4
4
  import LoggerProxy from '../common/logs/logger-proxy';
5
5
 
6
+ const MAX_OOO_DELTA_COUNT = 5; // when we receive an out-of-order delta and the queue builds up to MAX_OOO_DELTA_COUNT, we do a sync with Locus
7
+ const OOO_DELTA_WAIT_TIME = 10000; // [ms] minimum wait time before we do a sync if we get out-of-order deltas
8
+ const OOO_DELTA_WAIT_TIME_RANDOM_DELAY = 5000; // [ms] max random delay added to OOO_DELTA_WAIT_TIME
9
+
10
+ type LocusDeltaDto = {
11
+ baseSequence: {
12
+ rangeStart: number;
13
+ rangeEnd: number;
14
+ entries: number[];
15
+ };
16
+ sequence: {
17
+ rangeStart: number;
18
+ rangeEnd: number;
19
+ entries: number[];
20
+ };
21
+ syncUrl: string;
22
+ };
23
+
6
24
  /**
7
25
  * Locus Delta Parser
8
26
  * @private
@@ -10,11 +28,11 @@ import LoggerProxy from '../common/logs/logger-proxy';
10
28
  */
11
29
  export default class Parser {
12
30
  // processing status
13
- static status = {
14
- IDLE: 'IDLE',
15
- PAUSED: 'PAUSED',
16
- WORKING: 'WORKING',
17
- };
31
+ status:
32
+ | 'IDLE' // not doing anything
33
+ | 'PAUSED' // paused, because we are doing a sync
34
+ | 'WORKING' // processing a delta event
35
+ | 'BLOCKED'; // received an out-of-order delta, so waiting for the missing one
18
36
 
19
37
  // loci comparison states
20
38
  static loci = {
@@ -24,21 +42,59 @@ export default class Parser {
24
42
  DESYNC: 'DESYNC',
25
43
  USE_INCOMING: 'USE_INCOMING',
26
44
  USE_CURRENT: 'USE_CURRENT',
45
+ WAIT: 'WAIT',
27
46
  ERROR: 'ERROR',
28
47
  };
29
48
 
30
- queue: any;
49
+ queue: SortedQueue<LocusDeltaDto>;
31
50
  workingCopy: any;
51
+ syncTimer: null | number | NodeJS.Timeout;
32
52
 
33
53
  /**
34
54
  * @constructs Parser
35
55
  */
36
56
  constructor() {
37
- this.queue = new SimpleQueue();
38
- // @ts-ignore - This is declared as static class member and again being initialized here from same
39
- this.status = Parser.status.IDLE;
57
+ const deltaCompareFunc = (left: LocusDeltaDto, right: LocusDeltaDto) => {
58
+ const {LT, GT} = Parser.loci;
59
+ const {extractComparisonState: extract} = Parser;
60
+
61
+ if (Parser.isSequenceEmpty(left)) {
62
+ return -1;
63
+ }
64
+ if (Parser.isSequenceEmpty(right)) {
65
+ return 1;
66
+ }
67
+ const result = extract(Parser.compareSequence(left.baseSequence, right.baseSequence));
68
+
69
+ if (result === LT) {
70
+ return -1;
71
+ }
72
+ if (result === GT) {
73
+ return 1;
74
+ }
75
+
76
+ return 0;
77
+ };
78
+
79
+ this.queue = new SortedQueue<LocusDeltaDto>(deltaCompareFunc);
80
+ this.status = 'IDLE';
40
81
  this.onDeltaAction = null;
41
82
  this.workingCopy = null;
83
+ this.syncTimer = null;
84
+ }
85
+
86
+ /**
87
+ * Returns a debug string representing a locus delta - useful for logging
88
+ *
89
+ * @param {LocusDeltaDto} locus Locus delta
90
+ * @returns {string}
91
+ */
92
+ static locus2string(locus: LocusDeltaDto) {
93
+ if (!locus.sequence?.entries) {
94
+ return 'invalid';
95
+ }
96
+
97
+ return locus.sequence.entries.length ? `seq=${locus.sequence.entries.at(-1)}` : 'empty';
42
98
  }
43
99
 
44
100
  /**
@@ -208,7 +264,7 @@ export default class Parser {
208
264
  * @returns {string} loci comparison state
209
265
  */
210
266
  private static compareDelta(current, incoming) {
211
- const {LT, GT, EQ, DESYNC, USE_INCOMING} = Parser.loci;
267
+ const {LT, GT, EQ, DESYNC, USE_INCOMING, WAIT} = Parser.loci;
212
268
 
213
269
  const {extractComparisonState: extract} = Parser;
214
270
  const {packComparisonResult: pack} = Parser;
@@ -228,6 +284,17 @@ export default class Parser {
228
284
  comparison = USE_INCOMING;
229
285
  break;
230
286
 
287
+ case LT:
288
+ if (extract(Parser.compareSequence(incoming.baseSequence, incoming.sequence)) === EQ) {
289
+ // special case where Locus sends a delta with baseSequence === sequence to trigger a sync,
290
+ // because the delta event is too large to be sent over mercury connection
291
+ comparison = DESYNC;
292
+ } else {
293
+ // the incoming locus has baseSequence from the future, so it is out-of-order,
294
+ // we are missing 1 or more locus that should be in front of it, we need to wait for it
295
+ comparison = WAIT;
296
+ }
297
+ break;
231
298
  default:
232
299
  comparison = DESYNC;
233
300
  }
@@ -235,6 +302,49 @@ export default class Parser {
235
302
  return pack(comparison, result);
236
303
  }
237
304
 
305
+ /**
306
+ * Compares Locus sequences - it should be called only for full Locus DTOs, not deltas
307
+ *
308
+ * @param {Types~Locus} current Current working copy
309
+ * @param {Types~Locus} incomingFullDto New Full Locus DTO
310
+ * @returns {string} either Parser.loci.USE_INCOMING or Parser.loci.USE_CURRENT
311
+ */
312
+ static compareFullDtoSequence(current, incomingFullDto) {
313
+ if (Parser.isSequenceEmpty(current) || Parser.isSequenceEmpty(incomingFullDto)) {
314
+ return Parser.loci.USE_INCOMING;
315
+ }
316
+
317
+ // the sequence.entries list will always contain at least 1 entry
318
+ // https://sqbu-github.cisco.com/WebExSquared/cloud-apps/wiki/Locus-Sequence-Comparison-Algorithm
319
+
320
+ return incomingFullDto.sequence.entries.slice(-1)[0] > current.sequence.entries.slice(-1)[0]
321
+ ? Parser.loci.USE_INCOMING
322
+ : Parser.loci.USE_CURRENT;
323
+ }
324
+
325
+ /**
326
+ * Returns true if the incoming full locus DTO is newer than the current working copy
327
+ *
328
+ * @param {Types~Locus} incomingFullDto New Full Locus DTO
329
+ * @returns {string} either Parser.loci.USE_INCOMING or Parser.loci.USE_CURRENT
330
+ */
331
+ isNewFullLocus(incomingFullDto) {
332
+ if (!Parser.isLoci(incomingFullDto)) {
333
+ LoggerProxy.logger.info('Locus-info:parser#isNewFullLocus --> Ignoring non-locus object.');
334
+
335
+ return false;
336
+ }
337
+
338
+ if (!this.workingCopy) {
339
+ // we don't have a working copy yet, so any full locus is better than nothing
340
+ return true;
341
+ }
342
+
343
+ const comparisonResult = Parser.compareFullDtoSequence(this.workingCopy, incomingFullDto);
344
+
345
+ return comparisonResult === Parser.loci.USE_INCOMING;
346
+ }
347
+
238
348
  /**
239
349
  * Compares Locus sequences
240
350
  * @param {Types~Locus} current Current working copy
@@ -393,17 +503,10 @@ export default class Parser {
393
503
  */
394
504
  isValidLocus(newLoci) {
395
505
  let isValid = false;
396
- const {IDLE} = Parser.status;
397
506
  const {isLoci} = Parser;
398
- // @ts-ignore
399
- const setStatus = (status) => {
400
- // @ts-ignore
401
- this.status = status;
402
- };
403
507
 
404
508
  // one or both objects are not locus delta events
405
509
  if (!isLoci(this.workingCopy) || !isLoci(newLoci)) {
406
- setStatus(IDLE);
407
510
  LoggerProxy.logger.info(
408
511
  'Locus-info:parser#processDeltaEvent --> Ignoring non-locus object. workingCopy:',
409
512
  this.workingCopy,
@@ -455,19 +558,25 @@ export default class Parser {
455
558
  * @returns {undefined}
456
559
  */
457
560
  nextEvent() {
458
- // @ts-ignore
459
- if (this.status === Parser.status.PAUSED) {
561
+ if (this.status === 'PAUSED') {
460
562
  LoggerProxy.logger.info('Locus-info:parser#nextEvent --> Locus parser paused.');
461
563
 
462
564
  return;
463
565
  }
464
566
 
567
+ if (this.status === 'BLOCKED') {
568
+ LoggerProxy.logger.info(
569
+ 'Locus-info:parser#nextEvent --> Locus parser blocked by out-of-order delta.'
570
+ );
571
+
572
+ return;
573
+ }
574
+
465
575
  // continue processing until queue is empty
466
576
  if (this.queue.size() > 0) {
467
577
  this.processDeltaEvent();
468
578
  } else {
469
- // @ts-ignore
470
- this.status = Parser.status.IDLE;
579
+ this.status = 'IDLE';
471
580
  }
472
581
  }
473
582
 
@@ -478,7 +587,7 @@ export default class Parser {
478
587
  * @param {Types~Locus} locus Locus delta
479
588
  * @returns {undefined}
480
589
  */
481
- // eslint-disable-next-line no-unused-vars
590
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
482
591
  onDeltaAction(action: string, locus) {}
483
592
 
484
593
  /**
@@ -489,15 +598,20 @@ export default class Parser {
489
598
  onDeltaEvent(loci) {
490
599
  // enqueue the new loci
491
600
  this.queue.enqueue(loci);
492
- // start processing events in the queue if idle
493
- // and a function handler is defined
494
- // @ts-ignore
495
- if (this.status === Parser.status.IDLE && this.onDeltaAction) {
496
- // Update status, ensure we only process one event at a time.
497
- // @ts-ignore
498
- this.status = Parser.status.WORKING;
499
601
 
500
- this.processDeltaEvent();
602
+ if (this.onDeltaAction) {
603
+ if (this.status === 'BLOCKED') {
604
+ if (this.queue.size() > MAX_OOO_DELTA_COUNT) {
605
+ this.triggerSync('queue too big, blocked on out-of-order delta');
606
+ } else {
607
+ this.processDeltaEvent();
608
+ }
609
+ } else if (this.status === 'IDLE') {
610
+ // Update status, ensure we only process one event at a time.
611
+ this.status = 'WORKING';
612
+
613
+ this.processDeltaEvent();
614
+ }
501
615
  }
502
616
  }
503
617
 
@@ -516,11 +630,55 @@ export default class Parser {
516
630
  * @returns {undefined}
517
631
  */
518
632
  pause() {
519
- // @ts-ignore
520
- this.status = Parser.status.PAUSED;
633
+ this.status = 'PAUSED';
521
634
  LoggerProxy.logger.info('Locus-info:parser#pause --> Locus parser paused.');
522
635
  }
523
636
 
637
+ /**
638
+ * Triggers a sync with Locus
639
+ *
640
+ * @param {string} reason used just for logging
641
+ * @returns {undefined}
642
+ */
643
+ private triggerSync(reason: string) {
644
+ LoggerProxy.logger.info(`Locus-info:parser#triggerSync --> doing sync, reason: ${reason}`);
645
+ this.stopSyncTimer();
646
+ this.pause();
647
+ this.onDeltaAction(Parser.loci.DESYNC, this.workingCopy);
648
+ }
649
+
650
+ /**
651
+ * Starts a timer with a random delay. When that timer expires we will do a sync.
652
+ *
653
+ * The main purpose of this timer is to handle a case when we get some out-of-order deltas,
654
+ * so we start waiting to receive the missing delta. If that delta never arrives, this timer
655
+ * will trigger a sync with Locus.
656
+ *
657
+ * @returns {undefined}
658
+ */
659
+ private startSyncTimer() {
660
+ if (this.syncTimer === null) {
661
+ const timeout = OOO_DELTA_WAIT_TIME + Math.random() * OOO_DELTA_WAIT_TIME_RANDOM_DELAY;
662
+
663
+ this.syncTimer = setTimeout(() => {
664
+ this.syncTimer = null;
665
+ this.triggerSync('timer expired, blocked on out-of-order delta');
666
+ }, timeout);
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Stops the timer for triggering a sync
672
+ *
673
+ * @returns {undefined}
674
+ */
675
+ private stopSyncTimer() {
676
+ if (this.syncTimer !== null) {
677
+ clearTimeout(this.syncTimer);
678
+ this.syncTimer = null;
679
+ }
680
+ }
681
+
524
682
  /**
525
683
  * Processes next locus delta in the queue,
526
684
  * continues until the queue is empty
@@ -528,11 +686,13 @@ export default class Parser {
528
686
  * @returns {undefined}
529
687
  */
530
688
  processDeltaEvent() {
531
- const {DESYNC, USE_INCOMING} = Parser.loci;
689
+ const {DESYNC, USE_INCOMING, WAIT} = Parser.loci;
532
690
  const {extractComparisonState: extract} = Parser;
533
691
  const newLoci = this.queue.dequeue();
534
692
 
535
693
  if (!this.isValidLocus(newLoci)) {
694
+ this.nextEvent();
695
+
536
696
  return;
537
697
  }
538
698
 
@@ -543,6 +703,8 @@ export default class Parser {
543
703
  // for full debugging.
544
704
  LoggerProxy.logger.debug(`Locus-info:parser#processDeltaEvent --> Locus Debug: ${result}`);
545
705
 
706
+ let needToWait = false;
707
+
546
708
  if (lociComparison === DESYNC) {
547
709
  // wait for desync response
548
710
  this.pause();
@@ -551,15 +713,39 @@ export default class Parser {
551
713
  // Note: The working copy of parser gets updated in .onFullLocus()
552
714
  // and here when USE_INCOMING locus.
553
715
  this.workingCopy = newLoci;
716
+ } else if (lociComparison === WAIT) {
717
+ // we've taken newLoci from the front of the queue, so put it back there as we have to wait
718
+ // for the one that should be in front of it, before we can process it
719
+ this.queue.enqueue(newLoci);
720
+ needToWait = true;
721
+ }
722
+
723
+ if (needToWait) {
724
+ this.status = 'BLOCKED';
725
+ this.startSyncTimer();
726
+ } else {
727
+ this.stopSyncTimer();
728
+
729
+ if (this.status === 'BLOCKED') {
730
+ // we are not blocked anymore
731
+ this.status = 'WORKING';
732
+
733
+ LoggerProxy.logger.info(
734
+ `Locus-info:parser#processDeltaEvent --> received delta that we were waiting for ${Parser.locus2string(
735
+ newLoci
736
+ )}, not blocked anymore`
737
+ );
738
+ }
554
739
  }
555
740
 
556
741
  if (this.onDeltaAction) {
557
742
  LoggerProxy.logger.info(
558
- `Locus-info:parser#processDeltaEvent --> Locus Delta Action: ${lociComparison}`
743
+ `Locus-info:parser#processDeltaEvent --> Locus Delta ${Parser.locus2string(
744
+ newLoci
745
+ )}, Action: ${lociComparison}`
559
746
  );
560
747
 
561
- // eslint-disable-next-line no-useless-call
562
- this.onDeltaAction.call(this, lociComparison, newLoci);
748
+ this.onDeltaAction(lociComparison, newLoci);
563
749
  }
564
750
 
565
751
  this.nextEvent();
@@ -571,8 +757,7 @@ export default class Parser {
571
757
  */
572
758
  resume() {
573
759
  LoggerProxy.logger.info('Locus-info:parser#resume --> Locus parser resumed.');
574
- // @ts-ignore
575
- this.status = Parser.status.WORKING;
760
+ this.status = 'WORKING';
576
761
  this.nextEvent();
577
762
  }
578
763
 
@@ -14,7 +14,6 @@ import {
14
14
  AUDIO,
15
15
  VIDEO,
16
16
  MediaContent,
17
- SELF_ROLES,
18
17
  } from '../constants';
19
18
  import ParameterError from '../common/errors/parameter';
20
19
 
@@ -37,6 +36,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
37
36
  remoteMuted: SelfUtils.getRemoteMuted(self),
38
37
  unmuteAllowed: SelfUtils.getUnmuteAllowed(self),
39
38
  localAudioUnmuteRequested: SelfUtils.getLocalAudioUnmuteRequested(self),
39
+ localAudioUnmuteRequestedTimeStamp: SelfUtils.getLocalAudioUnmuteRequestedTimeStamp(self),
40
40
  localAudioUnmuteRequired: SelfUtils.getLocalAudioUnmuteRequired(self),
41
41
  lastModified: SelfUtils.getLastModified(self),
42
42
  modifiedBy: SelfUtils.getModifiedBy(self),
@@ -65,6 +65,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
65
65
  isSharingBlocked: SelfUtils.isSharingBlocked(self),
66
66
  breakoutSessions: SelfUtils.getBreakoutSessions(self),
67
67
  breakout: SelfUtils.getBreakout(self),
68
+ interpretation: SelfUtils.getInterpretation(self),
68
69
  };
69
70
  }
70
71
 
@@ -73,6 +74,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
73
74
 
74
75
  SelfUtils.getBreakoutSessions = (self) => self?.controls?.breakout?.sessions;
75
76
  SelfUtils.getBreakout = (self) => self?.controls?.breakout;
77
+ SelfUtils.getInterpretation = (self) => self?.controls?.interpretation;
76
78
 
77
79
  SelfUtils.getLayout = (self) =>
78
80
  Array.isArray(self?.controls?.layouts) ? self.controls.layouts[0].type : undefined;
@@ -108,7 +110,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
108
110
  current
109
111
  );
110
112
  updates.moderatorChanged = SelfUtils.moderatorChanged(previous, current);
111
- updates.isUpgradeToModeratorOrCohost = SelfUtils.isUpgradeToModeratorOrCohost(previous, current);
113
+ updates.isRolesChanged = SelfUtils.isRolesChanged(previous, current);
112
114
  updates.isMediaInactiveOrReleased = SelfUtils.wasMediaInactiveOrReleased(previous, current);
113
115
  updates.isUserObserving = SelfUtils.isDeviceObserving(previous, current);
114
116
  updates.layoutChanged = SelfUtils.layoutChanged(previous, current);
@@ -125,6 +127,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
125
127
  previous?.canNotViewTheParticipantList !== current.canNotViewTheParticipantList;
126
128
  updates.isSharingBlockedChanged = previous?.isSharingBlocked !== current.isSharingBlocked;
127
129
  updates.breakoutsChanged = SelfUtils.breakoutsChanged(previous, current);
130
+ updates.interpretationChanged = SelfUtils.interpretationChanged(previous, current);
128
131
 
129
132
  return {
130
133
  previous,
@@ -153,6 +156,9 @@ SelfUtils.layoutChanged = (previous: any, current: any) =>
153
156
  SelfUtils.breakoutsChanged = (previous, current) =>
154
157
  !isEqual(previous?.breakoutSessions, current?.breakoutSessions) && !!current?.breakout;
155
158
 
159
+ SelfUtils.interpretationChanged = (previous, current) =>
160
+ !isEqual(previous?.interpretation, current?.interpretation) && !!current?.interpretation;
161
+
156
162
  SelfUtils.isMediaInactive = (previous, current) => {
157
163
  if (
158
164
  previous &&
@@ -270,6 +276,10 @@ SelfUtils.getRemoteMuted = (self: any) => {
270
276
 
271
277
  SelfUtils.getLocalAudioUnmuteRequested = (self) => !!self?.controls?.audio?.requestedToUnmute;
272
278
 
279
+ // requestedToUnmute timestamp
280
+ SelfUtils.getLocalAudioUnmuteRequestedTimeStamp = (self) =>
281
+ Date.parse(self?.controls?.audio?.lastModifiedRequestedToUnmute) || 0;
282
+
273
283
  SelfUtils.getUnmuteAllowed = (self) => {
274
284
  if (!self || !self.controls || !self.controls.audio) {
275
285
  return null;
@@ -340,30 +350,19 @@ SelfUtils.moderatorChanged = (oldSelf, changedSelf) => {
340
350
  };
341
351
 
342
352
  /**
353
+ * determine whether the roles of self is changed or not
343
354
  * @param {Object} oldSelf
344
355
  * @param {Object} changedSelf
345
356
  * @returns {Boolean}
346
- * @throws {Error} if changed self was undefined
347
357
  */
348
- SelfUtils.isUpgradeToModeratorOrCohost = (oldSelf, changedSelf) => {
349
- if (!oldSelf) {
350
- return false;
351
- }
358
+ SelfUtils.isRolesChanged = (oldSelf, changedSelf) => {
352
359
  if (!changedSelf) {
353
- throw new ParameterError(
354
- 'New self must be defined to determine if self transitioned moderator or cohost status.'
355
- );
360
+ // no new self means no change
361
+ return false;
356
362
  }
357
- const isAttendeeOnly =
358
- oldSelf.roles.includes(SELF_ROLES.ATTENDEE) &&
359
- !oldSelf.roles.includes(SELF_ROLES.COHOST) &&
360
- !oldSelf.roles.includes(SELF_ROLES.MODERATOR);
361
- const isCohost = changedSelf.roles.includes(SELF_ROLES.COHOST);
362
- const isModerator = changedSelf.roles.includes(SELF_ROLES.MODERATOR);
363
-
364
- return isAttendeeOnly && (isCohost || isModerator);
365
- };
366
363
 
364
+ return !isEqual(oldSelf?.roles, changedSelf?.roles);
365
+ };
367
366
  /**
368
367
  * @param {Object} oldSelf
369
368
  * @param {Object} changedSelf
@@ -443,7 +442,10 @@ SelfUtils.localAudioUnmuteRequestedByServer = (oldSelf: any = {}, changedSelf: a
443
442
  );
444
443
  }
445
444
 
446
- return changedSelf.localAudioUnmuteRequested && !oldSelf.localAudioUnmuteRequested;
445
+ return (
446
+ changedSelf.localAudioUnmuteRequested &&
447
+ changedSelf.localAudioUnmuteRequestedTimeStamp > oldSelf.localAudioUnmuteRequestedTimeStamp
448
+ );
447
449
  };
448
450
 
449
451
  SelfUtils.localAudioUnmuteRequiredByServer = (oldSelf: any = {}, changedSelf: any) => {
@@ -496,4 +498,14 @@ SelfUtils.getMediaStatus = (mediaSessions = []) => {
496
498
  return mediaStatus;
497
499
  };
498
500
 
501
+ SelfUtils.getReplacedBreakoutMoveId = (self: any, deviceId: string) => {
502
+ if (self && Array.isArray(self.devices)) {
503
+ const joinedDevice = self.devices.find((device) => deviceId === device.url);
504
+ if (Array.isArray(joinedDevice?.replaces)) {
505
+ return joinedDevice.replaces[0]?.breakoutMoveId;
506
+ }
507
+ }
508
+
509
+ return null;
510
+ };
499
511
  export default SelfUtils;