mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(voice): stop screen shares failing to reach their viewers (#2835)
This commit is contained in:
1 parent
f32356801d
commit
bc47a724af
119 files changed
+27942
-12524
No files matched your search
@@ -21,17 +21,30 @@ import {getAdvancedSettingsCategorySectionId} from '@app/features/user/component
|
||||
import {CompactComboboxRow} from '@app/features/user/components/modals/tabs/components/CompactComboboxRow';
|
||||
import {useMediaPermission} from '@app/features/user/components/modals/tabs/hooks/useMediaPermission';
|
||||
import styles from '@app/features/user/components/modals/tabs/UserVideoTab.module.css';
|
||||
import {
|
||||
resolveUserVideoTabScreenShareState,
|
||||
SCREEN_SHARE_PRESET_DESCRIPTORS,
|
||||
} from '@app/features/user/components/modals/tabs/UserVideoTabState';
|
||||
import * as VoiceSettingsCommands from '@app/features/voice/commands/VoiceSettingsCommands';
|
||||
import {CameraPreviewModalStandalone} from '@app/features/voice/components/modals/CameraPreviewModal';
|
||||
import type VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
||||
import type {CameraResolution, ScreenshareResolution} from '@app/features/voice/state/VoiceSettings';
|
||||
import {
|
||||
resolveScreenShareFrameRate,
|
||||
CAPTURE_DEVICES_USE_THE_GAMING_PRESET_DESCRIPTOR,
|
||||
type OfferedScreenShareResolution,
|
||||
} from '@app/features/voice/components/StreamSettingsMenuContentStateMachine';
|
||||
import ActiveScreenShareSource from '@app/features/voice/state/ActiveScreenShareSource';
|
||||
import ScreenShareDelivery from '@app/features/voice/state/ScreenShareDelivery';
|
||||
import type VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
||||
import type {CameraResolution} from '@app/features/voice/state/VoiceSettings';
|
||||
import {
|
||||
resolveScreenShareQualityPick,
|
||||
type ScreenShareQualityInput,
|
||||
type SupportedScreenShareFrameRate,
|
||||
} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import {buildSettingsDeviceOptions} from '@app/features/voice/utils/SettingsDeviceOptions';
|
||||
import {hasHigherVideoQuality} from '@app/features/voice/utils/VideoQualityEntitlement';
|
||||
import {resolveEffectiveDeviceId} from '@app/features/voice/utils/VoiceDeviceManager';
|
||||
import {formatScreenShareDeliveryStatus} from '@app/features/voice/utils/VoiceMessageDescriptors';
|
||||
import type {I18n} from '@lingui/core';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {Trans, useLingui} from '@lingui/react/macro';
|
||||
import {CrownIcon, GearIcon} from '@phosphor-icons/react';
|
||||
@@ -39,9 +52,10 @@ import {observer} from 'mobx-react-lite';
|
||||
import type React from 'react';
|
||||
import {useCallback, useMemo} from 'react';
|
||||
|
||||
const FPS_15_DESCRIPTOR = msg({message: '15 FPS'});
|
||||
const FPS_30_DESCRIPTOR = msg({message: '30 FPS'});
|
||||
const FPS_60_DESCRIPTOR = msg({message: '60 FPS'});
|
||||
const FPS_DESCRIPTOR = msg({
|
||||
message: '{frameRate} FPS',
|
||||
comment: 'Frame rate option label in the video tab. FPS is a technical token and frameRate is a whole number.',
|
||||
});
|
||||
|
||||
const LOW_480P_LABEL = '480p';
|
||||
const MEDIUM_720P_LABEL = '720p';
|
||||
@@ -62,13 +76,27 @@ const SELF_HOSTED_VIDEO_QUALITY_LIMIT_DESCRIPTOR = msg({
|
||||
message: 'This instance currently allows screen sharing up to 720p at 30 FPS.',
|
||||
comment: 'Neutral video settings note shown when higher screen share quality is disabled by instance limits.',
|
||||
});
|
||||
const STANDARD_720P_LABEL = '720p';
|
||||
const HIGH_DEFINITION_1080P_LABEL = '1080p';
|
||||
const QUAD_HD_1440P_LABEL = '1440p';
|
||||
const SCREEN_SHARE_RESOLUTION_LABELS: Record<Exclude<OfferedScreenShareResolution, 'source'>, string> = {
|
||||
low_480p: '480p',
|
||||
medium: '720p',
|
||||
high: '1080p',
|
||||
ultra: '1440p',
|
||||
};
|
||||
const SOURCE_DESCRIPTOR = msg({
|
||||
message: 'Source',
|
||||
comment: 'Short label in the video tab for the native source resolution option. Keep it concise.',
|
||||
});
|
||||
const SCREEN_SHARE_PRESET_OWNED_DESCRIPTOR = msg({
|
||||
message: 'Set by the {preset} preset. Picking a value here switches to Custom.',
|
||||
comment:
|
||||
'Video settings note shown when a streaming preset decides the screen share resolution and frame rate. preset is the Gaming or Screen share preset label, and Custom is the third preset label.',
|
||||
});
|
||||
const SCREEN_SHARE_TIER_LIMITED_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Your saved {savedResolution} at {savedFrameRate} FPS needs {premiumProductName}, so shares use {resolution} at {frameRate} FPS.',
|
||||
comment:
|
||||
'Video settings note shown when the saved screen share quality is above what this account can send. Resolutions are short labels such as 1440p or Source and rates are whole numbers. FPS is a technical token.',
|
||||
});
|
||||
const CAMERA_2_DESCRIPTOR = msg({
|
||||
message: 'Camera',
|
||||
comment: 'Short label in the video tab. Keep it concise.',
|
||||
@@ -94,6 +122,10 @@ const OPEN_ADVANCED_MEDIA_SETTINGS_DESCRIPTOR = msg({
|
||||
comment: 'Button label in the video tab that jumps to the advanced settings media section.',
|
||||
});
|
||||
|
||||
function getScreenShareResolutionLabel(i18n: I18n, resolution: OfferedScreenShareResolution): string {
|
||||
return resolution === 'source' ? i18n._(SOURCE_DESCRIPTOR) : SCREEN_SHARE_RESOLUTION_LABELS[resolution];
|
||||
}
|
||||
|
||||
interface VideoTabProps {
|
||||
voiceSettings: typeof VoiceSettings;
|
||||
hasPremium: boolean;
|
||||
@@ -103,7 +135,15 @@ interface VideoTabProps {
|
||||
export const VideoTab: React.FC<VideoTabProps> = observer(
|
||||
({voiceSettings, hasPremium: _hasPremium, autoRequestPermission = false}) => {
|
||||
const {i18n} = useLingui();
|
||||
const {videoDeviceId, cameraResolution, mirrorCamera, screenshareResolution, videoFrameRate} = voiceSettings;
|
||||
const {
|
||||
videoDeviceId,
|
||||
cameraResolution,
|
||||
mirrorCamera,
|
||||
screenshareResolution,
|
||||
videoFrameRate,
|
||||
streamingMode,
|
||||
screenShareContentHint,
|
||||
} = voiceSettings;
|
||||
const hasHigherQuality = hasHigherVideoQuality();
|
||||
const isSelfHosted = RuntimeConfig.isSelfHosted();
|
||||
const {
|
||||
@@ -132,45 +172,31 @@ export const VideoTab: React.FC<VideoTabProps> = observer(
|
||||
]
|
||||
: []),
|
||||
];
|
||||
const screenshareResolutionOptions: ReadonlyArray<ComboboxOption<ScreenshareResolution>> = [
|
||||
{value: 'low_480p', label: LOW_480P_LABEL},
|
||||
{
|
||||
value: 'medium',
|
||||
label: STANDARD_720P_LABEL,
|
||||
},
|
||||
...(hasHigherQuality || !isSelfHosted
|
||||
? [
|
||||
{
|
||||
value: 'high' as const,
|
||||
label: HIGH_DEFINITION_1080P_LABEL,
|
||||
isDisabled: !hasHigherQuality,
|
||||
},
|
||||
{
|
||||
value: 'ultra' as const,
|
||||
label: QUAD_HD_1440P_LABEL,
|
||||
isDisabled: !hasHigherQuality,
|
||||
},
|
||||
{
|
||||
value: 'source' as const,
|
||||
label: i18n._(SOURCE_DESCRIPTOR),
|
||||
isDisabled: !hasHigherQuality,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
const frameRateOptions: ReadonlyArray<ComboboxOption<SupportedScreenShareFrameRate>> = hasHigherQuality
|
||||
? [
|
||||
{value: 15, label: i18n._(FPS_15_DESCRIPTOR)},
|
||||
{value: 30, label: i18n._(FPS_30_DESCRIPTOR)},
|
||||
{value: 60, label: i18n._(FPS_60_DESCRIPTOR)},
|
||||
]
|
||||
: [
|
||||
{value: 15, label: i18n._(FPS_15_DESCRIPTOR)},
|
||||
{value: 30, label: i18n._(FPS_30_DESCRIPTOR)},
|
||||
];
|
||||
const resolvedVideoFrameRate = resolveScreenShareFrameRate(videoFrameRate);
|
||||
const effectiveVideoFrameRate: SupportedScreenShareFrameRate =
|
||||
!hasHigherQuality && resolvedVideoFrameRate === 60 ? 30 : resolvedVideoFrameRate;
|
||||
const liveShareContext = ActiveScreenShareSource.getShareContext();
|
||||
const screenShareQuality: ScreenShareQualityInput = {
|
||||
mode: streamingMode,
|
||||
storedResolution: screenshareResolution,
|
||||
storedFrameRate: videoFrameRate,
|
||||
entitled: hasHigherQuality,
|
||||
context: liveShareContext ?? 'display',
|
||||
};
|
||||
const screenShareState = resolveUserVideoTabScreenShareState({
|
||||
quality: screenShareQuality,
|
||||
hintSetting: screenShareContentHint,
|
||||
selfHosted: isSelfHosted,
|
||||
});
|
||||
const screenshareResolutionOptions: ReadonlyArray<ComboboxOption<OfferedScreenShareResolution>> =
|
||||
screenShareState.resolutionOptions.map((option) => ({
|
||||
value: option.value,
|
||||
label: getScreenShareResolutionLabel(i18n, option.value),
|
||||
isDisabled: option.isDisabled,
|
||||
}));
|
||||
const frameRateOptions: ReadonlyArray<ComboboxOption<SupportedScreenShareFrameRate>> =
|
||||
screenShareState.frameRateOptions.map((value) => ({
|
||||
value,
|
||||
label: i18n._(FPS_DESCRIPTOR, {frameRate: value}),
|
||||
}));
|
||||
const deliveryStatus = formatScreenShareDeliveryStatus(i18n, ScreenShareDelivery.plan, ScreenShareDelivery.notice);
|
||||
const handleCameraPreview = async () => {
|
||||
const granted = await requestPermission();
|
||||
if (granted) {
|
||||
@@ -184,12 +210,16 @@ export const VideoTab: React.FC<VideoTabProps> = observer(
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleScreenshareResolutionChange = useCallback((value: ScreenshareResolution) => {
|
||||
VoiceSettingsCommands.update({screenshareResolution: value, streamingMode: 'custom'});
|
||||
}, []);
|
||||
const handleVideoFrameRateChange = useCallback((value: SupportedScreenShareFrameRate) => {
|
||||
VoiceSettingsCommands.update({videoFrameRate: value, streamingMode: 'custom'});
|
||||
}, []);
|
||||
const handleScreenshareResolutionChange = (value: OfferedScreenShareResolution) => {
|
||||
const patch = resolveScreenShareQualityPick(screenShareQuality, {axis: 'resolution', resolution: value});
|
||||
if (patch === null) return;
|
||||
VoiceSettingsCommands.update(patch);
|
||||
};
|
||||
const handleVideoFrameRateChange = (value: SupportedScreenShareFrameRate) => {
|
||||
const patch = resolveScreenShareQualityPick(screenShareQuality, {axis: 'frameRate', frameRate: value});
|
||||
if (patch === null) return;
|
||||
VoiceSettingsCommands.update(patch);
|
||||
};
|
||||
const handleOpenAdvancedVideoSettings = useCallback(() => {
|
||||
ComponentBus.dispatch('USER_SETTINGS_TAB_SELECT', {
|
||||
tab: 'advanced_settings',
|
||||
@@ -271,10 +301,10 @@ export const VideoTab: React.FC<VideoTabProps> = observer(
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.controlGroup} data-flx="user.video-tab.screen-sharing-group">
|
||||
<CompactComboboxRow<ScreenshareResolution>
|
||||
<CompactComboboxRow<OfferedScreenShareResolution>
|
||||
label={i18n._(SCREEN_SHARE_QUALITY_DESCRIPTOR)}
|
||||
options={screenshareResolutionOptions}
|
||||
value={screenshareResolution}
|
||||
value={screenShareState.resolution}
|
||||
onChange={handleScreenshareResolutionChange}
|
||||
isSearchable={false}
|
||||
controlWidth="small"
|
||||
@@ -313,7 +343,7 @@ export const VideoTab: React.FC<VideoTabProps> = observer(
|
||||
<CompactComboboxRow<SupportedScreenShareFrameRate>
|
||||
label={<Trans>Frame rate</Trans>}
|
||||
options={frameRateOptions}
|
||||
value={effectiveVideoFrameRate}
|
||||
value={screenShareState.frameRate}
|
||||
onChange={handleVideoFrameRateChange}
|
||||
isSearchable={false}
|
||||
controlWidth="small"
|
||||
@@ -336,6 +366,45 @@ export const VideoTab: React.FC<VideoTabProps> = observer(
|
||||
{i18n._(SELF_HOSTED_VIDEO_QUALITY_LIMIT_DESCRIPTOR)}
|
||||
</div>
|
||||
)}
|
||||
{screenShareState.preset !== null && (
|
||||
<div className={styles.frameRateNote} data-flx="user.video-tab.preset-note">
|
||||
{i18n._(SCREEN_SHARE_PRESET_OWNED_DESCRIPTOR, {
|
||||
preset: i18n._(SCREEN_SHARE_PRESET_DESCRIPTORS[screenShareState.preset]),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{screenShareState.presetOverriddenByContext && (
|
||||
<div className={styles.frameRateNote} data-flx="user.video-tab.preset-context-note">
|
||||
{i18n._(CAPTURE_DEVICES_USE_THE_GAMING_PRESET_DESCRIPTOR)}
|
||||
</div>
|
||||
)}
|
||||
{screenShareState.saved !== null && (
|
||||
<div className={styles.frameRateNote} data-flx="user.video-tab.tier-limit-note">
|
||||
<CrownIcon
|
||||
weight="fill"
|
||||
size={remFromPx(14)}
|
||||
className={styles.frameRateIcon}
|
||||
data-flx="user.video-tab.tier-limit-icon"
|
||||
/>
|
||||
{i18n._(SCREEN_SHARE_TIER_LIMITED_DESCRIPTOR, {
|
||||
frameRate: screenShareState.frameRate,
|
||||
premiumProductName: PREMIUM_PRODUCT_NAME,
|
||||
resolution: getScreenShareResolutionLabel(i18n, screenShareState.resolution),
|
||||
savedFrameRate: screenShareState.saved.frameRate,
|
||||
savedResolution: getScreenShareResolutionLabel(i18n, screenShareState.saved.resolution),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{deliveryStatus.sending !== null && (
|
||||
<div className={styles.frameRateNote} data-flx="user.video-tab.sending-note">
|
||||
{deliveryStatus.sending}
|
||||
</div>
|
||||
)}
|
||||
{deliveryStatus.notice !== null && (
|
||||
<div className={styles.frameRateNote} data-flx="user.video-tab.delivery-notice">
|
||||
{deliveryStatus.notice}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.actionRow} data-flx="user.video-tab.advanced-video-settings-section">
|
||||
<span className={styles.actionLabel} data-flx="user.video-tab.advanced-video-settings-label">
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
contextAllowsScreenShareResolution,
|
||||
OFFERED_SCREEN_SHARE_RESOLUTIONS,
|
||||
type OfferedScreenShareResolution,
|
||||
offeredScreenShareResolution,
|
||||
selectStreamSettingsPresetOverriddenByContext,
|
||||
} from '@app/features/voice/components/StreamSettingsMenuContentStateMachine';
|
||||
import type {StreamingMode} from '@app/features/voice/state/VoiceSettings';
|
||||
import type {ScreenShareContentHint} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {
|
||||
resolveScreenShareFrameRate,
|
||||
resolveScreenShareTarget,
|
||||
type ScreenShareContext,
|
||||
type ScreenShareQualityInput,
|
||||
SUPPORTED_SCREEN_SHARE_FRAME_RATES,
|
||||
type SupportedScreenShareFrameRate,
|
||||
} from '@app/features/voice/utils/ScreenShareOptions';
|
||||
import type {MessageDescriptor} from '@lingui/core';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
|
||||
export const SCREEN_SHARE_PRESET_DESCRIPTORS: Record<Exclude<StreamingMode, 'custom'>, MessageDescriptor> = {
|
||||
gaming: msg({
|
||||
message: 'Gaming',
|
||||
comment: 'Video settings label for the motion-focused screen share quality preset.',
|
||||
}),
|
||||
screenshare: msg({
|
||||
message: 'Screen share',
|
||||
comment: 'Video settings label for the text-focused screen share quality preset.',
|
||||
}),
|
||||
};
|
||||
|
||||
const FREE_SCREEN_SHARE_RESOLUTIONS: ReadonlyArray<OfferedScreenShareResolution> = ['low_480p', 'medium'];
|
||||
const PREMIUM_SCREEN_SHARE_RESOLUTIONS: ReadonlyArray<OfferedScreenShareResolution> = ['high', 'ultra', 'source'];
|
||||
const FREE_SCREEN_SHARE_FRAME_RATES: ReadonlyArray<SupportedScreenShareFrameRate> = [15, 30];
|
||||
const PREMIUM_SCREEN_SHARE_FRAME_RATES: ReadonlyArray<SupportedScreenShareFrameRate> = [15, 30, 60];
|
||||
|
||||
export interface UserVideoTabScreenShareInput {
|
||||
quality: ScreenShareQualityInput;
|
||||
hintSetting: ScreenShareContentHint;
|
||||
selfHosted: boolean;
|
||||
}
|
||||
|
||||
export interface UserVideoTabResolutionOption {
|
||||
value: OfferedScreenShareResolution;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
|
||||
export interface UserVideoTabScreenShareState {
|
||||
resolution: OfferedScreenShareResolution;
|
||||
frameRate: SupportedScreenShareFrameRate;
|
||||
resolutionOptions: ReadonlyArray<UserVideoTabResolutionOption>;
|
||||
frameRateOptions: ReadonlyArray<SupportedScreenShareFrameRate>;
|
||||
preset: Exclude<StreamingMode, 'custom'> | null;
|
||||
presetOverriddenByContext: boolean;
|
||||
saved: {resolution: OfferedScreenShareResolution; frameRate: SupportedScreenShareFrameRate} | null;
|
||||
}
|
||||
|
||||
function buildResolutionOptions(
|
||||
entitled: boolean,
|
||||
selfHosted: boolean,
|
||||
context: ScreenShareContext,
|
||||
effective: OfferedScreenShareResolution,
|
||||
): ReadonlyArray<UserVideoTabResolutionOption> {
|
||||
const offersPremium = entitled || !selfHosted;
|
||||
return OFFERED_SCREEN_SHARE_RESOLUTIONS.filter(
|
||||
(value) =>
|
||||
value === effective ||
|
||||
FREE_SCREEN_SHARE_RESOLUTIONS.includes(value) ||
|
||||
(offersPremium &&
|
||||
PREMIUM_SCREEN_SHARE_RESOLUTIONS.includes(value) &&
|
||||
contextAllowsScreenShareResolution(value, context)),
|
||||
).map((value) => ({value, isDisabled: !entitled && PREMIUM_SCREEN_SHARE_RESOLUTIONS.includes(value)}));
|
||||
}
|
||||
|
||||
function buildFrameRateOptions(
|
||||
entitled: boolean,
|
||||
effective: SupportedScreenShareFrameRate,
|
||||
): ReadonlyArray<SupportedScreenShareFrameRate> {
|
||||
const offered = entitled ? PREMIUM_SCREEN_SHARE_FRAME_RATES : FREE_SCREEN_SHARE_FRAME_RATES;
|
||||
return SUPPORTED_SCREEN_SHARE_FRAME_RATES.filter((value) => value === effective || offered.includes(value));
|
||||
}
|
||||
|
||||
export function resolveUserVideoTabScreenShareState(input: UserVideoTabScreenShareInput): UserVideoTabScreenShareState {
|
||||
const target = resolveScreenShareTarget({...input.quality, sourceDimensions: null, hintSetting: input.hintSetting});
|
||||
const resolution = offeredScreenShareResolution(target.resolution);
|
||||
return {
|
||||
resolution,
|
||||
frameRate: target.frameRate,
|
||||
resolutionOptions: buildResolutionOptions(
|
||||
input.quality.entitled,
|
||||
input.selfHosted,
|
||||
input.quality.context,
|
||||
resolution,
|
||||
),
|
||||
frameRateOptions: buildFrameRateOptions(input.quality.entitled, target.frameRate),
|
||||
preset: input.quality.mode === 'custom' ? null : input.quality.mode,
|
||||
presetOverriddenByContext: selectStreamSettingsPresetOverriddenByContext(input.quality.mode, input.quality.context),
|
||||
saved: target.tierLimited
|
||||
? {
|
||||
resolution: offeredScreenShareResolution(input.quality.storedResolution),
|
||||
frameRate: resolveScreenShareFrameRate(input.quality.storedFrameRate),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
+22
-87
@@ -11,23 +11,26 @@ import {SwitchGroup, SwitchGroupItem} from '@app/features/ui/components/SwitchGr
|
||||
import PiP from '@app/features/ui/state/PiP';
|
||||
import {getElectronAPI, isDesktop} from '@app/features/ui/utils/NativeUtils';
|
||||
import styles from '@app/features/user/components/modals/tabs/AdvancedSettingsTab.module.css';
|
||||
import {selectScreenShareEncoderPathDescription} from '@app/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControlsState';
|
||||
import {CompactComboboxRow} from '@app/features/user/components/modals/tabs/components/CompactComboboxRow';
|
||||
import PrivacyPreferences from '@app/features/user/state/PrivacyPreferences';
|
||||
import * as VoiceSettingsCommands from '@app/features/voice/commands/VoiceSettingsCommands';
|
||||
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
||||
import type {
|
||||
CodecPreference,
|
||||
ScreenShareBackupCodecMode,
|
||||
ScreenShareContentHint,
|
||||
ScreenShareEncoderMode,
|
||||
ScreenShareScalabilityModePreference,
|
||||
ScreenShareSoftwareQuality,
|
||||
} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {
|
||||
describeScreenShareEncoderPath,
|
||||
getCodecCapabilityReport,
|
||||
selectAutomaticScreenShareCodec,
|
||||
} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {getGpuEncoderReportSync, loadGpuEncoderReport} from '@app/features/voice/utils/GpuEncoderCapabilities';
|
||||
import {
|
||||
getGpuEncoderReportSync,
|
||||
type HardwareEncodeReport,
|
||||
loadGpuEncoderReport,
|
||||
} from '@app/features/voice/utils/GpuEncoderCapabilities';
|
||||
import {setOpenH264Enabled} from '@app/features/voice/utils/OpenH264Status';
|
||||
import {CODEC_DISPLAY_LABEL} from '@app/features/voice/utils/ScreenShareCodecPolicy';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
@@ -57,10 +60,6 @@ const ENCODER_PATH_DESCRIPTOR = msg({
|
||||
message: 'Encoder path',
|
||||
comment: 'Label for an advanced screen-share select. Refers to hardware or software video encoding.',
|
||||
});
|
||||
const ENCODER_PATH_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message: 'Encoder preference for new screen shares.',
|
||||
comment: 'Description for the encoder path select.',
|
||||
});
|
||||
const ENCODER_PATH_AUTO_DESCRIPTOR = msg({
|
||||
message: 'Automatic',
|
||||
comment: 'Option label for an encoder path select.',
|
||||
@@ -73,26 +72,6 @@ const ENCODER_PATH_SOFTWARE_DESCRIPTOR = msg({
|
||||
message: 'Prefer software',
|
||||
comment: 'Option label for an encoder path select. Refers to software video encoders.',
|
||||
});
|
||||
const SOFTWARE_QUALITY_DESCRIPTOR = msg({
|
||||
message: 'Software encoder quality',
|
||||
comment: 'Label for an advanced screen-share select. Refers to software video encoder quality bias.',
|
||||
});
|
||||
const SOFTWARE_QUALITY_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message: 'Higher quality can increase CPU usage and latency.',
|
||||
comment: 'Description for a software encoder quality select. Keep AV1 and CPU literal.',
|
||||
});
|
||||
const SOFTWARE_QUALITY_REALTIME_DESCRIPTOR = msg({
|
||||
message: 'Real-time',
|
||||
comment: 'Option label for a software encoder quality select. Means fastest/lower-latency.',
|
||||
});
|
||||
const SOFTWARE_QUALITY_BALANCED_DESCRIPTOR = msg({
|
||||
message: 'Balanced',
|
||||
comment: 'Option label for a software encoder quality select.',
|
||||
});
|
||||
const SOFTWARE_QUALITY_QUALITY_DESCRIPTOR = msg({
|
||||
message: 'Quality',
|
||||
comment: 'Option label for a software encoder quality select. Means slower/higher-quality.',
|
||||
});
|
||||
const SVC_MODE_DESCRIPTOR = msg({
|
||||
message: 'SVC mode',
|
||||
comment: 'Label for an advanced screen-share select. SVC is a WebRTC acronym and should stay uppercase.',
|
||||
@@ -113,33 +92,14 @@ const SVC_MODE_TEMPORAL_DESCRIPTOR = msg({
|
||||
message: 'Temporal layers',
|
||||
comment: 'Option label for an SVC mode select.',
|
||||
});
|
||||
const SVC_MODE_SPATIAL_DESCRIPTOR = msg({
|
||||
message: 'Spatial and temporal layers',
|
||||
comment: 'Option label for an SVC mode select.',
|
||||
});
|
||||
const BACKUP_CODEC_DESCRIPTOR = msg({
|
||||
message: 'H.264 backup stream',
|
||||
comment: 'Label for an advanced screen-share select. H.264 is a codec name and should stay literal.',
|
||||
});
|
||||
const BACKUP_CODEC_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message: 'Adds H.264 for mixed clients. Requires more encoding.',
|
||||
comment: 'Description for an H.264 backup stream select. Keep H.264, CPU, and GPU literal.',
|
||||
});
|
||||
const BACKUP_CODEC_OFF_DESCRIPTOR = msg({
|
||||
message: 'Off',
|
||||
comment: 'Option label for an H.264 backup stream select.',
|
||||
});
|
||||
const BACKUP_CODEC_H264_SIMULCAST_DESCRIPTOR = msg({
|
||||
message: 'H.264 simulcast backup',
|
||||
comment: 'Option label for an H.264 backup stream select. H.264 is a codec name and should stay literal.',
|
||||
});
|
||||
const CONTENT_HINT_DESCRIPTOR = msg({
|
||||
message: 'Content hint',
|
||||
comment: 'Label for an advanced screen-share select. Refers to the WebRTC MediaStreamTrack contentHint value.',
|
||||
});
|
||||
const CONTENT_HINT_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message: 'Helps the browser choose motion, detail, or text handling.',
|
||||
comment: 'Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.',
|
||||
message: 'Used in Custom mode. The Gaming and Screen share presets set their own hint.',
|
||||
comment:
|
||||
'Description for a WebRTC content hint select. Custom, Gaming and Screen share are the stream quality preset labels.',
|
||||
});
|
||||
const CONTENT_HINT_AUTO_DESCRIPTOR = msg({
|
||||
message: 'Automatic',
|
||||
@@ -191,9 +151,7 @@ const HEVC_SCREEN_SHARE_OPT_IN_DESCRIPTOR = msg({
|
||||
});
|
||||
const SCREEN_SHARE_CODEC_OPTION_ORDER = ['av1', 'h265', 'h264', 'vp9', 'vp8'] as const;
|
||||
|
||||
export const ScreenShareCodecControl = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const encoderMode = VoiceSettings.getScreenShareEncoderMode();
|
||||
function useLoadedGpuEncoderReport(): HardwareEncodeReport | null {
|
||||
const [gpuReport, setGpuReport] = useState(() => getGpuEncoderReportSync());
|
||||
useEffect(() => {
|
||||
if (gpuReport) return;
|
||||
@@ -205,7 +163,14 @@ export const ScreenShareCodecControl = observer(() => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [gpuReport]);
|
||||
const automaticCodec = selectAutomaticScreenShareCodec(encoderMode).codec;
|
||||
return gpuReport;
|
||||
}
|
||||
|
||||
export const ScreenShareCodecControl = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const encoderMode = VoiceSettings.getScreenShareEncoderMode();
|
||||
useLoadedGpuEncoderReport();
|
||||
const automaticCodec = describeScreenShareEncoderPath(encoderMode).codec;
|
||||
const av1OptIn = VoiceSettings.getScreenShareAv1OptIn();
|
||||
const hevcOptIn = VoiceSettings.getScreenShareHevcOptIn();
|
||||
const codecCapabilities = getCodecCapabilityReport();
|
||||
@@ -313,25 +278,17 @@ export const ScreenSharePreviewBehaviorControl = observer(() => {
|
||||
|
||||
const ScreenShareEncoderControlsContent = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
useLoadedGpuEncoderReport();
|
||||
const {hardwareUnavailable} = describeScreenShareEncoderPath(VoiceSettings.screenShareEncoderMode);
|
||||
const encoderModeOptions: ReadonlyArray<ComboboxOption<ScreenShareEncoderMode>> = [
|
||||
{value: 'auto', label: i18n._(ENCODER_PATH_AUTO_DESCRIPTOR)},
|
||||
{value: 'hardware', label: i18n._(ENCODER_PATH_HARDWARE_DESCRIPTOR)},
|
||||
{value: 'software', label: i18n._(ENCODER_PATH_SOFTWARE_DESCRIPTOR)},
|
||||
];
|
||||
const softwareQualityOptions: ReadonlyArray<ComboboxOption<ScreenShareSoftwareQuality>> = [
|
||||
{value: 'realtime', label: i18n._(SOFTWARE_QUALITY_REALTIME_DESCRIPTOR)},
|
||||
{value: 'balanced', label: i18n._(SOFTWARE_QUALITY_BALANCED_DESCRIPTOR)},
|
||||
{value: 'quality', label: i18n._(SOFTWARE_QUALITY_QUALITY_DESCRIPTOR)},
|
||||
];
|
||||
const scalabilityModeOptions: ReadonlyArray<ComboboxOption<ScreenShareScalabilityModePreference>> = [
|
||||
{value: 'auto', label: i18n._(SVC_MODE_AUTO_DESCRIPTOR)},
|
||||
{value: 'single_layer', label: i18n._(SVC_MODE_SINGLE_LAYER_DESCRIPTOR)},
|
||||
{value: 'temporal', label: i18n._(SVC_MODE_TEMPORAL_DESCRIPTOR)},
|
||||
{value: 'spatial', label: i18n._(SVC_MODE_SPATIAL_DESCRIPTOR)},
|
||||
];
|
||||
const backupCodecOptions: ReadonlyArray<ComboboxOption<ScreenShareBackupCodecMode>> = [
|
||||
{value: 'off', label: i18n._(BACKUP_CODEC_OFF_DESCRIPTOR)},
|
||||
{value: 'h264_simulcast', label: i18n._(BACKUP_CODEC_H264_SIMULCAST_DESCRIPTOR)},
|
||||
];
|
||||
const contentHintOptions: ReadonlyArray<ComboboxOption<ScreenShareContentHint>> = [
|
||||
{value: 'auto', label: i18n._(CONTENT_HINT_AUTO_DESCRIPTOR)},
|
||||
@@ -343,7 +300,7 @@ const ScreenShareEncoderControlsContent = observer(() => {
|
||||
<div className={styles.controlStackCompact} data-flx="user.advanced-settings-tab.screen-share-encoder-controls">
|
||||
<CompactComboboxRow<ScreenShareEncoderMode>
|
||||
label={i18n._(ENCODER_PATH_DESCRIPTOR)}
|
||||
description={i18n._(ENCODER_PATH_DESCRIPTION_DESCRIPTOR)}
|
||||
description={i18n._(selectScreenShareEncoderPathDescription(hardwareUnavailable))}
|
||||
value={VoiceSettings.screenShareEncoderMode}
|
||||
options={encoderModeOptions}
|
||||
onChange={(value) => VoiceSettingsCommands.update({screenShareEncoderMode: value})}
|
||||
@@ -352,17 +309,6 @@ const ScreenShareEncoderControlsContent = observer(() => {
|
||||
dataFlx="user.advanced-settings-tab.select.screen-share-encoder-path"
|
||||
data-flx="user.advanced-settings-tab.advanced-video-controls.screen-share-encoder-controls-content.compact-combobox-row.update"
|
||||
/>
|
||||
<CompactComboboxRow<ScreenShareSoftwareQuality>
|
||||
label={i18n._(SOFTWARE_QUALITY_DESCRIPTOR)}
|
||||
description={i18n._(SOFTWARE_QUALITY_DESCRIPTION_DESCRIPTOR)}
|
||||
value={VoiceSettings.screenShareSoftwareQuality}
|
||||
options={softwareQualityOptions}
|
||||
onChange={(value) => VoiceSettingsCommands.update({screenShareSoftwareQuality: value})}
|
||||
isSearchable={false}
|
||||
controlWidth="small"
|
||||
dataFlx="user.advanced-settings-tab.select.screen-share-software-quality"
|
||||
data-flx="user.advanced-settings-tab.advanced-video-controls.screen-share-encoder-controls-content.compact-combobox-row.update--2"
|
||||
/>
|
||||
<CompactComboboxRow<ScreenShareScalabilityModePreference>
|
||||
label={i18n._(SVC_MODE_DESCRIPTOR)}
|
||||
description={i18n._(SVC_MODE_DESCRIPTION_DESCRIPTOR)}
|
||||
@@ -374,17 +320,6 @@ const ScreenShareEncoderControlsContent = observer(() => {
|
||||
dataFlx="user.advanced-settings-tab.select.screen-share-svc-mode"
|
||||
data-flx="user.advanced-settings-tab.advanced-video-controls.screen-share-encoder-controls-content.compact-combobox-row.update--3"
|
||||
/>
|
||||
<CompactComboboxRow<ScreenShareBackupCodecMode>
|
||||
label={i18n._(BACKUP_CODEC_DESCRIPTOR)}
|
||||
description={i18n._(BACKUP_CODEC_DESCRIPTION_DESCRIPTOR)}
|
||||
value={VoiceSettings.screenShareBackupCodecMode}
|
||||
options={backupCodecOptions}
|
||||
onChange={(value) => VoiceSettingsCommands.update({screenShareBackupCodecMode: value})}
|
||||
isSearchable={false}
|
||||
controlWidth="large"
|
||||
dataFlx="user.advanced-settings-tab.select.screen-share-backup-codec"
|
||||
data-flx="user.advanced-settings-tab.advanced-video-controls.screen-share-encoder-controls-content.compact-combobox-row.update--4"
|
||||
/>
|
||||
<CompactComboboxRow<ScreenShareContentHint>
|
||||
label={i18n._(CONTENT_HINT_DESCRIPTOR)}
|
||||
description={i18n._(CONTENT_HINT_DESCRIPTION_DESCRIPTOR)}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {MessageDescriptor} from '@lingui/core';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
|
||||
const ENCODER_PATH_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message: 'Encoder preference for new screen shares.',
|
||||
comment: 'Description for the encoder path select.',
|
||||
});
|
||||
const ENCODER_PATH_NO_HARDWARE_DESCRIPTOR = msg({
|
||||
message: 'No hardware encoder was found on this device, so Prefer hardware works like Automatic.',
|
||||
comment:
|
||||
'Description for the encoder path select, shown when this device has no hardware video encoder. Prefer hardware and Automatic are the option labels of the same select.',
|
||||
});
|
||||
|
||||
export function selectScreenShareEncoderPathDescription(hardwareUnavailable: boolean): MessageDescriptor {
|
||||
return hardwareUnavailable ? ENCODER_PATH_NO_HARDWARE_DESCRIPTOR : ENCODER_PATH_DESCRIPTION_DESCRIPTOR;
|
||||
}
|
||||
+1
-1
@@ -171,7 +171,7 @@ const BITRATE_DESCRIPTOR = msg({
|
||||
comment: 'Settings search synonym. Used to match this term when the user types it in the settings search bar.',
|
||||
});
|
||||
const TUNE_LOW_LEVEL_SCREEN_SHARE_ENCODER_BEHAVIOR_FOR_THE_NEXT_STREAM_DESCRIPTOR = msg({
|
||||
message: 'Encoder, SVC, backup stream, and bitrate presets',
|
||||
message: 'Encoder, SVC, and bitrate presets',
|
||||
comment: 'Settings search entry description. One-line summary of what the settings search entry controls.',
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user