@webex/plugin-meetings 2.31.4 → 2.33.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/dist/constants.js +60 -36
- package/dist/constants.js.map +1 -1
- package/dist/media/properties.js +1 -1
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +72 -30
- package/dist/meeting/index.js.map +1 -1
- package/dist/peer-connection-manager/index.js +9 -11
- package/dist/peer-connection-manager/index.js.map +1 -1
- package/dist/peer-connection-manager/util.js +99 -3
- package/dist/peer-connection-manager/util.js.map +1 -1
- package/dist/reachability/index.js +30 -0
- package/dist/reachability/index.js.map +1 -1
- package/dist/roap/turnDiscovery.js +8 -0
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/package.json +18 -17
- package/src/constants.ts +45 -19
- package/src/media/properties.js +1 -1
- package/src/meeting/index.js +28 -5
- package/src/peer-connection-manager/index.js +11 -12
- package/src/peer-connection-manager/util.ts +117 -0
- package/src/reachability/index.js +24 -0
- package/src/roap/turnDiscovery.ts +7 -0
- package/test/unit/spec/meeting/index.js +56 -50
- package/test/unit/spec/peerconnection-manager/utils.js +30 -7
- package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +389 -0
- package/test/unit/spec/reachability/index.ts +52 -0
- package/test/unit/spec/roap/turnDiscovery.ts +14 -0
- package/src/peer-connection-manager/util.js +0 -19
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TURN_DISCOVERY_TIMEOUT","TurnDiscovery","roapRequest","turnInfo","url","username","password","defer","LoggerProxy","logger","warn","reject","Error","responseTimer","setTimeout","info","promise","roapMessage","headers","expectedHeaders","headerName","field","foundHeaders","forEach","receivedHeader","expectedHeader","startsWith","substring","length","clearTimeout","undefined","resolve","meeting","isReconnecting","seq","roapSeq","Defer","messageType","ROAP","ROAP_TYPES","TURN_DISCOVERY_REQUEST","version","ROAP_VERSION","sendRoap","correlationId","locusSelfUrl","selfUrl","mediaId","audioMuted","isAudioMuted","videoMuted","isVideoMuted","meetingId","id","then","mediaConnections","setRoapSeq","updateMediaConnections","OK","config","experimental","enableTurnDiscovery","sendRoapTurnDiscoveryRequest","waitForTurnDiscoveryResponse","sendRoapOK","catch","e","Metrics","sendBehavioralMetric","BEHAVIORAL_METRICS","TURN_DISCOVERY_FAILURE","correlation_id","locus_id","locusUrl","split","pop","reason","message","stack"],"sources":["turnDiscovery.ts"],"sourcesContent":["import {Defer} from '@webex/common';\n\nimport Metrics from '../metrics';\nimport BEHAVIORAL_METRICS from '../metrics/constants';\nimport LoggerProxy from '../common/logs/logger-proxy';\nimport {ROAP} from '../constants';\n\nimport RoapRequest from './request';\n\nconst TURN_DISCOVERY_TIMEOUT = 10; // in seconds\n\n/**\n * Handles the process of finding out TURN server information from Linus.\n * This is achieved by sending a TURN_DISCOVERY_REQUEST.\n */\nexport default class TurnDiscovery {\n private roapRequest: RoapRequest;\n\n private defer?: Defer; // used for waiting for the response\n\n private turnInfo: {\n url: string;\n username: string;\n password: string;\n };\n\n private responseTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * Constructor\n *\n * @param {RoapRequest} roapRequest\n */\n constructor(roapRequest: RoapRequest) {\n this.roapRequest = roapRequest;\n this.turnInfo = {\n url: '',\n username: '',\n password: '',\n };\n }\n\n\n /**\n * waits for TURN_DISCOVERY_RESPONSE message to arrive\n *\n * @returns {Promise}\n * @private\n * @memberof Roap\n */\n waitForTurnDiscoveryResponse() {\n if (!this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> TURN discovery is not in progress');\n\n return Promise.reject(new Error('waitForTurnDiscoveryResponse() called before sendRoapTurnDiscoveryRequest()'));\n }\n\n const {defer} = this;\n\n this.responseTimer = setTimeout(() => {\n LoggerProxy.logger.warn(`Roap:turnDiscovery#waitForTurnDiscoveryResponse --> timeout! no response arrived within ${TURN_DISCOVERY_TIMEOUT} seconds`);\n\n defer.reject(new Error('Timed out waiting for TURN_DISCOVERY_RESPONSE'));\n }, TURN_DISCOVERY_TIMEOUT * 1000);\n\n LoggerProxy.logger.info('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> waiting for TURN_DISCOVERY_RESPONSE...');\n\n return defer.promise;\n }\n\n /**\n * handles TURN_DISCOVERY_RESPONSE roap message\n *\n * @param {Object} roapMessage\n * @returns {void}\n * @public\n * @memberof Roap\n */\n handleTurnDiscoveryResponse(roapMessage) {\n const {headers} = roapMessage;\n\n if (!this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response');\n\n return;\n }\n\n const expectedHeaders = [\n {headerName: 'x-cisco-turn-url', field: 'url'},\n {headerName: 'x-cisco-turn-username', field: 'username'},\n {headerName: 'x-cisco-turn-password', field: 'password'},\n ];\n\n let foundHeaders = 0;\n\n headers?.forEach((receivedHeader) => {\n // check if it matches any of our expected headers\n expectedHeaders.forEach((expectedHeader) => {\n if (receivedHeader.startsWith(`${expectedHeader.headerName}=`)) {\n this.turnInfo[expectedHeader.field] = receivedHeader.substring(expectedHeader.headerName.length + 1);\n foundHeaders += 1;\n }\n });\n });\n\n clearTimeout(this.responseTimer);\n this.responseTimer = undefined;\n\n if (foundHeaders !== expectedHeaders.length) {\n LoggerProxy.logger.warn(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received: ${JSON.stringify(headers)}`);\n this.defer.reject(new Error(`TURN_DISCOVERY_RESPONSE missing some headers: ${JSON.stringify(headers)}`));\n }\n else {\n LoggerProxy.logger.info(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response, url=${this.turnInfo.url}`);\n this.defer.resolve();\n }\n }\n\n /**\n * sends the TURN_DISCOVERY_REQUEST roap request\n *\n * @param {Meeting} meeting\n * @param {Boolean} isReconnecting\n * @returns {Promise}\n * @private\n * @memberof Roap\n */\n sendRoapTurnDiscoveryRequest(meeting, isReconnecting) {\n const seq = meeting.roapSeq + 1;\n\n if (this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> already in progress');\n\n return Promise.resolve();\n }\n\n this.defer = new Defer();\n\n const roapMessage = {\n messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,\n version: ROAP.ROAP_VERSION,\n seq,\n };\n\n LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> sending TURN_DISCOVERY_REQUEST');\n\n return this.roapRequest\n .sendRoap({\n roapMessage,\n correlationId: meeting.correlationId,\n locusSelfUrl: meeting.selfUrl,\n mediaId: isReconnecting ? '' : meeting.mediaId,\n audioMuted: meeting.isAudioMuted(),\n videoMuted: meeting.isVideoMuted(),\n meetingId: meeting.id\n })\n .then(({mediaConnections}) => {\n meeting.setRoapSeq(seq);\n\n if (mediaConnections) {\n meeting.updateMediaConnections(mediaConnections);\n }\n });\n }\n\n /**\n * Sends the OK message that server expects to receive\n * after it sends us TURN_DISCOVERY_RESPONSE\n *\n * @param {Meeting} meeting\n * @returns {Promise}\n */\n sendRoapOK(meeting) {\n LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapOK --> sending OK');\n\n return this.roapRequest.sendRoap({\n roapMessage: {\n messageType: ROAP.ROAP_TYPES.OK,\n version: ROAP.ROAP_VERSION,\n seq: meeting.roapSeq\n },\n locusSelfUrl: meeting.selfUrl,\n mediaId: meeting.mediaId,\n correlationId: meeting.correlationId,\n audioMuted: meeting.isAudioMuted(),\n videoMuted: meeting.isVideoMuted(),\n meetingId: meeting.id\n });\n }\n\n /**\n * Retrieves TURN server information from the backend by doing\n * a roap message exchange:\n * client server\n * | -----TURN_DISCOVERY_REQUEST-----> |\n * | <----TURN_DISCOVERY_RESPONSE----- |\n * | --------------OK----------------> |\n *\n * @param {Meeting} meeting\n * @param {Boolean} isReconnecting should be set to true if this is a new\n * media connection just after a reconnection\n * @returns {Promise}\n */\n doTurnDiscovery(meeting, isReconnecting) {\n if (!meeting.config.experimental.enableTurnDiscovery) {\n LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery disabled in config, skipping it');\n\n return Promise.resolve(undefined);\n }\n\n return this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting)\n .then(() => this.waitForTurnDiscoveryResponse())\n .then(() => this.sendRoapOK(meeting))\n .then(() => {\n this.defer = undefined;\n\n LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');\n\n return this.turnInfo;\n })\n .catch((e) => {\n // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN\n LoggerProxy.logger.info(`Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`);\n\n Metrics.sendBehavioralMetric(\n BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE,\n {\n correlation_id: meeting.correlationId,\n locus_id: meeting.locusUrl.split('/').pop(),\n reason: e.message,\n stack: e.stack\n }\n );\n\n return Promise.resolve(undefined);\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"names":["TURN_DISCOVERY_TIMEOUT","TurnDiscovery","roapRequest","turnInfo","url","username","password","defer","LoggerProxy","logger","warn","reject","Error","responseTimer","setTimeout","info","promise","roapMessage","headers","expectedHeaders","headerName","field","foundHeaders","forEach","receivedHeader","expectedHeader","startsWith","substring","length","clearTimeout","undefined","resolve","meeting","isReconnecting","seq","roapSeq","Defer","messageType","ROAP","ROAP_TYPES","TURN_DISCOVERY_REQUEST","version","ROAP_VERSION","sendRoap","correlationId","locusSelfUrl","selfUrl","mediaId","audioMuted","isAudioMuted","videoMuted","isVideoMuted","meetingId","id","then","mediaConnections","setRoapSeq","updateMediaConnections","OK","isAnyClusterReachable","webex","meetings","reachability","config","experimental","enableTurnDiscovery","sendRoapTurnDiscoveryRequest","waitForTurnDiscoveryResponse","sendRoapOK","catch","e","Metrics","sendBehavioralMetric","BEHAVIORAL_METRICS","TURN_DISCOVERY_FAILURE","correlation_id","locus_id","locusUrl","split","pop","reason","message","stack"],"sources":["turnDiscovery.ts"],"sourcesContent":["import {Defer} from '@webex/common';\n\nimport Metrics from '../metrics';\nimport BEHAVIORAL_METRICS from '../metrics/constants';\nimport LoggerProxy from '../common/logs/logger-proxy';\nimport {ROAP} from '../constants';\n\nimport RoapRequest from './request';\n\nconst TURN_DISCOVERY_TIMEOUT = 10; // in seconds\n\n/**\n * Handles the process of finding out TURN server information from Linus.\n * This is achieved by sending a TURN_DISCOVERY_REQUEST.\n */\nexport default class TurnDiscovery {\n private roapRequest: RoapRequest;\n\n private defer?: Defer; // used for waiting for the response\n\n private turnInfo: {\n url: string;\n username: string;\n password: string;\n };\n\n private responseTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * Constructor\n *\n * @param {RoapRequest} roapRequest\n */\n constructor(roapRequest: RoapRequest) {\n this.roapRequest = roapRequest;\n this.turnInfo = {\n url: '',\n username: '',\n password: '',\n };\n }\n\n\n /**\n * waits for TURN_DISCOVERY_RESPONSE message to arrive\n *\n * @returns {Promise}\n * @private\n * @memberof Roap\n */\n waitForTurnDiscoveryResponse() {\n if (!this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> TURN discovery is not in progress');\n\n return Promise.reject(new Error('waitForTurnDiscoveryResponse() called before sendRoapTurnDiscoveryRequest()'));\n }\n\n const {defer} = this;\n\n this.responseTimer = setTimeout(() => {\n LoggerProxy.logger.warn(`Roap:turnDiscovery#waitForTurnDiscoveryResponse --> timeout! no response arrived within ${TURN_DISCOVERY_TIMEOUT} seconds`);\n\n defer.reject(new Error('Timed out waiting for TURN_DISCOVERY_RESPONSE'));\n }, TURN_DISCOVERY_TIMEOUT * 1000);\n\n LoggerProxy.logger.info('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> waiting for TURN_DISCOVERY_RESPONSE...');\n\n return defer.promise;\n }\n\n /**\n * handles TURN_DISCOVERY_RESPONSE roap message\n *\n * @param {Object} roapMessage\n * @returns {void}\n * @public\n * @memberof Roap\n */\n handleTurnDiscoveryResponse(roapMessage) {\n const {headers} = roapMessage;\n\n if (!this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response');\n\n return;\n }\n\n const expectedHeaders = [\n {headerName: 'x-cisco-turn-url', field: 'url'},\n {headerName: 'x-cisco-turn-username', field: 'username'},\n {headerName: 'x-cisco-turn-password', field: 'password'},\n ];\n\n let foundHeaders = 0;\n\n headers?.forEach((receivedHeader) => {\n // check if it matches any of our expected headers\n expectedHeaders.forEach((expectedHeader) => {\n if (receivedHeader.startsWith(`${expectedHeader.headerName}=`)) {\n this.turnInfo[expectedHeader.field] = receivedHeader.substring(expectedHeader.headerName.length + 1);\n foundHeaders += 1;\n }\n });\n });\n\n clearTimeout(this.responseTimer);\n this.responseTimer = undefined;\n\n if (foundHeaders !== expectedHeaders.length) {\n LoggerProxy.logger.warn(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received: ${JSON.stringify(headers)}`);\n this.defer.reject(new Error(`TURN_DISCOVERY_RESPONSE missing some headers: ${JSON.stringify(headers)}`));\n }\n else {\n LoggerProxy.logger.info(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response, url=${this.turnInfo.url}`);\n this.defer.resolve();\n }\n }\n\n /**\n * sends the TURN_DISCOVERY_REQUEST roap request\n *\n * @param {Meeting} meeting\n * @param {Boolean} isReconnecting\n * @returns {Promise}\n * @private\n * @memberof Roap\n */\n sendRoapTurnDiscoveryRequest(meeting, isReconnecting) {\n const seq = meeting.roapSeq + 1;\n\n if (this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> already in progress');\n\n return Promise.resolve();\n }\n\n this.defer = new Defer();\n\n const roapMessage = {\n messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,\n version: ROAP.ROAP_VERSION,\n seq,\n };\n\n LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> sending TURN_DISCOVERY_REQUEST');\n\n return this.roapRequest\n .sendRoap({\n roapMessage,\n correlationId: meeting.correlationId,\n locusSelfUrl: meeting.selfUrl,\n mediaId: isReconnecting ? '' : meeting.mediaId,\n audioMuted: meeting.isAudioMuted(),\n videoMuted: meeting.isVideoMuted(),\n meetingId: meeting.id\n })\n .then(({mediaConnections}) => {\n meeting.setRoapSeq(seq);\n\n if (mediaConnections) {\n meeting.updateMediaConnections(mediaConnections);\n }\n });\n }\n\n /**\n * Sends the OK message that server expects to receive\n * after it sends us TURN_DISCOVERY_RESPONSE\n *\n * @param {Meeting} meeting\n * @returns {Promise}\n */\n sendRoapOK(meeting) {\n LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapOK --> sending OK');\n\n return this.roapRequest.sendRoap({\n roapMessage: {\n messageType: ROAP.ROAP_TYPES.OK,\n version: ROAP.ROAP_VERSION,\n seq: meeting.roapSeq\n },\n locusSelfUrl: meeting.selfUrl,\n mediaId: meeting.mediaId,\n correlationId: meeting.correlationId,\n audioMuted: meeting.isAudioMuted(),\n videoMuted: meeting.isVideoMuted(),\n meetingId: meeting.id\n });\n }\n\n /**\n * Retrieves TURN server information from the backend by doing\n * a roap message exchange:\n * client server\n * | -----TURN_DISCOVERY_REQUEST-----> |\n * | <----TURN_DISCOVERY_RESPONSE----- |\n * | --------------OK----------------> |\n *\n * @param {Meeting} meeting\n * @param {Boolean} isReconnecting should be set to true if this is a new\n * media connection just after a reconnection\n * @returns {Promise}\n */\n doTurnDiscovery(meeting, isReconnecting) {\n const isAnyClusterReachable = meeting.webex.meetings.reachability.isAnyClusterReachable();\n\n if (isAnyClusterReachable) {\n LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> reachability has not failed, skipping TURN discovery');\n return Promise.resolve(undefined);\n }\n\n if (!meeting.config.experimental.enableTurnDiscovery) {\n LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery disabled in config, skipping it');\n\n return Promise.resolve(undefined);\n }\n\n return this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting)\n .then(() => this.waitForTurnDiscoveryResponse())\n .then(() => this.sendRoapOK(meeting))\n .then(() => {\n this.defer = undefined;\n\n LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');\n\n return this.turnInfo;\n })\n .catch((e) => {\n // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN\n LoggerProxy.logger.info(`Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`);\n\n Metrics.sendBehavioralMetric(\n BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE,\n {\n correlation_id: meeting.correlationId,\n locus_id: meeting.locusUrl.split('/').pop(),\n reason: e.message,\n stack: e.stack\n }\n );\n\n return Promise.resolve(undefined);\n });\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/plugin-meetings",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.33.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
|
|
6
6
|
"contributors": [
|
|
@@ -24,29 +24,30 @@
|
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@webex/plugin-meetings": "2.
|
|
28
|
-
"@webex/test-helper-chai": "2.
|
|
29
|
-
"@webex/test-helper-mocha": "2.
|
|
30
|
-
"@webex/test-helper-mock-webex": "2.
|
|
31
|
-
"@webex/test-helper-retry": "2.
|
|
32
|
-
"@webex/test-helper-test-users": "2.
|
|
27
|
+
"@webex/plugin-meetings": "2.33.0",
|
|
28
|
+
"@webex/test-helper-chai": "2.33.0",
|
|
29
|
+
"@webex/test-helper-mocha": "2.33.0",
|
|
30
|
+
"@webex/test-helper-mock-webex": "2.33.0",
|
|
31
|
+
"@webex/test-helper-retry": "2.33.0",
|
|
32
|
+
"@webex/test-helper-test-users": "2.33.0",
|
|
33
33
|
"chai": "^4.3.4",
|
|
34
34
|
"chai-as-promised": "^7.1.1",
|
|
35
35
|
"jsdom-global": "3.0.2",
|
|
36
36
|
"sinon": "^9.2.4"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@webex/common": "2.
|
|
39
|
+
"@webex/common": "2.33.0",
|
|
40
40
|
"@webex/internal-media-core": "^0.0.7-beta",
|
|
41
|
-
"@webex/internal-plugin-conversation": "2.
|
|
42
|
-
"@webex/internal-plugin-device": "2.
|
|
43
|
-
"@webex/internal-plugin-mercury": "2.
|
|
44
|
-
"@webex/internal-plugin-metrics": "2.
|
|
45
|
-
"@webex/internal-plugin-support": "2.
|
|
46
|
-
"@webex/internal-plugin-user": "2.
|
|
47
|
-
"@webex/plugin-people": "2.
|
|
48
|
-
"@webex/plugin-rooms": "2.
|
|
49
|
-
"@webex/
|
|
41
|
+
"@webex/internal-plugin-conversation": "2.33.0",
|
|
42
|
+
"@webex/internal-plugin-device": "2.33.0",
|
|
43
|
+
"@webex/internal-plugin-mercury": "2.33.0",
|
|
44
|
+
"@webex/internal-plugin-metrics": "2.33.0",
|
|
45
|
+
"@webex/internal-plugin-support": "2.33.0",
|
|
46
|
+
"@webex/internal-plugin-user": "2.33.0",
|
|
47
|
+
"@webex/plugin-people": "2.33.0",
|
|
48
|
+
"@webex/plugin-rooms": "2.33.0",
|
|
49
|
+
"@webex/ts-sdp": "^1.0.1",
|
|
50
|
+
"@webex/webex-core": "2.33.0",
|
|
50
51
|
"bowser": "^2.11.0",
|
|
51
52
|
"btoa": "^1.2.1",
|
|
52
53
|
"dotenv": "^4.0.0",
|
package/src/constants.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {hydraTypes} from '@webex/common';
|
|
1
|
+
import { hydraTypes } from '@webex/common';
|
|
2
2
|
|
|
3
3
|
// *********** LOWERCASE / CAMELCASE STRINGS ************
|
|
4
4
|
|
|
@@ -964,37 +964,40 @@ export const MQA_STATS = {
|
|
|
964
964
|
export const QUALITY_LEVELS = {
|
|
965
965
|
LOW: 'LOW',
|
|
966
966
|
MEDIUM: 'MEDIUM',
|
|
967
|
-
HIGH: 'HIGH'
|
|
967
|
+
HIGH: 'HIGH',
|
|
968
|
+
'360p': '360p',
|
|
969
|
+
'480p': '480p',
|
|
970
|
+
'720p': '720p',
|
|
971
|
+
'1080p': '1080p',
|
|
968
972
|
};
|
|
969
973
|
|
|
970
|
-
|
|
971
|
-
|
|
974
|
+
|
|
975
|
+
export const AVALIABLE_RESOLUTIONS = {
|
|
976
|
+
'360p': {
|
|
972
977
|
video: {
|
|
973
978
|
width: {
|
|
974
|
-
max:
|
|
975
|
-
ideal:
|
|
979
|
+
max: 640,
|
|
980
|
+
ideal: 640
|
|
976
981
|
},
|
|
977
982
|
height: {
|
|
978
|
-
max:
|
|
979
|
-
ideal:
|
|
983
|
+
max: 360,
|
|
984
|
+
ideal: 360
|
|
980
985
|
}
|
|
981
986
|
}
|
|
982
987
|
},
|
|
983
|
-
|
|
984
|
-
[QUALITY_LEVELS.MEDIUM]: {
|
|
988
|
+
'480p': {
|
|
985
989
|
video: {
|
|
986
990
|
width: {
|
|
987
991
|
max: 640,
|
|
988
992
|
ideal: 640
|
|
989
993
|
},
|
|
990
994
|
height: {
|
|
991
|
-
max:
|
|
992
|
-
ideal:
|
|
995
|
+
max: 480,
|
|
996
|
+
ideal: 480
|
|
993
997
|
}
|
|
994
998
|
}
|
|
995
999
|
},
|
|
996
|
-
|
|
997
|
-
[QUALITY_LEVELS.HIGH]: {
|
|
1000
|
+
'720p': {
|
|
998
1001
|
video: {
|
|
999
1002
|
width: {
|
|
1000
1003
|
max: 1280,
|
|
@@ -1005,20 +1008,43 @@ export const VIDEO_RESOLUTIONS = {
|
|
|
1005
1008
|
ideal: 720
|
|
1006
1009
|
}
|
|
1007
1010
|
}
|
|
1011
|
+
},
|
|
1012
|
+
'1080p': {
|
|
1013
|
+
video: {
|
|
1014
|
+
width: {
|
|
1015
|
+
max: 1920,
|
|
1016
|
+
ideal: 1920
|
|
1017
|
+
},
|
|
1018
|
+
height: {
|
|
1019
|
+
max: 1080,
|
|
1020
|
+
ideal: 1080
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1008
1023
|
}
|
|
1009
1024
|
};
|
|
1010
1025
|
|
|
1026
|
+
export const VIDEO_RESOLUTIONS = {
|
|
1027
|
+
[QUALITY_LEVELS.LOW]: AVALIABLE_RESOLUTIONS['480p'],
|
|
1028
|
+
[QUALITY_LEVELS.MEDIUM]: AVALIABLE_RESOLUTIONS['720p'],
|
|
1029
|
+
[QUALITY_LEVELS.HIGH]: AVALIABLE_RESOLUTIONS['1080p'],
|
|
1030
|
+
[QUALITY_LEVELS['360p']]: AVALIABLE_RESOLUTIONS['360p'],
|
|
1031
|
+
[QUALITY_LEVELS['480p']]: AVALIABLE_RESOLUTIONS['480p'],
|
|
1032
|
+
[QUALITY_LEVELS['720p']]: AVALIABLE_RESOLUTIONS['720p'],
|
|
1033
|
+
[QUALITY_LEVELS['1080p']]: AVALIABLE_RESOLUTIONS['1080p'],
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1011
1036
|
/**
|
|
1012
1037
|
* Max frame sizes based on h264 configs
|
|
1013
1038
|
* https://en.wikipedia.org/wiki/Advanced_Video_Coding
|
|
1014
1039
|
*/
|
|
1015
|
-
export const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1040
|
+
export const REMOTE_VIDEO_CONSTRAINTS = {
|
|
1041
|
+
MAX_FS: {
|
|
1042
|
+
[QUALITY_LEVELS.LOW]: 1620,
|
|
1043
|
+
[QUALITY_LEVELS.MEDIUM]: 3600,
|
|
1044
|
+
[QUALITY_LEVELS.HIGH]: 8192
|
|
1045
|
+
}
|
|
1019
1046
|
};
|
|
1020
1047
|
|
|
1021
|
-
|
|
1022
1048
|
/*
|
|
1023
1049
|
* mqa Interval for sending stats metrics
|
|
1024
1050
|
*/
|
package/src/media/properties.js
CHANGED
|
@@ -25,7 +25,7 @@ export default class MediaProperties {
|
|
|
25
25
|
this.remoteShare = options.remoteShare;
|
|
26
26
|
this.remoteAudioTrack = options.remoteAudioTrack;
|
|
27
27
|
this.remoteVideoTrack = options.remoteVideoTrack;
|
|
28
|
-
this.localQualityLevel = options.localQualityLevel || QUALITY_LEVELS
|
|
28
|
+
this.localQualityLevel = options.localQualityLevel || QUALITY_LEVELS['720p'];
|
|
29
29
|
this.remoteQualityLevel = options.remoteQualityLevel || QUALITY_LEVELS.HIGH;
|
|
30
30
|
this.mediaSettings = {};
|
|
31
31
|
this.videoDeviceId = null;
|
package/src/meeting/index.js
CHANGED
|
@@ -125,6 +125,7 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
125
125
|
* @property {String} audio.deviceId
|
|
126
126
|
* @property {Object} video
|
|
127
127
|
* @property {String} video.deviceId
|
|
128
|
+
* @property {String} video.localVideoQuality // [240p, 360p, 480p, 720p, 1080p]
|
|
128
129
|
*/
|
|
129
130
|
|
|
130
131
|
/**
|
|
@@ -2742,6 +2743,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2742
2743
|
aspectRatio, frameRate, height, width, deviceId
|
|
2743
2744
|
} = videoTrack.getSettings();
|
|
2744
2745
|
|
|
2746
|
+
const {localQualityLevel} = this.mediaProperties;
|
|
2747
|
+
|
|
2748
|
+
if (Number(localQualityLevel.slice(0, -1)) > height) {
|
|
2749
|
+
LoggerProxy.logger.error(`Meeting:index#setLocalVideoTrack --> Local video quality of ${localQualityLevel} not supported,
|
|
2750
|
+
downscaling to highest possible resolution of ${height}p`);
|
|
2751
|
+
|
|
2752
|
+
this.mediaProperties.setLocalQualityLevel(`${height}p`);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2745
2755
|
this.mediaProperties.setLocalVideoTrack(videoTrack);
|
|
2746
2756
|
if (this.video) this.video.applyClientStateLocally(this);
|
|
2747
2757
|
|
|
@@ -4004,6 +4014,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4004
4014
|
LoggerProxy.logger.warn('Meeting:index#getMediaStreams --> Please use `meeting.shareScreen()` to manually start the screen share after successfully joining the meeting');
|
|
4005
4015
|
}
|
|
4006
4016
|
|
|
4017
|
+
if (!audioVideo.video) {
|
|
4018
|
+
audioVideo = {...audioVideo, video: {...audioVideo.video, ...VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel].video}};
|
|
4019
|
+
}
|
|
4007
4020
|
// extract deviceId if exists otherwise default to null.
|
|
4008
4021
|
const {deviceId: preferredVideoDevice} = (audioVideo && audioVideo.video || {deviceId: null});
|
|
4009
4022
|
const lastVideoDeviceId = this.mediaProperties.getVideoDeviceId();
|
|
@@ -5342,7 +5355,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5342
5355
|
/**
|
|
5343
5356
|
* Sets the quality of the local video stream
|
|
5344
5357
|
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
5345
|
-
* @returns {Promise}
|
|
5358
|
+
* @returns {Promise<MediaStream>} localStream
|
|
5346
5359
|
*/
|
|
5347
5360
|
setLocalVideoQuality(level) {
|
|
5348
5361
|
LoggerProxy.logger.log(`Meeting:index#setLocalVideoQuality --> Setting quality to ${level}`);
|
|
@@ -5371,13 +5384,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5371
5384
|
sendShare: this.mediaProperties.mediaDirection.sendShare
|
|
5372
5385
|
};
|
|
5373
5386
|
|
|
5387
|
+
// When changing local video quality level
|
|
5388
|
+
// Need to stop current track first as chrome doesn't support resolution upscaling(for eg. changing 480p to 720p)
|
|
5389
|
+
// Without feeding it a new track
|
|
5390
|
+
// open bug link: https://bugs.chromium.org/p/chromium/issues/detail?id=943469
|
|
5391
|
+
if (isBrowser('chrome') && this.mediaProperties.videoTrack) Media.stopTracks(this.mediaProperties.videoTrack);
|
|
5392
|
+
|
|
5374
5393
|
return this.getMediaStreams(mediaDirection, VIDEO_RESOLUTIONS[level])
|
|
5375
|
-
.then(([localStream]) =>
|
|
5376
|
-
this.updateVideo({
|
|
5394
|
+
.then(async ([localStream]) => {
|
|
5395
|
+
await this.updateVideo({
|
|
5377
5396
|
sendVideo: true,
|
|
5378
5397
|
receiveVideo: true,
|
|
5379
5398
|
stream: localStream
|
|
5380
|
-
})
|
|
5399
|
+
});
|
|
5400
|
+
|
|
5401
|
+
return localStream;
|
|
5402
|
+
});
|
|
5381
5403
|
}
|
|
5382
5404
|
|
|
5383
5405
|
/**
|
|
@@ -5410,9 +5432,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5410
5432
|
}
|
|
5411
5433
|
|
|
5412
5434
|
/**
|
|
5413
|
-
*
|
|
5435
|
+
* This is deprecated, please use setLocalVideoQuality for setting local and setRemoteQualityLevel for remote
|
|
5414
5436
|
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
5415
5437
|
* @returns {Promise}
|
|
5438
|
+
* @deprecated After FHD support
|
|
5416
5439
|
*/
|
|
5417
5440
|
setMeetingQuality(level) {
|
|
5418
5441
|
LoggerProxy.logger.log(`Meeting:index#setMeetingQuality --> Setting quality to ${level}`);
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
PEER_CONNECTION_STATE,
|
|
21
21
|
OFFER,
|
|
22
22
|
QUALITY_LEVELS,
|
|
23
|
-
|
|
23
|
+
REMOTE_VIDEO_CONSTRAINTS
|
|
24
24
|
} from '../constants';
|
|
25
25
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
26
26
|
import {error, eventType} from '../metrics/config';
|
|
@@ -70,18 +70,16 @@ const insertBandwidthLimit = (sdpLines, index) => {
|
|
|
70
70
|
* @param {String} [level=QUALITY_LEVELS.HIGH] quality level for max-fs
|
|
71
71
|
* @returns {String}
|
|
72
72
|
*/
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
const setRemoteVideoConstraints = (sdp, level = QUALITY_LEVELS.HIGH) => {
|
|
74
|
+
const maxFs = REMOTE_VIDEO_CONSTRAINTS.MAX_FS[level];
|
|
75
|
+
|
|
76
|
+
if (!maxFs) {
|
|
77
|
+
throw new ParameterError(`setRemoteVideoConstraints: unable to set max framesize, value for level "${level}" is not defined`);
|
|
76
78
|
}
|
|
77
|
-
// eslint-disable-next-line no-warning-comments
|
|
78
|
-
// TODO convert with sdp parser, no munging
|
|
79
|
-
let replaceSdp = sdp;
|
|
80
|
-
const maxFsLine = `${SDP.MAX_FS}${MAX_FRAMESIZES[level]}`;
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
const modifiedSdp = PeerConnectionUtils.adjustH264Profile(sdp, maxFs);
|
|
83
81
|
|
|
84
|
-
return
|
|
82
|
+
return modifiedSdp;
|
|
85
83
|
};
|
|
86
84
|
|
|
87
85
|
|
|
@@ -188,8 +186,8 @@ pc.iceCandidate = (peerConnection, {remoteQualityLevel}) =>
|
|
|
188
186
|
const miliseconds = parseInt(Math.abs(Date.now() - now), 4);
|
|
189
187
|
|
|
190
188
|
peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
|
|
191
|
-
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
192
189
|
peerConnection.sdp = PeerConnectionUtils.convertCLineToIpv4(peerConnection.sdp);
|
|
190
|
+
peerConnection.sdp = setRemoteVideoConstraints(peerConnection.sdp, remoteQualityLevel);
|
|
193
191
|
|
|
194
192
|
const invalidSdpPresent = isSdpInvalid(peerConnection.sdp);
|
|
195
193
|
|
|
@@ -540,8 +538,9 @@ pc.createAnswer = (params, {meetingId, remoteQualityLevel}) => {
|
|
|
540
538
|
.then(() => pc.iceCandidate(peerConnection, {remoteQualityLevel}))
|
|
541
539
|
.then(() => {
|
|
542
540
|
peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
|
|
543
|
-
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
544
541
|
peerConnection.sdp = PeerConnectionUtils.convertCLineToIpv4(peerConnection.sdp);
|
|
542
|
+
peerConnection.sdp = setRemoteVideoConstraints(peerConnection.sdp, remoteQualityLevel);
|
|
543
|
+
|
|
545
544
|
if (!checkH264Support(peerConnection.sdp)) {
|
|
546
545
|
throw new MediaError('openH264 is downloading please Wait. Upload logs if not working on second try');
|
|
547
546
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { parse } from '@webex/ts-sdp';
|
|
2
|
+
|
|
3
|
+
interface IPeerConnectionUtils {
|
|
4
|
+
convertCLineToIpv4: (sdp: string) => string;
|
|
5
|
+
adjustH264Profile: (sdp: string, maxFsValue: number) => string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const PeerConnectionUtils = {} as IPeerConnectionUtils;
|
|
9
|
+
|
|
10
|
+
// max-fs values for all H264 profile levels
|
|
11
|
+
const maxFsForProfileLevel = {
|
|
12
|
+
10: 99,
|
|
13
|
+
11: 396,
|
|
14
|
+
12: 396,
|
|
15
|
+
13: 396,
|
|
16
|
+
20: 396,
|
|
17
|
+
21: 792,
|
|
18
|
+
22: 1620,
|
|
19
|
+
30: 1620,
|
|
20
|
+
31: 3600,
|
|
21
|
+
32: 5120,
|
|
22
|
+
40: 8192,
|
|
23
|
+
41: 8192,
|
|
24
|
+
42: 8704,
|
|
25
|
+
50: 22080,
|
|
26
|
+
51: 36864,
|
|
27
|
+
52: 36864,
|
|
28
|
+
60: 139264,
|
|
29
|
+
61: 139264,
|
|
30
|
+
62: 139264,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const framesPerSecond = 30;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert C line to IPv4
|
|
37
|
+
* @param {string} sdp
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
PeerConnectionUtils.convertCLineToIpv4 = (sdp: string) => {
|
|
41
|
+
let replaceSdp = sdp;
|
|
42
|
+
|
|
43
|
+
// TODO: remove this once linus supports Ipv6 c line.currently linus rejects SDP with c line having ipv6 candidates we are
|
|
44
|
+
// mocking ipv6 to ipv4 candidates
|
|
45
|
+
// https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-299232
|
|
46
|
+
replaceSdp = replaceSdp.replace(/c=IN IP6 .*/gi, 'c=IN IP4 0.0.0.0');
|
|
47
|
+
|
|
48
|
+
return replaceSdp;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* estimate profile levels for max-fs & max-mbps values
|
|
53
|
+
* @param {string} sdp
|
|
54
|
+
* @param {number} maxFsValue
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
PeerConnectionUtils.adjustH264Profile = (sdp: string, maxFsValue: number) => {
|
|
58
|
+
// converting with ts-sdp parser, no munging
|
|
59
|
+
const parsedSdp = parse(sdp);
|
|
60
|
+
|
|
61
|
+
parsedSdp.avMedia.forEach((media) => {
|
|
62
|
+
if (media.type === 'video') {
|
|
63
|
+
media.codecs.forEach((codec) => {
|
|
64
|
+
if (codec.name?.toUpperCase() === 'H264') {
|
|
65
|
+
// there should really be just 1 fmtp line, but just in case, we process all of them
|
|
66
|
+
codec.fmtParams = codec.fmtParams.map((fmtp) => {
|
|
67
|
+
const parsedRegex = fmtp.match(/(.*)profile-level-id=(\w{4})(\w{2})(.*)/);
|
|
68
|
+
|
|
69
|
+
if (parsedRegex && parsedRegex.length === 5) {
|
|
70
|
+
const stuffBeforeProfileLevelId = parsedRegex[1];
|
|
71
|
+
const profile = parsedRegex[2].toLowerCase();
|
|
72
|
+
const levelId = parseInt(parsedRegex[3], 16);
|
|
73
|
+
const stuffAfterProfileLevelId = parsedRegex[4];
|
|
74
|
+
|
|
75
|
+
if (!maxFsForProfileLevel[levelId]) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`found unsupported h264 profile level id value in the SDP: ${levelId}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (maxFsForProfileLevel[levelId] === maxFsValue) {
|
|
82
|
+
// profile level already matches our desired max-fs value, so we don't need to do anything
|
|
83
|
+
return fmtp;
|
|
84
|
+
}
|
|
85
|
+
if (maxFsForProfileLevel[levelId] < maxFsValue) {
|
|
86
|
+
// profile level has too low max-fs, so we need to override it (this is upgrading)
|
|
87
|
+
return `${fmtp};max-fs=${maxFsValue};max-mbps=${maxFsValue * framesPerSecond}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// profile level has too high max-fs value, so we need to use a lower level
|
|
91
|
+
|
|
92
|
+
// find highest level that has the matching maxFs
|
|
93
|
+
const newLevelId = Object.keys(maxFsForProfileLevel)
|
|
94
|
+
.reverse()
|
|
95
|
+
.find((key) => maxFsForProfileLevel[key] === maxFsValue);
|
|
96
|
+
|
|
97
|
+
if (newLevelId) {
|
|
98
|
+
// Object.keys returns keys as strings, so we need to parse it to an int again and then convert to hex
|
|
99
|
+
const newLevelIdHex = parseInt(newLevelId, 10).toString(16);
|
|
100
|
+
|
|
101
|
+
return `${stuffBeforeProfileLevelId}profile-level-id=${profile}${newLevelIdHex};max-mbps=${maxFsValue * framesPerSecond}${stuffAfterProfileLevelId}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw new Error(`unsupported maxFsValue: ${maxFsValue}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return fmtp;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return parsedSdp.toString();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export default PeerConnectionUtils;
|
|
@@ -91,6 +91,30 @@ export default class Reachability {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* fetches reachability data and checks for cluster reachability
|
|
96
|
+
* @returns {boolean}
|
|
97
|
+
* @public
|
|
98
|
+
* @memberof Reachability
|
|
99
|
+
*/
|
|
100
|
+
isAnyClusterReachable() {
|
|
101
|
+
let reachable = false;
|
|
102
|
+
const reachabilityData = window.localStorage.getItem(REACHABILITY.localStorage);
|
|
103
|
+
|
|
104
|
+
if (reachabilityData) {
|
|
105
|
+
try {
|
|
106
|
+
const reachabilityResults = JSON.parse(reachabilityData);
|
|
107
|
+
|
|
108
|
+
reachable = Object.values(reachabilityResults).some((result) => result.udp?.reachable === 'true' || result.tcp?.reachable === 'true');
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
LoggerProxy.logger.error(`Roap:request#attachReachabilityData --> Error in parsing reachability data: ${e}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return reachable;
|
|
116
|
+
}
|
|
117
|
+
|
|
94
118
|
|
|
95
119
|
/**
|
|
96
120
|
* Generate peerConnection config settings
|
|
@@ -202,6 +202,13 @@ export default class TurnDiscovery {
|
|
|
202
202
|
* @returns {Promise}
|
|
203
203
|
*/
|
|
204
204
|
doTurnDiscovery(meeting, isReconnecting) {
|
|
205
|
+
const isAnyClusterReachable = meeting.webex.meetings.reachability.isAnyClusterReachable();
|
|
206
|
+
|
|
207
|
+
if (isAnyClusterReachable) {
|
|
208
|
+
LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> reachability has not failed, skipping TURN discovery');
|
|
209
|
+
return Promise.resolve(undefined);
|
|
210
|
+
}
|
|
211
|
+
|
|
205
212
|
if (!meeting.config.experimental.enableTurnDiscovery) {
|
|
206
213
|
LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery disabled in config, skipping it');
|
|
207
214
|
|