mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -445,6 +445,19 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
data-flx="user.voice-tab.render-auto-gain-control-switch.switch.update-auto-gain-control"
|
data-flx="user.voice-tab.render-auto-gain-control-switch.switch.update-auto-gain-control"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
const renderStereoMicrophoneSwitch = (dataFlx: string) =>
|
||||||
|
stereoMicrophoneAvailable && (
|
||||||
|
<Switch
|
||||||
|
label={i18n._(STEREO_MICROPHONE_DESCRIPTOR)}
|
||||||
|
description={i18n._(STEREO_MICROPHONE_DESCRIPTION_DESCRIPTOR)}
|
||||||
|
value={isStereoMicrophoneEnabled()}
|
||||||
|
onChange={(value) => {
|
||||||
|
VoiceSettings.stereoMicrophone = value;
|
||||||
|
}}
|
||||||
|
ariaLabel={i18n._(STEREO_MICROPHONE_DESCRIPTOR)}
|
||||||
|
data-flx={dataFlx}
|
||||||
|
/>
|
||||||
|
);
|
||||||
const renderCustomProfile = () => (
|
const renderCustomProfile = () => (
|
||||||
<div className={styles.profileSubSection} data-flx="user.voice-tab.render-custom-profile.profile-sub-section">
|
<div className={styles.profileSubSection} data-flx="user.voice-tab.render-custom-profile.profile-sub-section">
|
||||||
{renderPttControls()}
|
{renderPttControls()}
|
||||||
@@ -496,18 +509,7 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
dataFlx="user.voice-tab.render-custom-profile.select.set-noise-suppression-method"
|
dataFlx="user.voice-tab.render-custom-profile.select.set-noise-suppression-method"
|
||||||
data-flx="user.user-voice-tab.render-custom-profile.compact-combobox-row.set-noise-suppression-method"
|
data-flx="user.user-voice-tab.render-custom-profile.compact-combobox-row.set-noise-suppression-method"
|
||||||
/>
|
/>
|
||||||
{stereoMicrophoneAvailable && (
|
{renderStereoMicrophoneSwitch('user.voice-tab.render-custom-profile.switch.set-stereo-microphone')}
|
||||||
<Switch
|
|
||||||
label={i18n._(STEREO_MICROPHONE_DESCRIPTOR)}
|
|
||||||
description={i18n._(STEREO_MICROPHONE_DESCRIPTION_DESCRIPTOR)}
|
|
||||||
value={isStereoMicrophoneEnabled()}
|
|
||||||
onChange={(value) => {
|
|
||||||
VoiceSettings.stereoMicrophone = value;
|
|
||||||
}}
|
|
||||||
ariaLabel={i18n._(STEREO_MICROPHONE_DESCRIPTOR)}
|
|
||||||
data-flx="user.voice-tab.render-custom-profile.switch.set-stereo-microphone"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Switch
|
<Switch
|
||||||
label={i18n._(VOICE_ECHO_CANCELLATION_DESCRIPTOR)}
|
label={i18n._(VOICE_ECHO_CANCELLATION_DESCRIPTOR)}
|
||||||
value={echoCancellation}
|
value={echoCancellation}
|
||||||
@@ -669,6 +671,11 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
{renderAutoGainControlSwitch()}
|
{renderAutoGainControlSwitch()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{voiceProcessingMode === 'studio' && stereoMicrophoneAvailable && (
|
||||||
|
<div className={styles.profileSubSection} data-flx="user.voice-tab.studio-profile-sub-section">
|
||||||
|
{renderStereoMicrophoneSwitch('user.voice-tab.studio-profile.switch.set-stereo-microphone')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{voiceProcessingMode === 'studio' && pttCombo?.key && isPushToTalk && (
|
{voiceProcessingMode === 'studio' && pttCombo?.key && isPushToTalk && (
|
||||||
<WarningAlert data-flx="user.voice-tab.warning-alert--2">
|
<WarningAlert data-flx="user.voice-tab.warning-alert--2">
|
||||||
<Trans>Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it.</Trans>
|
<Trans>Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it.</Trans>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ import EntranceSoundLibrary from '@app/features/voice/state/EntranceSoundLibrary
|
|||||||
import LocalVoiceState from '@app/features/voice/state/LocalVoiceState';
|
import LocalVoiceState from '@app/features/voice/state/LocalVoiceState';
|
||||||
import ParticipantVolume from '@app/features/voice/state/ParticipantVolume';
|
import ParticipantVolume from '@app/features/voice/state/ParticipantVolume';
|
||||||
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
||||||
import {buildMicrophonePublishOptions} from '@app/features/voice/utils/AudioPublishOptions';
|
import {buildMicrophonePublishOptions, sendsStereoMicrophone} from '@app/features/voice/utils/AudioPublishOptions';
|
||||||
import {
|
import {
|
||||||
buildCameraPublishOptions,
|
buildCameraPublishOptions,
|
||||||
findVideoPublishCodecPolicyViolation,
|
findVideoPublishCodecPolicyViolation,
|
||||||
@@ -605,24 +605,31 @@ export class VoiceEngineV2AppMediaExecutionAdapter extends Store {
|
|||||||
return applyNoiseSuppressionOverride(profile, readEffectiveNoiseSuppression(MICROPHONE_CAPTURE_SAMPLE_RATE));
|
return applyNoiseSuppressionOverride(profile, readEffectiveNoiseSuppression(MICROPHONE_CAPTURE_SAMPLE_RATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMicrophoneCaptureOptions(options: VoiceEngineV2MicrophoneOptions = {}): AudioCaptureOptions {
|
private resolveMicrophoneChannelBitrate(channelId: string | null): number {
|
||||||
|
const channel = channelId ? Channels.getChannel(channelId) : null;
|
||||||
|
const guild = channel?.guildId ? Guilds.getGuild(channel.guildId) : null;
|
||||||
|
return resolveVoiceChannelBitrate(channel?.bitrate, guild?.features);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMicrophoneCaptureOptions(
|
||||||
|
options: VoiceEngineV2MicrophoneOptions = {},
|
||||||
|
channelId: string | null = this.getActiveChannelId(),
|
||||||
|
): AudioCaptureOptions {
|
||||||
const profile = this.resolveActiveMicrophoneProfile();
|
const profile = this.resolveActiveMicrophoneProfile();
|
||||||
|
const stereo = sendsStereoMicrophone(this.resolveMicrophoneChannelBitrate(channelId), profile.stereoCapture);
|
||||||
return {
|
return {
|
||||||
deviceId: options.deviceId ?? this.resolveInputDeviceId(),
|
deviceId: options.deviceId ?? this.resolveInputDeviceId(),
|
||||||
echoCancellation: options.echoCancellation ?? profile.echoCancellation,
|
echoCancellation: options.echoCancellation ?? profile.echoCancellation,
|
||||||
noiseSuppression: options.noiseSuppression ?? profile.browserNoiseSuppression,
|
noiseSuppression: options.noiseSuppression ?? profile.browserNoiseSuppression,
|
||||||
autoGainControl: options.autoGainControl ?? profile.autoGainControl,
|
autoGainControl: options.autoGainControl ?? profile.autoGainControl,
|
||||||
voiceIsolation: false,
|
voiceIsolation: false,
|
||||||
...(profile.stereoCapture ? {channelCount: {ideal: 2}} : {}),
|
...(stereo ? {channelCount: {ideal: 2}} : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMicrophonePublishOptions(channelId: string | null): TrackPublishOptions | undefined {
|
private getMicrophonePublishOptions(channelId: string | null): TrackPublishOptions | undefined {
|
||||||
const channel = channelId ? Channels.getChannel(channelId) : null;
|
|
||||||
const guild = channel?.guildId ? Guilds.getGuild(channel.guildId) : null;
|
|
||||||
const channelBitrate = resolveVoiceChannelBitrate(channel?.bitrate, guild?.features);
|
|
||||||
const profile = this.resolveActiveMicrophoneProfile();
|
const profile = this.resolveActiveMicrophoneProfile();
|
||||||
return buildMicrophonePublishOptions(channelBitrate, profile.stereoCapture);
|
return buildMicrophonePublishOptions(this.resolveMicrophoneChannelBitrate(channelId), profile.stereoCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshMicrophonePublishSettings(room: Room | null, channelId: string | null): Promise<void> {
|
async refreshMicrophonePublishSettings(room: Room | null, channelId: string | null): Promise<void> {
|
||||||
@@ -902,7 +909,7 @@ export class VoiceEngineV2AppMediaExecutionAdapter extends Store {
|
|||||||
assertObjectLike<MicrophoneEnableState>(state, 'publishMicrophoneAudioTrack.state');
|
assertObjectLike<MicrophoneEnableState>(state, 'publishMicrophoneAudioTrack.state');
|
||||||
await ctx.room.localParticipant.setMicrophoneEnabled(
|
await ctx.room.localParticipant.setMicrophoneEnabled(
|
||||||
true,
|
true,
|
||||||
this.getMicrophoneCaptureOptions(ctx.options),
|
this.getMicrophoneCaptureOptions(ctx.options, ctx.channelId),
|
||||||
this.getMicrophonePublishOptions(ctx.channelId),
|
this.getMicrophonePublishOptions(ctx.channelId),
|
||||||
);
|
);
|
||||||
state.microphoneWasPublished = true;
|
state.microphoneWasPublished = true;
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ export function normaliseAudioBitrateBps(value: number | null | undefined): numb
|
|||||||
return Math.min(Math.max(bitsPerSecond, VOICE_CHANNEL_MIN_AUDIO_BITRATE_BPS), OPUS_MAX_AUDIO_BITRATE_BPS);
|
return Math.min(Math.max(bitsPerSecond, VOICE_CHANNEL_MIN_AUDIO_BITRATE_BPS), OPUS_MAX_AUDIO_BITRATE_BPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sendsStereoMicrophone(channelBitrate: number | null | undefined, stereoCapture: boolean): boolean {
|
||||||
|
const maxBitrate = normaliseAudioBitrateBps(channelBitrate);
|
||||||
|
return stereoCapture && maxBitrate !== undefined && maxBitrate >= STEREO_VOICE_MIN_AUDIO_BITRATE_BPS;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildMicrophonePublishOptions(
|
export function buildMicrophonePublishOptions(
|
||||||
channelBitrate: number | null | undefined,
|
channelBitrate: number | null | undefined,
|
||||||
stereoCapture = false,
|
stereoCapture = false,
|
||||||
@@ -29,7 +34,7 @@ export function buildMicrophonePublishOptions(
|
|||||||
},
|
},
|
||||||
dtx: false,
|
dtx: false,
|
||||||
red: true,
|
red: true,
|
||||||
forceStereo: stereoCapture && maxBitrate >= STEREO_VOICE_MIN_AUDIO_BITRATE_BPS ? undefined : false,
|
forceStereo: sendsStereoMicrophone(maxBitrate, stereoCapture) ? undefined : false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,9 +84,8 @@ export function supportsStereoCapture(profile: ResolvedVoiceProcessing): boolean
|
|||||||
|
|
||||||
export function resolveStereoCapture(effective: EffectiveNoiseSuppression, profile: ResolvedVoiceProcessing): boolean {
|
export function resolveStereoCapture(effective: EffectiveNoiseSuppression, profile: ResolvedVoiceProcessing): boolean {
|
||||||
if (!supportsStereoCapture(profile)) return false;
|
if (!supportsStereoCapture(profile)) return false;
|
||||||
if (profile.mode === 'studio') return true;
|
if (profile.mode === 'voice') return false;
|
||||||
if (profile.mode === 'custom') return effective.stereoPreferred;
|
return effective.stereoPreferred;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyNoiseSuppressionOverride(
|
export function applyNoiseSuppressionOverride(
|
||||||
|
|||||||
Reference in New Issue
Block a user