mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
fix(voice): stop clamping stored video quality preferences (#2415)
This commit is contained in:
1 parent
6c08813f9b
commit
3e15b97c8c
47 files changed
+3725
-3732
No files matched your search
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
|
||||
import {isLimitToggleEnabled} from '@app/features/app/utils/LimitUtils';
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import * as PremiumModalCommands from '@app/features/premium/commands/PremiumModalCommands';
|
||||
import {shouldShowPremiumFeatures} from '@app/features/premium/utils/PremiumUtils';
|
||||
@@ -52,6 +50,7 @@ import {
|
||||
type StreamSettingsShareContext,
|
||||
shouldReconfigureLinuxAudioForActiveStreamSettings,
|
||||
} from '@app/features/voice/utils/StreamSettingsUpdatePolicy';
|
||||
import {hasHigherVideoQuality as resolveHigherVideoQuality} from '@app/features/voice/utils/VideoQualityEntitlement';
|
||||
import {formatVoiceAudioDeviceLabel} from '@app/features/voice/utils/VoiceMessageDescriptors';
|
||||
import type {NativeAudioAvailability} from '@app/types/electron.d';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
@@ -83,8 +82,8 @@ const RAZOR_SHARP_TEXT_AT_NATIVE_SOURCE_15_FPS_DESCRIPTOR = msg({
|
||||
comment:
|
||||
'Description for the high-tier Screen share streaming preset (Plutonium). Source resolution and frame rate are technical tokens.',
|
||||
});
|
||||
const SHARPER_TEXT_AT_720P_15_FPS_DESCRIPTOR = msg({
|
||||
message: 'Sharper text at 720p, 15 FPS',
|
||||
const SHARPER_TEXT_AT_720P_30_FPS_DESCRIPTOR = msg({
|
||||
message: 'Sharper text at 720p, 30 FPS',
|
||||
comment:
|
||||
'Description for the free-tier Screen share streaming preset. Resolution and frame rate are technical tokens.',
|
||||
});
|
||||
@@ -187,19 +186,7 @@ const PremiumBadge = () => (
|
||||
);
|
||||
|
||||
export function useHasHigherVideoQuality(): boolean {
|
||||
return useMemo(
|
||||
() =>
|
||||
isLimitToggleEnabled(
|
||||
{
|
||||
feature_higher_video_quality: LimitResolver.resolve({
|
||||
key: 'feature_higher_video_quality',
|
||||
fallback: 0,
|
||||
}),
|
||||
},
|
||||
'feature_higher_video_quality',
|
||||
),
|
||||
[],
|
||||
);
|
||||
return resolveHigherVideoQuality();
|
||||
}
|
||||
|
||||
function supportsStreamAudioCapture(shareContext: StreamSettingsShareContext): boolean {
|
||||
@@ -437,7 +424,7 @@ export const StreamSettingsMenuContent = observer(
|
||||
label: i18n._(SCREENSHARE_DESCRIPTOR),
|
||||
description: hasHigherVideoQuality
|
||||
? i18n._(RAZOR_SHARP_TEXT_AT_NATIVE_SOURCE_15_FPS_DESCRIPTOR)
|
||||
: i18n._(SHARPER_TEXT_AT_720P_15_FPS_DESCRIPTOR),
|
||||
: i18n._(SHARPER_TEXT_AT_720P_30_FPS_DESCRIPTOR),
|
||||
isPremium: false,
|
||||
});
|
||||
}
|
||||
@@ -451,7 +438,6 @@ export const StreamSettingsMenuContent = observer(
|
||||
}, [isDeviceShare, hasHigherVideoQuality, i18n.locale]);
|
||||
const resolutionOptions: Array<Option<ScreenshareResolution>> = useMemo(() => {
|
||||
const options: Array<Option<ScreenshareResolution>> = [
|
||||
{value: 'low_240p', label: '240p', isPremium: false},
|
||||
{value: 'low_480p', label: '480p', isPremium: false},
|
||||
{value: 'medium', label: '720p', isPremium: false},
|
||||
];
|
||||
@@ -575,6 +561,10 @@ export const StreamSettingsMenuContent = observer(
|
||||
? formatVoiceAudioDeviceLabel(i18n, selectedAudioDevice, i18n._(UNNAMED_INPUT_DESCRIPTOR))
|
||||
: i18n._(SYSTEM_DEFAULT_DESCRIPTOR);
|
||||
if (variant === 'compactLive') {
|
||||
const presetQuality =
|
||||
currentMode === 'custom'
|
||||
? null
|
||||
: resolveStreamingModeSettings(effectiveMode, currentResolution, currentFrameRate, true);
|
||||
const selectCompactResolution = (option: Option<ScreenshareResolution>) => {
|
||||
if (option.isPremium && !hasHigherVideoQuality) {
|
||||
if (showPremiumFeatures) PremiumModalCommands.open();
|
||||
@@ -584,7 +574,7 @@ export const StreamSettingsMenuContent = observer(
|
||||
VoiceSettingsCommands.update({
|
||||
streamingMode: 'custom',
|
||||
screenshareResolution: option.value,
|
||||
videoFrameRate: effectiveQuality.frameRate,
|
||||
...(presetQuality ? {videoFrameRate: presetQuality.frameRate} : {}),
|
||||
});
|
||||
runApply();
|
||||
};
|
||||
@@ -596,7 +586,7 @@ export const StreamSettingsMenuContent = observer(
|
||||
if (currentMode === 'custom' && effectiveQuality.frameRate === option.value) return;
|
||||
VoiceSettingsCommands.update({
|
||||
streamingMode: 'custom',
|
||||
screenshareResolution: effectiveQuality.resolution,
|
||||
...(presetQuality ? {screenshareResolution: presetQuality.resolution} : {}),
|
||||
videoFrameRate: option.value,
|
||||
});
|
||||
runApply();
|
||||
|
||||
@@ -87,7 +87,6 @@ import {
|
||||
resolveStreamingModeSettings,
|
||||
} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import {
|
||||
normaliseDeviceScreenShareSettings,
|
||||
startConfiguredDeviceScreenShare,
|
||||
startConfiguredDisplayScreenShare,
|
||||
switchConfiguredDeviceScreenShare,
|
||||
@@ -1268,12 +1267,6 @@ const ScreenSharePickerModalLoadedContent = observer(
|
||||
if (deviceCards.some((card) => card.id === selectedDeviceId)) return;
|
||||
setSelectedDeviceId(null);
|
||||
}, [deviceCards, selectedDeviceId]);
|
||||
useEffect(() => {
|
||||
if (activeTab !== 'devices') {
|
||||
return;
|
||||
}
|
||||
normaliseDeviceScreenShareSettings();
|
||||
}, [activeTab]);
|
||||
const handleStartSelection = useCallback(
|
||||
async (cardId: string) => {
|
||||
if (pendingSelectionIdRef.current) return;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import Config from '@app/features/app/config/Config';
|
||||
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
|
||||
import {isLimitToggleEnabled} from '@app/features/app/utils/LimitUtils';
|
||||
import {getCachedDesktopTroubleshootingSettings} from '@app/features/devtools/utils/DesktopTroubleshootingUtils';
|
||||
import type {AppMetricsSnapshot, DesktopInfo, GpuInfo} from '@app/features/platform/types/Electron';
|
||||
import {getElectronAPI} from '@app/features/ui/utils/NativeUtils';
|
||||
@@ -17,6 +15,7 @@ import {getPublishedScreenShareMaxBitrateBps} from '@app/features/voice/engine/v
|
||||
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
||||
import {getNativeAudioCaptureDiagnosticState} from '@app/features/voice/utils/NativeAudioCaptureBridge';
|
||||
import {getScreenShareBitrateBps, resolveStreamingModeSettings} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import {hasHigherVideoQuality} from '@app/features/voice/utils/VideoQualityEntitlement';
|
||||
import {
|
||||
buildVoiceStatsForNerdsPresentation,
|
||||
type StatsForNerdsData,
|
||||
@@ -105,18 +104,6 @@ function getSystemInfo(): StatsForNerdsData['system'] {
|
||||
};
|
||||
}
|
||||
|
||||
function hasHigherVideoQuality(): boolean {
|
||||
return isLimitToggleEnabled(
|
||||
{
|
||||
feature_higher_video_quality: LimitResolver.resolve({
|
||||
key: 'feature_higher_video_quality',
|
||||
fallback: 0,
|
||||
}),
|
||||
},
|
||||
'feature_higher_video_quality',
|
||||
);
|
||||
}
|
||||
|
||||
export function useStatsForNerds({enabled = true}: UseStatsForNerdsOptions = {}): StatsForNerdsData {
|
||||
useMediaEngineVersion();
|
||||
const [gpuInfo, setGpuInfo] = useState<GpuInfo | null>(null);
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import {ScreenShareRollbackIncompleteError} from '@app/features/voice/utils/ScreenShareRollbackIncompleteError';
|
||||
import {classifyVideoEncoderAcceleration} from '@app/features/voice/utils/VideoAccelerationClassification';
|
||||
import {hasHigherVideoQuality} from '@app/features/voice/utils/VideoQualityEntitlement';
|
||||
import {
|
||||
BackupCodecPolicy,
|
||||
type LocalParticipant,
|
||||
@@ -199,6 +200,7 @@ export function getEffectiveScreenShareEncoding(publishOptions?: TrackPublishOpt
|
||||
VoiceSettings.getStreamingMode(),
|
||||
VoiceSettings.getScreenshareResolution(),
|
||||
VoiceSettings.getVideoFrameRate(),
|
||||
hasHigherVideoQuality(),
|
||||
);
|
||||
screenShareEncoding = getScreenShareEncoding(settings.resolution, settings.frameRate);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
|
||||
import {isLimitToggleEnabled} from '@app/features/app/utils/LimitUtils';
|
||||
import AppStorage from '@app/features/platform/state/PersistentStorage';
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import {makePersistent} from '@app/features/platform/utils/MobXPersistence';
|
||||
@@ -13,6 +11,7 @@ import type {
|
||||
ScreenShareScalabilityModePreference,
|
||||
ScreenShareSoftwareQuality,
|
||||
} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {hasHigherVideoQuality} from '@app/features/voice/utils/VideoQualityEntitlement';
|
||||
import {areVoiceBackgroundsAvailable} from '@app/features/voice/utils/VoiceBackgroundAvailability';
|
||||
import {
|
||||
DEFAULT_VOICE_PROCESSING_MODE,
|
||||
@@ -53,6 +52,7 @@ const VIDEO_FRAME_RATE_DEFAULT = 30;
|
||||
const DEFAULT_BROWSER_NOISE_SUPPRESSION = true;
|
||||
const DEFAULT_DEEP_FILTER_NOISE_SUPPRESSION = false;
|
||||
const FREE_VIDEO_FRAME_RATE_MAX = 30;
|
||||
const RETIRED_SCREENSHARE_RESOLUTION_REPLACEMENT: ScreenshareResolution = 'low_480p';
|
||||
const PREMIUM_SCREENSHARE_RESOLUTIONS: ReadonlySet<ScreenshareResolution> = new Set(['high', 'ultra', 'source']);
|
||||
export const CAMERA_EFFECT_STRENGTH_MIN = 0;
|
||||
export const CAMERA_EFFECT_STRENGTH_MAX = 100;
|
||||
@@ -293,6 +293,32 @@ function applyScreenShareHevcOptOutMigrationV1(parsed: Record<string, unknown>):
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyVideoQualityNormalisationMigration(parsed: Record<string, unknown>): boolean {
|
||||
let changed = false;
|
||||
if (parsed.screenshareResolution !== undefined) {
|
||||
const resolution = validateScreenshareResolution(parsed.screenshareResolution);
|
||||
if (resolution !== parsed.screenshareResolution) {
|
||||
parsed.screenshareResolution = resolution;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (parsed.cameraResolution !== undefined) {
|
||||
const resolution = validateCameraResolution(parsed.cameraResolution);
|
||||
if (resolution !== parsed.cameraResolution) {
|
||||
parsed.cameraResolution = resolution;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (parsed.videoFrameRate !== undefined) {
|
||||
const frameRate = clampVideoFrameRate(parsed.videoFrameRate);
|
||||
if (frameRate !== parsed.videoFrameRate) {
|
||||
parsed.videoFrameRate = frameRate;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function applyNoiseSuppressionStandardDefaultMigrationV1(parsed: Record<string, unknown>): boolean {
|
||||
if (parsed.noiseSuppressionStandardDefaultMigratedV1 === true) {
|
||||
return false;
|
||||
@@ -398,11 +424,10 @@ class VoiceSettings {
|
||||
private listeners = new Set<() => void>();
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable<this, 'hasHigherVideoQuality' | 'listeners' | 'notifyListeners'>(
|
||||
makeAutoObservable<this, 'listeners' | 'notifyListeners'>(
|
||||
this,
|
||||
{
|
||||
listeners: false,
|
||||
hasHigherVideoQuality: false,
|
||||
getInputDeviceId: false,
|
||||
getOutputDeviceId: false,
|
||||
getVideoDeviceId: false,
|
||||
@@ -494,6 +519,7 @@ class VoiceSettings {
|
||||
changed = applyScreenShareContentHintDefaultMigrationV1(parsed) || changed;
|
||||
changed = applyOutputVolumeRecalibrationMigrationV1(parsed) || changed;
|
||||
changed = applyNoiseSuppressionStandardDefaultMigrationV1(parsed) || changed;
|
||||
changed = applyVideoQualityNormalisationMigration(parsed) || changed;
|
||||
changed = applyScreenShareAv1OptOutMigrationV1(parsed) || changed;
|
||||
changed = applyScreenShareHevcOptOutMigrationV1(parsed) || changed;
|
||||
if (changed) {
|
||||
@@ -664,19 +690,6 @@ class VoiceSettings {
|
||||
this.adaptiveScreenShareQualityPrefV2 = value;
|
||||
}
|
||||
|
||||
private hasHigherVideoQuality(): boolean {
|
||||
const featureFlag = isLimitToggleEnabled(
|
||||
{
|
||||
feature_higher_video_quality: LimitResolver.resolve({
|
||||
key: 'feature_higher_video_quality',
|
||||
fallback: 0,
|
||||
}),
|
||||
},
|
||||
'feature_higher_video_quality',
|
||||
);
|
||||
return featureFlag;
|
||||
}
|
||||
|
||||
getInputDeviceId(): string {
|
||||
return this.inputDeviceId;
|
||||
}
|
||||
@@ -757,7 +770,7 @@ class VoiceSettings {
|
||||
}
|
||||
|
||||
getCameraResolution(): CameraResolution {
|
||||
if (this.cameraResolution === 'high' && !this.hasHigherVideoQuality()) {
|
||||
if (this.cameraResolution === 'high' && !hasHigherVideoQuality()) {
|
||||
return 'medium';
|
||||
}
|
||||
return this.cameraResolution;
|
||||
@@ -768,14 +781,14 @@ class VoiceSettings {
|
||||
}
|
||||
|
||||
getScreenshareResolution(): ScreenshareResolution {
|
||||
if (PREMIUM_SCREENSHARE_RESOLUTIONS.has(this.screenshareResolution) && !this.hasHigherVideoQuality()) {
|
||||
if (PREMIUM_SCREENSHARE_RESOLUTIONS.has(this.screenshareResolution) && !hasHigherVideoQuality()) {
|
||||
return 'medium';
|
||||
}
|
||||
return this.screenshareResolution;
|
||||
}
|
||||
|
||||
getVideoFrameRate(): number {
|
||||
if (this.videoFrameRate > FREE_VIDEO_FRAME_RATE_MAX && !this.hasHigherVideoQuality()) {
|
||||
if (this.videoFrameRate > FREE_VIDEO_FRAME_RATE_MAX && !hasHigherVideoQuality()) {
|
||||
return FREE_VIDEO_FRAME_RATE_MAX;
|
||||
}
|
||||
return this.videoFrameRate;
|
||||
@@ -1126,11 +1139,13 @@ class VoiceSettings {
|
||||
if (!validVoiceProcessingModes.includes(voiceProcessingMode)) {
|
||||
voiceProcessingMode = DEFAULT_VOICE_PROCESSING_MODE;
|
||||
}
|
||||
let cameraResolution = data.cameraResolution ?? this.cameraResolution;
|
||||
let screenshareResolution = data.screenshareResolution ?? this.screenshareResolution;
|
||||
let videoFrameRate = clampVideoFrameRate(data.videoFrameRate ?? this.videoFrameRate);
|
||||
const cameraResolution =
|
||||
data.cameraResolution === undefined ? undefined : validateCameraResolution(data.cameraResolution);
|
||||
const screenshareResolution =
|
||||
data.screenshareResolution === undefined ? undefined : validateScreenshareResolution(data.screenshareResolution);
|
||||
const videoFrameRate = data.videoFrameRate === undefined ? undefined : clampVideoFrameRate(data.videoFrameRate);
|
||||
const streamingMode = validateStreamingMode(data.streamingMode ?? this.streamingMode);
|
||||
let backgroundImages = validateBackgroundImages(data.backgroundImages ?? this.backgroundImages);
|
||||
const backgroundImages = validateBackgroundImages(data.backgroundImages ?? this.backgroundImages);
|
||||
let backgroundImageId = data.backgroundImageId ?? this.backgroundImageId;
|
||||
const screenShareEncoderMode = validateScreenShareEncoderMode(
|
||||
data.screenShareEncoderMode ?? this.screenShareEncoderMode,
|
||||
@@ -1146,34 +1161,6 @@ class VoiceSettings {
|
||||
);
|
||||
const screenShareAv1OptIn = data.screenShareAv1OptIn ?? this.screenShareAv1OptIn;
|
||||
const screenShareHevcOptIn = data.screenShareHevcOptIn ?? this.screenShareHevcOptIn;
|
||||
const validCameraResolutions: Array<CameraResolution> = ['low', 'medium', 'high'];
|
||||
if (!validCameraResolutions.includes(cameraResolution)) {
|
||||
cameraResolution = 'medium';
|
||||
}
|
||||
const hasHigherQuality = this.hasHigherVideoQuality();
|
||||
const validScreenshareResolutions: Array<ScreenshareResolution> = [
|
||||
'low_240p',
|
||||
'low_480p',
|
||||
'medium',
|
||||
'high',
|
||||
'ultra',
|
||||
'source',
|
||||
];
|
||||
if (!validScreenshareResolutions.includes(screenshareResolution)) {
|
||||
screenshareResolution = 'medium';
|
||||
}
|
||||
if (!hasHigherQuality) {
|
||||
if (screenshareResolution === 'high' || screenshareResolution === 'ultra' || screenshareResolution === 'source') {
|
||||
screenshareResolution = 'medium';
|
||||
}
|
||||
if (cameraResolution === 'high') {
|
||||
cameraResolution = 'medium';
|
||||
}
|
||||
videoFrameRate = Math.min(30, videoFrameRate);
|
||||
if (backgroundImages.length > 3) {
|
||||
backgroundImages = backgroundImages.slice(0, 3);
|
||||
}
|
||||
}
|
||||
if (backgroundImageId !== NONE_BACKGROUND_ID && backgroundImageId !== BLUR_BACKGROUND_ID) {
|
||||
const imageExists = backgroundImages.some((img: BackgroundImage) => img.id === backgroundImageId);
|
||||
if (!imageExists) {
|
||||
@@ -1198,7 +1185,7 @@ class VoiceSettings {
|
||||
cameraResolution,
|
||||
mirrorCamera: data.mirrorCamera ?? this.mirrorCamera,
|
||||
screenshareResolution,
|
||||
videoFrameRate: clampVideoFrameRate(videoFrameRate),
|
||||
videoFrameRate,
|
||||
streamingMode,
|
||||
hideStreamPreview: data.hideStreamPreview ?? this.hideStreamPreview,
|
||||
muteStreamAudio: data.muteStreamAudio ?? this.muteStreamAudio,
|
||||
@@ -1321,6 +1308,17 @@ function validateScreenShareContentHint(hint: unknown): ScreenShareContentHint {
|
||||
: DEFAULT_SCREEN_SHARE_CONTENT_HINT;
|
||||
}
|
||||
|
||||
function validateCameraResolution(value: unknown): CameraResolution {
|
||||
return value === 'low' || value === 'medium' || value === 'high' ? value : 'medium';
|
||||
}
|
||||
|
||||
function validateScreenshareResolution(value: unknown): ScreenshareResolution {
|
||||
if (value === 'low_240p') return RETIRED_SCREENSHARE_RESOLUTION_REPLACEMENT;
|
||||
return value === 'low_480p' || value === 'medium' || value === 'high' || value === 'ultra' || value === 'source'
|
||||
? value
|
||||
: 'medium';
|
||||
}
|
||||
|
||||
function validateStreamingMode(mode: unknown): StreamingMode {
|
||||
return mode === 'gaming' || mode === 'screenshare' || mode === 'custom' ? mode : 'screenshare';
|
||||
}
|
||||
|
||||
@@ -20,7 +20,12 @@ const AppStorage = (await import('@app/features/platform/state/PersistentStorage
|
||||
|
||||
AppStorage.setItem(
|
||||
'VoiceSettings',
|
||||
JSON.stringify({preferredScreenShareCodec: 'av1', screenShareContentHintPrefV2: 'auto'}),
|
||||
JSON.stringify({
|
||||
preferredScreenShareCodec: 'av1',
|
||||
screenShareContentHintPrefV2: 'auto',
|
||||
screenshareResolution: 'low_240p',
|
||||
__mps__: {version: 1},
|
||||
}),
|
||||
);
|
||||
|
||||
const storageWrites: Array<string> = [];
|
||||
@@ -151,3 +156,42 @@ describe('screen share content hint default', () => {
|
||||
expect(VoiceSettings.getScreenShareContentHintOverride()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('stored video quality preferences', () => {
|
||||
it('retires a stored 240p screen share preference to 480p on first launch', () => {
|
||||
const migrated = JSON.parse(storageWrites[0]);
|
||||
expect(migrated.screenshareResolution).toBe('low_480p');
|
||||
expect(VoiceSettings.screenshareResolution).toBe('low_480p');
|
||||
});
|
||||
|
||||
it('upgrades a retired 240p patch to 480p', () => {
|
||||
VoiceSettings.updateSettings({screenshareResolution: 'low_240p'});
|
||||
expect(VoiceSettings.screenshareResolution).toBe('low_480p');
|
||||
});
|
||||
|
||||
it('keeps premium video quality choices stored while the entitlement is missing', () => {
|
||||
VoiceSettings.updateSettings({screenshareResolution: 'ultra', cameraResolution: 'high', videoFrameRate: 60});
|
||||
VoiceSettings.updateSettings({outputDeviceId: 'default'});
|
||||
expect(VoiceSettings.screenshareResolution).toBe('ultra');
|
||||
expect(VoiceSettings.cameraResolution).toBe('high');
|
||||
expect(VoiceSettings.videoFrameRate).toBe(60);
|
||||
});
|
||||
|
||||
it('still hands out free-tier video quality while the entitlement is missing', () => {
|
||||
VoiceSettings.updateSettings({screenshareResolution: 'ultra', cameraResolution: 'high', videoFrameRate: 60});
|
||||
expect(VoiceSettings.getScreenshareResolution()).toBe('medium');
|
||||
expect(VoiceSettings.getCameraResolution()).toBe('medium');
|
||||
expect(VoiceSettings.getVideoFrameRate()).toBe(30);
|
||||
});
|
||||
|
||||
it('keeps every stored background image while the entitlement is missing', () => {
|
||||
const images = [1, 2, 3, 4, 5].map((index) => ({id: `background-${index}`, createdAt: index}));
|
||||
runInAction(() => {
|
||||
VoiceSettings.backgroundImages = images;
|
||||
});
|
||||
VoiceSettings.updateSettings({outputDeviceId: 'default'});
|
||||
expect(VoiceSettings.backgroundImages).toHaveLength(5);
|
||||
VoiceSettings.updateSettings({backgroundImages: images.slice(0, 4)});
|
||||
expect(VoiceSettings.backgroundImages).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
@@ -179,6 +179,24 @@ describe('buildScreenShareOptions', () => {
|
||||
frameRate: 30,
|
||||
});
|
||||
});
|
||||
it('starts the free-tier screenshare preset at 720p 30 fps', () => {
|
||||
expect(resolveStreamingModeSettings('screenshare', 'medium', 15, false)).toEqual({
|
||||
resolution: 'medium',
|
||||
frameRate: 30,
|
||||
});
|
||||
});
|
||||
it('lifts a retired free-tier 240p custom choice back to 720p', () => {
|
||||
expect(resolveStreamingModeSettings('custom', 'low_240p', 30, false)).toEqual({
|
||||
resolution: 'medium',
|
||||
frameRate: 30,
|
||||
});
|
||||
});
|
||||
it('keeps 480p as the lowest free-tier rung', () => {
|
||||
expect(resolveStreamingModeSettings('custom', 'low_480p', 30, false)).toEqual({
|
||||
resolution: 'low_480p',
|
||||
frameRate: 30,
|
||||
});
|
||||
});
|
||||
it('offers the browser picker system audio for every surface when audio is requested', () => {
|
||||
const {captureOptions} = buildScreenShareOptions({
|
||||
resolution: 'medium',
|
||||
|
||||
@@ -136,7 +136,7 @@ const FREE_STREAMING_MODE_PRESETS: Record<
|
||||
}
|
||||
> = {
|
||||
gaming: {resolution: 'medium', frameRate: 30},
|
||||
screenshare: {resolution: 'medium', frameRate: 15},
|
||||
screenshare: {resolution: 'medium', frameRate: 30},
|
||||
};
|
||||
|
||||
export interface BuiltScreenShareOptions {
|
||||
@@ -222,7 +222,7 @@ export function buildScreenShareOptions(
|
||||
}
|
||||
|
||||
const FREE_TIER_FALLBACK_RESOLUTION: ScreenshareResolution = 'medium';
|
||||
const FREE_TIER_RESOLUTIONS: ReadonlyArray<ScreenshareResolution> = ['low_240p', 'low_480p', 'medium'];
|
||||
const FREE_TIER_RESOLUTIONS: ReadonlyArray<ScreenshareResolution> = ['low_480p', 'medium'];
|
||||
const FREE_TIER_MAX_FRAME_RATE: SupportedScreenShareFrameRate = 30;
|
||||
|
||||
function isFreeTierResolution(resolution: ScreenshareResolution): boolean {
|
||||
@@ -248,7 +248,7 @@ export function resolveStreamingModeSettings(
|
||||
mode: StreamingMode,
|
||||
customResolution: ScreenshareResolution,
|
||||
customFrameRate: number,
|
||||
hasHigherQuality: boolean = true,
|
||||
hasHigherQuality: boolean,
|
||||
): {
|
||||
resolution: ScreenshareResolution;
|
||||
frameRate: SupportedScreenShareFrameRate;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
|
||||
import {isLimitToggleEnabled} from '@app/features/app/utils/LimitUtils';
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import {getElectronAPI, supportsDesktopScreenShareAudioCapture} from '@app/features/ui/utils/NativeUtils';
|
||||
import * as VoiceSettingsCommands from '@app/features/voice/commands/VoiceSettingsCommands';
|
||||
import MediaEngine from '@app/features/voice/engine/MediaEngineFacade';
|
||||
import ScreenShareCodecNegotiation from '@app/features/voice/engine/ScreenShareCodecNegotiation';
|
||||
import ActiveScreenShareSource from '@app/features/voice/state/ActiveScreenShareSource';
|
||||
@@ -49,6 +46,7 @@ import {
|
||||
ScreenSharePortalUnavailableError,
|
||||
} from '@app/features/voice/utils/ScreenSharePortalUnavailableError';
|
||||
import {executeScreenShareOperation} from '@app/features/voice/utils/ScreenShareUtils';
|
||||
import {hasHigherVideoQuality} from '@app/features/voice/utils/VideoQualityEntitlement';
|
||||
import type {NativeAudioStartOptions, VirtmicNode} from '@app/types/electron.d';
|
||||
import type {ScreenShareCaptureOptions, VideoCodec} from 'livekit-client';
|
||||
|
||||
@@ -152,18 +150,6 @@ export async function stopActiveLinuxScreenShareAudioLink(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
function hasHigherVideoQuality(): boolean {
|
||||
return isLimitToggleEnabled(
|
||||
{
|
||||
feature_higher_video_quality: LimitResolver.resolve({
|
||||
key: 'feature_higher_video_quality',
|
||||
fallback: 0,
|
||||
}),
|
||||
},
|
||||
'feature_higher_video_quality',
|
||||
);
|
||||
}
|
||||
|
||||
function didScreenShareStart(): boolean {
|
||||
return Boolean(MediaEngine.room?.localParticipant?.isScreenShareEnabled || LocalVoiceState.getSelfStream());
|
||||
}
|
||||
@@ -177,25 +163,6 @@ function getScreenShareContentSource(
|
||||
return 'display';
|
||||
}
|
||||
|
||||
export function normaliseDeviceScreenShareSettings(): void {
|
||||
const nextStreamingMode = normaliseStreamingModeForContext(VoiceSettings.getStreamingMode(), 'device');
|
||||
const nextResolution = normaliseResolutionForContext(
|
||||
VoiceSettings.getScreenshareResolution(),
|
||||
'device',
|
||||
hasHigherVideoQuality(),
|
||||
);
|
||||
if (
|
||||
nextStreamingMode === VoiceSettings.getStreamingMode() &&
|
||||
nextResolution === VoiceSettings.getScreenshareResolution()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
VoiceSettingsCommands.update({
|
||||
streamingMode: nextStreamingMode,
|
||||
screenshareResolution: nextResolution,
|
||||
});
|
||||
}
|
||||
|
||||
function shouldIncludeAudioForShare(
|
||||
shareContext: ScreenShareContext,
|
||||
displayShareEnvironment: DisplayShareEnvironment,
|
||||
@@ -695,7 +662,6 @@ export async function switchConfiguredDisplayScreenShare(
|
||||
|
||||
export async function startConfiguredDeviceScreenShare(videoDeviceId: string): Promise<boolean> {
|
||||
return scheduleConfiguredScreenShareMutation(async () => {
|
||||
normaliseDeviceScreenShareSettings();
|
||||
const {captureOptions, publishOptions, includeAudio, audioDeviceId} = getConfiguredScreenShareOptions(
|
||||
'device',
|
||||
'desktop-custom',
|
||||
@@ -723,7 +689,6 @@ export async function startConfiguredDeviceScreenShare(videoDeviceId: string): P
|
||||
|
||||
export async function switchConfiguredDeviceScreenShare(videoDeviceId: string): Promise<boolean> {
|
||||
return scheduleConfiguredScreenShareMutation(async () => {
|
||||
normaliseDeviceScreenShareSettings();
|
||||
const {captureOptions, publishOptions, includeAudio, audioDeviceId} = getConfiguredScreenShareOptions(
|
||||
'device',
|
||||
'desktop-custom',
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import Config from '@app/features/app/config/Config';
|
||||
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
|
||||
import {isLimitToggleEnabled} from '@app/features/app/utils/LimitUtils';
|
||||
import {
|
||||
getCachedDesktopTroubleshootingSettings,
|
||||
getDesktopTroubleshootingSettings,
|
||||
@@ -36,6 +34,7 @@ import {
|
||||
} from '@app/features/voice/utils/NativeAudioCaptureBridge';
|
||||
import {getDisplayShareEnvironment} from '@app/features/voice/utils/ScreenShareEnvironment';
|
||||
import {getScreenShareBitrateBps, resolveStreamingModeSettings} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import {hasHigherVideoQuality} from '@app/features/voice/utils/VideoQualityEntitlement';
|
||||
import {
|
||||
buildVoiceStatsForNerdsPresentation,
|
||||
type StatsForNerdsData,
|
||||
@@ -461,18 +460,6 @@ async function collectDesktopMetadata(): Promise<Record<string, unknown>> {
|
||||
};
|
||||
}
|
||||
|
||||
function hasHigherVideoQuality(): boolean {
|
||||
return isLimitToggleEnabled(
|
||||
{
|
||||
feature_higher_video_quality: LimitResolver.resolve({
|
||||
key: 'feature_higher_video_quality',
|
||||
fallback: 0,
|
||||
}),
|
||||
},
|
||||
'feature_higher_video_quality',
|
||||
);
|
||||
}
|
||||
|
||||
export function collectStatsForNerdsSnapshot(): StatsForNerdsData {
|
||||
const adaptiveQualitySnapshot = AdaptiveScreenShareEngine.qualitySnapshot;
|
||||
const effectiveScreenShareSettings = resolveStreamingModeSettings(
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
|
||||
import {isLimitToggleEnabled} from '@app/features/app/utils/LimitUtils';
|
||||
|
||||
export function hasHigherVideoQuality(): boolean {
|
||||
return isLimitToggleEnabled(
|
||||
{
|
||||
feature_higher_video_quality: LimitResolver.resolve({
|
||||
key: 'feature_higher_video_quality',
|
||||
fallback: 0,
|
||||
}),
|
||||
},
|
||||
'feature_higher_video_quality',
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user