Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 5 additions & 31 deletions packages/@webex/plugin-meetings/src/meetings/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* globals window */

import {CapabilityState, WebCapabilities} from '@webex/web-capabilities';
import {
_CREATED_,
_INCOMING_,
Expand Down Expand Up @@ -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<boolean>}
*/
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
Expand All @@ -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,
{
Expand Down
155 changes: 155 additions & 0 deletions packages/@webex/plugin-meetings/test/unit/spec/meetings/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
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';
import Metrics from '@webex/plugin-meetings/src/metrics';
import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';

Expand Down Expand Up @@ -350,4 +354,155 @@ describe('plugin-meetings', () => {
assert.equal(correlationId, false);
});
});

describe('#checkH264Support', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe could be nice to have additional tests for next things here if we addng tests for this method:

  • No MEDIA_CODEC_MISSING re-triggered logic
  • MEDIA_CODEC_LOADED fires when codec becomes available
  • Logger calls for loaded/missing messages

let isCapableOfReceivingVideoCodecStub;
let triggerStub;
let loggerErrorStub;
let loggerLogStub;

beforeEach(() => {
isCapableOfReceivingVideoCodecStub = sinon.stub(
WebCapabilities,
'isCapableOfReceivingVideoCodec'
);
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 () => {
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();
});

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();
});
});
});
Loading