Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 3 additions & 16 deletions .codex/skills/run-headless-simulator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,21 +221,8 @@ Video constraint:
Noise suppression:

- `none`
- `deepfilternet`
- `rnnoise`
- `iris-carthy`
- `krisp-high`
- `krisp-medium`
- `krisp-low`
- `krisp-high-with-bvc`
- `krisp-medium-with-bvc`
- `ai-coustics-sparrow-xxs`
- `ai-coustics-sparrow-xs`
- `ai-coustics-sparrow-s`
- `ai-coustics-sparrow-l`
- `ai-coustics-sparrow-xxs-48khz`
- `ai-coustics-sparrow-xs-48khz`
- `ai-coustics-rook-s-48khz`
- `ai-coustics-rook-l-48khz`
- `hyper-voice`

Cloudflare participants run with `none` only.

Run `just dev --logging info headless --help` or `hyper-client-simulator headless --help` if a flag fails.
389 changes: 224 additions & 165 deletions browser/src/participant/cloudflare/mod.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion browser/src/participant/frontend/lite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ impl ParticipantInnerLite {
video_constraint_publish_webcam: VideoConstraint::default(),
video_constraint_subscribe: VideoConstraint::default(),
video_max_concurrent_tracks: None,
noise_suppression: NoiseSuppression::default(),
noise_suppression: NoiseSuppression::Disabled,
muted: !self.context.launch_spec.settings.audio_enabled,
video_activated: self.context.launch_spec.settings.video_enabled,
..Default::default()
Expand Down
4 changes: 2 additions & 2 deletions browser/src/participant/shared/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ mod tests {
video_enabled: false,
screenshare_enabled: true,
auto_gain_control: true,
noise_suppression: NoiseSuppression::RNNoise,
noise_suppression: NoiseSuppression::HyperVoice,
transport: TransportMode::WebRTC,
video_constraint_publish_webcam: VideoConstraint::P480,
video_constraint_subscribe: VideoConstraint::P720,
Expand All @@ -126,7 +126,7 @@ mod tests {
assert!(!spec.settings.video_enabled);
assert!(spec.settings.screenshare_enabled);
assert!(spec.settings.auto_gain_control);
assert_eq!(spec.settings.noise_suppression, NoiseSuppression::RNNoise);
assert_eq!(spec.settings.noise_suppression, NoiseSuppression::HyperVoice);
assert_eq!(spec.settings.transport, TransportMode::WebRTC);
assert_eq!(spec.settings.video_constraint_publish_webcam, VideoConstraint::P480);
assert_eq!(spec.settings.video_constraint_subscribe, VideoConstraint::P720);
Expand Down
4 changes: 2 additions & 2 deletions browser/tests/cloudflare_driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ async fn cloudflare_runtime_delegates_hyper_core_authentication_to_the_worker()
"videoActivated": true,
"screenshareActivated": false,
"autoGainControl": true,
"noiseSuppression": "ai-coustics-rook-s-48khz",
"noiseSuppression": "none",
"transportMode": "webrtc",
"videoConstraintPublishWebcam": "1080p",
"videoConstraintSubscribe": "none",
Expand Down Expand Up @@ -317,7 +317,7 @@ async fn cloudflare_runtime_delegates_hyper_core_authentication_to_the_worker()
assert!(started.joined);
assert_eq!(
started.noise_suppression,
client_simulator_config::NoiseSuppression::AiCousticsRookS48khz
client_simulator_config::NoiseSuppression::Disabled
);

participant.close().await;
Expand Down
71 changes: 23 additions & 48 deletions config/src/client_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,60 +92,16 @@ impl VideoMaxConcurrentTracksPreset {
}
}

/// A value of hyper.video's noise suppression setting.
#[derive(Debug, Default, Clone, Copy, Display, EnumIter, EnumString, Serialize, Deserialize, PartialEq, PartialOrd)]
pub enum NoiseSuppression {
#[default]
#[strum(to_string = "none")]
#[serde(rename = "none")]
Disabled,
#[strum(to_string = "deepfilternet")]
#[serde(rename = "deepfilternet")]
Deepfilternet,
#[strum(to_string = "rnnoise")]
#[serde(rename = "rnnoise")]
RNNoise,
#[strum(to_string = "iris-carthy")]
#[serde(rename = "iris-carthy")]
IRISCarthy,
#[strum(to_string = "krisp-high")]
#[serde(rename = "krisp-high")]
KrispHigh,
#[strum(to_string = "krisp-medium")]
#[serde(rename = "krisp-medium")]
KrispMedium,
#[strum(to_string = "krisp-low")]
#[serde(rename = "krisp-low")]
KrispLow,
#[strum(to_string = "krisp-high-with-bvc")]
#[serde(rename = "krisp-high-with-bvc")]
KrispHighWithBVC,
#[strum(to_string = "krisp-medium-with-bvc")]
#[serde(rename = "krisp-medium-with-bvc")]
KrispMediumWithBVC,
#[strum(to_string = "ai-coustics-sparrow-xxs")]
#[serde(rename = "ai-coustics-sparrow-xxs")]
AiCousticsSparrowXxs,
#[strum(to_string = "ai-coustics-sparrow-xs")]
#[serde(rename = "ai-coustics-sparrow-xs")]
AiCousticsSparrowXs,
#[strum(to_string = "ai-coustics-sparrow-s")]
#[serde(rename = "ai-coustics-sparrow-s")]
AiCousticsSparrowS,
#[strum(to_string = "ai-coustics-sparrow-l")]
#[serde(rename = "ai-coustics-sparrow-l")]
AiCousticsSparrowL,
#[strum(to_string = "ai-coustics-sparrow-xxs-48khz")]
#[serde(rename = "ai-coustics-sparrow-xxs-48khz")]
AiCousticsSparrowXxs48khz,
#[strum(to_string = "ai-coustics-sparrow-xs-48khz")]
#[serde(rename = "ai-coustics-sparrow-xs-48khz")]
AiCousticsSparrowXs48khz,
#[strum(to_string = "ai-coustics-rook-s-48khz")]
#[serde(rename = "ai-coustics-rook-s-48khz")]
AiCousticsRookS48khz,
#[strum(to_string = "ai-coustics-rook-l-48khz")]
#[serde(rename = "ai-coustics-rook-l-48khz")]
AiCousticsRookL48khz,
#[strum(to_string = "hyper-voice")]
#[serde(rename = "hyper-voice")]
HyperVoice,
}

#[derive(Debug, Default, Clone, Copy, Display, EnumIter, EnumString, Serialize, Deserialize, PartialEq, Eq)]
Expand All @@ -168,6 +124,7 @@ impl ParticipantBackendKind {
#[cfg(test)]
mod tests {
use super::{
NoiseSuppression,
ParticipantBackendKind,
VideoConstraint,
VideoMaxConcurrentTracksPreset,
Expand Down Expand Up @@ -197,6 +154,24 @@ mod tests {
}
}

#[test]
fn noise_suppression_round_trips_hyper_media_setting_names() {
let values = [
("none", NoiseSuppression::Disabled),
("hyper-voice", NoiseSuppression::HyperVoice),
];

for (raw, expected) in values {
assert_eq!(NoiseSuppression::from_str(raw).unwrap(), expected);
assert_eq!(expected.to_string(), raw);
assert_eq!(serde_json::to_value(expected).unwrap(), raw);
assert_eq!(
serde_json::from_value::<NoiseSuppression>(raw.into()).unwrap(),
expected
);
}
}

#[test]
fn video_track_limit_presets_convert_to_nullable_track_counts() {
assert_eq!(VideoMaxConcurrentTracksPreset::Unlimited.to_option(), None);
Expand Down
67 changes: 57 additions & 10 deletions config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ pub use client_config::{
};
pub use cloudflare_config::CloudflareConfig;
use color_eyre::Result;
use config::{
builder::DefaultState,
ConfigBuilder,
};
pub use device_farm_config::{
default_device_farm_project_arn,
default_device_farm_region,
Expand All @@ -59,6 +63,7 @@ use serde::{
use std::{
collections::HashMap,
path::Path,
str::FromStr as _,
};

#[derive(Clone, Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -238,20 +243,40 @@ impl Config {

builder = builder.add_source(Config::default());

let config_files = [("config.yaml", config::FileFormat::Yaml)];

for (file, format) in &config_files {
let source = config::File::from(config_dir.join(file))
.format(*format)
.required(false);
builder = builder.add_source(source);
}
let config_file = config_dir.join("config.yaml");
builder = builder.add_source(
config::File::from(config_file.as_path())
.format(config::FileFormat::Yaml)
.required(false),
);

builder = builder.add_source(args);

let cfg: Self = builder.build()?.try_deserialize()?;
Self::from_builder(builder, &config_file)
}

Ok(cfg)
/// Builds the configuration from `builder`, reading a noise suppression that hyper.video doesn't take as `none`.
///
/// [`Config::save`] writes the noise suppression, so `config_file` can name an engine that an older simulator
/// offered. hyper.video drops stored values that it doesn't take, and the simulator does the same, with a warning,
/// so that every subcommand still starts. The CLI and `--participant` JSON reject an unknown value instead, because
/// there it is a typo.
fn from_builder(builder: ConfigBuilder<DefaultState>, config_file: &Path) -> Result<Self, config::ConfigError> {
let config = builder.build_cloned()?;
match config.get_string("noise_suppression") {
Ok(value) if NoiseSuppression::from_str(&value).is_err() => {
let fallback = NoiseSuppression::Disabled;
warn!(
"{} names noise suppression {value}, which hyper.video doesn't take; using {fallback}",
config_file.display()
);
builder
.set_override("noise_suppression", fallback.to_string())?
.build()?
.try_deserialize()
}
_ => config.try_deserialize(),
}
}

pub fn fake_media_with_description(&self) -> FakeMediaWithDescription {
Expand Down Expand Up @@ -372,6 +397,28 @@ remote_url_options:
assert_eq!(config.backend, ParticipantBackendKind::Local);
}

/// A config file's noise suppression loads as `none` when it names an engine that hyper.video no longer offers.
#[test]
fn config_file_noise_suppression_loads_old_engine_as_none() {
let values = [
("krisp-high", NoiseSuppression::Disabled),
("hyper-voice", NoiseSuppression::HyperVoice),
];

for (raw, expected) in values {
let builder = config::Config::builder()
.add_source(Config::default())
.add_source(config::File::from_str(
&format!("noise_suppression: {raw}"),
config::FileFormat::Yaml,
));

let config = Config::from_builder(builder, Path::new("config.yaml")).expect("config should load");

assert_eq!(config.noise_suppression, expected, "{raw}");
}
}

#[test]
fn parses_cloudflare_backend_and_nested_cloudflare_config() {
let config: Config = config::Config::builder()
Expand Down
3 changes: 2 additions & 1 deletion docs/browser-driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The current command set is:
| `ToggleAudio` | Toggle microphone mute state. | [`browser/src/participant/mod.rs`](../browser/src/participant/mod.rs) `:242-244`, [`tui/src/tui/components/participants.rs`](../tui/src/tui/components/participants.rs) `:264-270` |
| `ToggleVideo` | Toggle camera on/off. | [`browser/src/participant/mod.rs`](../browser/src/participant/mod.rs) `:246-248`, [`tui/src/tui/components/participants.rs`](../tui/src/tui/components/participants.rs) `:272-277` |
| `ToggleScreenshare` | Toggle screen sharing on/off. | [`browser/src/participant/mod.rs`](../browser/src/participant/mod.rs) `:250-256`, [`tui/src/tui/components/participants.rs`](../tui/src/tui/components/participants.rs) `:279-284` |
| `SetNoiseSuppression(NoiseSuppression)` | Set the noise suppression model. | [`browser/src/participant/mod.rs`](../browser/src/participant/mod.rs) `:258-260`, [`tui/src/tui/components/participants.rs`](../tui/src/tui/components/participants.rs) `:160-166`, `:187-195` |
| `SetNoiseSuppression(NoiseSuppression)` | Set the noise suppression model: `none` or `hyper-voice`. | [`browser/src/participant/mod.rs`](../browser/src/participant/mod.rs) `:258-260`, [`tui/src/tui/components/participants.rs`](../tui/src/tui/components/participants.rs) `:160-166`, `:187-195` |
| `SetWebcamResolutions(WebcamResolution)` | Set outgoing camera resolution. The enum variant name is pluralized in code today. | [`browser/src/participant/mod.rs`](../browser/src/participant/mod.rs) `:262-268`, [`tui/src/tui/components/participants.rs`](../tui/src/tui/components/participants.rs) `:170-176`, `:209-217` |
| `ToggleBackgroundBlur` | Toggle background blur. | [`browser/src/participant/mod.rs`](../browser/src/participant/mod.rs) `:270-272`, [`tui/src/tui/components/participants.rs`](../tui/src/tui/components/participants.rs) `:294-299` |

Expand Down Expand Up @@ -93,6 +93,7 @@ For parity with the current local backend, `start()` should apply those launch s

- Hyper Core applies noise suppression, blur, camera resolution, and transport before join, then applies audio/video in the lobby, and applies initial screenshare after joining. See [`browser/src/participant/local/core.rs`](../browser/src/participant/local/core.rs) `:143-171`.
- Hyper Lite currently only applies audio/video/screenshare and ignores advanced media settings. See [`browser/src/participant/local/lite.rs`](../browser/src/participant/local/lite.rs) `:90-106`, `:153-175`.
- The Cloudflare backend applies noise suppression `none` only, because the worker accepts no other value of hyper.video's setting. It sends `none` in place of `hyper-voice`, with a warning, and refuses `SetNoiseSuppression` with it. See [`browser/src/participant/cloudflare/mod.rs`](../browser/src/participant/cloudflare/mod.rs).

There is currently no runtime command for:

Expand Down
1 change: 1 addition & 0 deletions src/headless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub struct HeadlessArgs {
#[clap(long = "auto-gain-control", value_parser = clap::builder::BoolishValueParser::new())]
pub auto_gain_control: Option<bool>,

/// Noise suppression model: none or hyper-voice. Cloudflare participants always run with none.
#[clap(long = "noise-suppression", value_name = "MODEL")]
pub noise_suppression: Option<NoiseSuppression>,

Expand Down
Loading