mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
fix(voice): never auto-select AV1 or HEVC for screen sharing (#2111)
This commit is contained in:
1 parent
059bcc6c53
commit
3b1eb56713
13 files changed
+555
-387
No files matched your search
+14
-1
@@ -42,8 +42,10 @@ import {UnreadBadgeCustomizationControl} from '@app/features/user/components/mod
|
||||
import {
|
||||
EmulatedDecodeCodecCapControl,
|
||||
OpenH264Control,
|
||||
ScreenShareAv1OptInControl,
|
||||
ScreenShareCodecControl,
|
||||
ScreenShareEncoderControls,
|
||||
ScreenShareHevcOptInControl,
|
||||
ScreenSharePreviewBehaviorControl,
|
||||
} from '@app/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls';
|
||||
import {
|
||||
@@ -83,6 +85,8 @@ export const DIRECT_CONTROL_ITEM_IDS = new Set([
|
||||
'voice-video-new-device-alerts',
|
||||
'voice-video-connection-volume-controls',
|
||||
'voice-video-screen-share-codec',
|
||||
'voice-video-screen-share-av1-opt-in',
|
||||
'voice-video-screen-share-hevc-opt-in',
|
||||
'voice-video-emulated-decode-codec-cap',
|
||||
'voice-video-openh264-codec',
|
||||
'voice-video-screen-share-preview-behavior',
|
||||
@@ -96,7 +100,6 @@ export const DIRECT_CONTROL_ITEM_IDS = new Set([
|
||||
|
||||
export const FULL_WIDTH_CONTROL_ITEM_IDS = new Set([
|
||||
'appearance-voice-channel-join-behavior',
|
||||
'voice-video-screen-share-codec',
|
||||
'voice-video-screen-share-preview-behavior',
|
||||
]);
|
||||
|
||||
@@ -122,6 +125,8 @@ export const COMPACT_SWITCH_CONTROL_ITEM_IDS = new Set([
|
||||
'voice-video-new-device-alerts',
|
||||
'voice-video-connection-volume-controls',
|
||||
'voice-video-openh264-codec',
|
||||
'voice-video-screen-share-av1-opt-in',
|
||||
'voice-video-screen-share-hevc-opt-in',
|
||||
'advanced-unread-badge-customization',
|
||||
'client-developer-mode',
|
||||
'accessibility-stay-interactive-unfocused',
|
||||
@@ -268,6 +273,14 @@ export const AdvancedSettingControl = observer(({item}: {item: SearchableSetting
|
||||
return (
|
||||
<ScreenShareCodecControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.screen-share-codec-control" />
|
||||
);
|
||||
case 'voice-video-screen-share-av1-opt-in':
|
||||
return (
|
||||
<ScreenShareAv1OptInControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.screen-share-av1-opt-in-control" />
|
||||
);
|
||||
case 'voice-video-screen-share-hevc-opt-in':
|
||||
return (
|
||||
<ScreenShareHevcOptInControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.screen-share-hevc-opt-in-control" />
|
||||
);
|
||||
case 'voice-video-emulated-decode-codec-cap':
|
||||
return (
|
||||
<EmulatedDecodeCodecCapControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.emulated-decode-codec-cap-control" />
|
||||
|
||||
+105
-5
@@ -11,7 +11,6 @@ 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 {CodecSelectorSection} from '@app/features/user/components/modals/tabs/components/CodecSelectorSection';
|
||||
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';
|
||||
@@ -24,12 +23,18 @@ import type {
|
||||
ScreenShareScalabilityModePreference,
|
||||
ScreenShareSoftwareQuality,
|
||||
} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {
|
||||
getCodecCapabilityReport,
|
||||
selectAutomaticScreenShareCodec,
|
||||
} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {getGpuEncoderReportSync, 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';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import {GearIcon} from '@phosphor-icons/react';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import {useCallback, useEffect} from 'react';
|
||||
import {useCallback, useEffect, useState} from 'react';
|
||||
|
||||
const OPENH264_LABEL_DESCRIPTOR = msg({
|
||||
message: 'OpenH264 Video Codec provided by Cisco Systems, Inc.',
|
||||
@@ -210,9 +215,104 @@ const resolveScreenShareBitrateInput = (
|
||||
return getNearestNumericOptionValue(valueInMbps, options);
|
||||
};
|
||||
|
||||
export const ScreenShareCodecControl = observer(() => (
|
||||
<CodecSelectorSection data-flx="user.advanced-settings-tab.advanced-video-controls.screen-share-codec-control.codec-selector-section" />
|
||||
));
|
||||
const PREFERRED_SCREEN_SHARE_CODEC_DESCRIPTOR = msg({
|
||||
message: 'Preferred screen share codec',
|
||||
comment: 'Accessible label for the advanced screen-share codec preference select.',
|
||||
});
|
||||
const AUTOMATIC_CODEC_OPTION_DESCRIPTOR = msg({
|
||||
message: 'Automatic ({codec})',
|
||||
comment: 'Option label for the screen-share codec select. codec is the automatically selected codec label.',
|
||||
});
|
||||
const AV1_REQUIRES_OPT_IN_OPTION_DESCRIPTOR = msg({
|
||||
message: 'AV1 (requires opt-in)',
|
||||
comment:
|
||||
'Option label for the screen-share codec select while the AV1 opt-in is off and the option is disabled. AV1 is a codec name and should stay literal.',
|
||||
});
|
||||
const H265_REQUIRES_OPT_IN_OPTION_DESCRIPTOR = msg({
|
||||
message: 'H.265 (requires opt-in)',
|
||||
comment:
|
||||
'Option label for the screen-share codec select while the HEVC opt-in is off and the option is disabled. H.265 is a codec name and should stay literal.',
|
||||
});
|
||||
const AV1_SCREEN_SHARE_OPT_IN_DESCRIPTOR = msg({
|
||||
message: 'Allow AV1 for screen sharing',
|
||||
comment: 'Switch label for the AV1 screen-share opt-in. AV1 is a codec name and should stay literal.',
|
||||
});
|
||||
const HEVC_SCREEN_SHARE_OPT_IN_DESCRIPTOR = msg({
|
||||
message: 'Allow H.265 (HEVC) for screen sharing',
|
||||
comment:
|
||||
'Switch label for the H.265/HEVC screen-share opt-in. H.265 and HEVC are codec names and should stay literal.',
|
||||
});
|
||||
const SCREEN_SHARE_CODEC_OPTION_ORDER = ['av1', 'h265', 'h264', 'vp9', 'vp8'] as const;
|
||||
|
||||
export const ScreenShareCodecControl = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const encoderMode = VoiceSettings.getScreenShareEncoderMode();
|
||||
const [gpuReport, setGpuReport] = useState(() => getGpuEncoderReportSync());
|
||||
useEffect(() => {
|
||||
if (gpuReport) return;
|
||||
let cancelled = false;
|
||||
void loadGpuEncoderReport().then((report) => {
|
||||
if (!cancelled) setGpuReport(report);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [gpuReport]);
|
||||
const automaticCodec = selectAutomaticScreenShareCodec(encoderMode).codec;
|
||||
const av1OptIn = VoiceSettings.getScreenShareAv1OptIn();
|
||||
const hevcOptIn = VoiceSettings.getScreenShareHevcOptIn();
|
||||
const codecCapabilities = getCodecCapabilityReport();
|
||||
const options: ReadonlyArray<ComboboxOption<CodecPreference>> = [
|
||||
{value: 'auto', label: i18n._(AUTOMATIC_CODEC_OPTION_DESCRIPTOR, {codec: CODEC_DISPLAY_LABEL[automaticCodec]})},
|
||||
...SCREEN_SHARE_CODEC_OPTION_ORDER.map((codec) => ({
|
||||
value: codec,
|
||||
label:
|
||||
codec === 'av1' && !av1OptIn
|
||||
? i18n._(AV1_REQUIRES_OPT_IN_OPTION_DESCRIPTOR)
|
||||
: codec === 'h265' && !hevcOptIn
|
||||
? i18n._(H265_REQUIRES_OPT_IN_OPTION_DESCRIPTOR)
|
||||
: CODEC_DISPLAY_LABEL[codec],
|
||||
isDisabled: !codecCapabilities[codec].supported,
|
||||
})),
|
||||
];
|
||||
return (
|
||||
<Combobox<CodecPreference, false>
|
||||
value={VoiceSettings.getPreferredScreenShareCodec()}
|
||||
options={options}
|
||||
onChange={(value) => VoiceSettingsCommands.update({preferredScreenShareCodec: value})}
|
||||
density="compact"
|
||||
isSearchable={false}
|
||||
aria-label={i18n._(PREFERRED_SCREEN_SHARE_CODEC_DESCRIPTOR)}
|
||||
data-flx="user.advanced-settings-tab.select.preferred-screen-share-codec"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const ScreenShareAv1OptInControl = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
return (
|
||||
<Switch
|
||||
ariaLabel={i18n._(AV1_SCREEN_SHARE_OPT_IN_DESCRIPTOR)}
|
||||
value={VoiceSettings.getScreenShareAv1OptIn()}
|
||||
onChange={(value) => VoiceSettingsCommands.update({screenShareAv1OptIn: value})}
|
||||
compact
|
||||
data-flx="user.advanced-settings-tab.switch.screen-share-av1-opt-in"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const ScreenShareHevcOptInControl = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
return (
|
||||
<Switch
|
||||
ariaLabel={i18n._(HEVC_SCREEN_SHARE_OPT_IN_DESCRIPTOR)}
|
||||
value={VoiceSettings.getScreenShareHevcOptIn()}
|
||||
onChange={(value) => VoiceSettingsCommands.update({screenShareHevcOptIn: value})}
|
||||
compact
|
||||
data-flx="user.advanced-settings-tab.switch.screen-share-hevc-opt-in"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const EmulatedDecodeCodecCapControl = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {AutomaticScreenShareCodecSelection} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {CODEC_DISPLAY_LABEL} from '@app/features/voice/utils/ScreenShareCodecPolicy';
|
||||
import type {I18n} from '@lingui/core';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
|
||||
const AUTO_DESCRIPTION_FIREFOX_VP8_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because Firefox screen sharing works most reliably with it.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as VP8 and should not be translated. Keep Firefox as a product name.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_NON_CHROMIUM_H264_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because this browser supports it and it is the best fallback outside Chromium.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.264 and should not be translated. Keep Chromium as a product name.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_DESKTOP_NON_CHROMIUM_H264_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because your computer supports it and it is the best fallback outside Chromium.',
|
||||
comment:
|
||||
'Desktop-client description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.264 and should not be translated. Keep Chromium as a product name.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_NON_CHROMIUM_VP8_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because this browser does not expose H.264, so VP8 is the safest fallback.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as VP8 and should not be translated. Keep codec names literal.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_DESKTOP_NON_CHROMIUM_VP8_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because your computer does not expose H.264, so VP8 is the safest fallback.',
|
||||
comment:
|
||||
'Desktop-client description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as VP8 and should not be translated. Keep codec names literal.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_HARDWARE_AV1_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because your GPU can encode it in hardware, with the best compression for screen sharing.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as AV1 and should not be translated. Keep GPU as an acronym.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_HARDWARE_H264_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because your GPU can encode it in hardware and it is the most compatible screen-share default.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.264 and should not be translated. Keep GPU as an acronym.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_HARDWARE_H265_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because your GPU can encode it in hardware, with excellent compression and low CPU usage.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.265 and should not be translated. Keep GPU as an acronym.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_HARDWARE_VP9_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because your GPU can encode it in hardware and it gives strong screen-share quality.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as VP9 and should not be translated. Keep GPU as an acronym.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_SOFTWARE_AV1_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because software encoding is preferred and this browser exposes AV1.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as AV1 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_DESKTOP_SOFTWARE_AV1_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because software encoding is preferred and your computer exposes AV1.',
|
||||
comment:
|
||||
'Desktop-client description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as AV1 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_SOFTWARE_H265_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because no hardware encoder is available and this browser exposes H.265.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.265 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_DESKTOP_SOFTWARE_H265_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because no hardware encoder is available and your computer exposes H.265.',
|
||||
comment:
|
||||
'Desktop-client description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.265 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_SOFTWARE_VP9_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because software encoding is preferred and this browser exposes VP9.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as VP9 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_DESKTOP_SOFTWARE_VP9_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because software encoding is preferred and your computer exposes VP9.',
|
||||
comment:
|
||||
'Desktop-client description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as VP9 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_SOFTWARE_H264_DESCRIPTOR = msg({
|
||||
message: 'Automatic would use {codecName} because no higher-quality encoder is available and H.264 is compatible.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.264 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_SOFTWARE_VP8_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because software encoding is preferred and VP8 is the safest software fallback.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as VP8 and should not be translated.',
|
||||
});
|
||||
const AUTO_DESCRIPTION_OPENH264_REQUIRED_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Automatic would use {codecName} because no video encoder is exposed. On Linux, OpenH264 may need to be installed.',
|
||||
comment:
|
||||
'Description shown under the Automatic option in the screen-share codec radio group. {codecName} is a codec label such as H.264 and should not be translated. Keep OpenH264 and Linux literal.',
|
||||
});
|
||||
|
||||
export function getAutomaticDescription(
|
||||
i18n: I18n,
|
||||
selection: AutomaticScreenShareCodecSelection,
|
||||
options: {isDesktopClient?: boolean} = {},
|
||||
): string {
|
||||
const codecName = CODEC_DISPLAY_LABEL[selection.codec];
|
||||
switch (selection.reason) {
|
||||
case 'firefox-vp8':
|
||||
return i18n._(AUTO_DESCRIPTION_FIREFOX_VP8_DESCRIPTOR, {codecName});
|
||||
case 'non-chromium-h264':
|
||||
return i18n._(
|
||||
options.isDesktopClient
|
||||
? AUTO_DESCRIPTION_DESKTOP_NON_CHROMIUM_H264_DESCRIPTOR
|
||||
: AUTO_DESCRIPTION_NON_CHROMIUM_H264_DESCRIPTOR,
|
||||
{codecName},
|
||||
);
|
||||
case 'non-chromium-vp8':
|
||||
return i18n._(
|
||||
options.isDesktopClient
|
||||
? AUTO_DESCRIPTION_DESKTOP_NON_CHROMIUM_VP8_DESCRIPTOR
|
||||
: AUTO_DESCRIPTION_NON_CHROMIUM_VP8_DESCRIPTOR,
|
||||
{codecName},
|
||||
);
|
||||
case 'hardware-av1':
|
||||
return i18n._(AUTO_DESCRIPTION_HARDWARE_AV1_DESCRIPTOR, {codecName});
|
||||
case 'hardware-h265':
|
||||
return i18n._(AUTO_DESCRIPTION_HARDWARE_H265_DESCRIPTOR, {codecName});
|
||||
case 'hardware-h264':
|
||||
return i18n._(AUTO_DESCRIPTION_HARDWARE_H264_DESCRIPTOR, {codecName});
|
||||
case 'hardware-vp9':
|
||||
return i18n._(AUTO_DESCRIPTION_HARDWARE_VP9_DESCRIPTOR, {codecName});
|
||||
case 'software-av1':
|
||||
return i18n._(
|
||||
options.isDesktopClient
|
||||
? AUTO_DESCRIPTION_DESKTOP_SOFTWARE_AV1_DESCRIPTOR
|
||||
: AUTO_DESCRIPTION_SOFTWARE_AV1_DESCRIPTOR,
|
||||
{codecName},
|
||||
);
|
||||
case 'software-h265':
|
||||
return i18n._(
|
||||
options.isDesktopClient
|
||||
? AUTO_DESCRIPTION_DESKTOP_SOFTWARE_H265_DESCRIPTOR
|
||||
: AUTO_DESCRIPTION_SOFTWARE_H265_DESCRIPTOR,
|
||||
{codecName},
|
||||
);
|
||||
case 'software-vp9':
|
||||
return i18n._(
|
||||
options.isDesktopClient
|
||||
? AUTO_DESCRIPTION_DESKTOP_SOFTWARE_VP9_DESCRIPTOR
|
||||
: AUTO_DESCRIPTION_SOFTWARE_VP9_DESCRIPTOR,
|
||||
{codecName},
|
||||
);
|
||||
case 'software-h264':
|
||||
return i18n._(AUTO_DESCRIPTION_SOFTWARE_H264_DESCRIPTOR, {codecName});
|
||||
case 'software-vp8':
|
||||
return i18n._(AUTO_DESCRIPTION_SOFTWARE_VP8_DESCRIPTOR, {codecName});
|
||||
case 'openh264-required':
|
||||
return i18n._(AUTO_DESCRIPTION_OPENH264_REQUIRED_DESCRIPTOR, {codecName});
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
.codecPicker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.codecRadioGroup {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.codecLabelRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.codecLabel {
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.codecBadge {
|
||||
flex: 0 0 auto;
|
||||
border-radius: 62.4375rem;
|
||||
padding: 0.0625rem 0.5rem;
|
||||
background-color: var(--background-modifier-accent);
|
||||
color: var(--text-primary-muted);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
border: 0.0625rem solid transparent;
|
||||
}
|
||||
|
||||
.codecBadgeRecommended {
|
||||
background-color: var(--brand-primary);
|
||||
color: white;
|
||||
border-color: var(--brand-primary);
|
||||
}
|
||||
|
||||
.codecHint {
|
||||
margin: 0;
|
||||
color: var(--status-warning);
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.codecAdvancedLink {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
getCachedDesktopTroubleshootingSettings,
|
||||
getDesktopTroubleshootingSettings,
|
||||
} from '@app/features/devtools/utils/DesktopTroubleshootingUtils';
|
||||
import {ComponentBus} from '@app/features/platform/utils/ComponentBus';
|
||||
import type {RadioOption} from '@app/features/ui/radio_group/RadioGroup';
|
||||
import {RadioGroup} from '@app/features/ui/radio_group/RadioGroup';
|
||||
import {isDesktop} from '@app/features/ui/utils/NativeUtils';
|
||||
import styles from '@app/features/user/components/modals/tabs/components/CodecSelectorSection.module.css';
|
||||
import {getUserSettingsTabLabel} from '@app/features/user/components/settings_utils/SettingsConstants';
|
||||
import * as VoiceSettingsCommands from '@app/features/voice/commands/VoiceSettingsCommands';
|
||||
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
||||
import {
|
||||
type CodecPreference,
|
||||
getCodecCapabilityReport,
|
||||
getLiveKitSupportedCodecs,
|
||||
selectAutomaticScreenShareCodec,
|
||||
} from '@app/features/voice/utils/CodecCapabilityDetector';
|
||||
import {
|
||||
getGpuEncoderReportSync,
|
||||
type HardwareEncodeReport,
|
||||
loadGpuEncoderReport,
|
||||
} from '@app/features/voice/utils/GpuEncoderCapabilities';
|
||||
import {CODEC_DISPLAY_LABEL} from '@app/features/voice/utils/ScreenShareCodecPolicy';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {Trans, useLingui} from '@lingui/react/macro';
|
||||
import {clsx} from 'clsx';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {getAutomaticDescription} from './CodecSelectorDescription';
|
||||
|
||||
const AUTO_DESCRIPTOR = msg({
|
||||
message: 'Automatic',
|
||||
comment: 'Label for the auto codec option in the screen-share codec radio group. Keep it concise.',
|
||||
});
|
||||
const RECOMMENDED_DESCRIPTOR = msg({
|
||||
message: 'Recommended',
|
||||
comment: 'Badge shown next to the auto codec option in the screen-share codec radio group. Keep it concise.',
|
||||
});
|
||||
const SCREEN_SHARE_VIDEO_CODEC_DESCRIPTOR = msg({
|
||||
message: 'Screen share video codec',
|
||||
comment: 'Accessible label for the screen-share codec radio group.',
|
||||
});
|
||||
|
||||
interface CodecRadioOption extends RadioOption<CodecPreference> {
|
||||
value: CodecPreference;
|
||||
}
|
||||
|
||||
export const CodecSelectorSection = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const isDesktopClient = isDesktop();
|
||||
const advancedSettingsLabel = getUserSettingsTabLabel(i18n, 'advanced_settings');
|
||||
const preference = VoiceSettings.getPreferredScreenShareCodec();
|
||||
const encoderMode = VoiceSettings.getScreenShareEncoderMode();
|
||||
const [gpuReport, setGpuReport] = useState<HardwareEncodeReport | null>(() => getGpuEncoderReportSync());
|
||||
const [hardwareAccelDisabled, setHardwareAccelDisabled] = useState<boolean | null>(
|
||||
() => getCachedDesktopTroubleshootingSettings()?.disableHardwareAcceleration ?? null,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (gpuReport) return;
|
||||
let cancelled = false;
|
||||
void loadGpuEncoderReport().then((report) => {
|
||||
if (!cancelled) setGpuReport(report);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [gpuReport]);
|
||||
useEffect(() => {
|
||||
if (!isDesktopClient || hardwareAccelDisabled !== null) return;
|
||||
let cancelled = false;
|
||||
void getDesktopTroubleshootingSettings().then((settings) => {
|
||||
if (!cancelled && settings) setHardwareAccelDisabled(settings.disableHardwareAcceleration);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [hardwareAccelDisabled, isDesktopClient]);
|
||||
const report = useMemo(() => getCodecCapabilityReport(), [gpuReport, hardwareAccelDisabled]);
|
||||
const liveKitCodecs = useMemo(() => getLiveKitSupportedCodecs(), []);
|
||||
const automaticSelection = useMemo(
|
||||
() => selectAutomaticScreenShareCodec(encoderMode),
|
||||
[encoderMode, gpuReport, hardwareAccelDisabled],
|
||||
);
|
||||
const options = useMemo<ReadonlyArray<CodecRadioOption>>(() => {
|
||||
const autoOption: CodecRadioOption = {
|
||||
value: 'auto',
|
||||
name: (
|
||||
<div className={styles.codecLabelRow} data-flx="user.codec-selector-section.codec-label-row">
|
||||
<span className={styles.codecLabel} data-flx="user.codec-selector-section.codec-label">
|
||||
{i18n._(AUTO_DESCRIPTOR)}
|
||||
</span>
|
||||
<span
|
||||
className={clsx(styles.codecBadge, styles.codecBadgeRecommended)}
|
||||
data-flx="user.codec-selector-section.codec-badge"
|
||||
>
|
||||
{i18n._(RECOMMENDED_DESCRIPTOR)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
desc: getAutomaticDescription(i18n, automaticSelection, {isDesktopClient}),
|
||||
};
|
||||
const codecOptions = liveKitCodecs.map<CodecRadioOption>((codec) => ({
|
||||
value: codec,
|
||||
name: CODEC_DISPLAY_LABEL[codec],
|
||||
disabled: !report[codec].supported,
|
||||
}));
|
||||
return [autoOption, ...codecOptions];
|
||||
}, [automaticSelection, liveKitCodecs, report, i18n.locale, isDesktopClient]);
|
||||
const handleChange = useCallback((value: CodecPreference) => {
|
||||
VoiceSettingsCommands.update({preferredScreenShareCodec: value});
|
||||
}, []);
|
||||
const openAdvancedSettingsTab = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
ComponentBus.dispatch('USER_SETTINGS_TAB_SELECT', {tab: 'advanced_settings'});
|
||||
}, []);
|
||||
const hardwareAccelOff = isDesktopClient && hardwareAccelDisabled === true;
|
||||
return (
|
||||
<div className={styles.codecPicker} data-flx="user.codec-selector-section.codec-picker">
|
||||
<RadioGroup<CodecPreference>
|
||||
aria-label={i18n._(SCREEN_SHARE_VIDEO_CODEC_DESCRIPTOR)}
|
||||
options={options}
|
||||
value={preference}
|
||||
onChange={handleChange}
|
||||
className={styles.codecRadioGroup}
|
||||
data-flx="user.codec-selector-section.radio-group.change"
|
||||
/>
|
||||
{hardwareAccelOff ? (
|
||||
<p className={styles.codecHint} data-flx="user.codec-selector-section.codec-hint">
|
||||
<Trans>
|
||||
Hardware acceleration is off, so codecs will encode on your CPU.{' '}
|
||||
<button
|
||||
type="button"
|
||||
className={styles.codecAdvancedLink}
|
||||
onClick={openAdvancedSettingsTab}
|
||||
data-flx="user.codec-selector-section.codec-advanced-link.open-advanced-settings-tab.button"
|
||||
>
|
||||
Turn it back on in {advancedSettingsLabel}
|
||||
</button>
|
||||
.
|
||||
</Trans>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user