quickblox 2.24.0-beta.1 → 2.24.1-alpha.0
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/package.json +1 -1
- package/quickblox.d.ts +34 -1
- package/quickblox.js +206 -133
- package/quickblox.min.js +1 -1
- package/src/modules/chat/qbChat.js +107 -56
- package/src/modules/chat/qbNoticeConsts.js +2 -1
- package/src/modules/webrtc/qbRTCPeerConnection.js +95 -74
- package/src/plugins/streamManagement.js +235 -0
- package/src/qbConfig.js +2 -2
|
@@ -44,6 +44,7 @@ NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.UPDATED_MESSAGE] =
|
|
|
44
44
|
NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.DELETED_MESSAGE] = true;
|
|
45
45
|
NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.UPDATED_DIALOG] = true;
|
|
46
46
|
NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.DELETED_DIALOG] = true;
|
|
47
|
+
NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.CREATED_DIALOG] = true;
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Cross-env: get all direct child elements of `parent` whose tag name equals
|
|
@@ -327,6 +328,91 @@ function _parseCustomData(extraParams) {
|
|
|
327
328
|
return out;
|
|
328
329
|
}
|
|
329
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Build a partial QBChatDialog object from a dialog-notice <extraParams>.
|
|
333
|
+
* Shared by NoticeUpdatedDialog and NoticeCreatedDialog (SR-2952) — the server
|
|
334
|
+
* sends a full dialog snapshot in both cases. Fields with null/empty values are
|
|
335
|
+
* omitted by the server, so every read is guarded.
|
|
336
|
+
*
|
|
337
|
+
* CreatedDialog additionally carries created_at/updated_at/user_id (creator) and
|
|
338
|
+
* has no last_message* fields (dialog just created); UpdatedDialog carries
|
|
339
|
+
* last_message* and (per server contract) may also carry created_at/updated_at/
|
|
340
|
+
* user_id. Reading them here for both is safe — absent fields are simply skipped.
|
|
341
|
+
*
|
|
342
|
+
* @param {Element} extraParams
|
|
343
|
+
* @param {String} dialogId - already-resolved dialog_id text.
|
|
344
|
+
* @return {Object} partial dialog snapshot.
|
|
345
|
+
*/
|
|
346
|
+
function _parseDialogSnapshot(extraParams, dialogId) {
|
|
347
|
+
var dlg = {};
|
|
348
|
+
dlg._id = dialogId;
|
|
349
|
+
|
|
350
|
+
var name = chatUtils.getElementText(extraParams, 'name');
|
|
351
|
+
if (name !== undefined && name !== null && name !== '') {
|
|
352
|
+
dlg.name = name;
|
|
353
|
+
}
|
|
354
|
+
var photo = chatUtils.getElementText(extraParams, 'photo');
|
|
355
|
+
if (photo !== undefined && photo !== null && photo !== '') {
|
|
356
|
+
dlg.photo = photo;
|
|
357
|
+
}
|
|
358
|
+
var typeText = chatUtils.getElementText(extraParams, 'type');
|
|
359
|
+
var typeNum = parseInt(typeText, 10);
|
|
360
|
+
if (!isNaN(typeNum)) {
|
|
361
|
+
dlg.type = typeNum;
|
|
362
|
+
}
|
|
363
|
+
var occupants = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'occupants_ids'));
|
|
364
|
+
if (occupants.length > 0) {
|
|
365
|
+
dlg.occupants_ids = occupants;
|
|
366
|
+
}
|
|
367
|
+
var admins = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'admin_ids'));
|
|
368
|
+
if (admins.length > 0) {
|
|
369
|
+
dlg.admin_ids = admins;
|
|
370
|
+
}
|
|
371
|
+
var isJoinReqText = chatUtils.getElementText(extraParams, 'is_join_required');
|
|
372
|
+
if (isJoinReqText !== undefined && isJoinReqText !== null && isJoinReqText !== '') {
|
|
373
|
+
var isJoinReq = parseInt(isJoinReqText, 10);
|
|
374
|
+
dlg.is_join_required = isNaN(isJoinReq) ? isJoinReqText : isJoinReq;
|
|
375
|
+
}
|
|
376
|
+
var roomJid = chatUtils.getElementText(extraParams, 'xmpp_room_jid');
|
|
377
|
+
if (roomJid !== undefined && roomJid !== null && roomJid !== '') {
|
|
378
|
+
// Server may pretty-print xmpp_room_jid with surrounding whitespace.
|
|
379
|
+
dlg.xmpp_room_jid = roomJid.trim();
|
|
380
|
+
}
|
|
381
|
+
var customData = _parseCustomData(extraParams);
|
|
382
|
+
if (customData !== undefined) {
|
|
383
|
+
dlg.custom_data = customData;
|
|
384
|
+
}
|
|
385
|
+
// Creation / modification metadata (present on CreatedDialog; also valid on
|
|
386
|
+
// UpdatedDialog per server contract). Kept as ISO-8601 strings verbatim.
|
|
387
|
+
var createdAt = chatUtils.getElementText(extraParams, 'created_at');
|
|
388
|
+
if (createdAt !== undefined && createdAt !== null && createdAt !== '') {
|
|
389
|
+
dlg.created_at = createdAt;
|
|
390
|
+
}
|
|
391
|
+
var updatedAt = chatUtils.getElementText(extraParams, 'updated_at');
|
|
392
|
+
if (updatedAt !== undefined && updatedAt !== null && updatedAt !== '') {
|
|
393
|
+
dlg.updated_at = updatedAt;
|
|
394
|
+
}
|
|
395
|
+
var userIdText = chatUtils.getElementText(extraParams, 'user_id');
|
|
396
|
+
var userId = parseInt(userIdText, 10);
|
|
397
|
+
if (!isNaN(userId)) {
|
|
398
|
+
dlg.user_id = userId;
|
|
399
|
+
}
|
|
400
|
+
// Last message fields (present on UpdatedDialog; absent on a freshly
|
|
401
|
+
// CreatedDialog — the guards below simply skip them then).
|
|
402
|
+
var lmText = chatUtils.getElementText(extraParams, 'last_message');
|
|
403
|
+
if (lmText) { dlg.last_message = lmText; }
|
|
404
|
+
var lmId = chatUtils.getElementText(extraParams, 'last_message_id');
|
|
405
|
+
if (lmId) { dlg.last_message_id = lmId; }
|
|
406
|
+
var lmDsText = chatUtils.getElementText(extraParams, 'last_message_date_sent');
|
|
407
|
+
var lmDs = parseInt(lmDsText, 10);
|
|
408
|
+
if (!isNaN(lmDs)) { dlg.last_message_date_sent = lmDs; }
|
|
409
|
+
var lmUidText = chatUtils.getElementText(extraParams, 'last_message_user_id');
|
|
410
|
+
var lmUid = parseInt(lmUidText, 10);
|
|
411
|
+
if (!isNaN(lmUid)) { dlg.last_message_user_id = lmUid; }
|
|
412
|
+
|
|
413
|
+
return dlg;
|
|
414
|
+
}
|
|
415
|
+
|
|
330
416
|
/**
|
|
331
417
|
* Parse a Notice headline stanza into {type, payload} ready for routing.
|
|
332
418
|
* Returns null if stanza is not a Notice (no urn:xmpp:notice:0 namespace, or
|
|
@@ -422,59 +508,12 @@ function _parseNoticeStanza(stanza) {
|
|
|
422
508
|
message: msg
|
|
423
509
|
};
|
|
424
510
|
}
|
|
425
|
-
} else if (type === IDS.UPDATED_DIALOG) {
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
dlg._id = dialogId;
|
|
429
|
-
var name = chatUtils.getElementText(extraParams, 'name');
|
|
430
|
-
if (name !== undefined && name !== null && name !== '') {
|
|
431
|
-
dlg.name = name;
|
|
432
|
-
}
|
|
433
|
-
var photo = chatUtils.getElementText(extraParams, 'photo');
|
|
434
|
-
if (photo !== undefined && photo !== null && photo !== '') {
|
|
435
|
-
dlg.photo = photo;
|
|
436
|
-
}
|
|
437
|
-
var typeText = chatUtils.getElementText(extraParams, 'type');
|
|
438
|
-
var typeNum = parseInt(typeText, 10);
|
|
439
|
-
if (!isNaN(typeNum)) {
|
|
440
|
-
dlg.type = typeNum;
|
|
441
|
-
}
|
|
442
|
-
var occupants = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'occupants_ids'));
|
|
443
|
-
if (occupants.length > 0) {
|
|
444
|
-
dlg.occupants_ids = occupants;
|
|
445
|
-
}
|
|
446
|
-
var admins = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'admin_ids'));
|
|
447
|
-
if (admins.length > 0) {
|
|
448
|
-
dlg.admin_ids = admins;
|
|
449
|
-
}
|
|
450
|
-
var isJoinReqText = chatUtils.getElementText(extraParams, 'is_join_required');
|
|
451
|
-
if (isJoinReqText !== undefined && isJoinReqText !== null && isJoinReqText !== '') {
|
|
452
|
-
var isJoinReq = parseInt(isJoinReqText, 10);
|
|
453
|
-
dlg.is_join_required = isNaN(isJoinReq) ? isJoinReqText : isJoinReq;
|
|
454
|
-
}
|
|
455
|
-
var roomJid = chatUtils.getElementText(extraParams, 'xmpp_room_jid');
|
|
456
|
-
if (roomJid !== undefined && roomJid !== null && roomJid !== '') {
|
|
457
|
-
dlg.xmpp_room_jid = roomJid;
|
|
458
|
-
}
|
|
459
|
-
var customData = _parseCustomData(extraParams);
|
|
460
|
-
if (customData !== undefined) {
|
|
461
|
-
dlg.custom_data = customData;
|
|
462
|
-
}
|
|
463
|
-
// Last message fields.
|
|
464
|
-
var lmText = chatUtils.getElementText(extraParams, 'last_message');
|
|
465
|
-
if (lmText) { dlg.last_message = lmText; }
|
|
466
|
-
var lmId = chatUtils.getElementText(extraParams, 'last_message_id');
|
|
467
|
-
if (lmId) { dlg.last_message_id = lmId; }
|
|
468
|
-
var lmDsText = chatUtils.getElementText(extraParams, 'last_message_date_sent');
|
|
469
|
-
var lmDs = parseInt(lmDsText, 10);
|
|
470
|
-
if (!isNaN(lmDs)) { dlg.last_message_date_sent = lmDs; }
|
|
471
|
-
var lmUidText = chatUtils.getElementText(extraParams, 'last_message_user_id');
|
|
472
|
-
var lmUid = parseInt(lmUidText, 10);
|
|
473
|
-
if (!isNaN(lmUid)) { dlg.last_message_user_id = lmUid; }
|
|
474
|
-
|
|
511
|
+
} else if (type === IDS.UPDATED_DIALOG || type === IDS.CREATED_DIALOG) {
|
|
512
|
+
// Full server dialog snapshot. UpdatedDialog and CreatedDialog (SR-2952)
|
|
513
|
+
// share the same shape; they differ only by moduleIdentifier (routing).
|
|
475
514
|
payload = {
|
|
476
515
|
kind: 'dialog',
|
|
477
|
-
dialog:
|
|
516
|
+
dialog: _parseDialogSnapshot(extraParams, dialogId)
|
|
478
517
|
};
|
|
479
518
|
} else {
|
|
480
519
|
return null;
|
|
@@ -521,6 +560,10 @@ function _routeNoticeEvent(chatProxy, parsed) {
|
|
|
521
560
|
if (typeof chatProxy.onDialogUpdatedListener === 'function') {
|
|
522
561
|
Utils.safeCallbackCall(chatProxy.onDialogUpdatedListener, p.dialog);
|
|
523
562
|
}
|
|
563
|
+
} else if (type === IDS.CREATED_DIALOG) {
|
|
564
|
+
if (typeof chatProxy.onDialogCreatedListener === 'function') {
|
|
565
|
+
Utils.safeCallbackCall(chatProxy.onDialogCreatedListener, p.dialog);
|
|
566
|
+
}
|
|
524
567
|
}
|
|
525
568
|
}
|
|
526
569
|
|
|
@@ -621,6 +664,15 @@ function ChatProxy(service) {
|
|
|
621
664
|
this.onMessageReactionChangedListener = null;
|
|
622
665
|
this.onDialogDeletedListener = null;
|
|
623
666
|
this.onDialogUpdatedListener = null;
|
|
667
|
+
/**
|
|
668
|
+
* NoticeCreatedDialog listener (SR-2952). Fired when a Group or Private
|
|
669
|
+
* dialog is created and the current user has the Notice feature enabled.
|
|
670
|
+
* Signature: onDialogCreatedListener(dialog). The dialog is a partial
|
|
671
|
+
* snapshot from the creation notice — it carries created_at/updated_at/
|
|
672
|
+
* user_id (creator) but no last_message* fields (the dialog is brand new),
|
|
673
|
+
* and for a private dialog no xmpp_room_jid. Not fired for Public dialogs.
|
|
674
|
+
*/
|
|
675
|
+
this.onDialogCreatedListener = null;
|
|
624
676
|
|
|
625
677
|
// [QC-1550] XMPP connection is considered "verified" only after the first
|
|
626
678
|
// successful pong response. Strophe emits Status.CONNECTED at the transport
|
|
@@ -2465,7 +2517,8 @@ ChatProxy.prototype = {
|
|
|
2465
2517
|
* Subscribe the current XMPP session to Notice Feature stanzas
|
|
2466
2518
|
* (urn:xmpp:notice:0). After a successful IQ result the server starts
|
|
2467
2519
|
* delivering NoticeUpdatedMessage / NoticeDeletedMessage /
|
|
2468
|
-
* NoticeUpdatedDialog / NoticeDeletedDialog
|
|
2520
|
+
* NoticeCreatedDialog / NoticeUpdatedDialog / NoticeDeletedDialog
|
|
2521
|
+
* headline stanzas.
|
|
2469
2522
|
*
|
|
2470
2523
|
* The local enabled flag is reset to false on chat disconnect — consumers
|
|
2471
2524
|
* must call enableNotices() again after a reconnect (intentional divergence
|
|
@@ -2473,10 +2526,8 @@ ChatProxy.prototype = {
|
|
|
2473
2526
|
*
|
|
2474
2527
|
* TODO(2.25.0): make auto-enable SDK-internal — fire on initial connect and
|
|
2475
2528
|
* on relogin after session-expired, skip on Stream-Management reconnect.
|
|
2476
|
-
* Spec & three-state matrix
|
|
2477
|
-
*
|
|
2478
|
-
* backlog-2.25.0-auto-enable-notices.md
|
|
2479
|
-
* jira-stories/_jira-src-06-auto-enable-notices.md
|
|
2529
|
+
* Spec & three-state matrix are tracked in the internal 2.25.0 backlog
|
|
2530
|
+
* (auto-enable notices).
|
|
2480
2531
|
*
|
|
2481
2532
|
* Do not call enableNotices again before the previous callback fires —
|
|
2482
2533
|
* concurrent enable calls are not specified.
|
|
@@ -23,7 +23,8 @@ var NOTICE_MODULE_IDENTIFIER = {
|
|
|
23
23
|
UPDATED_MESSAGE: 'NoticeUpdatedMessage',
|
|
24
24
|
DELETED_MESSAGE: 'NoticeDeletedMessage',
|
|
25
25
|
UPDATED_DIALOG: 'NoticeUpdatedDialog',
|
|
26
|
-
DELETED_DIALOG: 'NoticeDeletedDialog'
|
|
26
|
+
DELETED_DIALOG: 'NoticeDeletedDialog',
|
|
27
|
+
CREATED_DIALOG: 'NoticeCreatedDialog'
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
module.exports = {
|
|
@@ -542,92 +542,110 @@ function _getStats(peer, lastResults, successCallback, errorCallback) {
|
|
|
542
542
|
|
|
543
543
|
peer.getStats(null).then(function (results) {
|
|
544
544
|
results.forEach(function (result) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (result.bytesReceived && result.type === 'inbound-rtp') {
|
|
548
|
-
item = statistic.remote[result.mediaType];
|
|
549
|
-
item.bitrate = _getBitratePerSecond(result, lastResults, false);
|
|
550
|
-
item.bytesReceived = result.bytesReceived;
|
|
551
|
-
item.packetsReceived = result.packetsReceived;
|
|
552
|
-
item.timestamp = result.timestamp;
|
|
553
|
-
if (result.mediaType === 'video' && result.framerateMean) {
|
|
554
|
-
item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
|
|
555
|
-
}
|
|
556
|
-
} else if (result.bytesSent && result.type === 'outbound-rtp') {
|
|
557
|
-
item = statistic.local[result.mediaType];
|
|
558
|
-
item.bitrate = _getBitratePerSecond(result, lastResults, true);
|
|
559
|
-
item.bytesSent = result.bytesSent;
|
|
560
|
-
item.packetsSent = result.packetsSent;
|
|
561
|
-
item.timestamp = result.timestamp;
|
|
562
|
-
if (result.mediaType === 'video' && result.framerateMean) {
|
|
563
|
-
item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
|
|
564
|
-
}
|
|
565
|
-
} else if (result.type === 'local-candidate') {
|
|
566
|
-
item = statistic.local.candidate;
|
|
567
|
-
if (result.candidateType === 'host' && result.mozLocalTransport === 'udp' && result.transport === 'udp') {
|
|
568
|
-
item.protocol = result.transport;
|
|
569
|
-
item.ip = result.ipAddress;
|
|
570
|
-
item.port = result.portNumber;
|
|
571
|
-
} else if (!Helpers.getVersionFirefox()) {
|
|
572
|
-
item.protocol = result.protocol;
|
|
573
|
-
item.ip = result.ip;
|
|
574
|
-
item.port = result.port;
|
|
575
|
-
}
|
|
576
|
-
} else if (result.type === 'remote-candidate') {
|
|
577
|
-
item = statistic.remote.candidate;
|
|
578
|
-
item.protocol = result.protocol || result.transport;
|
|
579
|
-
item.ip = result.ip || result.ipAddress;
|
|
580
|
-
item.port = result.port || result.portNumber;
|
|
581
|
-
} else if (result.type === 'track' && result.kind === 'video' && !Helpers.getVersionFirefox()) {
|
|
582
|
-
if (result.remoteSource) {
|
|
583
|
-
item = statistic.remote.video;
|
|
584
|
-
item.frameHeight = result.frameHeight;
|
|
585
|
-
item.frameWidth = result.frameWidth;
|
|
586
|
-
item.framesPerSecond = _getFramesPerSecond(result, lastResults, false);
|
|
587
|
-
} else {
|
|
588
|
-
item = statistic.local.video;
|
|
589
|
-
item.frameHeight = result.frameHeight;
|
|
590
|
-
item.frameWidth = result.frameWidth;
|
|
591
|
-
item.framesPerSecond = _getFramesPerSecond(result, lastResults, true);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
545
|
+
_applyStatReport(statistic, result, lastResults);
|
|
594
546
|
});
|
|
595
547
|
successCallback(statistic, results);
|
|
596
548
|
}, errorCallback);
|
|
549
|
+
}
|
|
597
550
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
551
|
+
function _applyStatReport(statistic, result, lastResults) {
|
|
552
|
+
var item;
|
|
553
|
+
// mediaType is deprecated in the W3C webrtc-stats spec and replaced by kind.
|
|
554
|
+
// Safari/WebKit omits mediaType on some reports and provides only kind, so we
|
|
555
|
+
// normalize once and use it for both the statistic lookup and the 'video' check.
|
|
556
|
+
var mediaType = result.mediaType || result.kind;
|
|
557
|
+
|
|
558
|
+
if (result.bytesReceived && result.type === 'inbound-rtp') {
|
|
559
|
+
item = statistic.remote[mediaType];
|
|
560
|
+
|
|
561
|
+
if (item) {
|
|
562
|
+
item.bitrate = _getBitratePerSecond(result, lastResults, false);
|
|
563
|
+
item.bytesReceived = result.bytesReceived;
|
|
564
|
+
item.packetsReceived = result.packetsReceived;
|
|
565
|
+
item.timestamp = result.timestamp;
|
|
566
|
+
if (mediaType === 'video' && result.framerateMean) {
|
|
567
|
+
item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
Helpers.traceWarning('_getStats: skipping inbound-rtp report with unknown mediaType/kind: ' + mediaType);
|
|
571
|
+
}
|
|
572
|
+
} else if (result.bytesSent && result.type === 'outbound-rtp') {
|
|
573
|
+
item = statistic.local[mediaType];
|
|
574
|
+
|
|
575
|
+
if (item) {
|
|
576
|
+
item.bitrate = _getBitratePerSecond(result, lastResults, true);
|
|
577
|
+
item.bytesSent = result.bytesSent;
|
|
578
|
+
item.packetsSent = result.packetsSent;
|
|
579
|
+
item.timestamp = result.timestamp;
|
|
580
|
+
if (mediaType === 'video' && result.framerateMean) {
|
|
581
|
+
item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
Helpers.traceWarning('_getStats: skipping outbound-rtp report with unknown mediaType/kind: ' + mediaType);
|
|
585
|
+
}
|
|
586
|
+
} else if (result.type === 'local-candidate') {
|
|
587
|
+
item = statistic.local.candidate;
|
|
588
|
+
if (result.candidateType === 'host' && result.mozLocalTransport === 'udp' && result.transport === 'udp') {
|
|
589
|
+
item.protocol = result.transport;
|
|
590
|
+
item.ip = result.ipAddress;
|
|
591
|
+
item.port = result.portNumber;
|
|
592
|
+
} else if (!Helpers.getVersionFirefox()) {
|
|
593
|
+
item.protocol = result.protocol;
|
|
594
|
+
item.ip = result.ip;
|
|
595
|
+
item.port = result.port;
|
|
596
|
+
}
|
|
597
|
+
} else if (result.type === 'remote-candidate') {
|
|
598
|
+
item = statistic.remote.candidate;
|
|
599
|
+
item.protocol = result.protocol || result.transport;
|
|
600
|
+
item.ip = result.ip || result.ipAddress;
|
|
601
|
+
item.port = result.port || result.portNumber;
|
|
602
|
+
} else if (result.type === 'track' && result.kind === 'video' && !Helpers.getVersionFirefox()) {
|
|
603
|
+
if (result.remoteSource) {
|
|
604
|
+
item = statistic.remote.video;
|
|
605
|
+
item.frameHeight = result.frameHeight;
|
|
606
|
+
item.frameWidth = result.frameWidth;
|
|
607
|
+
item.framesPerSecond = _getFramesPerSecond(result, lastResults, false);
|
|
609
608
|
} else {
|
|
610
|
-
|
|
609
|
+
item = statistic.local.video;
|
|
610
|
+
item.frameHeight = result.frameHeight;
|
|
611
|
+
item.frameWidth = result.frameWidth;
|
|
612
|
+
item.framesPerSecond = _getFramesPerSecond(result, lastResults, true);
|
|
611
613
|
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
612
616
|
|
|
613
|
-
|
|
617
|
+
function _getBitratePerSecond(result, lastResults, isLocal) {
|
|
618
|
+
var lastResult = lastResults && lastResults.get(result.id),
|
|
619
|
+
seconds = lastResult ? ((result.timestamp - lastResult.timestamp) / 1000) : 5,
|
|
620
|
+
kilo = 1024,
|
|
621
|
+
bit = 8,
|
|
622
|
+
bitrate;
|
|
623
|
+
|
|
624
|
+
if (!lastResult) {
|
|
625
|
+
bitrate = 0;
|
|
626
|
+
} else if (isLocal) {
|
|
627
|
+
bitrate = bit * (result.bytesSent - lastResult.bytesSent) / (kilo * seconds);
|
|
628
|
+
} else {
|
|
629
|
+
bitrate = bit * (result.bytesReceived - lastResult.bytesReceived) / (kilo * seconds);
|
|
614
630
|
}
|
|
615
631
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
seconds = lastResult ? ((result.timestamp - lastResult.timestamp) / 1000) : 5,
|
|
619
|
-
framesPerSecond;
|
|
632
|
+
return Math.round(bitrate);
|
|
633
|
+
}
|
|
620
634
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
} else {
|
|
626
|
-
framesPerSecond = (result.framesReceived - lastResult.framesReceived) / seconds;
|
|
627
|
-
}
|
|
635
|
+
function _getFramesPerSecond(result, lastResults, isLocal) {
|
|
636
|
+
var lastResult = lastResults && lastResults.get(result.id),
|
|
637
|
+
seconds = lastResult ? ((result.timestamp - lastResult.timestamp) / 1000) : 5,
|
|
638
|
+
framesPerSecond;
|
|
628
639
|
|
|
629
|
-
|
|
640
|
+
if (!lastResult) {
|
|
641
|
+
framesPerSecond = 0;
|
|
642
|
+
} else if (isLocal) {
|
|
643
|
+
framesPerSecond = (result.framesSent - lastResult.framesSent) / seconds;
|
|
644
|
+
} else {
|
|
645
|
+
framesPerSecond = (result.framesReceived - lastResult.framesReceived) / seconds;
|
|
630
646
|
}
|
|
647
|
+
|
|
648
|
+
return Math.round(framesPerSecond * 10) / 10;
|
|
631
649
|
}
|
|
632
650
|
|
|
633
651
|
// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
|
|
@@ -814,4 +832,7 @@ function setMediaBitrate(sdp, media, bitrate) {
|
|
|
814
832
|
return newLines.join('\n');
|
|
815
833
|
}
|
|
816
834
|
|
|
835
|
+
// PRIVATE - exposed for unit tests only, not part of the public SDK contract.
|
|
836
|
+
qbRTCPeerConnection._applyStatReport = _applyStatReport;
|
|
837
|
+
|
|
817
838
|
module.exports = qbRTCPeerConnection;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* QuickBlox JavaScript SDK
|
|
5
|
+
* Chat Stream Management plugin
|
|
6
|
+
* doc: http://xmpp.org/extensions/xep-0198.html
|
|
7
|
+
*
|
|
8
|
+
* To enable this features add to config
|
|
9
|
+
* ```javascript
|
|
10
|
+
* streamManagement: {
|
|
11
|
+
- enable: true
|
|
12
|
+
- }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Uses listener by QB.chat.onSentMessageCallback
|
|
16
|
+
*
|
|
17
|
+
* ```javascript
|
|
18
|
+
* QB.chat.onSentMessageCallback = function (messageLost, messageSent) {
|
|
19
|
+
* if (messageLost) {
|
|
20
|
+
* console.error('sendErrorCallback', messageLost);
|
|
21
|
+
* } else {
|
|
22
|
+
* console.info('sendMessageSuccessCallback', messageSent);
|
|
23
|
+
* }
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** JSHint inline rules */
|
|
29
|
+
/* globals $build */
|
|
30
|
+
|
|
31
|
+
var Utils = require('../qbUtils'),
|
|
32
|
+
chatUtils = require('../modules/chat/qbChatHelpers');
|
|
33
|
+
|
|
34
|
+
function StreamManagement(options) {
|
|
35
|
+
|
|
36
|
+
this._NS = 'urn:xmpp:sm:3';
|
|
37
|
+
|
|
38
|
+
this._isStreamManagementEnabled = false;
|
|
39
|
+
|
|
40
|
+
// Counter of the incoming stanzas
|
|
41
|
+
this._clientProcessedStanzasCounter = null;
|
|
42
|
+
|
|
43
|
+
// The client send stanza counter.
|
|
44
|
+
this._clientSentStanzasCounter = null;
|
|
45
|
+
|
|
46
|
+
this._intervalId = null;
|
|
47
|
+
|
|
48
|
+
this._timeInterval = Utils.getTimeIntervalForCallBackMessage();
|
|
49
|
+
|
|
50
|
+
this.sentMessageCallback = null;
|
|
51
|
+
|
|
52
|
+
if(Utils.getEnv().browser){
|
|
53
|
+
this._parser = new DOMParser();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// connection
|
|
57
|
+
this._c = null;
|
|
58
|
+
|
|
59
|
+
this._nodeBuilder = null;
|
|
60
|
+
// Original connection.send method
|
|
61
|
+
this._originalSend = null;
|
|
62
|
+
|
|
63
|
+
// In progress stanzas queue
|
|
64
|
+
this._stanzasQueue = [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
StreamManagement.prototype.enable = function (connection, client) {
|
|
69
|
+
var self = this,
|
|
70
|
+
stanza,
|
|
71
|
+
enableParams = {
|
|
72
|
+
xmlns: self._NS
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if(!self._isStreamManagementEnabled){
|
|
76
|
+
self._c = connection;
|
|
77
|
+
self._originalSend = this._c.send;
|
|
78
|
+
self._c.send = this.send.bind(self);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if(Utils.getEnv().browser){
|
|
82
|
+
this._clientProcessedStanzasCounter = null;
|
|
83
|
+
this._clientSentStanzasCounter = null;
|
|
84
|
+
self._addEnableHandlers();
|
|
85
|
+
stanza = $build('enable', enableParams);
|
|
86
|
+
} else {
|
|
87
|
+
self._nodeBuilder = client.Stanza;
|
|
88
|
+
self._addEnableHandlers();
|
|
89
|
+
stanza = chatUtils.createStanza(self._nodeBuilder, enableParams, 'enable');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
self._c.send(stanza);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
StreamManagement.prototype._timeoutCallback = function () {
|
|
96
|
+
var self = this,
|
|
97
|
+
now = Date.now(),
|
|
98
|
+
updatedStanzasQueue = [];
|
|
99
|
+
|
|
100
|
+
if(self._stanzasQueue.length){
|
|
101
|
+
for(var i = 0; i < self._stanzasQueue.length; i++){
|
|
102
|
+
if(self._stanzasQueue[i] && self._stanzasQueue[i].time < now){
|
|
103
|
+
self.sentMessageCallback(self._stanzasQueue[i].message);
|
|
104
|
+
} else {
|
|
105
|
+
updatedStanzasQueue.push(self._stanzasQueue[i]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
self._stanzasQueue = updatedStanzasQueue;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
StreamManagement.prototype._addEnableHandlers = function () {
|
|
114
|
+
var self = this;
|
|
115
|
+
|
|
116
|
+
if (Utils.getEnv().browser) {
|
|
117
|
+
self._c.XAddTrackedHandler(_incomingStanzaHandler.bind(self));
|
|
118
|
+
} else {
|
|
119
|
+
self._c.on('stanza', _incomingStanzaHandler.bind(self));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function _incomingStanzaHandler (stanza){
|
|
123
|
+
/*
|
|
124
|
+
* Getting incoming stanza tagName
|
|
125
|
+
* */
|
|
126
|
+
|
|
127
|
+
var tagName = stanza.name || stanza.tagName || stanza.nodeTree.tagName;
|
|
128
|
+
|
|
129
|
+
if(tagName === 'enabled'){
|
|
130
|
+
self._isStreamManagementEnabled = true;
|
|
131
|
+
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (self._isStreamManagementEnabled && tagName === 'message') {
|
|
136
|
+
clearInterval(self._intervalId);
|
|
137
|
+
self._intervalId = setInterval(self._timeoutCallback.bind(self), self._timeInterval);
|
|
138
|
+
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if(chatUtils.getAttr(stanza, 'xmlns') !== self._NS){
|
|
143
|
+
self._increaseReceivedStanzasCounter();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if(tagName === 'r'){
|
|
147
|
+
var params = {
|
|
148
|
+
xmlns: self._NS,
|
|
149
|
+
h: self._clientProcessedStanzasCounter
|
|
150
|
+
},
|
|
151
|
+
answerStanza = Utils.getEnv().browser ? $build('a', params) :
|
|
152
|
+
chatUtils.createStanza(self._nodeBuilder, params, 'a');
|
|
153
|
+
|
|
154
|
+
self._originalSend.call(self._c, answerStanza);
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if(tagName === 'a'){
|
|
160
|
+
var h = parseInt(chatUtils.getAttr(stanza, 'h'));
|
|
161
|
+
|
|
162
|
+
self._checkCounterOnIncomeStanza(h);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
StreamManagement.prototype.send = function (stanza, message) {
|
|
170
|
+
var self = this,
|
|
171
|
+
stanzaXML = stanza.nodeTree ? this._parser.parseFromString(stanza.nodeTree.outerHTML, "application/xml").childNodes[0] : stanza,
|
|
172
|
+
tagName = stanzaXML.name || stanzaXML.tagName || stanzaXML.nodeTree.tagName,
|
|
173
|
+
type = chatUtils.getAttr(stanzaXML, 'type'),
|
|
174
|
+
bodyContent = chatUtils.getElementText(stanzaXML, 'body') || '',
|
|
175
|
+
attachments = chatUtils.getAllElements(stanzaXML, 'attachment') || '';
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
self._originalSend.call(self._c, stanza);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
Utils.QBLog('[QBChat]', e.message);
|
|
181
|
+
} finally {
|
|
182
|
+
if (tagName === 'message' && (type === 'chat' || type === 'groupchat') && (bodyContent || attachments.length)) {
|
|
183
|
+
self._sendStanzasRequest({
|
|
184
|
+
message: message,
|
|
185
|
+
time: Date.now() + self._timeInterval,
|
|
186
|
+
expect: self._clientSentStanzasCounter
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
self._clientSentStanzasCounter++;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
StreamManagement.prototype._sendStanzasRequest = function (data) {
|
|
196
|
+
var self = this;
|
|
197
|
+
|
|
198
|
+
if(self._isStreamManagementEnabled){
|
|
199
|
+
self._stanzasQueue.push(data);
|
|
200
|
+
|
|
201
|
+
var stanza = Utils.getEnv().browser ? $build('r', { xmlns: self._NS }) :
|
|
202
|
+
chatUtils.createStanza(self._nodeBuilder, { xmlns: self._NS }, 'r');
|
|
203
|
+
|
|
204
|
+
if (self._c.connected) {
|
|
205
|
+
self._originalSend.call(self._c, stanza);
|
|
206
|
+
} else {
|
|
207
|
+
self._checkCounterOnIncomeStanza();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
StreamManagement.prototype.getClientSentStanzasCounter = function(){
|
|
213
|
+
return this._clientSentStanzasCounter;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
StreamManagement.prototype._checkCounterOnIncomeStanza = function (count){
|
|
217
|
+
var updatedStanzasQueue = [];
|
|
218
|
+
|
|
219
|
+
if(this._stanzasQueue.length){
|
|
220
|
+
for(var i = 0; i < this._stanzasQueue.length; i++){
|
|
221
|
+
if(this._stanzasQueue[i].expect == count){
|
|
222
|
+
this.sentMessageCallback(null, this._stanzasQueue[i].message);
|
|
223
|
+
} else {
|
|
224
|
+
updatedStanzasQueue.push(this._stanzasQueue[i]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
this._stanzasQueue = updatedStanzasQueue;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
StreamManagement.prototype._increaseReceivedStanzasCounter = function(){
|
|
232
|
+
this._clientProcessedStanzasCounter++;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
module.exports = StreamManagement;
|