Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0e4c10d
feat: av1 attempt
fnowakow Sep 25, 2025
8227743
fix: old media request
fnowakow Sep 26, 2025
a6d44d9
feat: preferred av1 codec
fnowakow Oct 7, 2025
a3b2525
feat: enhance media request handling for AV1 codec support
fnowakow Oct 20, 2025
c247499
feat: add maxPicSize handling and utility functions for media requests
fnowakow Oct 22, 2025
3f74f74
refactor: remove unused import from constants file
fnowakow Oct 27, 2025
2008cf1
refactor: consolidate codec handling by removing constants and introd…
fnowakow Oct 29, 2025
19f1a54
feat: update media request handling to support new remote video resol…
fnowakow Oct 29, 2025
a442b1f
feat: enhance media request manager to handle maxPicSize updates and …
fnowakow Oct 29, 2025
9a04467
fix: update getEffectiveMaxPicSize to correctly return maxPicSize if set
fnowakow Oct 29, 2025
b7a78a1
fix: revert useless moved type
fnowakow Nov 3, 2025
a7387cb
refactor: update codec handling to use web-capabilities for codec ava…
fnowakow Nov 3, 2025
73148b1
refactor: media codec helper improvements
fnowakow Nov 3, 2025
4eafad3
refactor: rename bitrate function to improve clarity and update usage…
fnowakow Nov 3, 2025
3d5b1bb
refactor: rename picSizeToFrameSize to pixelsToMacroblocks
fnowakow Nov 3, 2025
5a6ff63
refactor: update function names in receiveSlot for clarity
fnowakow Nov 3, 2025
4137ec1
refactor: replace string literals with enum for remote video resolutions
fnowakow Nov 3, 2025
9d7222c
refactor: deprecate old types and update remote video resolution hand…
fnowakow Nov 3, 2025
c69a904
refactor: update codec availability checks to use WebCapabilities for…
fnowakow Nov 3, 2025
628569e
fix: addressed PR comments
fnowakow Nov 12, 2025
486a5b6
refactor: update codecInfo method to accept functions for maxFs and m…
fnowakow Nov 12, 2025
1cd822d
refactor: update tests
fnowakow Nov 17, 2025
59d0ef8
refactor: enhance media codec helpers and update remote media resolut…
fnowakow Nov 17, 2025
1c80746
test: add unit tests for AV1 and H264 media codec helpers
fnowakow Nov 17, 2025
758b704
test: add stubs for video codec capability checks in meeting tests
fnowakow Nov 17, 2025
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
2 changes: 1 addition & 1 deletion packages/@webex/media-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/internal-media-core": "2.18.5",
"@webex/internal-media-core": "link:../../../../webrtc-media-core",
"@webex/ts-events": "^1.1.0",
"@webex/web-media-effects": "2.27.1"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/@webex/plugin-meetings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,12 @@
"jsdom-global": "3.0.2",
"prettier": "^2.7.1",
"sinon": "^9.2.4",
"typed-emitter": "^2.1.0",
"typescript": "^4.7.4"
},
"dependencies": {
"@webex/common": "workspace:*",
"@webex/event-dictionary-ts": "^1.0.1819",
"@webex/internal-media-core": "2.18.5",
"@webex/internal-media-core": "link:../../../../webrtc-media-core",
"@webex/internal-plugin-conversation": "workspace:*",
"@webex/internal-plugin-device": "workspace:*",
"@webex/internal-plugin-llm": "workspace:*",
Expand All @@ -75,7 +74,7 @@
"@webex/plugin-people": "workspace:*",
"@webex/plugin-rooms": "workspace:*",
"@webex/ts-sdp": "^1.8.1",
"@webex/web-capabilities": "^1.6.0",
"@webex/web-capabilities": "link:../../../../web-capabilities",
"@webex/webex-core": "workspace:*",
"ampersand-collection": "^2.0.2",
"bowser": "^2.11.0",
Expand All @@ -86,6 +85,7 @@
"javascript-state-machine": "^3.1.0",
"jwt-decode": "3.1.2",
"lodash": "^4.17.21",
"typed-emitter": "^2.1.0",
"uuid": "^3.3.2",
"webrtc-adapter": "^8.1.2"
},
Expand Down
35 changes: 4 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 @@ -151,32 +152,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 @@ -192,22 +167,20 @@ 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;
const isH264Available =
WebCapabilities.isCapableOfReceivingVideoCodec('video/H264') === CapabilityState.CAPABLE;

// Disable notifications and start H.264 download only
if (disableNotifications) {
hasH264Codec();

return;
}

// Codec loaded trigger event notification
if (await hasH264Codec()) {
if (isH264Available) {
Trigger.trigger(
this,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {RemoteVideoResolution} from '../types';
import {AV1CodecInfo, H264CodecInfo, SupportedResolution} from './types';

export const H264_CODEC_PARAMETERS = {
'90p': {
maxFs: 60,
},
'180p': {
maxFs: 240,
},
'360p': {
maxFs: 920,
},
'540p': {
maxFs: 2040,
},
'720p': {
maxFs: 3600,
},
'1080p': {
maxFs: 8192,
},
} satisfies Record<SupportedResolution, Omit<H264CodecInfo, 'codec'>>;

export const AV1_CODEC_PARAMETERS = {
'90p': {
maxPicSize: 147_456,
levelIdx: 0,
maxWidth: 1152,
maxHeight: 2048,
maxDecodeRate: 5_529_600,
},
'180p': {
maxPicSize: 147_456,
levelIdx: 0,
maxWidth: 1152,
maxHeight: 2048,
maxDecodeRate: 5_529_600,
},
'360p': {
maxPicSize: 278_784,
levelIdx: 1,
maxWidth: 2816,
maxHeight: 1584,
maxDecodeRate: 10_454_400,
},
'540p': {
maxPicSize: 665_856,
levelIdx: 4,
maxWidth: 4352,
maxHeight: 2448,
maxDecodeRate: 24_969_600,
},
'720p': {
maxPicSize: 1_065_024,
levelIdx: 5,
maxWidth: 5504,
maxHeight: 3096,
maxDecodeRate: 39_938_400,
},
'1080p': {
maxPicSize: 2_359_296,
levelIdx: 9,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So far, I believe @k-wasniowski said that we won't support the different levelIdx, ot I'm missing something?

maxWidth: 6144,
maxHeight: 3456,
maxDecodeRate: 155_713_536,
},
} satisfies Record<SupportedResolution, Omit<AV1CodecInfo, 'codec'>>;

export const CODEC_DEFAULTS = {
h264: {
...H264_CODEC_PARAMETERS['1080p'],
maxFps: 3000,
maxMbps: 245760,
},
av1: {
...AV1_CODEC_PARAMETERS['1080p'],
tier: 0,
},
};

export const PANE_SIZE_TO_RESOLUTION = {
thumbnail: '90p',
'very small': '180p',
small: '360p',
medium: '720p',
large: '1080p',
best: '1080p',
} satisfies Record<RemoteVideoResolution, SupportedResolution>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/* eslint-disable class-methods-use-this */
import {
AV1Codec,
getRecommendedMaxBitrateForPicSize,
getFrameSizeForPicSize,
CodecInfo as WcmeCodecInfo,
} from '@webex/internal-media-core';
import {AV1_CODEC_PARAMETERS, CODEC_DEFAULTS, PANE_SIZE_TO_RESOLUTION} from './constants';
import {MediaCodecHelper, AV1CodecInfo, SupportedResolution} from './types';
import {MediaRequest, RemoteVideoResolution} from '../types';
import LoggerProxy from '../../common/logs/logger-proxy';

/**
* Class for AV1 media codec info
*/
export default class MediaCodecHelperAV1 implements MediaCodecHelper {
/**
* Gets the AV1 codec info
*
* @param {Object} options - The options for the AV1 codec info
* @returns {AV1CodecInfo} The AV1 codec info
*/
getCodecInfo(options: {getMaxPicSize?: () => number}): AV1CodecInfo {
if (!options.getMaxPicSize) {
return undefined;
}

return this.getParameters(options.getMaxPicSize());
}

/**
* Degrades the media request
*
* @param {MediaRequest} mr - The media request to degrade
* @param {Resolution} resolution - The resolution to degrade to
* @returns {number} The total macroblocks requested
*/
degradeMediaRequest(mr: MediaRequest, resolution: SupportedResolution): number {
if (mr.codecInfo?.codec !== 'av1') {
return 0;
}

const preferredMaxPicSize = mr.preferredMaxPicSize
? mr.preferredMaxPicSize
: CODEC_DEFAULTS.av1.maxPicSize;

mr.codecInfo.maxPicSize = Math.min(
preferredMaxPicSize,
mr.codecInfo.maxPicSize || CODEC_DEFAULTS.av1.maxPicSize,
AV1_CODEC_PARAMETERS[resolution].maxPicSize
);

// we only consider sources with "live" state
const slotsWithLiveSource = mr.receiveSlots.filter((rs) => rs.sourceState === 'live');

return getFrameSizeForPicSize(mr.codecInfo.maxPicSize) * slotsWithLiveSource.length;
}

/**
* Gets the max payload bits per second
*
* @param {MediaRequest} mediaRequest - The media request to get the max payload bits per second from
* @returns {number} The max payload bits per second
*/
getMaxPayloadBitsPerSecond(mediaRequest: MediaRequest): number {
if (mediaRequest.codecInfo?.codec !== 'av1') {
return 0;
}

return getRecommendedMaxBitrateForPicSize(mediaRequest.codecInfo.maxPicSize);
}

/**
* Gets the WCME codec infos
*
* @param {MediaRequest} mr - The media request to get the WCME codec infos from
* @returns {WcmeCodecInfo[]} The WCME codec infos
*/
getWCMECodecInfos(mr: MediaRequest): WcmeCodecInfo[] {
if (mr.codecInfo?.codec !== 'av1') {
return [];
}

return [
WcmeCodecInfo.fromAv1(
45,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could we have any constant for this param here?

new AV1Codec(
mr.codecInfo.levelIdx || CODEC_DEFAULTS.av1.levelIdx,
mr.codecInfo.tier || CODEC_DEFAULTS.av1.tier,
mr.codecInfo.maxWidth || CODEC_DEFAULTS.av1.maxWidth,
mr.codecInfo.maxHeight || CODEC_DEFAULTS.av1.maxHeight,
mr.codecInfo.maxPicSize || CODEC_DEFAULTS.av1.maxPicSize,
mr.codecInfo.maxDecodeRate || CODEC_DEFAULTS.av1.maxDecodeRate
)
),
];
}

/**
* Gets the highest compatible AV1 codec parameters for the given maximum picture size
*
* @param {number} maxPicSize - The maximum picture size
* @returns {AV1CodecInfo} The AV1 codec parameters
*/
private getParameters(maxPicSize: number): AV1CodecInfo {
const parameters = Object.values(AV1_CODEC_PARAMETERS)
// filter out parameters with a max picture size greater than the given max picture size
.filter((entry) => maxPicSize <= entry.maxPicSize)
// sort by max picture size descending
.sort((a, b) => b.maxPicSize - a.maxPicSize);

// return the highest compatible AV1 codec parameters
return {
codec: 'av1',
...parameters[0],
};
}

/**
* Converts pane size into av1 maxPicSize
*
* @param {RemoteVideoResolution} paneSize - The pane size to get the max pic size for
* @returns {number} The max pic size
*/
getMaxPicSize(paneSize: RemoteVideoResolution): number {
let resolution: SupportedResolution;

if (paneSize in PANE_SIZE_TO_RESOLUTION) {
resolution = PANE_SIZE_TO_RESOLUTION[paneSize];
} else {
LoggerProxy.logger.warn(
`MediaCodecHelperAV1#getMaxPicSize --> unsupported paneSize: ${paneSize}, using "medium" instead`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: you can use in log PANE_SIZE_TO_RESOLUTION.medium

);
resolution = PANE_SIZE_TO_RESOLUTION.medium;
}

return AV1_CODEC_PARAMETERS[resolution].maxPicSize;
}

/**
* Gets the max pic size for the given width and height
*
* @param {number} width - The width of the video element
* @param {number} height - The height of the video element
* @returns {number | undefined} The max pic size for the given width and height, or undefined if the width or height is 0
*/
getSizeHintMaxPicSize(width: number, height: number): number | undefined {
if (width === 0 || height === 0) {
return undefined;
}

// we switch to the next resolution level when the height is 10% more than the current resolution height
// except for 1080p - we switch to it immediately when the height is more than 720p
const threshold = 1.1;
const getThresholdHeight = (h: number) => Math.round(h * threshold);

if (height < getThresholdHeight(90)) {
return AV1_CODEC_PARAMETERS['90p'].maxPicSize;
}
if (height < getThresholdHeight(180)) {
return AV1_CODEC_PARAMETERS['180p'].maxPicSize;
}
if (height < getThresholdHeight(360)) {
return AV1_CODEC_PARAMETERS['360p'].maxPicSize;
}
if (height < getThresholdHeight(540)) {
return AV1_CODEC_PARAMETERS['540p'].maxPicSize;
}
if (height <= 720) {
return AV1_CODEC_PARAMETERS['720p'].maxPicSize;
}

return AV1_CODEC_PARAMETERS['1080p'].maxPicSize;
}
}
Loading
Loading