mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
feat(voice): scale screen share bitrate to the selected rung (#2414)
This commit is contained in:
1 parent
8158d44732
commit
6c08813f9b
42 files changed
+1123
-1175
No files matched your search
@@ -9,11 +9,10 @@ import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource';
|
||||
import VoiceSettings, {type ScreenshareResolution, type StreamingMode} from '@app/features/voice/state/VoiceSettings';
|
||||
import {isNativeScreenShareTrack} from '@app/features/voice/utils/native_screen_capture_bridge/shared';
|
||||
import {
|
||||
getScreenShareBitrateBps,
|
||||
getScreenShareDimensions,
|
||||
getScreenShareEncoding,
|
||||
resolveScreenShareFrameRate,
|
||||
SCREEN_SHARE_DEGRADATION_PREFERENCE,
|
||||
SCREEN_SHARE_FORCED_VIDEO_BITRATE_BPS,
|
||||
SUPPORTED_SCREEN_SHARE_FRAME_RATES,
|
||||
type SupportedScreenShareFrameRate,
|
||||
} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
@@ -30,8 +29,6 @@ const FRAME_RATE_GRACE_FPS = 8;
|
||||
const STEP_DOWN_COOLDOWN_MS = 10_000;
|
||||
const STEP_UP_COOLDOWN_MS = 5_000;
|
||||
const MAX_STEP_UP_COOLDOWN_MS = 30_000;
|
||||
const BANDWIDTH_BITRATE_STEP_FACTOR = 0.6;
|
||||
const OBSERVED_TARGET_BITRATE_HEADROOM = 1.1;
|
||||
|
||||
const SCREEN_SHARE_240P_DESCRIPTOR = msg({
|
||||
message: '240p',
|
||||
@@ -78,11 +75,6 @@ const QUALITY_LIMITED_DESCRIPTOR = msg({
|
||||
comment: 'Fallback reason label in adaptive screen-share quality toasts.',
|
||||
context: 'adaptive-screen-share',
|
||||
});
|
||||
const LOWERED_SCREEN_SHARE_BITRATE_DESCRIPTOR = msg({
|
||||
message: 'Lowered screen share bitrate - {reason}',
|
||||
comment: 'Toast shown after adaptive screen-share quality lowers bitrate. {reason} is the limitation reason.',
|
||||
context: 'adaptive-screen-share',
|
||||
});
|
||||
const LOWERED_SCREEN_SHARE_FRAME_RATE_DESCRIPTOR = msg({
|
||||
message: 'Lowered screen share to {frameRate} FPS - {reason}',
|
||||
comment:
|
||||
@@ -129,7 +121,6 @@ export interface OutboundVideoAdaptationStats {
|
||||
framesPerSecond?: number;
|
||||
frameWidth?: number;
|
||||
frameHeight?: number;
|
||||
targetBitrate?: number;
|
||||
}
|
||||
|
||||
export type PreviousOutboundVideoSample = OutboundVideoAdaptationStats | null;
|
||||
@@ -171,12 +162,6 @@ function shouldPreferFrameRateStepDown(mode: StreamingMode, reason: QualityLimit
|
||||
return reason === 'cpu';
|
||||
}
|
||||
|
||||
function getSenderMaxBitrate(sender: RTCRtpSender): number | null {
|
||||
const encodings = sender.getParameters().encodings;
|
||||
const maxBitrate = encodings?.find((encoding) => typeof encoding.maxBitrate === 'number')?.maxBitrate;
|
||||
return typeof maxBitrate === 'number' && maxBitrate > 0 ? maxBitrate : null;
|
||||
}
|
||||
|
||||
function getHigherResolution(
|
||||
resolution: ScreenshareResolution,
|
||||
configuredResolution: ScreenshareResolution,
|
||||
@@ -255,7 +240,6 @@ export function extractOutboundVideoAdaptationStats(stats: RTCStatsReport): Outb
|
||||
framesPerSecond?: number;
|
||||
frameWidth?: number;
|
||||
frameHeight?: number;
|
||||
targetBitrate?: number;
|
||||
};
|
||||
if (!isOutboundVideoReport(report)) continue;
|
||||
return {
|
||||
@@ -266,7 +250,6 @@ export function extractOutboundVideoAdaptationStats(stats: RTCStatsReport): Outb
|
||||
framesPerSecond: typeof report.framesPerSecond === 'number' ? report.framesPerSecond : undefined,
|
||||
frameWidth: typeof report.frameWidth === 'number' ? report.frameWidth : undefined,
|
||||
frameHeight: typeof report.frameHeight === 'number' ? report.frameHeight : undefined,
|
||||
targetBitrate: typeof report.targetBitrate === 'number' ? report.targetBitrate : undefined,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@@ -299,16 +282,6 @@ export function isOutboundFrameRateBelowTarget(
|
||||
return frameRate < Math.max(1, targetFrameRate - FRAME_RATE_GRACE_FPS);
|
||||
}
|
||||
|
||||
export function computeAdaptiveBitrate(
|
||||
frameRate: number,
|
||||
maxBitrateBps?: number,
|
||||
observedTargetBitrateBps?: number,
|
||||
): number {
|
||||
const base = getScreenShareEncoding(frameRate, maxBitrateBps).maxBitrate ?? 0;
|
||||
if (typeof observedTargetBitrateBps !== 'number' || observedTargetBitrateBps <= 0) return base;
|
||||
return Math.min(base, Math.round(observedTargetBitrateBps * OBSERVED_TARGET_BITRATE_HEADROOM));
|
||||
}
|
||||
|
||||
export function computeResolutionScale(
|
||||
fromResolution: ScreenshareResolution,
|
||||
toResolution: ScreenshareResolution,
|
||||
@@ -323,16 +296,15 @@ export async function applyResolutionFrameRateAndBitrate(
|
||||
sender: RTCRtpSender,
|
||||
resolution: ScreenshareResolution,
|
||||
frameRate: SupportedScreenShareFrameRate,
|
||||
maxBitrateBps?: number,
|
||||
observedTargetBitrateBps?: number,
|
||||
): Promise<void> {
|
||||
const dimensions = getScreenShareDimensions(resolution);
|
||||
const maxBitrate = getScreenShareBitrateBps(resolution, frameRate);
|
||||
if (isNativeScreenShareTrack(track.mediaStreamTrack)) {
|
||||
const updated = updateVoiceEngineV2ScreenEncodingFromMediaEngine({
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
frameRate,
|
||||
maxBitrateBps: computeAdaptiveBitrate(frameRate, maxBitrateBps, observedTargetBitrateBps),
|
||||
maxBitrateBps: maxBitrate,
|
||||
});
|
||||
if (!updated) {
|
||||
logger.warn('Skipped native screen-share encoding update because v2 screen state is unavailable', {
|
||||
@@ -361,7 +333,6 @@ export async function applyResolutionFrameRateAndBitrate(
|
||||
}
|
||||
const parameters = sender.getParameters();
|
||||
const encodings = parameters.encodings?.length ? parameters.encodings : [{}];
|
||||
const maxBitrate = computeAdaptiveBitrate(frameRate, maxBitrateBps, observedTargetBitrateBps);
|
||||
parameters.degradationPreference = SCREEN_SHARE_DEGRADATION_PREFERENCE;
|
||||
parameters.encodings = encodings.map((encoding) => ({
|
||||
...encoding,
|
||||
@@ -391,7 +362,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
private lastStepUpAt = 0;
|
||||
private stepUpCooldownMs = STEP_UP_COOLDOWN_MS;
|
||||
private previousSample: PreviousOutboundVideoSample = null;
|
||||
private bandwidthBitrateStepActive = false;
|
||||
|
||||
get qualitySnapshot(): AdaptiveQualitySnapshot {
|
||||
const configured = getConfiguredQuality();
|
||||
@@ -449,7 +419,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
this.lastStepUpAt = 0;
|
||||
this.stepUpCooldownMs = STEP_UP_COOLDOWN_MS;
|
||||
this.previousSample = null;
|
||||
this.bandwidthBitrateStepActive = false;
|
||||
this.configuredResolution = null;
|
||||
this.configuredFrameRate = null;
|
||||
this.effectiveResolution = null;
|
||||
@@ -474,7 +443,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
screenShare.sender,
|
||||
snapshot.configuredResolution,
|
||||
snapshot.configuredFrameRate,
|
||||
SCREEN_SHARE_FORCED_VIDEO_BITRATE_BPS,
|
||||
);
|
||||
logger.info('Restored configured screen share quality', {
|
||||
resolution: snapshot.configuredResolution,
|
||||
@@ -528,7 +496,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
this.stepUpStreak = 0;
|
||||
} else if (stats.qualityLimitationReason === 'none') {
|
||||
this.stepDownStreak = 0;
|
||||
this.bandwidthBitrateStepActive = false;
|
||||
if (this.isAdapted) {
|
||||
this.stepUpStreak++;
|
||||
}
|
||||
@@ -566,30 +533,20 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
const nextFrameRate = shouldPreferFrameRateStepDown(streamingMode, reason)
|
||||
? getLowerFrameRate(currentFrameRate)
|
||||
: null;
|
||||
const currentSenderMaxBitrate =
|
||||
getSenderMaxBitrate(screenShare.sender) ??
|
||||
computeAdaptiveBitrate(currentFrameRate, SCREEN_SHARE_FORCED_VIDEO_BITRATE_BPS, stats.targetBitrate);
|
||||
const canApplyBandwidthBitrateStep =
|
||||
reason === 'bandwidth' && !nextFrameRate && !this.bandwidthBitrateStepActive && currentSenderMaxBitrate > 0;
|
||||
const nextResolution =
|
||||
nextFrameRate || canApplyBandwidthBitrateStep ? currentResolution : getLowerResolution(currentResolution);
|
||||
const nextResolution = nextFrameRate ? currentResolution : getLowerResolution(currentResolution);
|
||||
if (!nextResolution) {
|
||||
this.stepDownStreak = 0;
|
||||
return;
|
||||
}
|
||||
const nextMaxBitrateBps = canApplyBandwidthBitrateStep
|
||||
? Math.max(1, Math.round(currentSenderMaxBitrate * BANDWIDTH_BITRATE_STEP_FACTOR))
|
||||
: SCREEN_SHARE_FORCED_VIDEO_BITRATE_BPS;
|
||||
const nextEffectiveFrameRate = nextFrameRate ?? currentFrameRate;
|
||||
const stepKind = nextFrameRate ? 'framerate' : canApplyBandwidthBitrateStep ? 'bitrate' : 'resolution';
|
||||
const nextMaxBitrateBps = getScreenShareBitrateBps(nextResolution, nextEffectiveFrameRate);
|
||||
const stepKind = nextFrameRate ? 'framerate' : 'resolution';
|
||||
try {
|
||||
await applyResolutionFrameRateAndBitrate(
|
||||
screenShare.track,
|
||||
screenShare.sender,
|
||||
nextResolution,
|
||||
nextEffectiveFrameRate,
|
||||
nextMaxBitrateBps,
|
||||
canApplyBandwidthBitrateStep ? undefined : stats.targetBitrate,
|
||||
);
|
||||
this.update(() => {
|
||||
this.effectiveResolution = nextResolution;
|
||||
@@ -599,7 +556,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
this.stepUpStreak = 0;
|
||||
this.lastStepDownAt = Date.now();
|
||||
this.stepUpCooldownMs = Math.min(this.stepUpCooldownMs * 2, MAX_STEP_UP_COOLDOWN_MS);
|
||||
this.bandwidthBitrateStepActive = canApplyBandwidthBitrateStep;
|
||||
});
|
||||
logger.info('Reduced screen share quality adaptively', {
|
||||
fromResolution: currentResolution,
|
||||
@@ -611,9 +567,7 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
reason,
|
||||
});
|
||||
const reasonLabel = formatLimitationReasonForToast(reason);
|
||||
if (stepKind === 'bitrate') {
|
||||
showAdaptiveScreenShareToast(i18n._(LOWERED_SCREEN_SHARE_BITRATE_DESCRIPTOR, {reason: reasonLabel}));
|
||||
} else if (stepKind === 'framerate') {
|
||||
if (stepKind === 'framerate') {
|
||||
showAdaptiveScreenShareToast(
|
||||
i18n._(LOWERED_SCREEN_SHARE_FRAME_RATE_DESCRIPTOR, {
|
||||
frameRate: nextEffectiveFrameRate,
|
||||
@@ -656,7 +610,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
this.isAdapted = false;
|
||||
this.stepUpStreak = 0;
|
||||
this.stepUpCooldownMs = STEP_UP_COOLDOWN_MS;
|
||||
this.bandwidthBitrateStepActive = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -666,7 +619,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
screenShare.sender,
|
||||
nextResolution,
|
||||
configuredFrameRate,
|
||||
SCREEN_SHARE_FORCED_VIDEO_BITRATE_BPS,
|
||||
);
|
||||
this.update(() => {
|
||||
this.effectiveResolution = nextResolution;
|
||||
@@ -674,7 +626,6 @@ class AdaptiveScreenShareEngine extends Store {
|
||||
this.isAdapted = nextResolution !== configuredResolution;
|
||||
this.stepUpStreak = 0;
|
||||
this.lastStepUpAt = Date.now();
|
||||
this.bandwidthBitrateStepActive = false;
|
||||
if (!this.isAdapted) {
|
||||
this.stepUpCooldownMs = STEP_UP_COOLDOWN_MS;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from '@app/features/voice/engine/v2/VoiceEngineV2AppAdapterAssertions';
|
||||
import {VoiceEngineV2AppReconnectPolicy} from '@app/features/voice/engine/v2/VoiceEngineV2AppReconnectPolicy';
|
||||
import VoiceRegionTeleport from '@app/features/voice/state/VoiceRegionTeleport';
|
||||
import {SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import {
|
||||
getVideoDecoderExclusionsSync,
|
||||
loadVideoDecoderExclusions,
|
||||
@@ -139,6 +140,10 @@ function createWebAudioMixOption(): RoomOptions['webAudioMix'] {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ROOM_PUBLISH_DEFAULTS: RoomOptions['publishDefaults'] = {
|
||||
screenShareEncoding: {maxBitrate: SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS, maxFramerate: 30, priority: 'high'},
|
||||
};
|
||||
|
||||
function createRoomOptions(
|
||||
e2eeKey: string | null,
|
||||
subscriberVideoCodecExclusions: RoomOptions['subscriberVideoCodecExclusions'],
|
||||
@@ -151,6 +156,7 @@ function createRoomOptions(
|
||||
adaptiveStream: false,
|
||||
dynacast: true,
|
||||
webAudioMix: createWebAudioMixOption(),
|
||||
publishDefaults: ROOM_PUBLISH_DEFAULTS,
|
||||
subscriberVideoCodecExclusions,
|
||||
};
|
||||
let e2eeKeyProvider: ExternalE2EEKeyProvider | null = null;
|
||||
@@ -675,6 +681,7 @@ export class VoiceEngineV2AppConnectionHostAdapter extends Store {
|
||||
adaptiveStream: false,
|
||||
dynacast: true,
|
||||
webAudioMix: createWebAudioMixOption(),
|
||||
publishDefaults: ROOM_PUBLISH_DEFAULTS,
|
||||
subscriberVideoCodecExclusions: cachedExclusions && cachedExclusions.length > 0 ? cachedExclusions : undefined,
|
||||
};
|
||||
if (!this.isLatestConnectionAttempt(attemptId) || this.connectionState.room !== existingRoom) {
|
||||
|
||||
@@ -12,17 +12,20 @@ import {
|
||||
import {getGpuEncoderReportSync, loadGpuEncoderReport} from '@app/features/voice/utils/GpuEncoderCapabilities';
|
||||
import {loadNativeHardwareEncoderCapabilities} from '@app/features/voice/utils/NativeHardwareEncoderCapabilities';
|
||||
import {
|
||||
capScreenShareEncodingToDimensions,
|
||||
getScreenShareEncoding,
|
||||
resolveStreamingModeSettings,
|
||||
SCREEN_SHARE_DEGRADATION_PREFERENCE,
|
||||
SCREEN_SHARE_FORCED_VIDEO_BITRATE_BPS,
|
||||
SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS,
|
||||
} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import {ScreenShareRollbackIncompleteError} from '@app/features/voice/utils/ScreenShareRollbackIncompleteError';
|
||||
import {classifyVideoEncoderAcceleration} from '@app/features/voice/utils/VideoAccelerationClassification';
|
||||
import {
|
||||
BackupCodecPolicy,
|
||||
type LocalParticipant,
|
||||
type LocalTrackPublication,
|
||||
type ScreenShareCaptureOptions,
|
||||
Track,
|
||||
type TrackPublishOptions,
|
||||
type VideoCodec,
|
||||
type VideoEncoding,
|
||||
@@ -176,26 +179,13 @@ export async function releaseScreenShareCaptureCleanup(snapshot: ScreenShareCapt
|
||||
}
|
||||
}
|
||||
|
||||
function getCodecSpecificScreenShareBitrateCeiling(codec: VideoCodec): number | undefined {
|
||||
const acceleration = getCodecCapabilityReport()[codec].hardwareAccelerated;
|
||||
if (codec === 'h264' && acceleration === 'hardware') return 20000000;
|
||||
if ((codec === 'av1' || codec === 'vp9') && acceleration === 'software') return 40000000;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function clampScreenShareEncoding(encoding: VideoEncoding | undefined, codec: VideoCodec): VideoEncoding | undefined {
|
||||
function clampScreenShareEncoding(encoding: VideoEncoding | undefined): VideoEncoding | undefined {
|
||||
if (!encoding) return undefined;
|
||||
const maxBitrateBps = SCREEN_SHARE_FORCED_VIDEO_BITRATE_BPS;
|
||||
const codecCeilingBps = getCodecSpecificScreenShareBitrateCeiling(codec);
|
||||
const bitrateCeilingBps =
|
||||
maxBitrateBps !== undefined && codecCeilingBps !== undefined
|
||||
? Math.min(maxBitrateBps, codecCeilingBps)
|
||||
: (maxBitrateBps ?? codecCeilingBps);
|
||||
return {
|
||||
...encoding,
|
||||
maxBitrate:
|
||||
typeof encoding.maxBitrate === 'number' && bitrateCeilingBps !== undefined
|
||||
? Math.min(encoding.maxBitrate, bitrateCeilingBps)
|
||||
typeof encoding.maxBitrate === 'number'
|
||||
? Math.min(encoding.maxBitrate, SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS)
|
||||
: encoding.maxBitrate,
|
||||
priority: encoding.priority ?? 'high',
|
||||
};
|
||||
@@ -210,13 +200,10 @@ export function getEffectiveScreenShareEncoding(publishOptions?: TrackPublishOpt
|
||||
VoiceSettings.getScreenshareResolution(),
|
||||
VoiceSettings.getVideoFrameRate(),
|
||||
);
|
||||
screenShareEncoding = getScreenShareEncoding(settings.frameRate);
|
||||
screenShareEncoding = getScreenShareEncoding(settings.resolution, settings.frameRate);
|
||||
}
|
||||
if (!screenShareEncoding) return undefined;
|
||||
return clampScreenShareEncoding(
|
||||
adjustScreenShareEncodingForCodec(screenShareEncoding, preferredVideoCodec),
|
||||
preferredVideoCodec,
|
||||
);
|
||||
return clampScreenShareEncoding(adjustScreenShareEncodingForCodec(screenShareEncoding, preferredVideoCodec));
|
||||
}
|
||||
|
||||
function getScreenShareScalabilityModeForCodec(codec: VideoCodec): TrackPublishOptions['scalabilityMode'] | undefined {
|
||||
@@ -313,7 +300,7 @@ export async function getEffectivePublishOptions(
|
||||
videoCodec: preferredVideoCodec,
|
||||
screenShareEncoding: getEffectiveScreenShareEncoding({...publishOptions, videoCodec: preferredVideoCodec}),
|
||||
degradationPreference: SCREEN_SHARE_DEGRADATION_PREFERENCE,
|
||||
simulcast: isSvcScreenShareCodec(preferredVideoCodec) ? false : publishOptions?.simulcast,
|
||||
simulcast: isSvcScreenShareCodec(preferredVideoCodec) ? false : (publishOptions?.simulcast ?? false),
|
||||
scalabilityMode,
|
||||
...(backupCodec !== undefined ? {backupCodec} : {}),
|
||||
...(backupCodecPolicy !== undefined ? {backupCodecPolicy} : {}),
|
||||
@@ -332,6 +319,34 @@ export function applyScreenShareContentHint(
|
||||
}
|
||||
}
|
||||
|
||||
function distributeScreenShareBitrate(
|
||||
encodings: ReadonlyArray<RTCRtpEncodingParameters>,
|
||||
maxBitrate: number,
|
||||
): Array<number> {
|
||||
if (encodings.length === 1) return [maxBitrate];
|
||||
const weights = encodings.map((encoding) =>
|
||||
typeof encoding.maxBitrate === 'number' && encoding.maxBitrate > 0 ? encoding.maxBitrate : 0,
|
||||
);
|
||||
if (weights.some((weight) => weight <= 0)) {
|
||||
return encodings.map(() => Math.floor(maxBitrate / encodings.length));
|
||||
}
|
||||
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
||||
return weights.map((weight) => Math.floor((weight / total) * maxBitrate));
|
||||
}
|
||||
|
||||
export function getPublishedScreenShareMaxBitrateBps(
|
||||
participant: LocalParticipant | null | undefined,
|
||||
): number | undefined {
|
||||
const sender = participant?.getTrackPublication(Track.Source.ScreenShare)?.videoTrack?.sender;
|
||||
if (!sender) return undefined;
|
||||
let total = 0;
|
||||
for (const encoding of sender.getParameters().encodings ?? []) {
|
||||
if (typeof encoding.maxBitrate !== 'number') return undefined;
|
||||
total += encoding.maxBitrate;
|
||||
}
|
||||
return total > 0 ? total : undefined;
|
||||
}
|
||||
|
||||
export async function enforceScreenShareSenderParameters(
|
||||
sender: RTCRtpSender | undefined,
|
||||
publishOptions?: TrackPublishOptions,
|
||||
@@ -339,17 +354,24 @@ export async function enforceScreenShareSenderParameters(
|
||||
): Promise<boolean> {
|
||||
if (!sender) return false;
|
||||
const preferredVideoCodec = codecOverride ?? publishOptions?.videoCodec ?? getPreferredScreenShareCodec();
|
||||
const screenShareEncoding = getEffectiveScreenShareEncoding({...publishOptions, videoCodec: preferredVideoCodec});
|
||||
const effectiveEncoding = getEffectiveScreenShareEncoding({...publishOptions, videoCodec: preferredVideoCodec});
|
||||
const screenShareEncoding = effectiveEncoding
|
||||
? capScreenShareEncodingToDimensions(effectiveEncoding, sender.track?.getSettings())
|
||||
: undefined;
|
||||
const scalabilityMode = getEffectiveScreenShareScalabilityMode(preferredVideoCodec, publishOptions, {
|
||||
respectExplicit: !codecOverride || codecOverride === publishOptions?.videoCodec,
|
||||
});
|
||||
try {
|
||||
const params = sender.getParameters();
|
||||
const encodings = params.encodings?.length ? params.encodings : [{}];
|
||||
const bitrates =
|
||||
screenShareEncoding?.maxBitrate !== undefined
|
||||
? distributeScreenShareBitrate(encodings, screenShareEncoding.maxBitrate)
|
||||
: undefined;
|
||||
params.degradationPreference = SCREEN_SHARE_DEGRADATION_PREFERENCE;
|
||||
params.encodings = encodings.map((encoding) => ({
|
||||
params.encodings = encodings.map((encoding, index) => ({
|
||||
...encoding,
|
||||
...(screenShareEncoding?.maxBitrate !== undefined ? {maxBitrate: screenShareEncoding.maxBitrate} : {}),
|
||||
...(bitrates ? {maxBitrate: bitrates[index]} : {}),
|
||||
...(screenShareEncoding?.maxFramerate !== undefined ? {maxFramerate: screenShareEncoding.maxFramerate} : {}),
|
||||
priority: screenShareEncoding?.priority ?? encoding.priority ?? 'high',
|
||||
networkPriority: screenShareEncoding?.priority ?? encoding.networkPriority ?? 'high',
|
||||
|
||||
Reference in new issue
Block a user