From ac098becbffc798cfd8293ff174f43d113f925f8 Mon Sep 17 00:00:00 2001 From: Filip Nowakowski Date: Wed, 4 Mar 2026 12:45:25 +0100 Subject: [PATCH 1/3] fix(plugin-meetings): replace H.264 codec check with WebCapabilities integration --- .../plugin-meetings/src/meetings/util.ts | 36 ++------- .../test/unit/spec/meetings/utils.js | 80 +++++++++++++++++++ 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/packages/@webex/plugin-meetings/src/meetings/util.ts b/packages/@webex/plugin-meetings/src/meetings/util.ts index a3f1d98cfa2..b4f1f97b2d8 100644 --- a/packages/@webex/plugin-meetings/src/meetings/util.ts +++ b/packages/@webex/plugin-meetings/src/meetings/util.ts @@ -1,5 +1,6 @@ /* globals window */ +import {CapabilityState, WebCapabilities} from '@webex/web-capabilities'; import { _CREATED_, _INCOMING_, @@ -152,32 +153,6 @@ MeetingsUtil.parseDefaultSiteFromMeetingPreferences = (userPreferences) => { return result; }; -/** - * Will check to see if the H.264 media codec is supported. - * @async - * @private - * @returns {Promise} - */ -MeetingsUtil.hasH264Codec = async () => { - let hasCodec = false; - - try { - const pc = new window.RTCPeerConnection(); - const offer = await pc.createOffer({offerToReceiveVideo: true}); - - if (offer.sdp.match(/^a=rtpmap:\d+\s+H264\/\d+/m)) { - hasCodec = true; - } - pc.close(); - } catch (error) { - LoggerProxy.logger.warn( - 'Meetings:util#hasH264Codec --> Error creating peerConnection for H.264 test.' - ); - } - - return hasCodec; -}; - /** * Notifies the user whether or not the H.264 * codec is present. Will continuously check @@ -193,22 +168,21 @@ MeetingsUtil.checkH264Support = async function checkH264Support(options: { firstChecked: number; disableNotifications: boolean; }) { - const {hasH264Codec} = MeetingsUtil; const {firstChecked, disableNotifications} = options || {}; const delay = 5e3; // ms const maxDuration = 3e5; // ms const shouldTrigger = firstChecked === undefined; const shouldStopChecking = firstChecked && Date.now() - firstChecked >= maxDuration; - // Disable notifications and start H.264 download only if (disableNotifications) { - hasH264Codec(); - return; } + const isH264Available = + WebCapabilities.isCapableOfReceivingVideoCodec('video/H264') === CapabilityState.CAPABLE; + // Codec loaded trigger event notification - if (await hasH264Codec()) { + if (isH264Available) { Trigger.trigger( this, { diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js b/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js index dadda69b7f8..23bae2e8eb7 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js @@ -1,7 +1,13 @@ import {assert} from '@webex/test-helper-chai'; import sinon from 'sinon'; +import {CapabilityState, WebCapabilities} from '@webex/web-capabilities'; +import Trigger from '@webex/plugin-meetings/src/common/events/trigger-proxy'; +import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy'; import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util'; +import {EVENT_TRIGGERS} from '@webex/plugin-meetings/src/constants'; + +const originalCheckH264Support = MeetingsUtil.checkH264Support; import Metrics from '@webex/plugin-meetings/src/metrics'; import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants'; @@ -350,4 +356,78 @@ describe('plugin-meetings', () => { assert.equal(correlationId, false); }); }); + + describe('#checkH264Support', () => { + let isCapableOfReceivingVideoCodecStub; + let triggerStub; + let loggerErrorStub; + + beforeEach(() => { + MeetingsUtil.checkH264Support = originalCheckH264Support; + isCapableOfReceivingVideoCodecStub = sinon.stub( + WebCapabilities, + 'isCapableOfReceivingVideoCodec' + ); + triggerStub = sinon.stub(Trigger, 'trigger'); + loggerErrorStub = sinon.stub(LoggerProxy.logger, 'error'); + }); + + it('returns immediately without codec check when disableNotifications is true', async () => { + await MeetingsUtil.checkH264Support.call({}, {disableNotifications: true}); + + assert.notCalled(isCapableOfReceivingVideoCodecStub); + assert.notCalled(triggerStub); + }); + + it('triggers MEDIA_CODEC_LOADED when H.264 is capable', async () => { + isCapableOfReceivingVideoCodecStub.returns(CapabilityState.CAPABLE); + + await MeetingsUtil.checkH264Support.call({}); + + assert.calledWith(isCapableOfReceivingVideoCodecStub, 'video/H264'); + assert.calledWith( + triggerStub, + {}, + {file: 'meetings/util', function: 'checkH264Support'}, + EVENT_TRIGGERS.MEDIA_CODEC_LOADED + ); + }); + + it('triggers MEDIA_CODEC_MISSING and schedules retry when H.264 is not capable', async () => { + const clock = sinon.useFakeTimers(); + isCapableOfReceivingVideoCodecStub.returns(CapabilityState.NOT_CAPABLE); + + await MeetingsUtil.checkH264Support.call({}); + + assert.calledWith( + triggerStub, + {}, + {file: 'meetings/util', function: 'checkH264Support'}, + EVENT_TRIGGERS.MEDIA_CODEC_MISSING + ); + triggerStub.resetHistory(); + isCapableOfReceivingVideoCodecStub.resetHistory(); + + await clock.tick(5000); + + assert.called(isCapableOfReceivingVideoCodecStub); + clock.restore(); + }); + + it('logs error and stops retrying when past maxDuration', async () => { + const clock = sinon.useFakeTimers(); + isCapableOfReceivingVideoCodecStub.returns(CapabilityState.NOT_CAPABLE); + + const firstCheckTime = 1; + await clock.tick(firstCheckTime); + await MeetingsUtil.checkH264Support.call({}, {firstChecked: firstCheckTime}); + await clock.tick(300000); + + assert.calledWith( + loggerErrorStub, + 'Meetings:util#checkH264Support --> Timed out waiting for H264 codec to load.' + ); + clock.restore(); + }); + }); }); From 341128c711a817a129281a2452c7f4bc85365525 Mon Sep 17 00:00:00 2001 From: Filip Nowakowski Date: Wed, 4 Mar 2026 13:12:14 +0100 Subject: [PATCH 2/3] fix(plugin-meetings): cleanup --- .../@webex/plugin-meetings/test/unit/spec/meetings/utils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js b/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js index 23bae2e8eb7..b0dd0185413 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js @@ -6,8 +6,6 @@ import Trigger from '@webex/plugin-meetings/src/common/events/trigger-proxy'; import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy'; import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util'; import {EVENT_TRIGGERS} from '@webex/plugin-meetings/src/constants'; - -const originalCheckH264Support = MeetingsUtil.checkH264Support; import Metrics from '@webex/plugin-meetings/src/metrics'; import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants'; @@ -363,7 +361,6 @@ describe('plugin-meetings', () => { let loggerErrorStub; beforeEach(() => { - MeetingsUtil.checkH264Support = originalCheckH264Support; isCapableOfReceivingVideoCodecStub = sinon.stub( WebCapabilities, 'isCapableOfReceivingVideoCodec' From 7d7795567f9574f9e2aef53cdef816f4f0f94c07 Mon Sep 17 00:00:00 2001 From: Filip Nowakowski Date: Mon, 9 Mar 2026 09:52:55 +0100 Subject: [PATCH 3/3] test(meetings): enhance H.264 codec support tests with logging and retry behavior --- .../test/unit/spec/meetings/utils.js | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js b/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js index b0dd0185413..f1c7ecd8ed0 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js @@ -359,6 +359,7 @@ describe('plugin-meetings', () => { let isCapableOfReceivingVideoCodecStub; let triggerStub; let loggerErrorStub; + let loggerLogStub; beforeEach(() => { isCapableOfReceivingVideoCodecStub = sinon.stub( @@ -367,6 +368,7 @@ describe('plugin-meetings', () => { ); triggerStub = sinon.stub(Trigger, 'trigger'); loggerErrorStub = sinon.stub(LoggerProxy.logger, 'error'); + loggerLogStub = sinon.stub(LoggerProxy.logger, 'log'); }); it('returns immediately without codec check when disableNotifications is true', async () => { @@ -426,5 +428,81 @@ describe('plugin-meetings', () => { ); clock.restore(); }); + + it('does not re-trigger MEDIA_CODEC_MISSING on retries when codec remains not capable', async () => { + const clock = sinon.useFakeTimers(); + isCapableOfReceivingVideoCodecStub.returns(CapabilityState.NOT_CAPABLE); + + await MeetingsUtil.checkH264Support.call({}); + const missingTriggerCount = triggerStub.getCalls().filter( + (call) => call.args[2] === EVENT_TRIGGERS.MEDIA_CODEC_MISSING + ).length; + assert.equal(missingTriggerCount, 1, 'MEDIA_CODEC_MISSING should be triggered only once'); + + triggerStub.resetHistory(); + await clock.tick(5000); + + const missingTriggerCountAfterRetry = triggerStub.getCalls().filter( + (call) => call.args[2] === EVENT_TRIGGERS.MEDIA_CODEC_MISSING + ).length; + assert.equal( + missingTriggerCountAfterRetry, + 0, + 'MEDIA_CODEC_MISSING should not be re-triggered on retry' + ); + clock.restore(); + }); + + it('triggers MEDIA_CODEC_LOADED when codec becomes available on retry', async () => { + const clock = sinon.useFakeTimers(); + isCapableOfReceivingVideoCodecStub + .onFirstCall() + .returns(CapabilityState.NOT_CAPABLE) + .onSecondCall() + .returns(CapabilityState.CAPABLE); + + await MeetingsUtil.checkH264Support.call({}); + assert.calledWith( + triggerStub, + {}, + {file: 'meetings/util', function: 'checkH264Support'}, + EVENT_TRIGGERS.MEDIA_CODEC_MISSING + ); + + triggerStub.resetHistory(); + await clock.tick(5000); + + assert.calledWith( + triggerStub, + {}, + {file: 'meetings/util', function: 'checkH264Support'}, + EVENT_TRIGGERS.MEDIA_CODEC_LOADED + ); + clock.restore(); + }); + + it('logs loaded message when H.264 codec is capable', async () => { + isCapableOfReceivingVideoCodecStub.returns(CapabilityState.CAPABLE); + + await MeetingsUtil.checkH264Support.call({}); + + assert.calledWith( + loggerLogStub, + 'Meetings:util#checkH264Support --> H264 codec loaded successfully.' + ); + }); + + it('logs missing message when H.264 codec is not capable on first check', async () => { + const clock = sinon.useFakeTimers(); + isCapableOfReceivingVideoCodecStub.returns(CapabilityState.NOT_CAPABLE); + + await MeetingsUtil.checkH264Support.call({}); + + assert.calledWith( + loggerLogStub, + 'Meetings:util#checkH264Support --> H264 codec is missing.' + ); + clock.restore(); + }); }); });