@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.
@@ -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":";;;;;;;;;;;;;;;;;;;;;;AAAA;;AAEA;;AACA;;AACA;;AACA;;AAIA,IAAMA,sBAAsB,GAAG,EAA/B,C,CAAmC;;AAEnC;AACA;AACA;AACA;;IACqBC,a;EAGI;;EAUvB;AACF;AACA;AACA;AACA;EACE,uBAAYC,WAAZ,EAAsC;IAAA;IAAA;IAAA;IAAA;IAAA;IACpC,KAAKA,WAAL,GAAmBA,WAAnB;IACA,KAAKC,QAAL,GAAgB;MACdC,GAAG,EAAE,EADS;MAEdC,QAAQ,EAAE,EAFI;MAGdC,QAAQ,EAAE;IAHI,CAAhB;EAKD;EAGD;AACF;AACA;AACA;AACA;AACA;AACA;;;;;WACE,wCAA+B;MAC7B,IAAI,CAAC,KAAKC,KAAV,EAAiB;QACfC,oBAAA,CAAYC,MAAZ,CAAmBC,IAAnB,CAAwB,uFAAxB;;QAEA,OAAO,iBAAQC,MAAR,CAAe,IAAIC,KAAJ,CAAU,6EAAV,CAAf,CAAP;MACD;;MAED,IAAOL,KAAP,GAAgB,IAAhB,CAAOA,KAAP;MAEA,KAAKM,aAAL,GAAqBC,UAAU,CAAC,YAAM;QACpCN,oBAAA,CAAYC,MAAZ,CAAmBC,IAAnB,mGAAmHV,sBAAnH;;QAEAO,KAAK,CAACI,MAAN,CAAa,IAAIC,KAAJ,CAAU,+CAAV,CAAb;MACD,CAJ8B,EAI5BZ,sBAAsB,GAAG,IAJG,CAA/B;;MAMAQ,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,CAAwB,4FAAxB;;MAEA,OAAOR,KAAK,CAACS,OAAb;IACD;IAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;;;;WACE,qCAA4BC,WAA5B,EAAyC;MAAA;;MACvC,IAAOC,OAAP,GAAkBD,WAAlB,CAAOC,OAAP;;MAEA,IAAI,CAAC,KAAKX,KAAV,EAAiB;QACfC,oBAAA,CAAYC,MAAZ,CAAmBC,IAAnB,CAAwB,uFAAxB;;QAEA;MACD;;MAED,IAAMS,eAAe,GAAG,CACtB;QAACC,UAAU,EAAE,kBAAb;QAAiCC,KAAK,EAAE;MAAxC,CADsB,EAEtB;QAACD,UAAU,EAAE,uBAAb;QAAsCC,KAAK,EAAE;MAA7C,CAFsB,EAGtB;QAACD,UAAU,EAAE,uBAAb;QAAsCC,KAAK,EAAE;MAA7C,CAHsB,CAAxB;MAMA,IAAIC,YAAY,GAAG,CAAnB;MAEAJ,OAAO,SAAP,IAAAA,OAAO,WAAP,YAAAA,OAAO,CAAEK,OAAT,CAAiB,UAACC,cAAD,EAAoB;QACnC;QACAL,eAAe,CAACI,OAAhB,CAAwB,UAACE,cAAD,EAAoB;UAC1C,IAAID,cAAc,CAACE,UAAf,WAA6BD,cAAc,CAACL,UAA5C,OAAJ,EAAgE;YAC9D,KAAI,CAACjB,QAAL,CAAcsB,cAAc,CAACJ,KAA7B,IAAsCG,cAAc,CAACG,SAAf,CAAyBF,cAAc,CAACL,UAAf,CAA0BQ,MAA1B,GAAmC,CAA5D,CAAtC;YACAN,YAAY,IAAI,CAAhB;UACD;QACF,CALD;MAMD,CARD;MAUAO,YAAY,CAAC,KAAKhB,aAAN,CAAZ;MACA,KAAKA,aAAL,GAAqBiB,SAArB;;MAEA,IAAIR,YAAY,KAAKH,eAAe,CAACS,MAArC,EAA6C;QAC3CpB,oBAAA,CAAYC,MAAZ,CAAmBC,IAAnB,8FAA8G,wBAAeQ,OAAf,CAA9G;;QACA,KAAKX,KAAL,CAAWI,MAAX,CAAkB,IAAIC,KAAJ,yDAA2D,wBAAeM,OAAf,CAA3D,EAAlB;MACD,CAHD,MAIK;QACHV,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,6FAA6G,KAAKZ,QAAL,CAAcC,GAA3H;;QACA,KAAKG,KAAL,CAAWwB,OAAX;MACD;IACF;IAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;WACE,sCAA6BC,OAA7B,EAAsCC,cAAtC,EAAsD;MACpD,IAAMC,GAAG,GAAGF,OAAO,CAACG,OAAR,GAAkB,CAA9B;;MAEA,IAAI,KAAK5B,KAAT,EAAgB;QACdC,oBAAA,CAAYC,MAAZ,CAAmBC,IAAnB,CAAwB,yEAAxB;;QAEA,OAAO,iBAAQqB,OAAR,EAAP;MACD;;MAED,KAAKxB,KAAL,GAAa,IAAI6B,aAAJ,EAAb;MAEA,IAAMnB,WAAW,GAAG;QAClBoB,WAAW,EAAEC,gBAAA,CAAKC,UAAL,CAAgBC,sBADX;QAElBC,OAAO,EAAEH,gBAAA,CAAKI,YAFI;QAGlBR,GAAG,EAAHA;MAHkB,CAApB;;MAMA1B,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,CAAwB,oFAAxB;;MAEA,OAAO,KAAKb,WAAL,CACJyC,QADI,CACK;QACR1B,WAAW,EAAXA,WADQ;QAER2B,aAAa,EAAEZ,OAAO,CAACY,aAFf;QAGRC,YAAY,EAAEb,OAAO,CAACc,OAHd;QAIRC,OAAO,EAAEd,cAAc,GAAG,EAAH,GAAQD,OAAO,CAACe,OAJ/B;QAKRC,UAAU,EAAEhB,OAAO,CAACiB,YAAR,EALJ;QAMRC,UAAU,EAAElB,OAAO,CAACmB,YAAR,EANJ;QAORC,SAAS,EAAEpB,OAAO,CAACqB;MAPX,CADL,EAUJC,IAVI,CAUC,gBAAwB;QAAA,IAAtBC,gBAAsB,QAAtBA,gBAAsB;QAC5BvB,OAAO,CAACwB,UAAR,CAAmBtB,GAAnB;;QAEA,IAAIqB,gBAAJ,EAAsB;UACpBvB,OAAO,CAACyB,sBAAR,CAA+BF,gBAA/B;QACD;MACF,CAhBI,CAAP;IAiBD;IAED;AACF;AACA;AACA;AACA;AACA;AACA;;;;WACE,oBAAWvB,OAAX,EAAoB;MAClBxB,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,CAAwB,8CAAxB;;MAEA,OAAO,KAAKb,WAAL,CAAiByC,QAAjB,CAA0B;QAC/B1B,WAAW,EAAE;UACXoB,WAAW,EAAEC,gBAAA,CAAKC,UAAL,CAAgBmB,EADlB;UAEXjB,OAAO,EAAEH,gBAAA,CAAKI,YAFH;UAGXR,GAAG,EAAEF,OAAO,CAACG;QAHF,CADkB;QAM/BU,YAAY,EAAEb,OAAO,CAACc,OANS;QAO/BC,OAAO,EAAEf,OAAO,CAACe,OAPc;QAQ/BH,aAAa,EAAEZ,OAAO,CAACY,aARQ;QAS/BI,UAAU,EAAEhB,OAAO,CAACiB,YAAR,EATmB;QAU/BC,UAAU,EAAElB,OAAO,CAACmB,YAAR,EAVmB;QAW/BC,SAAS,EAAEpB,OAAO,CAACqB;MAXY,CAA1B,CAAP;IAaD;IAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;WACE,yBAAgBrB,OAAhB,EAAyBC,cAAzB,EAAyC;MAAA;;MACvC,IAAM0B,qBAAqB,GAAG3B,OAAO,CAAC4B,KAAR,CAAcC,QAAd,CAAuBC,YAAvB,CAAoCH,qBAApC,EAA9B;;MAEA,IAAIA,qBAAJ,EAA2B;QACzBnD,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,CAAwB,6FAAxB;;QACA,OAAO,iBAAQgB,OAAR,CAAgBD,SAAhB,CAAP;MACD;;MAED,IAAI,CAACE,OAAO,CAAC+B,MAAR,CAAeC,YAAf,CAA4BC,mBAAjC,EAAsD;QACpDzD,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,CAAwB,uFAAxB;;QAEA,OAAO,iBAAQgB,OAAR,CAAgBD,SAAhB,CAAP;MACD;;MAED,OAAO,KAAKoC,4BAAL,CAAkClC,OAAlC,EAA2CC,cAA3C,EACJqB,IADI,CACC;QAAA,OAAM,MAAI,CAACa,4BAAL,EAAN;MAAA,CADD,EAEJb,IAFI,CAEC;QAAA,OAAM,MAAI,CAACc,UAAL,CAAgBpC,OAAhB,CAAN;MAAA,CAFD,EAGJsB,IAHI,CAGC,YAAM;QACV,MAAI,CAAC/C,KAAL,GAAauB,SAAb;;QAEAtB,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,CAAwB,iEAAxB;;QAEA,OAAO,MAAI,CAACZ,QAAZ;MACD,CATI,EAUJkE,KAVI,CAUE,UAACC,CAAD,EAAO;QACZ;QACA9D,oBAAA,CAAYC,MAAZ,CAAmBM,IAAnB,kGAAkHuD,CAAlH;;QAEAC,gBAAA,CAAQC,oBAAR,CACEC,kBAAA,CAAmBC,sBADrB,EAEE;UACEC,cAAc,EAAE3C,OAAO,CAACY,aAD1B;UAEEgC,QAAQ,EAAE5C,OAAO,CAAC6C,QAAR,CAAiBC,KAAjB,CAAuB,GAAvB,EAA4BC,GAA5B,EAFZ;UAGEC,MAAM,EAAEV,CAAC,CAACW,OAHZ;UAIEC,KAAK,EAAEZ,CAAC,CAACY;QAJX,CAFF;;QAUA,OAAO,iBAAQnD,OAAR,CAAgBD,SAAhB,CAAP;MACD,CAzBI,CAAP;IA0BD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "2.31.4",
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.31.4",
28
- "@webex/test-helper-chai": "2.31.4",
29
- "@webex/test-helper-mocha": "2.31.4",
30
- "@webex/test-helper-mock-webex": "2.31.4",
31
- "@webex/test-helper-retry": "2.31.4",
32
- "@webex/test-helper-test-users": "2.31.4",
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.31.4",
39
+ "@webex/common": "2.33.0",
40
40
  "@webex/internal-media-core": "^0.0.7-beta",
41
- "@webex/internal-plugin-conversation": "2.31.4",
42
- "@webex/internal-plugin-device": "2.31.4",
43
- "@webex/internal-plugin-mercury": "2.31.4",
44
- "@webex/internal-plugin-metrics": "2.31.4",
45
- "@webex/internal-plugin-support": "2.31.4",
46
- "@webex/internal-plugin-user": "2.31.4",
47
- "@webex/plugin-people": "2.31.4",
48
- "@webex/plugin-rooms": "2.31.4",
49
- "@webex/webex-core": "2.31.4",
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
- export const VIDEO_RESOLUTIONS = {
971
- [QUALITY_LEVELS.LOW]: {
974
+
975
+ export const AVALIABLE_RESOLUTIONS = {
976
+ '360p': {
972
977
  video: {
973
978
  width: {
974
- max: 320,
975
- ideal: 320
979
+ max: 640,
980
+ ideal: 640
976
981
  },
977
982
  height: {
978
- max: 180,
979
- ideal: 180
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: 360,
992
- ideal: 360
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 MAX_FRAMESIZES = {
1016
- [QUALITY_LEVELS.LOW]: 1620,
1017
- [QUALITY_LEVELS.MEDIUM]: 3600,
1018
- [QUALITY_LEVELS.HIGH]: 8192
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
  */
@@ -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.HIGH;
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;
@@ -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
- * Sets the quality level of all meeting media (incoming/outgoing)
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
- MAX_FRAMESIZES
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 setMaxFs = (sdp, level = QUALITY_LEVELS.HIGH) => {
74
- if (!MAX_FRAMESIZES[level]) {
75
- throw new ParameterError(`setMaxFs: unable to set max framesize, value for level "${level}" is not defined`);
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
- replaceSdp = replaceSdp.replace(/(\na=fmtp:(\d+).*profile-level-id=.*)/gi, `$1;${maxFsLine}`);
80
+ const modifiedSdp = PeerConnectionUtils.adjustH264Profile(sdp, maxFs);
83
81
 
84
- return replaceSdp;
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