feat(voice): ship the screen share delivery rework to everyone (#2931)

This commit is contained in:
Hampus
2026-09-24 14:57:40 +02:00
committed by GitHub
parent 88038a1d5b
commit a0825e77c4
53 changed files with 171 additions and 1806 deletions
-54
View File
@@ -10524,7 +10524,6 @@
}, },
"gateway_rollout": {"$ref": "#/components/schemas/GatewayRolloutConfigResponse"}, "gateway_rollout": {"$ref": "#/components/schemas/GatewayRolloutConfigResponse"},
"voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionConfigResponse"}, "voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionConfigResponse"},
"screen_share_delivery": {"$ref": "#/components/schemas/ScreenShareDeliveryConfigResponse"},
"push_service_delivery": {"$ref": "#/components/schemas/PushServiceDeliveryConfigResponse"}, "push_service_delivery": {"$ref": "#/components/schemas/PushServiceDeliveryConfigResponse"},
"experiment_delivery": {"$ref": "#/components/schemas/ExperimentDeliveryConfigResponse"}, "experiment_delivery": {"$ref": "#/components/schemas/ExperimentDeliveryConfigResponse"},
"registration": { "registration": {
@@ -10953,7 +10952,6 @@
"sso", "sso",
"gateway_rollout", "gateway_rollout",
"voice_noise_suppression", "voice_noise_suppression",
"screen_share_delivery",
"push_service_delivery", "push_service_delivery",
"experiment_delivery", "experiment_delivery",
"registration", "registration",
@@ -11089,10 +11087,6 @@
"nullable": true, "nullable": true,
"allOf": [{"$ref": "#/components/schemas/VoiceNoiseSuppressionConfigUpdateRequest"}] "allOf": [{"$ref": "#/components/schemas/VoiceNoiseSuppressionConfigUpdateRequest"}]
}, },
"screen_share_delivery": {
"nullable": true,
"allOf": [{"$ref": "#/components/schemas/ScreenShareDeliveryConfigUpdateRequest"}]
},
"push_service_delivery": { "push_service_delivery": {
"nullable": true, "nullable": true,
"allOf": [{"$ref": "#/components/schemas/PushServiceDeliveryConfigUpdateRequest"}] "allOf": [{"$ref": "#/components/schemas/PushServiceDeliveryConfigUpdateRequest"}]
@@ -15208,24 +15202,6 @@
} }
} }
}, },
"ScreenShareDeliveryConfigUpdateRequest": {
"type": "object",
"properties": {
"enabled": {"type": "boolean"},
"rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000},
"rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64},
"included_user_ids": {
"maxItems": 1000,
"type": "array",
"items": {"type": "string", "pattern": "^\\d{1,20}$"}
},
"excluded_user_ids": {
"maxItems": 1000,
"type": "array",
"items": {"type": "string", "pattern": "^\\d{1,20}$"}
}
}
},
"VoiceNoiseSuppressionConfigUpdateRequest": { "VoiceNoiseSuppressionConfigUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -15327,36 +15303,6 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"ScreenShareDeliveryConfigResponse": {
"type": "object",
"properties": {
"enabled": {"default": false, "type": "boolean"},
"config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991},
"rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000},
"rollout_salt": {"default": "screen-share-delivery-v1", "type": "string", "minLength": 1, "maxLength": 64},
"included_user_ids": {
"default": [],
"maxItems": 1000,
"type": "array",
"items": {"type": "string", "pattern": "^\\d{1,20}$"}
},
"excluded_user_ids": {
"default": [],
"maxItems": 1000,
"type": "array",
"items": {"type": "string", "pattern": "^\\d{1,20}$"}
}
},
"required": [
"enabled",
"config_version",
"rollout_basis_points",
"rollout_salt",
"included_user_ids",
"excluded_user_ids"
],
"additionalProperties": false
},
"VoiceNoiseSuppressionConfigResponse": { "VoiceNoiseSuppressionConfigResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -23,8 +23,6 @@ pub struct InstanceConfigResponse {
#[serde(default)] #[serde(default)]
pub voice_noise_suppression: VoiceNoiseSuppressionConfigResponse, pub voice_noise_suppression: VoiceNoiseSuppressionConfigResponse,
#[serde(default)] #[serde(default)]
pub screen_share_delivery: ScreenShareDeliveryConfigResponse,
#[serde(default)]
pub push_service_delivery: PushServiceDeliveryConfigResponse, pub push_service_delivery: PushServiceDeliveryConfigResponse,
#[serde(default)] #[serde(default)]
pub experiment_delivery: ExperimentDeliveryConfigResponse, pub experiment_delivery: ExperimentDeliveryConfigResponse,
@@ -452,7 +450,6 @@ impl VoiceE2eeScope {
pub const EXPERIMENT_MAX_TARGETED_USERS: usize = 1_000; pub const EXPERIMENT_MAX_TARGETED_USERS: usize = 1_000;
pub const PUSH_SERVICE_DELIVERY_DEFAULT_SALT: &str = "push-service-delivery-v1"; pub const PUSH_SERVICE_DELIVERY_DEFAULT_SALT: &str = "push-service-delivery-v1";
pub const SCREEN_SHARE_DELIVERY_DEFAULT_SALT: &str = "screen-share-delivery-v1";
pub const VOICE_NS_MAX_GUILD_OVERRIDES: usize = 200; pub const VOICE_NS_MAX_GUILD_OVERRIDES: usize = 200;
impl NoiseSuppressionBackend { impl NoiseSuppressionBackend {
@@ -543,44 +540,6 @@ pub struct VoiceNoiseSuppressionConfigUpdateRequest {
pub suppression_strength: Option<u32>, pub suppression_strength: Option<u32>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct ScreenShareDeliveryConfigResponse {
pub enabled: bool,
pub config_version: u64,
pub rollout_basis_points: u32,
pub rollout_salt: String,
pub included_user_ids: Vec<String>,
pub excluded_user_ids: Vec<String>,
}
impl Default for ScreenShareDeliveryConfigResponse {
fn default() -> Self {
Self {
enabled: false,
config_version: 0,
rollout_basis_points: 0,
rollout_salt: SCREEN_SHARE_DELIVERY_DEFAULT_SALT.to_owned(),
included_user_ids: Vec::new(),
excluded_user_ids: Vec::new(),
}
}
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct ScreenShareDeliveryConfigUpdateRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rollout_basis_points: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rollout_salt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub included_user_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub excluded_user_ids: Option<Vec<String>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(default)] #[serde(default)]
pub struct PushServiceDeliveryConfigResponse { pub struct PushServiceDeliveryConfigResponse {
@@ -735,8 +694,6 @@ pub struct InstanceConfigUpdateRequest {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub voice_noise_suppression: Option<VoiceNoiseSuppressionConfigUpdateRequest>, pub voice_noise_suppression: Option<VoiceNoiseSuppressionConfigUpdateRequest>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub screen_share_delivery: Option<ScreenShareDeliveryConfigUpdateRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub push_service_delivery: Option<PushServiceDeliveryConfigUpdateRequest>, pub push_service_delivery: Option<PushServiceDeliveryConfigUpdateRequest>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub experiment_delivery: Option<ExperimentDeliveryConfigUpdateRequest>, pub experiment_delivery: Option<ExperimentDeliveryConfigUpdateRequest>,
@@ -1076,30 +1033,18 @@ mod tests {
.expect("admin schema"); .expect("admin schema");
let noise = serde_json::from_value::<VoiceNoiseSuppressionConfigResponse>(json!({})) let noise = serde_json::from_value::<VoiceNoiseSuppressionConfigResponse>(json!({}))
.expect("default noise config"); .expect("default noise config");
let screen_share = serde_json::from_value::<ScreenShareDeliveryConfigResponse>(json!({}))
.expect("default screen share config");
let delivery = serde_json::from_value::<ExperimentDeliveryConfigResponse>(json!({})) let delivery = serde_json::from_value::<ExperimentDeliveryConfigResponse>(json!({}))
.expect("default delivery config"); .expect("default delivery config");
let noise = serde_json::to_value(noise).expect("serializable noise config"); let noise = serde_json::to_value(noise).expect("serializable noise config");
let screen_share =
serde_json::to_value(screen_share).expect("serializable screen share config");
let delivery = serde_json::to_value(delivery).expect("serializable delivery config"); let delivery = serde_json::to_value(delivery).expect("serializable delivery config");
let generated_noise: generated_types::VoiceNoiseSuppressionConfigResponse = let generated_noise: generated_types::VoiceNoiseSuppressionConfigResponse =
serde_json::from_value(noise.clone()).expect("generated noise config contract"); serde_json::from_value(noise.clone()).expect("generated noise config contract");
let generated_screen_share: generated_types::ScreenShareDeliveryConfigResponse =
serde_json::from_value(screen_share.clone())
.expect("generated screen share config contract");
let generated_delivery: generated_types::ExperimentDeliveryConfigResponse = let generated_delivery: generated_types::ExperimentDeliveryConfigResponse =
serde_json::from_value(delivery.clone()).expect("generated delivery config contract"); serde_json::from_value(delivery.clone()).expect("generated delivery config contract");
assert_eq!( assert_eq!(
serde_json::to_value(generated_noise).expect("serializable generated noise config"), serde_json::to_value(generated_noise).expect("serializable generated noise config"),
noise noise
); );
assert_eq!(
serde_json::to_value(generated_screen_share)
.expect("serializable generated screen share config"),
screen_share
);
assert_eq!( assert_eq!(
serde_json::to_value(generated_delivery) serde_json::to_value(generated_delivery)
.expect("serializable generated delivery config"), .expect("serializable generated delivery config"),
@@ -1107,7 +1052,6 @@ mod tests {
); );
for (name, value) in [ for (name, value) in [
("VoiceNoiseSuppressionConfigResponse", noise), ("VoiceNoiseSuppressionConfigResponse", noise),
("ScreenShareDeliveryConfigResponse", screen_share),
("ExperimentDeliveryConfigResponse", delivery), ("ExperimentDeliveryConfigResponse", delivery),
] { ] {
for (field, value) in value.as_object().expect("config object") { for (field, value) in value.as_object().expect("config object") {
@@ -1119,29 +1063,6 @@ mod tests {
} }
} }
#[test]
fn screen_share_delivery_update_preserves_empty_lists_and_omitted_fields() {
let update = ScreenShareDeliveryConfigUpdateRequest {
included_user_ids: Some(Vec::new()),
excluded_user_ids: Some(Vec::new()),
..Default::default()
};
let value = serde_json::to_value(update).expect("serializable update");
serde_json::from_value::<generated_types::ScreenShareDeliveryConfigUpdateRequest>(
value.clone(),
)
.expect("generated update contract");
assert_eq!(
value,
json!({"included_user_ids": [], "excluded_user_ids": []})
);
assert_eq!(
serde_json::to_value(ScreenShareDeliveryConfigUpdateRequest::default())
.expect("serializable update"),
json!({})
);
}
#[test] #[test]
fn noise_suppression_update_preserves_empty_lists_and_omitted_fields() { fn noise_suppression_update_preserves_empty_lists_and_omitted_fields() {
let update = VoiceNoiseSuppressionConfigUpdateRequest { let update = VoiceNoiseSuppressionConfigUpdateRequest {
+2 -116
View File
@@ -19,9 +19,8 @@ use crate::{
InstanceServicesUpdateRequest, InstanceYoutubeIntegrationUpdateRequest, InstanceServicesUpdateRequest, InstanceYoutubeIntegrationUpdateRequest,
LimitConfigUpdateRequest, LimitRule, LimitRuleFilters, NoiseSuppressionBackend, LimitConfigUpdateRequest, LimitRule, LimitRuleFilters, NoiseSuppressionBackend,
PremiumMode, PushServiceDeliveryConfigUpdateRequest, RegistrationMode, PremiumMode, PushServiceDeliveryConfigUpdateRequest, RegistrationMode,
ScreenShareDeliveryConfigUpdateRequest, SsoConfigUpdateRequest, SsoConfigUpdateRequest, VOICE_NS_MAX_GUILD_OVERRIDES, VoiceE2eeScope,
VOICE_NS_MAX_GUILD_OVERRIDES, VoiceE2eeScope, VoiceNoiseSuppressionConfigUpdateRequest, VoiceNoiseSuppressionConfigUpdateRequest, VoiceNoiseSuppressionGuildOverride,
VoiceNoiseSuppressionGuildOverride,
}, },
}, },
config::AdminConfig, config::AdminConfig,
@@ -208,10 +207,6 @@ pub async fn instance_config_post(
Ok(update) => instance_config_result(client.update_instance_config(&update).await), Ok(update) => instance_config_result(client.update_instance_config(&update).await),
Err(message) => FlashData::error(message), Err(message) => FlashData::error(message),
}, },
"update_screen_share_delivery" => match build_screen_share_delivery_update(&form) {
Ok(update) => instance_config_result(client.update_instance_config(&update).await),
Err(message) => FlashData::error(message),
},
"update_push_service_delivery" => match build_push_service_delivery_update(&form) { "update_push_service_delivery" => match build_push_service_delivery_update(&form) {
Ok(update) => instance_config_result(client.update_instance_config(&update).await), Ok(update) => instance_config_result(client.update_instance_config(&update).await),
Err(message) => FlashData::error(message), Err(message) => FlashData::error(message),
@@ -653,38 +648,6 @@ fn build_voice_noise_suppression_update(
}) })
} }
fn build_screen_share_delivery_update(
form: &MultiValueForm,
) -> Result<InstanceConfigUpdateRequest, String> {
Ok(InstanceConfigUpdateRequest {
screen_share_delivery: Some(ScreenShareDeliveryConfigUpdateRequest {
enabled: Some(form.bool_value("screen_share_delivery_enabled")),
rollout_basis_points: parse_form_number(
form,
"screen_share_delivery_rollout_basis_points",
"Rollout basis points",
0,
EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX,
)?,
rollout_salt: parse_experiment_rollout_salt(
form,
"screen_share_delivery_rollout_salt",
)?,
included_user_ids: Some(parse_experiment_user_ids(
form.first("screen_share_delivery_included_user_ids")
.unwrap_or_default(),
"Included user IDs",
)?),
excluded_user_ids: Some(parse_experiment_user_ids(
form.first("screen_share_delivery_excluded_user_ids")
.unwrap_or_default(),
"Excluded user IDs",
)?),
}),
..Default::default()
})
}
fn build_push_service_delivery_update( fn build_push_service_delivery_update(
form: &MultiValueForm, form: &MultiValueForm,
) -> Result<InstanceConfigUpdateRequest, String> { ) -> Result<InstanceConfigUpdateRequest, String> {
@@ -1638,83 +1601,6 @@ mod tests {
} }
} }
#[test]
fn build_screen_share_delivery_update_reads_the_rollout_fields() {
let form = MultiValueForm::parse(
b"screen_share_delivery_enabled=true&screen_share_delivery_rollout_basis_points=%20250%20&screen_share_delivery_rollout_salt=%20screen-share-delivery-v2%20&screen_share_delivery_included_user_ids=1500000000000000001%0A1500000000000000002&screen_share_delivery_excluded_user_ids=1500000000000000003%2C%201500000000000000004",
);
let update = build_screen_share_delivery_update(&form)
.expect("valid form")
.screen_share_delivery
.expect("screen share delivery update");
assert_eq!(update.enabled, Some(true));
assert_eq!(update.rollout_basis_points, Some(250));
assert_eq!(
update.rollout_salt,
Some("screen-share-delivery-v2".to_owned())
);
assert_eq!(
update.included_user_ids,
Some(vec![
"1500000000000000001".to_owned(),
"1500000000000000002".to_owned()
])
);
assert_eq!(
update.excluded_user_ids,
Some(vec![
"1500000000000000003".to_owned(),
"1500000000000000004".to_owned()
])
);
}
#[test]
fn build_screen_share_delivery_update_leaves_the_feature_inert_when_nothing_is_submitted() {
let form = MultiValueForm::parse(b"_csrf=token");
let request = build_screen_share_delivery_update(&form).expect("valid form");
assert_eq!(
serde_json::to_value(request).expect("serializable update"),
serde_json::json!({"screen_share_delivery": {
"enabled": false,
"included_user_ids": [],
"excluded_user_ids": [],
}})
);
}
#[test]
fn build_screen_share_delivery_update_rejects_invalid_rollout_fields() {
for (form, message) in [
(
"screen_share_delivery_rollout_basis_points=10001",
"Rollout basis points must be a whole number between 0 and 10000",
),
(
"screen_share_delivery_rollout_basis_points=abc",
"Rollout basis points must be a whole number between 0 and 10000",
),
(
"screen_share_delivery_rollout_salt=%20%20",
"Rollout salt must be between 1 and 64 characters",
),
(
"screen_share_delivery_included_user_ids=123%2Cinvalid",
"Included user IDs entry 2 must contain 1 to 20 decimal digits",
),
(
"screen_share_delivery_excluded_user_ids=123%2Cinvalid",
"Excluded user IDs entry 2 must contain 1 to 20 decimal digits",
),
] {
let form = MultiValueForm::parse(form.as_bytes());
assert_eq!(
build_screen_share_delivery_update(&form).expect_err("invalid rollout field"),
message
);
}
}
#[test] #[test]
fn build_experiment_delivery_update_leaves_both_fields_unchanged_when_absent() { fn build_experiment_delivery_update_leaves_both_fields_unchanged_when_absent() {
let form = MultiValueForm::parse(b"_csrf=token"); let form = MultiValueForm::parse(b"_csrf=token");
@@ -7,8 +7,7 @@ use crate::{
InstanceMediaResponse, InstancePolicyResponse, InstanceRegistrationResponse, InstanceMediaResponse, InstancePolicyResponse, InstanceRegistrationResponse,
LimitConfigResponse, NoiseSuppressionBackend, PUSH_SERVICE_DELIVERY_DEFAULT_SALT, LimitConfigResponse, NoiseSuppressionBackend, PUSH_SERVICE_DELIVERY_DEFAULT_SALT,
PendingRegistrationResponse, PushServiceDeliveryConfigResponse, RegistrationUrlResponse, PendingRegistrationResponse, PushServiceDeliveryConfigResponse, RegistrationUrlResponse,
SCREEN_SHARE_DELIVERY_DEFAULT_SALT, ScreenShareDeliveryConfigResponse, SsoConfigResponse, SsoConfigResponse, VOICE_NS_MAX_GUILD_OVERRIDES, VoiceNoiseSuppressionConfigResponse,
VOICE_NS_MAX_GUILD_OVERRIDES, VoiceNoiseSuppressionConfigResponse,
}, },
config::AdminConfig, config::AdminConfig,
middleware::auth::AuthContext, middleware::auth::AuthContext,
@@ -149,7 +148,6 @@ pub fn instance_config_page(
html! { html! {
(gateway_rollout_section(base, csrf_token, &instance_config.gateway_rollout)) (gateway_rollout_section(base, csrf_token, &instance_config.gateway_rollout))
(voice_noise_suppression_section(base, csrf_token, &instance_config.voice_noise_suppression)) (voice_noise_suppression_section(base, csrf_token, &instance_config.voice_noise_suppression))
(screen_share_delivery_section(base, csrf_token, &instance_config.screen_share_delivery))
(push_service_delivery_section(base, csrf_token, &instance_config.push_service_delivery)) (push_service_delivery_section(base, csrf_token, &instance_config.push_service_delivery))
(experiment_delivery_section(base, csrf_token, &instance_config.experiment_delivery)) (experiment_delivery_section(base, csrf_token, &instance_config.experiment_delivery))
@if let Some(limit_config) = limit_config { @if let Some(limit_config) = limit_config {
@@ -1179,117 +1177,6 @@ fn voice_noise_suppression_section(
) )
} }
fn screen_share_delivery_section(
base: &str,
csrf_token: &str,
screen_share_delivery: &ScreenShareDeliveryConfigResponse,
) -> Markup {
let status = if screen_share_delivery.enabled {
("Live", BadgeVariant::Success)
} else {
("Inert", BadgeVariant::Default)
};
let included_user_ids = screen_share_delivery.included_user_ids.join("\n");
let excluded_user_ids = screen_share_delivery.excluded_user_ids.join("\n");
section_card_with_description(
"Screen Share Delivery",
"Pick how many clients publish screen shares through the reworked delivery path. While \
the master switch below is off nothing on this form reaches any client: every user \
keeps the screen share pipeline they have today, whatever the rest of these fields say. \
A client that is already sharing keeps the path it started on until the share ends.",
html! {
form method="post" action={(base) "/instance-config?action=update_screen_share_delivery"} {
(csrf_input(csrf_token))
div class="space-y-6" {
div class="flex flex-wrap items-center gap-2" {
h3 class="text-sm font-semibold text-neutral-900" { "Master switch" }
(badge(status.0, status.1))
span class="text-xs text-neutral-500" {
"Config version " (screen_share_delivery.config_version)
}
}
(checkbox(
"screen_share_delivery_enabled",
"true",
"Serve screen share delivery assignments to clients",
screen_share_delivery.enabled,
true,
))
p class="text-xs text-neutral-500" {
"Off is the safe state. With this unchecked every client is told the \
feature is inert and keeps its current behavior, so the rollout and \
targeting fields below have no effect at all."
}
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
(number_field(
"screen_share_delivery_rollout_basis_points",
"Rollout (basis points)",
&screen_share_delivery.rollout_basis_points.to_string(),
Some(0), Some(10000), "1",
Some("Share of users bucketed into the canary, in basis points: 0 is nobody, 100 is 1%, 10000 is everybody."),
))
div class="flex flex-col gap-2" {
(text_input(
"screen_share_delivery_rollout_salt",
"Rollout Salt",
&screen_share_delivery.rollout_salt,
SCREEN_SHARE_DELIVERY_DEFAULT_SALT,
))
p class="text-xs text-neutral-500" {
"Seeds the bucketing hash. Changing it reshuffles which users fall \
inside the percentage above. Leave it alone to keep the current \
cohort stable."
}
}
div class="flex flex-col gap-2" {
(textarea_input(
"screen_share_delivery_included_user_ids",
"Always-on User IDs",
"1500000000000000001\n1500000000000000002",
&included_user_ids,
4,
false,
))
(entry_count_hint(
screen_share_delivery.included_user_ids.len(),
EXPERIMENT_MAX_TARGETED_USERS,
))
p class="text-xs text-neutral-500" {
"One snowflake per line, or comma separated. These users are targeted \
regardless of the percentage above. IDs must contain 1 to 20 decimal \
digits. Invalid entries prevent the save. Blank entries and duplicate \
IDs are ignored."
}
}
div class="flex flex-col gap-2" {
(textarea_input(
"screen_share_delivery_excluded_user_ids",
"Never-on User IDs",
"1500000000000000003\n1500000000000000004",
&excluded_user_ids,
4,
false,
))
(entry_count_hint(
screen_share_delivery.excluded_user_ids.len(),
EXPERIMENT_MAX_TARGETED_USERS,
))
p class="text-xs text-neutral-500" {
"Same format. Exclusion wins over both the always-on list and the \
percentage. This is the per-user kill switch."
}
}
(form_actions(html! {
(submit_button("Save Screen Share Delivery Configuration"))
}))
}
}
},
)
}
fn push_service_delivery_section( fn push_service_delivery_section(
base: &str, base: &str,
csrf_token: &str, csrf_token: &str,
@@ -2042,25 +1929,6 @@ mod tests {
assert!(!markup.contains("at the cap")); assert!(!markup.contains("at the cap"));
} }
#[test]
fn screen_share_delivery_section_shows_list_counts_and_the_master_switch() {
let screen_share_delivery = ScreenShareDeliveryConfigResponse {
included_user_ids: vec!["1500000000000000001".to_owned()],
excluded_user_ids: vec![
"1500000000000000002".to_owned(),
"1500000000000000003".to_owned(),
],
..ScreenShareDeliveryConfigResponse::default()
};
let markup =
screen_share_delivery_section("/admin", "csrf", &screen_share_delivery).into_string();
assert!(markup.contains("action=update_screen_share_delivery"));
assert!(markup.contains("screen_share_delivery_enabled"));
assert!(markup.contains("1 of 1000 stored"));
assert!(markup.contains("2 of 1000 stored"));
assert!(!markup.contains("at the cap"));
}
#[test] #[test]
fn voice_noise_suppression_section_flags_a_list_at_its_cap() { fn voice_noise_suppression_section_flags_a_list_at_its_cap() {
let voice_noise_suppression = VoiceNoiseSuppressionConfigResponse { let voice_noise_suppression = VoiceNoiseSuppressionConfigResponse {
-18
View File
@@ -409,15 +409,6 @@ fn deserialize_instance_config_response_with_unknown_keys() {
"future_object_knob": {"nested": true}, "future_object_knob": {"nested": true},
"future_list_knob": ["a", "b"] "future_list_knob": ["a", "b"]
}, },
"screen_share_delivery": {
"enabled": true,
"config_version": 2,
"rollout_basis_points": 2500,
"rollout_salt": "screen-share-delivery-v1",
"included_user_ids": ["1500000000000000001"],
"future_delivery_knob": 9,
"excluded_user_ids": []
},
"push_service_delivery": { "push_service_delivery": {
"enabled": true, "enabled": true,
"config_version": 3, "config_version": 3,
@@ -555,14 +546,6 @@ fn deserialize_instance_config_response_with_unknown_keys() {
assert_eq!(resp.voice_noise_suppression.rollout_basis_points, 10000); assert_eq!(resp.voice_noise_suppression.rollout_basis_points, 10000);
assert_eq!(*resp.voice_noise_suppression.rollout_salt, "voice-ns-v1"); assert_eq!(*resp.voice_noise_suppression.rollout_salt, "voice-ns-v1");
assert_eq!(resp.voice_noise_suppression.enabled_backends.len(), 3); assert_eq!(resp.voice_noise_suppression.enabled_backends.len(), 3);
assert!(resp.screen_share_delivery.enabled);
assert_eq!(resp.screen_share_delivery.config_version, 2);
assert_eq!(resp.screen_share_delivery.rollout_basis_points, 2500);
assert_eq!(
*resp.screen_share_delivery.rollout_salt,
"screen-share-delivery-v1"
);
assert_eq!(resp.screen_share_delivery.included_user_ids.len(), 1);
assert_eq!(resp.experiment_delivery.poll_interval_seconds, 300); assert_eq!(resp.experiment_delivery.poll_interval_seconds, 300);
assert!(resp.policy.single_community_guild_id.is_none()); assert!(resp.policy.single_community_guild_id.is_none());
assert_eq!(resp.policy.services.gif_enabled, Some(true)); assert_eq!(resp.policy.services.gif_enabled, Some(true));
@@ -573,7 +556,6 @@ fn deserialize_instance_config_response_with_unknown_keys() {
.replace("\"future_rollout_knob\": 3,", "") .replace("\"future_rollout_knob\": 3,", "")
.replace("\"future_presentation_knob\": \"verbose\",", "") .replace("\"future_presentation_knob\": \"verbose\",", "")
.replace("\"future_knob\": 7,", "") .replace("\"future_knob\": 7,", "")
.replace("\"future_delivery_knob\": 9,", "")
.replace("\"future_object_knob\": {\"nested\": true},", "") .replace("\"future_object_knob\": {\"nested\": true},", "")
.replace("\"future_list_knob\": [\"a\", \"b\"],", "") .replace("\"future_list_knob\": [\"a\", \"b\"],", "")
.replace( .replace(
-9
View File
@@ -465,7 +465,6 @@ async fn mutating_admin_pages_render_usable_csrf_tokens() {
"/instance-config?action=update_gateway_rollout", "/instance-config?action=update_gateway_rollout",
"/instance-config?action=update_sso", "/instance-config?action=update_sso",
"/instance-config?action=update_voice_noise_suppression", "/instance-config?action=update_voice_noise_suppression",
"/instance-config?action=update_screen_share_delivery",
"/instance-config?action=update_experiment_delivery", "/instance-config?action=update_experiment_delivery",
][..], ][..],
), ),
@@ -1200,14 +1199,6 @@ fn instance_config() -> Value {
"guild_overrides": [], "guild_overrides": [],
"suppression_strength": 80 "suppression_strength": 80
}, },
"screen_share_delivery": {
"enabled": false,
"config_version": 0,
"rollout_basis_points": 0,
"rollout_salt": "screen-share-delivery-v1",
"included_user_ids": [],
"excluded_user_ids": []
},
"experiment_delivery": { "experiment_delivery": {
"poll_interval_seconds": 300, "poll_interval_seconds": 300,
"poll_jitter_percent": 15 "poll_jitter_percent": 15
@@ -36,7 +36,6 @@ import {
} from '@fluxer/schema/src/domains/admin/AdminSchemas'; } from '@fluxer/schema/src/domains/admin/AdminSchemas';
import {GatewayRolloutConfigSchema} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas'; import {GatewayRolloutConfigSchema} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas';
import {PushServiceDeliveryConfigSchema} from '@fluxer/schema/src/domains/admin/PushServiceDeliverySchemas'; import {PushServiceDeliveryConfigSchema} from '@fluxer/schema/src/domains/admin/PushServiceDeliverySchemas';
import {ScreenShareDeliveryConfigSchema} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import {VoiceNoiseSuppressionConfigSchema} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas'; import {VoiceNoiseSuppressionConfigSchema} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas';
import {UserIdParam} from '@fluxer/schema/src/domains/common/CommonParamSchemas'; import {UserIdParam} from '@fluxer/schema/src/domains/common/CommonParamSchemas';
import {ExperimentDeliveryConfigSchema} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; import {ExperimentDeliveryConfigSchema} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
@@ -65,7 +64,6 @@ async function buildInstanceConfigResponse(): Promise<InstanceConfigResponse> {
ssoConfig, ssoConfig,
gatewayRollout, gatewayRollout,
voiceNoiseSuppression, voiceNoiseSuppression,
screenShareDelivery,
pushServiceDelivery, pushServiceDelivery,
experimentDelivery, experimentDelivery,
registrationConfig, registrationConfig,
@@ -75,7 +73,6 @@ async function buildInstanceConfigResponse(): Promise<InstanceConfigResponse> {
instanceConfigRepository.getSsoConfig(), instanceConfigRepository.getSsoConfig(),
instanceConfigRepository.getGatewayRolloutConfig(), instanceConfigRepository.getGatewayRolloutConfig(),
instanceConfigRepository.getVoiceNoiseSuppressionConfig(), instanceConfigRepository.getVoiceNoiseSuppressionConfig(),
instanceConfigRepository.getScreenShareDeliveryConfig(),
instanceConfigRepository.getPushServiceDeliveryConfig(), instanceConfigRepository.getPushServiceDeliveryConfig(),
instanceConfigRepository.getExperimentDeliveryConfig(), instanceConfigRepository.getExperimentDeliveryConfig(),
instanceConfigRepository.getRegistrationConfig(), instanceConfigRepository.getRegistrationConfig(),
@@ -108,7 +105,6 @@ async function buildInstanceConfigResponse(): Promise<InstanceConfigResponse> {
}, },
gateway_rollout: gatewayRollout, gateway_rollout: gatewayRollout,
voice_noise_suppression: voiceNoiseSuppression, voice_noise_suppression: voiceNoiseSuppression,
screen_share_delivery: screenShareDelivery,
push_service_delivery: pushServiceDelivery, push_service_delivery: pushServiceDelivery,
experiment_delivery: experimentDelivery, experiment_delivery: experimentDelivery,
registration: { registration: {
@@ -273,18 +269,6 @@ export function InstanceConfigAdminController(app: HonoApp) {
); );
} }
} }
if (data.screen_share_delivery) {
const patch = omitUndefinedFields(data.screen_share_delivery);
if (Object.keys(patch).length > 0) {
await instanceConfigRepository.updateScreenShareDeliveryConfig((current) =>
ScreenShareDeliveryConfigSchema.parse({
...current,
...patch,
config_version: current.config_version + 1,
}),
);
}
}
if (data.push_service_delivery) { if (data.push_service_delivery) {
const patch = omitUndefinedFields(data.push_service_delivery); const patch = omitUndefinedFields(data.push_service_delivery);
if (Object.keys(patch).length > 0) { if (Object.keys(patch).length > 0) {
@@ -8,7 +8,6 @@ import {RateLimitConfigs} from '@app/api/RateLimitConfig';
import type {HonoApp} from '@app/api/types/HonoEnv'; import type {HonoApp} from '@app/api/types/HonoEnv';
import {entityTagMatches} from '@app/api/utils/EntityTag'; import {entityTagMatches} from '@app/api/utils/EntityTag';
import {Headers as HttpHeaders} from '@fluxer/constants/src/Headers'; import {Headers as HttpHeaders} from '@fluxer/constants/src/Headers';
import {resolveScreenShareDeliveryAssignment} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import {resolveVoiceNoiseSuppressionAssignment} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas'; import {resolveVoiceNoiseSuppressionAssignment} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas';
import {ExperimentAssignmentsResponse} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; import {ExperimentAssignmentsResponse} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
@@ -29,10 +28,9 @@ export function ExperimentController(app: HonoApp) {
}), }),
async (ctx) => { async (ctx) => {
const instanceConfigRepository = ctx.get('instanceConfigRepository'); const instanceConfigRepository = ctx.get('instanceConfigRepository');
const [delivery, voiceConfig, screenShareConfig] = await Promise.all([ const [delivery, voiceConfig] = await Promise.all([
instanceConfigRepository.getExperimentDeliveryConfig(), instanceConfigRepository.getExperimentDeliveryConfig(),
instanceConfigRepository.getVoiceNoiseSuppressionConfig(), instanceConfigRepository.getVoiceNoiseSuppressionConfig(),
instanceConfigRepository.getScreenShareDeliveryConfig(),
]); ]);
const userId = ctx.get('user').id.toString(); const userId = ctx.get('user').id.toString();
const body: ExperimentAssignmentsResponse = { const body: ExperimentAssignmentsResponse = {
@@ -40,7 +38,6 @@ export function ExperimentController(app: HonoApp) {
poll_jitter_percent: delivery.poll_jitter_percent, poll_jitter_percent: delivery.poll_jitter_percent,
assignments: { assignments: {
voice_noise_suppression: resolveVoiceNoiseSuppressionAssignment(voiceConfig, userId), voice_noise_suppression: resolveVoiceNoiseSuppressionAssignment(voiceConfig, userId),
screen_share_delivery: resolveScreenShareDeliveryAssignment(screenShareConfig, userId),
}, },
}; };
const etag = `"${createHash('sha256').update(JSON.stringify(body)).digest('hex')}"`; const etag = `"${createHash('sha256').update(JSON.stringify(body)).digest('hex')}"`;
@@ -6,10 +6,6 @@ import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHa
import {HTTP_STATUS} from '@app/api/test/TestConstants'; import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder'; import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
import {AdminACLs} from '@fluxer/constants/src/AdminACLs'; import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
import {
DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import { import {
DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG, DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
@@ -19,7 +15,6 @@ import {
DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT, DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT,
type ExperimentAssignmentsResponse, type ExperimentAssignmentsResponse,
type ExperimentDeliveryConfigResponse, type ExperimentDeliveryConfigResponse,
readScreenShareDeliveryAssignment,
readVoiceNoiseSuppressionAssignment, readVoiceNoiseSuppressionAssignment,
} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; } from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest'; import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
@@ -56,7 +51,6 @@ describe('GET /experiments', () => {
poll_jitter_percent: DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT, poll_jitter_percent: DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT,
assignments: { assignments: {
voice_noise_suppression: INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, voice_noise_suppression: INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
screen_share_delivery: INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
}, },
}); });
}); });
@@ -88,52 +82,6 @@ describe('GET /experiments', () => {
expect(readVoiceNoiseSuppressionAssignment(body).enabled).toBe(false); expect(readVoiceNoiseSuppressionAssignment(body).enabled).toBe(false);
}); });
it('populates the screen share assignment key even when the rollout is disabled', async () => {
const account = await createTestAccount(harness);
const body = await createBuilder<ExperimentAssignmentsResponse>(harness, account.token).get(ENDPOINT).execute();
expect(Object.hasOwn(body.assignments, 'screen_share_delivery')).toBe(true);
expect(readScreenShareDeliveryAssignment(body).enabled).toBe(false);
});
it('resolves the screen share caller through the allowlist', async () => {
const targeted = await createTestAccount(harness);
const untargeted = await createTestAccount(harness);
await getInstanceConfigRepository().setScreenShareDeliveryConfig({
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
enabled: true,
config_version: 4,
rollout_basis_points: 0,
included_user_ids: [targeted.userId],
});
const targetedBody = await createBuilder<ExperimentAssignmentsResponse>(harness, targeted.token)
.get(ENDPOINT)
.execute();
expect(targetedBody.assignments.screen_share_delivery).toEqual({enabled: true});
const untargetedBody = await createBuilder<ExperimentAssignmentsResponse>(harness, untargeted.token)
.get(ENDPOINT)
.execute();
expect(untargetedBody.assignments.screen_share_delivery).toEqual({enabled: false});
});
it('keeps the screen share exclusion ahead of a full rollout', async () => {
const excluded = await createTestAccount(harness);
await getInstanceConfigRepository().setScreenShareDeliveryConfig({
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
enabled: true,
rollout_basis_points: 10000,
included_user_ids: [excluded.userId],
excluded_user_ids: [excluded.userId],
});
const body = await createBuilder<ExperimentAssignmentsResponse>(harness, excluded.token).get(ENDPOINT).execute();
expect(body.assignments.screen_share_delivery).toEqual({enabled: false});
});
it('serves the delivery cadence from the delivery config and not from the voice config', async () => { it('serves the delivery cadence from the delivery config and not from the voice config', async () => {
const account = await createTestAccount(harness); const account = await createTestAccount(harness);
await getInstanceConfigRepository().setExperimentDeliveryConfig({ await getInstanceConfigRepository().setExperimentDeliveryConfig({
@@ -248,30 +196,6 @@ describe('GET /experiments', () => {
}); });
}); });
it('serves a fresh body once the screen share config changes', async () => {
const account = await createTestAccount(harness);
const first = await createBuilder<ExperimentAssignmentsResponse>(harness, account.token)
.get(ENDPOINT)
.executeWithResponse();
const staleEtag = first.response.headers.get('etag') as string;
await getInstanceConfigRepository().setScreenShareDeliveryConfig({
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
enabled: true,
config_version: 1,
rollout_basis_points: 10000,
});
const refreshed = await createBuilder<ExperimentAssignmentsResponse>(harness, account.token)
.get(ENDPOINT)
.header('If-None-Match', staleEtag)
.executeWithResponse();
expect(refreshed.response.status).toBe(HTTP_STATUS.OK);
expect(refreshed.response.headers.get('etag')).not.toBe(staleEtag);
expect(refreshed.json?.assignments.screen_share_delivery).toEqual({enabled: true});
});
it('serves a fresh body once the delivery config changes', async () => { it('serves a fresh body once the delivery config changes', async () => {
const account = await createTestAccount(harness); const account = await createTestAccount(harness);
@@ -328,44 +252,6 @@ describe('GET /experiments', () => {
}); });
}); });
it('bumps the screen share config version on every admin update without the client sending one', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), [
AdminACLs.AUTHENTICATE,
AdminACLs.INSTANCE_CONFIG_VIEW,
AdminACLs.INSTANCE_CONFIG_UPDATE,
]);
const afterFirst = await createBuilder<{screen_share_delivery: {config_version: number; enabled: boolean}}>(
harness,
admin.token,
)
.patch('/admin/instance/config')
.body({screen_share_delivery: {enabled: true, rollout_basis_points: 10000}})
.execute();
expect(afterFirst.screen_share_delivery).toMatchObject({config_version: 1, enabled: true});
const afterSecond = await createBuilder<{screen_share_delivery: {config_version: number; enabled: boolean}}>(
harness,
admin.token,
)
.patch('/admin/instance/config')
.body({screen_share_delivery: {rollout_salt: 'screen-share-delivery-v2'}})
.execute();
expect(afterSecond.screen_share_delivery).toMatchObject({config_version: 2, enabled: true});
const afterEmpty = await createBuilder<{screen_share_delivery: {config_version: number; enabled: boolean}}>(
harness,
admin.token,
)
.patch('/admin/instance/config')
.body({screen_share_delivery: {}})
.execute();
expect(afterEmpty.screen_share_delivery).toMatchObject({config_version: 2, enabled: true});
const body = await createBuilder<ExperimentAssignmentsResponse>(harness, admin.token).get(ENDPOINT).execute();
expect(body.assignments.screen_share_delivery).toEqual({enabled: true});
});
it('leaves the config version alone for an admin update that sets no field', async () => { it('leaves the config version alone for an admin update that sets no field', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), [ const admin = await setUserACLs(harness, await createTestAccount(harness), [
AdminACLs.AUTHENTICATE, AdminACLs.AUTHENTICATE,
@@ -18,10 +18,6 @@ import {InstanceConfigWriteRaceExecutor} from '@app/api/instance/tests/InstanceC
import {startDockerContainer} from '@app/api/test/DockerTestContainer'; import {startDockerContainer} from '@app/api/test/DockerTestContainer';
import {InMemoryCassandraQueryExecutor} from '@app/api/test/InMemoryCassandraQueryExecutor'; import {InMemoryCassandraQueryExecutor} from '@app/api/test/InMemoryCassandraQueryExecutor';
import {MockKVProvider} from '@app/api/test/mocks/MockKVProvider'; import {MockKVProvider} from '@app/api/test/mocks/MockKVProvider';
import {
DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
type ScreenShareDeliveryConfig,
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import { import {
DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG, DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
type VoiceNoiseSuppressionConfig, type VoiceNoiseSuppressionConfig,
@@ -39,7 +35,6 @@ import {
import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest'; import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config'; const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config';
const SCREEN_SHARE_DELIVERY_CONFIG_KEY = 'screen_share_delivery_config';
const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config'; const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
const APP_PUBLIC_CONFIG_KEY = 'app_public_config'; const APP_PUBLIC_CONFIG_KEY = 'app_public_config';
const INSTANCE_POLICY_CONFIG_KEY = 'instance_policy_config'; const INSTANCE_POLICY_CONFIG_KEY = 'instance_policy_config';
@@ -356,70 +351,6 @@ describe('InstanceConfigRepository', () => {
}); });
}); });
it('returns the default screen share delivery config when the key is absent', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await expect(repository.getScreenShareDeliveryConfig()).resolves.toEqual(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG);
});
it.each([
{name: 'unparseable text', stored: 'not-json'},
{name: 'a json array', stored: '[]'},
{name: 'out-of-range values', stored: '{"rollout_basis_points":99999}'},
{name: 'a non-boolean enabled flag', stored: '{"enabled":"yes"}'},
])('falls back to the default screen share delivery config for $name', async ({stored}) => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(SCREEN_SHARE_DELIVERY_CONFIG_KEY, stored);
await expect(repository.getScreenShareDeliveryConfig()).resolves.toEqual(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG);
});
it('round-trips a stored screen share delivery config', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
const config: ScreenShareDeliveryConfig = {
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
enabled: true,
config_version: 5,
rollout_basis_points: 2500,
rollout_salt: 'screen-share-delivery-v2',
included_user_ids: ['1400000000000000001'],
excluded_user_ids: ['1400000000000000002'],
};
await repository.setScreenShareDeliveryConfig(config);
await expect(repository.getScreenShareDeliveryConfig()).resolves.toEqual(config);
});
it('fills newly added screen share delivery fields from the schema defaults', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(
SCREEN_SHARE_DELIVERY_CONFIG_KEY,
JSON.stringify({enabled: true, config_version: 2, rollout_basis_points: 1000}),
);
await expect(repository.getScreenShareDeliveryConfig()).resolves.toEqual({
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
enabled: true,
config_version: 2,
rollout_basis_points: 1000,
});
});
it('returns the default experiment delivery config when the key is absent', async () => { it('returns the default experiment delivery config when the key is absent', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor(); const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor); setCassandraQueryExecutorForTesting(executor);
@@ -492,26 +423,6 @@ describe('InstanceConfigRepository', () => {
}); });
}); });
it('publishes a refresh so another repository observes the screen share delivery config', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const reader = createRepository(kvProvider);
const writer = createRepository(kvProvider);
await expect(reader.getScreenShareDeliveryConfig()).resolves.toEqual(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG);
await writer.setScreenShareDeliveryConfig({
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
enabled: true,
config_version: 1,
});
await vi.waitFor(async () => {
expect(await reader.getScreenShareDeliveryConfig()).toMatchObject({enabled: true, config_version: 1});
});
});
it('uses the registration URL id as the admin-visible registration code', async () => { it('uses the registration URL id as the admin-visible registration code', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor(); const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor); setCassandraQueryExecutorForTesting(executor);
@@ -36,10 +36,6 @@ import {
type PushServiceDeliveryConfig, type PushServiceDeliveryConfig,
PushServiceDeliveryConfigSchema, PushServiceDeliveryConfigSchema,
} from '@fluxer/schema/src/domains/admin/PushServiceDeliverySchemas'; } from '@fluxer/schema/src/domains/admin/PushServiceDeliverySchemas';
import {
type ScreenShareDeliveryConfig,
ScreenShareDeliveryConfigSchema,
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import { import {
type VoiceNoiseSuppressionConfig, type VoiceNoiseSuppressionConfig,
VoiceNoiseSuppressionConfigSchema, VoiceNoiseSuppressionConfigSchema,
@@ -66,7 +62,6 @@ import {z} from 'zod';
const GATEWAY_ROLLOUT_CONFIG_KEY = 'gateway_rollout_config'; const GATEWAY_ROLLOUT_CONFIG_KEY = 'gateway_rollout_config';
const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config'; const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config';
const SCREEN_SHARE_DELIVERY_CONFIG_KEY = 'screen_share_delivery_config';
const PUSH_SERVICE_DELIVERY_CONFIG_KEY = 'push_service_delivery_config'; const PUSH_SERVICE_DELIVERY_CONFIG_KEY = 'push_service_delivery_config';
const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config'; const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
const REGISTRATION_CONFIG_KEY = 'registration_config'; const REGISTRATION_CONFIG_KEY = 'registration_config';
@@ -374,7 +369,6 @@ type StoredConfigSection =
| 'app public' | 'app public'
| 'gateway rollout' | 'gateway rollout'
| 'voice noise suppression' | 'voice noise suppression'
| 'screen share delivery'
| 'push service delivery' | 'push service delivery'
| 'experiment delivery' | 'experiment delivery'
| 'instance policy' | 'instance policy'
@@ -514,10 +508,6 @@ function parseStoredVoiceNoiseSuppressionConfig(raw: string | null): VoiceNoiseS
return parseStoredConfigOrDefault(VoiceNoiseSuppressionConfigSchema, raw, 'voice noise suppression'); return parseStoredConfigOrDefault(VoiceNoiseSuppressionConfigSchema, raw, 'voice noise suppression');
} }
function parseStoredScreenShareDeliveryConfig(raw: string | null): ScreenShareDeliveryConfig {
return parseStoredConfigOrDefault(ScreenShareDeliveryConfigSchema, raw, 'screen share delivery');
}
function parseStoredPushServiceDeliveryConfig(raw: string | null): PushServiceDeliveryConfig { function parseStoredPushServiceDeliveryConfig(raw: string | null): PushServiceDeliveryConfig {
return parseStoredConfigOrDefault(PushServiceDeliveryConfigSchema, raw, 'push service delivery'); return parseStoredConfigOrDefault(PushServiceDeliveryConfigSchema, raw, 'push service delivery');
} }
@@ -1169,7 +1159,6 @@ export class InstanceConfigRepository {
parseStoredGatewayRolloutConfig(snapshot.get(GATEWAY_ROLLOUT_CONFIG_KEY) ?? null), parseStoredGatewayRolloutConfig(snapshot.get(GATEWAY_ROLLOUT_CONFIG_KEY) ?? null),
); );
parseStoredVoiceNoiseSuppressionConfig(snapshot.get(VOICE_NOISE_SUPPRESSION_CONFIG_KEY) ?? null); parseStoredVoiceNoiseSuppressionConfig(snapshot.get(VOICE_NOISE_SUPPRESSION_CONFIG_KEY) ?? null);
parseStoredScreenShareDeliveryConfig(snapshot.get(SCREEN_SHARE_DELIVERY_CONFIG_KEY) ?? null);
parseStoredPushServiceDeliveryConfig(snapshot.get(PUSH_SERVICE_DELIVERY_CONFIG_KEY) ?? null); parseStoredPushServiceDeliveryConfig(snapshot.get(PUSH_SERVICE_DELIVERY_CONFIG_KEY) ?? null);
parseStoredExperimentDeliveryConfig(snapshot.get(EXPERIMENT_DELIVERY_CONFIG_KEY) ?? null); parseStoredExperimentDeliveryConfig(snapshot.get(EXPERIMENT_DELIVERY_CONFIG_KEY) ?? null);
const policy = parseStoredInstancePolicyConfig(snapshot.get(INSTANCE_POLICY_CONFIG_KEY) ?? null); const policy = parseStoredInstancePolicyConfig(snapshot.get(INSTANCE_POLICY_CONFIG_KEY) ?? null);
@@ -1267,27 +1256,6 @@ export class InstanceConfigRepository {
); );
} }
async getScreenShareDeliveryConfig(): Promise<ScreenShareDeliveryConfig> {
const raw = await this.getConfig(SCREEN_SHARE_DELIVERY_CONFIG_KEY);
return parseStoredScreenShareDeliveryConfig(raw);
}
async setScreenShareDeliveryConfig(config: ScreenShareDeliveryConfig): Promise<void> {
await this.updateScreenShareDeliveryConfig(() => config);
}
updateScreenShareDeliveryConfig(
update: (current: ScreenShareDeliveryConfig) => ScreenShareDeliveryConfig,
): Promise<ScreenShareDeliveryConfig> {
return this.updateStoredConfig(SCREEN_SHARE_DELIVERY_CONFIG_KEY, (raw) =>
validateStoredConfig(
ScreenShareDeliveryConfigSchema,
update(parseStoredScreenShareDeliveryConfig(raw)),
'screen share delivery',
),
);
}
async getPushServiceDeliveryConfig(): Promise<PushServiceDeliveryConfig> { async getPushServiceDeliveryConfig(): Promise<PushServiceDeliveryConfig> {
const raw = await this.getConfig(PUSH_SERVICE_DELIVERY_CONFIG_KEY); const raw = await this.getConfig(PUSH_SERVICE_DELIVERY_CONFIG_KEY);
return parseStoredPushServiceDeliveryConfig(raw); return parseStoredPushServiceDeliveryConfig(raw);
+1 -8
View File
@@ -27190,8 +27190,7 @@
"assignments": { "assignments": {
"type": "object", "type": "object",
"properties": { "properties": {
"voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionAssignmentResponse"}, "voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionAssignmentResponse"}
"screen_share_delivery": {"$ref": "#/components/schemas/ScreenShareDeliveryAssignmentResponse"}
}, },
"additionalProperties": false "additionalProperties": false
} }
@@ -30695,12 +30694,6 @@
"additionalProperties": false "additionalProperties": false
}, },
"DonationCurrency": {"type": "string", "enum": ["usd", "eur", "brl", "inr", "pln", "try", "sek", "dkk", "nok"]}, "DonationCurrency": {"type": "string", "enum": ["usd", "eur", "brl", "inr", "pln", "try", "sek", "dkk", "nok"]},
"ScreenShareDeliveryAssignmentResponse": {
"type": "object",
"properties": {"enabled": {"type": "boolean"}},
"required": ["enabled"],
"additionalProperties": false
},
"VoiceNoiseSuppressionAssignmentResponse": { "VoiceNoiseSuppressionAssignmentResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -52,7 +52,6 @@ export interface InternalRoomOptions {
singlePeerConnection: boolean; singlePeerConnection: boolean;
subscriberVideoCodecExclusions?: Array<VideoCodec>; subscriberVideoCodecExclusions?: Array<VideoCodec>;
screenShareDelivery?: boolean;
h264HardwareProfiles?: ReadonlySet<string>; h264HardwareProfiles?: ReadonlySet<string>;
dataStream?: RoomDataStreamOptions; dataStream?: RoomDataStreamOptions;
} }
@@ -73,66 +73,34 @@ describe('applyVideoStartBitrate', () => {
} }
it('adds a start bitrate to a non-SVC codec section', () => { it('adds a start bitrate to a non-SVC codec section', () => {
for (const screenShareDelivery of [false, true]) { const media = videoMedia('camera-track', [
const media = videoMedia('camera-track', [ {payload: 96, config: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f'},
{payload: 96, config: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f'}, ]);
]); expect(applyVideoStartBitrate(media, 'camera-track', 'H264', 1000, false)).toBe(96);
expect(applyVideoStartBitrate(media, 'camera-track', 'H264', 1000, false, screenShareDelivery)).toBe(96); expect(media.fmtp[0]?.config).toBe(
expect(media.fmtp[0]?.config).toBe( 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f;x-google-start-bitrate=900',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f;x-google-start-bitrate=900', );
);
}
});
it('caps camera start bitrates but not screen share start bitrates while screen share delivery is off', () => {
const camera = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(camera, 'camera-track', 'H264', 3000);
expect(camera.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000');
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 6000, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=5400');
}); });
it('caps camera and screen share start bitrates at their own ceilings', () => { it('caps camera and screen share start bitrates at their own ceilings', () => {
const camera = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]); const camera = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(camera, 'camera-track', 'H264', 3000, false, true); applyVideoStartBitrate(camera, 'camera-track', 'H264', 3000, false);
expect(camera.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000'); expect(camera.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000');
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]); const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 6000, true, true); applyVideoStartBitrate(screen, 'screen-track', 'H264', 6000, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1500'); expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1500');
}); });
it('leaves a small screen share start bitrate on the floor while screen share delivery is off', () => {
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 300, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=270');
});
it('keeps a screen share start bitrate above the frame dropper cliff', () => { it('keeps a screen share start bitrate above the frame dropper cliff', () => {
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]); const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 300, true, true); applyVideoStartBitrate(screen, 'screen-track', 'H264', 300, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=600'); expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=600');
}); });
it('stamps only the lead payload type while screen share delivery is off', () => {
const media = multiPayloadScreenMedia();
expect(applyVideoStartBitrate(media, 'screen-track', 'H264', 6000, true)).toBe(116);
expect(media.fmtp.find((fmtp) => fmtp.payload === 116)?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f;x-google-start-bitrate=5400',
);
expect(media.fmtp.find((fmtp) => fmtp.payload === 102)?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f',
);
expect(media.fmtp.find((fmtp) => fmtp.payload === 108)?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
);
});
it('stamps every payload type the codec is offered under, not only the lead one', () => { it('stamps every payload type the codec is offered under, not only the lead one', () => {
const media = multiPayloadScreenMedia(); const media = multiPayloadScreenMedia();
expect(applyVideoStartBitrate(media, 'screen-track', 'H264', 6000, true, true)).toBe(116); expect(applyVideoStartBitrate(media, 'screen-track', 'H264', 6000, true)).toBe(116);
for (const fmtp of media.fmtp) { for (const fmtp of media.fmtp) {
expect(fmtp.config).toContain('x-google-start-bitrate=1500'); expect(fmtp.config).toContain('x-google-start-bitrate=1500');
} }
@@ -140,47 +108,35 @@ describe('applyVideoStartBitrate', () => {
}); });
it('only touches the fmtp line for the matching payload', () => { it('only touches the fmtp line for the matching payload', () => {
for (const screenShareDelivery of [false, true]) { const media = videoMedia(
const media = videoMedia( 'screen-track',
'screen-track', [
[ {payload: 96, config: 'profile-level-id=42e01f'},
{payload: 96, config: 'profile-level-id=42e01f'}, {payload: 98, config: 'profile-id=0'},
{payload: 98, config: 'profile-id=0'}, ],
], [
[ {payload: 96, codec: 'H264'},
{payload: 96, codec: 'H264'}, {payload: 98, codec: 'VP9'},
{payload: 98, codec: 'VP9'}, ],
], );
); applyVideoStartBitrate(media, 'screen-track', 'VP9', 1500, true);
applyVideoStartBitrate(media, 'screen-track', 'VP9', 1500, true, screenShareDelivery); expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f');
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f'); expect(media.fmtp[1]?.config).toBe('profile-id=0;x-google-start-bitrate=1350');
expect(media.fmtp[1]?.config).toBe('profile-id=0;x-google-start-bitrate=1350');
}
});
it('never appends a second start bitrate while screen share delivery is off', () => {
const media = videoMedia('camera-track', [
{payload: 96, config: 'profile-level-id=42e01f;x-google-start-bitrate=900'},
]);
applyVideoStartBitrate(media, 'camera-track', 'H264', 2000);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=900');
}); });
it('replaces a start bitrate an earlier offer wrote instead of keeping it', () => { it('replaces a start bitrate an earlier offer wrote instead of keeping it', () => {
const media = videoMedia('camera-track', [ const media = videoMedia('camera-track', [
{payload: 96, config: 'profile-level-id=42e01f;x-google-start-bitrate=900'}, {payload: 96, config: 'profile-level-id=42e01f;x-google-start-bitrate=900'},
]); ]);
applyVideoStartBitrate(media, 'camera-track', 'H264', 2000, false, true); applyVideoStartBitrate(media, 'camera-track', 'H264', 2000, false);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000'); expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000');
}); });
it('leaves other tracks and missing codecs alone', () => { it('leaves other tracks and missing codecs alone', () => {
for (const screenShareDelivery of [false, true]) { const media = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
const media = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]); expect(applyVideoStartBitrate(media, 'other-track', 'H264', 2000, false)).toBeUndefined();
expect(applyVideoStartBitrate(media, 'other-track', 'H264', 2000, false, screenShareDelivery)).toBeUndefined(); expect(applyVideoStartBitrate(media, 'camera-track', 'AV1', 2000, false)).toBe(0);
expect(applyVideoStartBitrate(media, 'camera-track', 'AV1', 2000, false, screenShareDelivery)).toBe(0); expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f');
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f');
}
}); });
}); });
@@ -49,7 +49,6 @@ export function applyVideoStartBitrate(
codec: string, codec: string,
maxbr: number, maxbr: number,
isScreenShare = false, isScreenShare = false,
screenShareDelivery = false,
): number | undefined { ): number | undefined {
if (!media.msid?.includes(cid)) { if (!media.msid?.includes(cid)) {
return undefined; return undefined;
@@ -65,25 +64,10 @@ export function applyVideoStartBitrate(
const calculatedStartBitrate = Math.round(maxbr * startBitrateMultiplier); const calculatedStartBitrate = Math.round(maxbr * startBitrateMultiplier);
let startBitrate = Math.min(calculatedStartBitrate, maxStartBitrateKbps); let startBitrate = Math.min(calculatedStartBitrate, maxStartBitrateKbps);
if (isScreenShare) { if (isScreenShare) {
startBitrate = screenShareDelivery startBitrate = Math.max(
? Math.max(minScreenShareStartBitrateKbps, Math.min(calculatedStartBitrate, maxScreenShareStartBitrateKbps)) minScreenShareStartBitrateKbps,
: calculatedStartBitrate; Math.min(calculatedStartBitrate, maxScreenShareStartBitrateKbps),
} );
if (!screenShareDelivery) {
const codecPayload = codecPayloads[0];
const fmtp = media.fmtp.find((entry) => entry.payload === codecPayload);
if (fmtp) {
if (!fmtp.config.includes(startBitrateParameter)) {
fmtp.config += `;${startBitrateParameter}=${startBitrate}`;
}
} else {
media.fmtp.push({
payload: codecPayload,
config: `${startBitrateParameter}=${startBitrate}`,
});
}
return codecPayload;
} }
for (const payload of codecPayloads) { for (const payload of codecPayloads) {
@@ -143,8 +127,6 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
excludedVideoDecoderMimeTypes: Set<string> = new Set(); excludedVideoDecoderMimeTypes: Set<string> = new Set();
private screenShareDelivery: boolean;
onOffer?: (offer: RTCSessionDescriptionInit, offerId: number) => void; onOffer?: (offer: RTCSessionDescriptionInit, offerId: number) => void;
onIceCandidate?: (candidate: RTCIceCandidate) => void; onIceCandidate?: (candidate: RTCIceCandidate) => void;
@@ -161,10 +143,9 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
onTrack?: (ev: RTCTrackEvent) => void; onTrack?: (ev: RTCTrackEvent) => void;
constructor(config?: RTCConfiguration, loggerOptions: LoggerOptions = {}, screenShareDelivery: boolean = false) { constructor(config?: RTCConfiguration, loggerOptions: LoggerOptions = {}) {
super(); super();
this.loggerOptions = loggerOptions; this.loggerOptions = loggerOptions;
this.screenShareDelivery = screenShareDelivery;
this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.PCTransport, () => this.logContext); this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.PCTransport, () => this.logContext);
this.iceLog = getLogger(LoggerNames.ICE, () => this.logContext); this.iceLog = getLogger(LoggerNames.ICE, () => this.logContext);
this.config = config; this.config = config;
@@ -412,7 +393,6 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
trackbr.codec, trackbr.codec,
trackbr.maxbr, trackbr.maxbr,
trackbr.isScreenShare, trackbr.isScreenShare,
this.screenShareDelivery,
); );
if (codecPayload === undefined) { if (codecPayload === undefined) {
return false; return false;
@@ -471,10 +451,7 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
for (const transceiver of this.getTransceivers()) { for (const transceiver of this.getTransceivers()) {
if (transceiver.receiver.track?.kind !== 'video') continue; if (transceiver.receiver.track?.kind !== 'video') continue;
if ((transceiver as {stopped?: boolean}).stopped) continue; if ((transceiver as {stopped?: boolean}).stopped) continue;
const receives = this.screenShareDelivery if (transceiver.direction !== 'recvonly') continue;
? transceiver.direction === 'recvonly'
: transceiver.direction === 'recvonly' || transceiver.direction === 'sendrecv';
if (!receives) continue;
if (typeof transceiver.setCodecPreferences !== 'function') continue; if (typeof transceiver.setCodecPreferences !== 'function') continue;
try { try {
transceiver.setCodecPreferences(allowed); transceiver.setCodecPreferences(allowed);
@@ -98,7 +98,6 @@ export class PCTransportManager {
loggerOptions: LoggerOptions, loggerOptions: LoggerOptions,
rtcConfig?: RTCConfiguration, rtcConfig?: RTCConfiguration,
subscriberVideoCodecExclusions?: Array<VideoCodec>, subscriberVideoCodecExclusions?: Array<VideoCodec>,
screenShareDelivery: boolean = false,
) { ) {
this.loggerOptions = loggerOptions; this.loggerOptions = loggerOptions;
this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.PCManager, () => this.logContext); this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.PCManager, () => this.logContext);
@@ -106,10 +105,10 @@ export class PCTransportManager {
this.isPublisherConnectionRequired = mode !== 'subscriber-primary'; this.isPublisherConnectionRequired = mode !== 'subscriber-primary';
this.isSubscriberConnectionRequired = mode === 'subscriber-primary'; this.isSubscriberConnectionRequired = mode === 'subscriber-primary';
this.publisher = new PCTransport(rtcConfig, loggerOptions, screenShareDelivery); this.publisher = new PCTransport(rtcConfig, loggerOptions);
this._mode = mode; this._mode = mode;
if (mode !== 'publisher-only') { if (mode !== 'publisher-only') {
this.subscriber = new PCTransport(rtcConfig, loggerOptions, screenShareDelivery); this.subscriber = new PCTransport(rtcConfig, loggerOptions);
this.subscriber.onConnectionStateChange = this.updateState; this.subscriber.onConnectionStateChange = this.updateState;
this.subscriber.onIceConnectionStateChange = this.updateState; this.subscriber.onIceConnectionStateChange = this.updateState;
this.subscriber.onSignalingStatechange = this.updateState; this.subscriber.onSignalingStatechange = this.updateState;
@@ -127,7 +126,7 @@ export class PCTransportManager {
}; };
} }
const receivingTransport = screenShareDelivery ? (this.subscriber ?? this.publisher) : this.subscriber; const receivingTransport = this.subscriber ?? this.publisher;
if (receivingTransport) { if (receivingTransport) {
for (const codec of subscriberVideoCodecExclusions ?? []) { for (const codec of subscriberVideoCodecExclusions ?? []) {
receivingTransport.excludedVideoDecoderMimeTypes.add(`video/${codec}`); receivingTransport.excludedVideoDecoderMimeTypes.add(`video/${codec}`);
@@ -25,41 +25,20 @@ describe('selectPublisherCodecPreferences', () => {
const mainLine = 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f'; const mainLine = 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f';
const highLine = 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f'; const highLine = 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f';
it('keeps Main and Baseline ahead of Constrained Baseline while screen share delivery is off', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const baseline = codec('video/H264', baselineLine);
const highProfile = codec('video/H264', highLine);
const rtx = codec('video/rtx');
const preferences = selectPublisherCodecPreferences('h264', [constrainedBaseline, rtx, baseline, highProfile]);
expect(preferences).toEqual([highProfile, baseline, constrainedBaseline, rtx]);
});
it('offers High first, then the one profile this server always registers, then the ones it registers nowhere', () => { it('offers High first, then the one profile this server always registers, then the ones it registers nowhere', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine); const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const baseline = codec('video/H264', baselineLine); const baseline = codec('video/H264', baselineLine);
const highProfile = codec('video/H264', highLine); const highProfile = codec('video/H264', highLine);
const rtx = codec('video/rtx'); const rtx = codec('video/rtx');
const preferences = selectPublisherCodecPreferences( const preferences = selectPublisherCodecPreferences('h264', [constrainedBaseline, rtx, baseline, highProfile]);
'h264',
[constrainedBaseline, rtx, baseline, highProfile],
true,
);
expect(preferences).toEqual([highProfile, constrainedBaseline, baseline, rtx]); expect(preferences).toEqual([highProfile, constrainedBaseline, baseline, rtx]);
}); });
it('leads with Main and then Baseline while screen share delivery is off', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const mainProfile = codec('video/H264', mainLine);
const baseline = codec('video/H264', baselineLine);
const preferences = selectPublisherCodecPreferences('h264', [mainProfile, baseline, constrainedBaseline]);
expect(preferences).toEqual([mainProfile, baseline, constrainedBaseline]);
});
it('never leads with Main or Baseline, which this server registers nowhere and deletes from the answer', () => { it('never leads with Main or Baseline, which this server registers nowhere and deletes from the answer', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine); const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const mainProfile = codec('video/H264', mainLine); const mainProfile = codec('video/H264', mainLine);
const baseline = codec('video/H264', baselineLine); const baseline = codec('video/H264', baselineLine);
const preferences = selectPublisherCodecPreferences('h264', [mainProfile, baseline, constrainedBaseline], true); const preferences = selectPublisherCodecPreferences('h264', [mainProfile, baseline, constrainedBaseline]);
expect(preferences).toEqual([constrainedBaseline, mainProfile, baseline]); expect(preferences).toEqual([constrainedBaseline, mainProfile, baseline]);
}); });
@@ -75,21 +54,8 @@ describe('selectPublisherCodecPreferences', () => {
]; ];
} }
it('sorts the capabilities Chromium reports by the old table while screen share delivery is off', () => {
const preferences = selectPublisherCodecPreferences('h264', chromiumCapabilities());
expect(preferences.map((entry) => entry.sdpFmtpLine)).toEqual([
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640034',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f',
]);
});
it('offers High first out of the capabilities Chromium reports, so the only hardware profile this server registers wins', () => { it('offers High first out of the capabilities Chromium reports, so the only hardware profile this server registers wins', () => {
const preferences = selectPublisherCodecPreferences('h264', chromiumCapabilities(), true); const preferences = selectPublisherCodecPreferences('h264', chromiumCapabilities());
expect(preferences.map((entry) => entry.sdpFmtpLine)).toEqual([ expect(preferences.map((entry) => entry.sdpFmtpLine)).toEqual([
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640034', 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640034',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f', 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
@@ -116,7 +82,7 @@ describe('selectPublisherCodecPreferences', () => {
return {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh}; return {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh};
} }
it('ranks High, Constrained High, Main and Baseline above Constrained Baseline while screen share delivery is off', () => { it('ranks High and Constrained High above Constrained Baseline, whatever level each one reports', () => {
const {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh} = levelSpread(); const {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh} = levelSpread();
const preferences = selectPublisherCodecPreferences('h264', [ const preferences = selectPublisherCodecPreferences('h264', [
mainProfile, mainProfile,
@@ -125,22 +91,6 @@ describe('selectPublisherCodecPreferences', () => {
constrainedHigh, constrainedHigh,
constrainedBaseline, constrainedBaseline,
]); ]);
expect(preferences).toEqual([
highProfileLevel31,
highProfileLevel51,
constrainedHigh,
mainProfile,
constrainedBaseline,
]);
});
it('ranks High and Constrained High above Constrained Baseline, whatever level each one reports', () => {
const {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh} = levelSpread();
const preferences = selectPublisherCodecPreferences(
'h264',
[mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh, constrainedBaseline],
true,
);
expect(preferences).toEqual([ expect(preferences).toEqual([
highProfileLevel31, highProfileLevel31,
highProfileLevel51, highProfileLevel51,
@@ -150,7 +100,7 @@ describe('selectPublisherCodecPreferences', () => {
]); ]);
}); });
it('ranks packetization-mode=1 above packetization-mode=0 in both arms, which no hardware encoder takes', () => { it('ranks packetization-mode=1 above packetization-mode=0, which no hardware encoder takes', () => {
const constrainedBaselineMode0 = codec( const constrainedBaselineMode0 = codec(
'video/H264', 'video/H264',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f', 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f',
@@ -167,7 +117,6 @@ describe('selectPublisherCodecPreferences', () => {
const capabilities = [highProfileMode0, constrainedBaselineMode0, constrainedBaselineMode1, highProfileMode1]; const capabilities = [highProfileMode0, constrainedBaselineMode0, constrainedBaselineMode1, highProfileMode1];
const expected = [highProfileMode1, constrainedBaselineMode1, highProfileMode0, constrainedBaselineMode0]; const expected = [highProfileMode1, constrainedBaselineMode1, highProfileMode0, constrainedBaselineMode0];
expect(selectPublisherCodecPreferences('h264', capabilities)).toEqual(expected); expect(selectPublisherCodecPreferences('h264', capabilities)).toEqual(expected);
expect(selectPublisherCodecPreferences('h264', capabilities, true)).toEqual(expected);
}); });
it('puts the chosen codec first and keeps every other codec in browser capability order', () => { it('puts the chosen codec first and keeps every other codec in browser capability order', () => {
@@ -175,7 +124,6 @@ describe('selectPublisherCodecPreferences', () => {
const vp8 = codec('video/VP8'); const vp8 = codec('video/VP8');
const rtx = codec('video/rtx'); const rtx = codec('video/rtx');
expect(selectPublisherCodecPreferences('vp9', [vp8, rtx, vp9])).toEqual([vp9, vp8, rtx]); expect(selectPublisherCodecPreferences('vp9', [vp8, rtx, vp9])).toEqual([vp9, vp8, rtx]);
expect(selectPublisherCodecPreferences('vp9', [vp8, rtx, vp9], true)).toEqual([vp9, vp8, rtx]);
}); });
it('keeps the other codecs so a later publication on the same connection can negotiate them', () => { it('keeps the other codecs so a later publication on the same connection can negotiate them', () => {
@@ -192,7 +140,7 @@ describe('selectPublisherCodecPreferences', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine); const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const capabilities = [vp8, highProfile, constrainedBaseline]; const capabilities = [vp8, highProfile, constrainedBaseline];
expect(selectPublisherCodecPreferences('vp8', capabilities)).toEqual([vp8, highProfile, constrainedBaseline]); expect(selectPublisherCodecPreferences('vp8', capabilities)).toEqual([vp8, highProfile, constrainedBaseline]);
expect(selectPublisherCodecPreferences('vp8', capabilities, true, new Set(['42e0']))).toEqual([ expect(selectPublisherCodecPreferences('vp8', capabilities, new Set(['42e0']))).toEqual([
vp8, vp8,
constrainedBaseline, constrainedBaseline,
highProfile, highProfile,
@@ -203,25 +151,15 @@ describe('selectPublisherCodecPreferences', () => {
expect(selectPublisherCodecPreferences('av1', [codec('video/VP8'), codec('video/rtx')])).toEqual([]); expect(selectPublisherCodecPreferences('av1', [codec('video/VP8'), codec('video/rtx')])).toEqual([]);
}); });
it('ignores the profiles this host measured while screen share delivery is off', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const highProfile = codec('video/H264', highLine);
const capabilities = [highProfile, constrainedBaseline];
expect(selectPublisherCodecPreferences('h264', capabilities, false, new Set(['42e0']))).toEqual([
highProfile,
constrainedBaseline,
]);
});
it('only lets a profile this host encodes in hardware outrank Constrained Baseline', () => { it('only lets a profile this host encodes in hardware outrank Constrained Baseline', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine); const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const highProfile = codec('video/H264', highLine); const highProfile = codec('video/H264', highLine);
const capabilities = [highProfile, constrainedBaseline]; const capabilities = [highProfile, constrainedBaseline];
expect(selectPublisherCodecPreferences('h264', capabilities, true, new Set(['42e0']))).toEqual([ expect(selectPublisherCodecPreferences('h264', capabilities, new Set(['42e0']))).toEqual([
constrainedBaseline, constrainedBaseline,
highProfile, highProfile,
]); ]);
expect(selectPublisherCodecPreferences('h264', capabilities, true, new Set(['6400', '42e0']))).toEqual([ expect(selectPublisherCodecPreferences('h264', capabilities, new Set(['6400', '42e0']))).toEqual([
highProfile, highProfile,
constrainedBaseline, constrainedBaseline,
]); ]);
@@ -231,8 +169,8 @@ describe('selectPublisherCodecPreferences', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine); const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const highProfile = codec('video/H264', highLine); const highProfile = codec('video/H264', highLine);
const capabilities = [constrainedBaseline, highProfile]; const capabilities = [constrainedBaseline, highProfile];
expect(selectPublisherCodecPreferences('h264', capabilities, true)).toEqual([highProfile, constrainedBaseline]); expect(selectPublisherCodecPreferences('h264', capabilities)).toEqual([highProfile, constrainedBaseline]);
expect(selectPublisherCodecPreferences('h264', capabilities, true, new Set())).toEqual([ expect(selectPublisherCodecPreferences('h264', capabilities, new Set())).toEqual([
highProfile, highProfile,
constrainedBaseline, constrainedBaseline,
]); ]);
@@ -245,7 +183,7 @@ describe('selectPublisherCodecPreferences', () => {
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=64001f', 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=64001f',
); );
expect( expect(
selectPublisherCodecPreferences('h264', [highProfileMode0, constrainedBaselineMode1], true, new Set(['6400'])), selectPublisherCodecPreferences('h264', [highProfileMode0, constrainedBaselineMode1], new Set(['6400'])),
).toEqual([constrainedBaselineMode1, highProfileMode0]); ).toEqual([constrainedBaselineMode1, highProfileMode0]);
}); });
}); });
@@ -123,13 +123,6 @@ const videoCodecMimeTypes: Record<VideoCodec, Array<string>> = {
vp8: ['video/vp8'], vp8: ['video/vp8'],
}; };
const h264ProfileRanks = new Map([ const h264ProfileRanks = new Map([
['6400', 0],
['640c', 1],
['4d00', 2],
['4200', 3],
['42e0', 4],
]);
const h264DeliveryProfileRanks = new Map([
['6400', 0], ['6400', 0],
['640c', 1], ['640c', 1],
['42e0', 2], ['42e0', 2],
@@ -139,8 +132,7 @@ const h264DeliveryProfileRanks = new Map([
const h264UnrankedProfileScore = 5; const h264UnrankedProfileScore = 5;
const h264MissingProfileScore = 6; const h264MissingProfileScore = 6;
const h264NonHardwareProfilePenalty = 8; const h264NonHardwareProfilePenalty = 8;
const h264PacketizationMode0Score = 10; const h264PacketizationMode0Score = 20;
const h264DeliveryPacketizationMode0Score = 20;
type RtpCodecCapability = RTCRtpCapabilities['codecs'][number] & {sdpFmtpLine?: string}; type RtpCodecCapability = RTCRtpCapabilities['codecs'][number] & {sdpFmtpLine?: string};
enum PCState { enum PCState {
@@ -523,7 +515,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
this.loggerOptions, this.loggerOptions,
rtcConfig, rtcConfig,
this.options.subscriberVideoCodecExclusions, this.options.subscriberVideoCodecExclusions,
this.options.screenShareDelivery ?? false,
); );
} else { } else {
this.participantSid = joinResponse.participant?.sid; this.participantSid = joinResponse.participant?.sid;
@@ -537,7 +528,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
this.loggerOptions, this.loggerOptions,
rtcConfig, rtcConfig,
this.options.subscriberVideoCodecExclusions, this.options.subscriberVideoCodecExclusions,
this.options.screenShareDelivery ?? false,
); );
} }
@@ -1062,12 +1052,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
if (typeof RTCRtpSender === 'undefined' || typeof RTCRtpSender.getCapabilities !== 'function') return; if (typeof RTCRtpSender === 'undefined' || typeof RTCRtpSender.getCapabilities !== 'function') return;
const capabilities = RTCRtpSender.getCapabilities('video'); const capabilities = RTCRtpSender.getCapabilities('video');
if (!capabilities) return; if (!capabilities) return;
const preferences = selectPublisherCodecPreferences( const preferences = selectPublisherCodecPreferences(codec, capabilities.codecs, this.options.h264HardwareProfiles);
codec,
capabilities.codecs,
this.options.screenShareDelivery ?? false,
this.options.h264HardwareProfiles,
);
if (preferences.length === 0) { if (preferences.length === 0) {
this.log.warn('sender cannot encode the requested codec, leaving the browser order in place', { this.log.warn('sender cannot encode the requested codec, leaving the browser order in place', {
...this.logContext, ...this.logContext,
@@ -1843,33 +1828,27 @@ function getFmtpParameter(sdpFmtpLine: string | undefined, key: string): string
function getH264PublisherCodecScore( function getH264PublisherCodecScore(
codec: RtpCodecCapability, codec: RtpCodecCapability,
screenShareDelivery: boolean,
hardwareProfiles: ReadonlySet<string> | undefined, hardwareProfiles: ReadonlySet<string> | undefined,
): number { ): number {
const profileLevelId = getFmtpParameter(codec.sdpFmtpLine, 'profile-level-id'); const profileLevelId = getFmtpParameter(codec.sdpFmtpLine, 'profile-level-id');
const packetizationMode = getFmtpParameter(codec.sdpFmtpLine, 'packetization-mode'); const packetizationMode = getFmtpParameter(codec.sdpFmtpLine, 'packetization-mode');
const mode0Score = screenShareDelivery ? h264DeliveryPacketizationMode0Score : h264PacketizationMode0Score; const packetizationScore = packetizationMode === '1' ? 0 : h264PacketizationMode0Score;
const packetizationScore = packetizationMode === '1' ? 0 : mode0Score;
if (!profileLevelId) return packetizationScore + h264MissingProfileScore; if (!profileLevelId) return packetizationScore + h264MissingProfileScore;
const profile = profileLevelId.slice(0, 4); const profile = profileLevelId.slice(0, 4);
if (!screenShareDelivery) {
return packetizationScore + (h264ProfileRanks.get(profile) ?? h264UnrankedProfileScore);
}
const isSoftwareOnly = hardwareProfiles !== undefined && hardwareProfiles.size > 0 && !hardwareProfiles.has(profile); const isSoftwareOnly = hardwareProfiles !== undefined && hardwareProfiles.size > 0 && !hardwareProfiles.has(profile);
const hardwareScore = isSoftwareOnly ? h264NonHardwareProfilePenalty : 0; const hardwareScore = isSoftwareOnly ? h264NonHardwareProfilePenalty : 0;
return packetizationScore + hardwareScore + (h264DeliveryProfileRanks.get(profile) ?? h264UnrankedProfileScore); return packetizationScore + hardwareScore + (h264ProfileRanks.get(profile) ?? h264UnrankedProfileScore);
} }
function preferHardwareH264Codecs( function preferHardwareH264Codecs(
codecs: ReadonlyArray<RtpCodecCapability>, codecs: ReadonlyArray<RtpCodecCapability>,
screenShareDelivery: boolean,
hardwareProfiles: ReadonlySet<string> | undefined, hardwareProfiles: ReadonlySet<string> | undefined,
): Array<RtpCodecCapability> { ): Array<RtpCodecCapability> {
return codecs return codecs
.map((codec, index) => ({ .map((codec, index) => ({
codec, codec,
index, index,
score: getH264PublisherCodecScore(codec, screenShareDelivery, hardwareProfiles), score: getH264PublisherCodecScore(codec, hardwareProfiles),
})) }))
.sort((a, b) => a.score - b.score || a.index - b.index) .sort((a, b) => a.score - b.score || a.index - b.index)
.map((entry) => entry.codec); .map((entry) => entry.codec);
@@ -1878,17 +1857,15 @@ function preferHardwareH264Codecs(
export function selectPublisherCodecPreferences( export function selectPublisherCodecPreferences(
codec: VideoCodec, codec: VideoCodec,
codecs: ReadonlyArray<RtpCodecCapability>, codecs: ReadonlyArray<RtpCodecCapability>,
screenShareDelivery: boolean = false,
h264HardwareProfiles?: ReadonlySet<string>, h264HardwareProfiles?: ReadonlySet<string>,
): Array<RtpCodecCapability> { ): Array<RtpCodecCapability> {
const mimeTypes = new Set(videoCodecMimeTypes[codec]); const mimeTypes = new Set(videoCodecMimeTypes[codec]);
const selected = codecs.filter((entry) => mimeTypes.has(entry.mimeType.toLowerCase())); const selected = codecs.filter((entry) => mimeTypes.has(entry.mimeType.toLowerCase()));
if (selected.length === 0) return []; if (selected.length === 0) return [];
const preferred = const preferred = codec === 'h264' ? preferHardwareH264Codecs(selected, h264HardwareProfiles) : selected;
codec === 'h264' ? preferHardwareH264Codecs(selected, screenShareDelivery, h264HardwareProfiles) : selected;
const isH264 = (entry: RtpCodecCapability): boolean => entry.mimeType.toLowerCase() === 'video/h264'; const isH264 = (entry: RtpCodecCapability): boolean => entry.mimeType.toLowerCase() === 'video/h264';
const remaining = codecs.filter((entry) => !mimeTypes.has(entry.mimeType.toLowerCase())); const remaining = codecs.filter((entry) => !mimeTypes.has(entry.mimeType.toLowerCase()));
const rankedH264 = preferHardwareH264Codecs(remaining.filter(isH264), screenShareDelivery, h264HardwareProfiles); const rankedH264 = preferHardwareH264Codecs(remaining.filter(isH264), h264HardwareProfiles);
let nextH264 = 0; let nextH264 = 0;
const rest = remaining.map((entry) => (isH264(entry) ? rankedH264[nextH264++] : entry)); const rest = remaining.map((entry) => (isH264(entry) ? rankedH264[nextH264++] : entry));
return [...preferred, ...rest]; return [...preferred, ...rest];
@@ -63,7 +63,6 @@ import {
publishDefaults, publishDefaults,
roomConnectOptionDefaults, roomConnectOptionDefaults,
roomOptionDefaults, roomOptionDefaults,
screenShareDeliveryPublishDefaults,
videoDefaults, videoDefaults,
} from './defaults.ts'; } from './defaults.ts';
import {ConnectionError, ConnectionErrorReason, UnexpectedConnectionState, UnsupportedServer} from './errors.ts'; import {ConnectionError, ConnectionErrorReason, UnexpectedConnectionState, UnsupportedServer} from './errors.ts';
@@ -228,7 +227,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
...options?.videoCaptureDefaults, ...options?.videoCaptureDefaults,
}; };
this.options.publishDefaults = { this.options.publishDefaults = {
...(this.options.screenShareDelivery ? screenShareDeliveryPublishDefaults : publishDefaults), ...publishDefaults,
...options?.publishDefaults, ...options?.publishDefaults,
}; };
@@ -8,7 +8,7 @@ import {AudioPresets, BackupCodecPolicy, ScreenSharePresets, VideoPresets} from
export const defaultVideoCodec = 'h264'; export const defaultVideoCodec = 'h264';
export const screenShareDeliveryPublishDefaults: TrackPublishDefaults = { export const publishDefaults: TrackPublishDefaults = {
audioPreset: AudioPresets.music, audioPreset: AudioPresets.music,
dtx: false, dtx: false,
red: true, red: true,
@@ -21,11 +21,6 @@ export const screenShareDeliveryPublishDefaults: TrackPublishDefaults = {
preConnectBuffer: false, preConnectBuffer: false,
} as const; } as const;
export const publishDefaults: TrackPublishDefaults = {
...screenShareDeliveryPublishDefaults,
degradationPreference: 'maintain-resolution',
};
export const audioDefaults: AudioCaptureOptions = { export const audioDefaults: AudioCaptureOptions = {
deviceId: {ideal: 'default'}, deviceId: {ideal: 'default'},
autoGainControl: true, autoGainControl: true,
@@ -814,7 +814,6 @@ export default class LocalParticipant extends Participant {
...this.roomOptions.publishDefaults, ...this.roomOptions.publishDefaults,
...options, ...options,
}; };
track.screenShareDelivery = this.roomOptions.screenShareDelivery ?? false;
const isStereoInput = const isStereoInput =
('channelCount' in track.mediaStreamTrack.getSettings() && ('channelCount' in track.mediaStreamTrack.getSettings() &&
track.mediaStreamTrack.getSettings().channelCount === 2) || track.mediaStreamTrack.getSettings().channelCount === 2) ||
@@ -1788,7 +1787,7 @@ export default class LocalParticipant extends Participant {
return; return;
} }
let subscribedCodecs = update.subscribedCodecs; let subscribedCodecs = update.subscribedCodecs;
if (this.roomOptions.screenShareDelivery && hasSingleRidlessEncoding(pub.videoTrack)) { if (hasSingleRidlessEncoding(pub.videoTrack)) {
subscribedCodecs = subscribedCodecs.filter((codec) => codec.qualities.some((quality) => quality.enabled)); subscribedCodecs = subscribedCodecs.filter((codec) => codec.qualities.some((quality) => quality.enabled));
if (subscribedCodecs.length === 0) { if (subscribedCodecs.length === 0) {
return; return;
@@ -41,8 +41,6 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
codec?: VideoCodec; codec?: VideoCodec;
screenShareDelivery: boolean = false;
get constraints() { get constraints() {
return this._constraints; return this._constraints;
} }
@@ -563,7 +561,7 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
); );
private debouncedTrackMuteHandler = debounce(async () => { private debouncedTrackMuteHandler = debounce(async () => {
if (this.screenShareDelivery && this.source === Track.Source.ScreenShare) { if (this.source === Track.Source.ScreenShare) {
this.log.debug('screen share capture went idle, keeping upstream published', this.logContext); this.log.debug('screen share capture went idle, keeping upstream published', this.logContext);
return; return;
} }
@@ -6,7 +6,6 @@ import type {VoiceNoiseSuppressionAssignmentResponse} from '@fluxer/schema/src/d
import { import {
type ExperimentAssignmentsResponse, type ExperimentAssignmentsResponse,
INERT_EXPERIMENT_ASSIGNMENTS_RESPONSE, INERT_EXPERIMENT_ASSIGNMENTS_RESPONSE,
readScreenShareDeliveryAssignment,
readVoiceNoiseSuppressionAssignment, readVoiceNoiseSuppressionAssignment,
} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; } from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
@@ -123,8 +122,6 @@ describe('ExperimentAssignments cold start', () => {
await settle(); await settle();
expect(ExperimentAssignments.response).toBe(INERT_EXPERIMENT_ASSIGNMENTS_RESPONSE); expect(ExperimentAssignments.response).toBe(INERT_EXPERIMENT_ASSIGNMENTS_RESPONSE);
expect(ExperimentAssignments.response.assignments.voice_noise_suppression).toBeUndefined(); expect(ExperimentAssignments.response.assignments.voice_noise_suppression).toBeUndefined();
expect(ExperimentAssignments.response.assignments.screen_share_delivery).toBeUndefined();
expect(readScreenShareDeliveryAssignment(ExperimentAssignments.response).enabled).toBe(false);
}); });
it('keeps the inert envelope while unauthenticated and retries later', async () => { it('keeps the inert envelope while unauthenticated and retries later', async () => {
@@ -170,32 +167,6 @@ describe('ExperimentAssignments response handling', () => {
expect(ExperimentAssignments.response).toEqual(CANARY_ENVELOPE); expect(ExperimentAssignments.response).toEqual(CANARY_ENVELOPE);
}); });
it('adopts a screen share delivery assignment beside the voice one', async () => {
await adopt({
...CANARY_ENVELOPE,
assignments: {...CANARY_ENVELOPE.assignments, screen_share_delivery: {enabled: true}},
});
expect(readScreenShareDeliveryAssignment(ExperimentAssignments.response).enabled).toBe(true);
expect(readVoiceNoiseSuppressionAssignment(ExperimentAssignments.response)).toEqual(CANARY_ASSIGNMENT);
});
it('reads screen share delivery as disabled when the envelope omits it', async () => {
await adopt(CANARY_ENVELOPE);
expect(readScreenShareDeliveryAssignment(ExperimentAssignments.response).enabled).toBe(false);
});
it('discards an envelope with a malformed screen share delivery assignment', async () => {
await adopt(CANARY_ENVELOPE);
vi.mocked(http.get).mockResolvedValue(
reply(200, {
...CANARY_ENVELOPE,
assignments: {...CANARY_ENVELOPE.assignments, screen_share_delivery: {enabled: 'yes'}},
}),
);
await vi.advanceTimersByTimeAsync(400_000);
expect(ExperimentAssignments.response).toEqual(CANARY_ENVELOPE);
});
it('accepts an envelope that carries no voice noise suppression assignment', async () => { it('accepts an envelope that carries no voice noise suppression assignment', async () => {
await adopt({poll_interval_seconds: 600, poll_jitter_percent: 0, assignments: {}}); await adopt({poll_interval_seconds: 600, poll_jitter_percent: 0, assignments: {}});
expect(ExperimentAssignments.response.assignments.voice_noise_suppression).toBeUndefined(); expect(ExperimentAssignments.response.assignments.voice_noise_suppression).toBeUndefined();
@@ -2,25 +2,18 @@
import {videoElementHasRenderedFrame} from '@app/features/voice/components/VideoElementFrameState'; import {videoElementHasRenderedFrame} from '@app/features/voice/components/VideoElementFrameState';
import {Store} from '@app/features/voice/engine/Store'; import {Store} from '@app/features/voice/engine/Store';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
export const LAST_FRAME_SNAPSHOTS_MAX = 8; export const LAST_FRAME_SNAPSHOTS_MAX = 8;
export const LAST_FRAME_SNAPSHOT_WIDTH_MAX = 640; export const LAST_FRAME_SNAPSHOT_WIDTH_MAX = 640;
export const LAST_FRAME_SNAPSHOT_JPEG_QUALITY = 0.7; export const LAST_FRAME_SNAPSHOT_JPEG_QUALITY = 0.7;
const LAST_FRAME_SNAPSHOT_LEGACY_WIDTH_MAX = 1280; function computeSnapshotDimensions(sourceWidth: number, sourceHeight: number): {width: number; height: number} {
if (sourceWidth <= LAST_FRAME_SNAPSHOT_WIDTH_MAX) {
function computeSnapshotDimensions(
sourceWidth: number,
sourceHeight: number,
widthMax: number,
): {width: number; height: number} {
if (sourceWidth <= widthMax) {
return {width: sourceWidth, height: sourceHeight}; return {width: sourceWidth, height: sourceHeight};
} }
const scale = widthMax / sourceWidth; const scale = LAST_FRAME_SNAPSHOT_WIDTH_MAX / sourceWidth;
return { return {
width: widthMax, width: LAST_FRAME_SNAPSHOT_WIDTH_MAX,
height: Math.max(1, Math.round(sourceHeight * scale)), height: Math.max(1, Math.round(sourceHeight * scale)),
}; };
} }
@@ -29,12 +22,11 @@ function drawSourceToOffscreenCanvas(
source: CanvasImageSource, source: CanvasImageSource,
sourceWidth: number, sourceWidth: number,
sourceHeight: number, sourceHeight: number,
widthMax: number,
): OffscreenCanvas | null { ): OffscreenCanvas | null {
if (typeof OffscreenCanvas === 'undefined') return null; if (typeof OffscreenCanvas === 'undefined') return null;
if (sourceWidth <= 0 || sourceHeight <= 0) return null; if (sourceWidth <= 0 || sourceHeight <= 0) return null;
try { try {
const {width, height} = computeSnapshotDimensions(sourceWidth, sourceHeight, widthMax); const {width, height} = computeSnapshotDimensions(sourceWidth, sourceHeight);
const canvas = new OffscreenCanvas(width, height); const canvas = new OffscreenCanvas(width, height);
const context = canvas.getContext('2d'); const context = canvas.getContext('2d');
if (!context) return null; if (!context) return null;
@@ -45,16 +37,11 @@ function drawSourceToOffscreenCanvas(
} }
} }
function drawSourceToDataUrl( function drawSourceToDataUrl(source: CanvasImageSource, sourceWidth: number, sourceHeight: number): string | null {
source: CanvasImageSource,
sourceWidth: number,
sourceHeight: number,
widthMax: number,
): string | null {
if (typeof document === 'undefined') return null; if (typeof document === 'undefined') return null;
if (sourceWidth <= 0 || sourceHeight <= 0) return null; if (sourceWidth <= 0 || sourceHeight <= 0) return null;
try { try {
const {width, height} = computeSnapshotDimensions(sourceWidth, sourceHeight, widthMax); const {width, height} = computeSnapshotDimensions(sourceWidth, sourceHeight);
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
@@ -114,38 +101,29 @@ class LastFrameSnapshotCache extends Store {
captureFromVideoElement(key: string, video: HTMLVideoElement | null): void { captureFromVideoElement(key: string, video: HTMLVideoElement | null): void {
if (!key) return; if (!key) return;
const deliveryEnabled = ScreenShareDeliveryRollout.enabled; if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
if (deliveryEnabled && typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
if (!videoElementHasRenderedFrame(video)) return; if (!videoElementHasRenderedFrame(video)) return;
const renderedVideo = video as HTMLVideoElement; const renderedVideo = video as HTMLVideoElement;
const widthMax = deliveryEnabled ? LAST_FRAME_SNAPSHOT_WIDTH_MAX : LAST_FRAME_SNAPSHOT_LEGACY_WIDTH_MAX; const captureId = this.nextCaptureId;
if (deliveryEnabled) { this.nextCaptureId += 1;
const captureId = this.nextCaptureId; this.captureIds.set(key, captureId);
this.nextCaptureId += 1; const canvas = drawSourceToOffscreenCanvas(renderedVideo, renderedVideo.videoWidth, renderedVideo.videoHeight);
this.captureIds.set(key, captureId); if (canvas) {
const canvas = drawSourceToOffscreenCanvas( void canvas
renderedVideo, .convertToBlob({type: 'image/jpeg', quality: LAST_FRAME_SNAPSHOT_JPEG_QUALITY})
renderedVideo.videoWidth, .then((blob) => {
renderedVideo.videoHeight, if (this.captureIds.get(key) !== captureId) return;
widthMax, this.captureIds.delete(key);
); this.retainSnapshot(key, URL.createObjectURL(blob));
if (canvas) { })
void canvas .catch(() => {
.convertToBlob({type: 'image/jpeg', quality: LAST_FRAME_SNAPSHOT_JPEG_QUALITY}) if (this.captureIds.get(key) !== captureId) return;
.then((blob) => { this.captureIds.delete(key);
if (this.captureIds.get(key) !== captureId) return; });
this.captureIds.delete(key); return;
this.retainSnapshot(key, URL.createObjectURL(blob));
})
.catch(() => {
if (this.captureIds.get(key) !== captureId) return;
this.captureIds.delete(key);
});
return;
}
this.captureIds.delete(key);
} }
const dataUrl = drawSourceToDataUrl(renderedVideo, renderedVideo.videoWidth, renderedVideo.videoHeight, widthMax); this.captureIds.delete(key);
const dataUrl = drawSourceToDataUrl(renderedVideo, renderedVideo.videoWidth, renderedVideo.videoHeight);
if (!dataUrl) return; if (!dataUrl) return;
this.retainSnapshot(key, dataUrl); this.retainSnapshot(key, dataUrl);
} }
@@ -11,7 +11,6 @@ import {
getLocalScreenShareVideoPublications, getLocalScreenShareVideoPublications,
isLiveLocalTrackPublication, isLiveLocalTrackPublication,
} from '@app/features/voice/engine/VoiceTrackPublicationUtils'; } from '@app/features/voice/engine/VoiceTrackPublicationUtils';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import VoiceSettings from '@app/features/voice/state/VoiceSettings'; import VoiceSettings from '@app/features/voice/state/VoiceSettings';
import { import {
buildScreenShareCodecProfile, buildScreenShareCodecProfile,
@@ -357,7 +356,6 @@ class ScreenShareCodecNegotiation {
encoderMode: ScreenShareEncoderMode; encoderMode: ScreenShareEncoderMode;
order: ReadonlyArray<VideoCodec>; order: ReadonlyArray<VideoCodec>;
} | null = null; } | null = null;
private frozenDelivery: {trackSid: string; enabled: boolean} | null = null;
private lastCodecChangeAt = 0; private lastCodecChangeAt = 0;
private suppressionTimer: ReturnType<typeof setTimeout> | null = null; private suppressionTimer: ReturnType<typeof setTimeout> | null = null;
private publishedTrackSid: string | null = null; private publishedTrackSid: string | null = null;
@@ -452,22 +450,9 @@ class ScreenShareCodecNegotiation {
this.localCodecs = buildLocalCodecAdvertisements(this.resolveCodecPreferenceOrder()); this.localCodecs = buildLocalCodecAdvertisements(this.resolveCodecPreferenceOrder());
} }
private resolveScreenShareDelivery(room: Room | null = this.room): boolean {
const trackSid = this.getLocalScreenSharePublication(room)?.trackSid ?? null;
if (trackSid === null) {
this.frozenDelivery = null;
return ScreenShareDeliveryRollout.enabled;
}
if (this.frozenDelivery?.trackSid === trackSid) return this.frozenDelivery.enabled;
const enabled = ScreenShareDeliveryRollout.enabled;
this.frozenDelivery = {trackSid, enabled};
return enabled;
}
private resolveCodecPreferenceOrder( private resolveCodecPreferenceOrder(
preference: CodecPreference = VoiceSettings.getPreferredScreenShareCodec(), preference: CodecPreference = VoiceSettings.getPreferredScreenShareCodec(),
): ReadonlyArray<VideoCodec> { ): ReadonlyArray<VideoCodec> {
if (!this.resolveScreenShareDelivery()) return getScreenShareCodecPreferenceOrder(preference);
const encoderMode = VoiceSettings.getScreenShareEncoderMode(); const encoderMode = VoiceSettings.getScreenShareEncoderMode();
const frozen = this.frozenCodecPreference; const frozen = this.frozenCodecPreference;
if ( if (
@@ -523,7 +508,7 @@ class ScreenShareCodecNegotiation {
unknownParticipants: number; unknownParticipants: number;
} { } {
const now = Date.now(); const now = Date.now();
const viewerIdentities = this.resolveScreenShareDelivery(room) ? this.getScreenShareViewerIdentities(room) : null; const viewerIdentities = this.getScreenShareViewerIdentities(room);
const knownRemoteCodecs: Array<Array<FluxerCodecAdvertisement>> = []; const knownRemoteCodecs: Array<Array<FluxerCodecAdvertisement>> = [];
const participants: Array<{identity: string; firstSeenAt: number}> = []; const participants: Array<{identity: string; firstSeenAt: number}> = [];
for (const participant of room?.remoteParticipants.values() ?? []) { for (const participant of room?.remoteParticipants.values() ?? []) {
@@ -640,7 +625,6 @@ class ScreenShareCodecNegotiation {
this.selectedCodec = null; this.selectedCodec = null;
this.localCodecs = []; this.localCodecs = [];
this.frozenCodecPreference = null; this.frozenCodecPreference = null;
this.frozenDelivery = null;
this.lastCodecChangeAt = 0; this.lastCodecChangeAt = 0;
this.publishedTrackSid = null; this.publishedTrackSid = null;
this.clearSuppressionTimer(); this.clearSuppressionTimer();
@@ -749,7 +733,6 @@ class ScreenShareCodecNegotiation {
const {knownRemoteCodecs, unknownParticipants} = this.getRemoteCodecInputs(room); const {knownRemoteCodecs, unknownParticipants} = this.getRemoteCodecInputs(room);
this.scheduleGraceReevaluations(room, bindingRevision); this.scheduleGraceReevaluations(room, bindingRevision);
const previousCodec = this.selectedCodec; const previousCodec = this.selectedCodec;
const delivery = this.resolveScreenShareDelivery(room);
this.negotiationSnapshot = transitionScreenShareCodecNegotiationSnapshot(this.negotiationSnapshot, { this.negotiationSnapshot = transitionScreenShareCodecNegotiationSnapshot(this.negotiationSnapshot, {
type: 'negotiation.evaluate', type: 'negotiation.evaluate',
localCodecs: this.localCodecs, localCodecs: this.localCodecs,
@@ -757,7 +740,7 @@ class ScreenShareCodecNegotiation {
unknownParticipants, unknownParticipants,
reason, reason,
codecPreference: this.resolveCodecPreferenceOrder(), codecPreference: this.resolveCodecPreferenceOrder(),
publishedCodec: delivery ? this.observePublishedScreenShareCodec(room) : null, publishedCodec: this.observePublishedScreenShareCodec(room),
}); });
const selection = this.negotiationSnapshot.context.selection; const selection = this.negotiationSnapshot.context.selection;
if (!selection) return null; if (!selection) return null;
@@ -765,11 +748,7 @@ class ScreenShareCodecNegotiation {
this.selectedCodec = selection.codec; this.selectedCodec = selection.codec;
return selection; return selection;
} }
if ( if (previousCodec !== null && Date.now() - this.lastCodecChangeAt < SCREEN_SHARE_CODEC_CHANGE_SUPPRESSION_MS) {
delivery &&
previousCodec !== null &&
Date.now() - this.lastCodecChangeAt < SCREEN_SHARE_CODEC_CHANGE_SUPPRESSION_MS
) {
logger.debug('Suppressed a screen share codec change inside the change window', { logger.debug('Suppressed a screen share codec change inside the change window', {
codec: selection.codec, codec: selection.codec,
previousCodec, previousCodec,
@@ -779,7 +758,7 @@ class ScreenShareCodecNegotiation {
return selection; return selection;
} }
this.selectedCodec = selection.codec; this.selectedCodec = selection.codec;
if (delivery) this.armCodecChangeSuppression(); this.armCodecChangeSuppression();
this.mediaSessionId = createId('media'); this.mediaSessionId = createId('media');
logger.info('Selected screen share codec from XState capability intersection', selection); logger.info('Selected screen share codec from XState capability intersection', selection);
await this.publishSessionUpdate(room, selection); await this.publishSessionUpdate(room, selection);
@@ -9,12 +9,7 @@ import {
type VoiceGatewayStateEvent, type VoiceGatewayStateEvent,
type VoiceGatewayStateSnapshot, type VoiceGatewayStateSnapshot,
} from '@app/features/voice/engine/VoiceGatewayStateMachine'; } from '@app/features/voice/engine/VoiceGatewayStateMachine';
import {describe, expect, it, vi} from 'vitest'; import {describe, expect, it} from 'vitest';
vi.mock('@app/features/voice/state/ScreenShareDeliveryRollout', () => ({
ScreenShareDeliveryRollout: {enabled: false},
default: {enabled: false},
}));
function voiceState(overrides: Partial<VoiceState> = {}): VoiceState { function voiceState(overrides: Partial<VoiceState> = {}): VoiceState {
return { return {
@@ -19,12 +19,7 @@ import {
type VoiceMediaGraphStreamTileState, type VoiceMediaGraphStreamTileState,
} from '@app/features/voice/engine/VoiceMediaGraphTileState'; } from '@app/features/voice/engine/VoiceMediaGraphTileState';
import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource'; import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource';
import {describe, expect, it, vi} from 'vitest'; import {describe, expect, it} from 'vitest';
vi.mock('@app/features/voice/state/ScreenShareDeliveryRollout', () => ({
ScreenShareDeliveryRollout: {enabled: false},
default: {enabled: false},
}));
const SIMULATION_SEEDS: ReadonlyArray<number> = Array.from({length: 20}, (_, index) => 1 + index * 7919); const SIMULATION_SEEDS: ReadonlyArray<number> = Array.from({length: 20}, (_, index) => 1 + index * 7919);
const SIMULATION_STEP_COUNT = 1000; const SIMULATION_STEP_COUNT = 1000;
@@ -11,7 +11,6 @@ import {
voiceMediaGraphStatsTrackKey, voiceMediaGraphStatsTrackKey,
} from '@app/features/voice/engine/VoiceMediaGraphStatsObservations'; } from '@app/features/voice/engine/VoiceMediaGraphStatsObservations';
import type {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource'; import type {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import type {VoiceEngineV2PerTrackStats} from '@fluxer/voice_engine_v2'; import type {VoiceEngineV2PerTrackStats} from '@fluxer/voice_engine_v2';
const VOICE_MEDIA_GRAPH_STATS_TRACK_LIMIT = 1024; const VOICE_MEDIA_GRAPH_STATS_TRACK_LIMIT = 1024;
@@ -77,9 +76,7 @@ function perTrackStatToInfo(track: VoiceEngineV2PerTrackStats): VoiceMediaGraphP
if (track.kind !== 'video') return null; if (track.kind !== 'video') return null;
const width = track.frameWidth ?? track.sourceFrameWidth; const width = track.frameWidth ?? track.sourceFrameWidth;
const height = track.frameHeight ?? track.sourceFrameHeight; const height = track.frameHeight ?? track.sourceFrameHeight;
const fps = ScreenShareDeliveryRollout.enabled const fps = track.effectiveFramesPerSecond ?? track.framesPerSecond;
? (track.effectiveFramesPerSecond ?? track.framesPerSecond)
: (track.effectiveFramesPerSecond ?? track.framesPerSecond ?? track.sourceFramesPerSecond);
const info: VoiceMediaGraphPartialTrackInfo = {}; const info: VoiceMediaGraphPartialTrackInfo = {};
if (isPositiveDimension(width) && isPositiveDimension(height)) { if (isPositiveDimension(width) && isPositiveDimension(height)) {
info.width = width; info.width = width;
@@ -193,9 +190,7 @@ function observationToPartialTrackInfo(
): VoiceMediaGraphPartialTrackInfo | null { ): VoiceMediaGraphPartialTrackInfo | null {
const width = observation.width ?? observation.sourceWidth ?? undefined; const width = observation.width ?? observation.sourceWidth ?? undefined;
const height = observation.height ?? observation.sourceHeight ?? undefined; const height = observation.height ?? observation.sourceHeight ?? undefined;
const fps = ScreenShareDeliveryRollout.enabled const fps = observation.fps ?? undefined;
? (observation.fps ?? undefined)
: (observation.fps ?? observation.sourceFps ?? undefined);
const info: VoiceMediaGraphPartialTrackInfo = {}; const info: VoiceMediaGraphPartialTrackInfo = {};
if (isPositiveDimension(width) && isPositiveDimension(height)) { if (isPositiveDimension(width) && isPositiveDimension(height)) {
info.width = width; info.width = width;
@@ -16,7 +16,6 @@ import {
} from '@app/features/voice/engine/VoiceStreamWatchState'; } from '@app/features/voice/engine/VoiceStreamWatchState';
import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource'; import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource';
import ParticipantVolume from '@app/features/voice/state/ParticipantVolume'; import ParticipantVolume from '@app/features/voice/state/ParticipantVolume';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import {ScreenShareWatchErrorCode, ScreenShareWatchFailures} from '@app/features/voice/state/ScreenShareWatchFailures'; import {ScreenShareWatchErrorCode, ScreenShareWatchFailures} from '@app/features/voice/state/ScreenShareWatchFailures';
import {monitorScreenShareDecodeHealth} from '@app/features/voice/utils/ScreenShareCodecDiagnostics'; import {monitorScreenShareDecodeHealth} from '@app/features/voice/utils/ScreenShareCodecDiagnostics';
import {markScreenShareDecodeFailure} from '@app/features/voice/utils/VideoDecoderCapabilities'; import {markScreenShareDecodeFailure} from '@app/features/voice/utils/VideoDecoderCapabilities';
@@ -108,30 +107,6 @@ interface ParticipantSpeakingDisposer {
dispose: () => void; dispose: () => void;
} }
type LatencyTunedReceiver = RTCRtpReceiver & {
jitterBufferTarget?: number;
playoutDelayHint?: number;
};
function getRemoteTrackReceiver(track: RemoteTrack): LatencyTunedReceiver | undefined {
return (track as RemoteTrack & {receiver?: LatencyTunedReceiver}).receiver;
}
function applyInteractiveReceiverBuffer(track: RemoteTrack, pub: RemoteTrackPublication): void {
const receiver = getRemoteTrackReceiver(track);
if (!receiver) return;
try {
if (pub.kind === Track.Kind.Video && pub.source === Track.Source.ScreenShare) {
receiver.jitterBufferTarget = 80;
receiver.playoutDelayHint = 0.04;
} else if (pub.kind === Track.Kind.Audio) {
receiver.jitterBufferTarget = 60;
}
} catch (error) {
logger.debug('Failed to apply interactive receiver buffer target', {error, source: pub.source, kind: pub.kind});
}
}
export function bindRoomEvents( export function bindRoomEvents(
room: Room, room: Room,
attemptId: number, attemptId: number,
@@ -141,7 +116,6 @@ export function bindRoomEvents(
dependencies: RoomEventDependencies, dependencies: RoomEventDependencies,
): void { ): void {
const guard = dependencies.connection.createGuardedHandler; const guard = dependencies.connection.createGuardedHandler;
const screenShareDeliveryEnabled = ScreenShareDeliveryRollout.enabled;
const participantSpeakingDisposers = new Map<string, ParticipantSpeakingDisposer>(); const participantSpeakingDisposers = new Map<string, ParticipantSpeakingDisposer>();
const screenShareDecodeMonitorCancels = new Map<string, () => void>(); const screenShareDecodeMonitorCancels = new Map<string, () => void>();
const remoteTrackLifecycleDisposers = new Map<string, () => void>(); const remoteTrackLifecycleDisposers = new Map<string, () => void>();
@@ -443,9 +417,6 @@ export function bindRoomEvents(
trackSid: pub.trackSid, trackSid: pub.trackSid,
}); });
} }
if (!screenShareDeliveryEnabled) {
applyInteractiveReceiverBuffer(track, pub);
}
monitorDecodeHealth(track, pub); monitorDecodeHealth(track, pub);
dependencies.remoteSpeaking.attachIfApplicable(participant, pub, track); dependencies.remoteSpeaking.attachIfApplicable(participant, pub, track);
bindRemoteTrackLifecycleIfApplicable(participant, pub, track); bindRemoteTrackLifecycleIfApplicable(participant, pub, track);
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import {isElectronPlatform} from '@app/features/platform/types/Platform';
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
import {Store} from '@app/features/voice/engine/Store'; import {Store} from '@app/features/voice/engine/Store';
import {sendVoiceStateDisconnect} from '@app/features/voice/engine/VoiceChannelConnector'; import {sendVoiceStateDisconnect} from '@app/features/voice/engine/VoiceChannelConnector';
@@ -37,7 +36,6 @@ import {
isReadyToRepublishTrack, isReadyToRepublishTrack,
} from '@app/features/voice/engine/v2/VoiceEngineV2AppAdapterAssertions'; } from '@app/features/voice/engine/v2/VoiceEngineV2AppAdapterAssertions';
import {VoiceEngineV2AppReconnectPolicy} from '@app/features/voice/engine/v2/VoiceEngineV2AppReconnectPolicy'; import {VoiceEngineV2AppReconnectPolicy} from '@app/features/voice/engine/v2/VoiceEngineV2AppReconnectPolicy';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import VoiceRegionTeleport from '@app/features/voice/state/VoiceRegionTeleport'; import VoiceRegionTeleport from '@app/features/voice/state/VoiceRegionTeleport';
import { import {
findVideoPublishCodecPolicyViolation, findVideoPublishCodecPolicyViolation,
@@ -45,10 +43,7 @@ import {
} from '@app/features/voice/utils/CodecCapabilityDetector'; } from '@app/features/voice/utils/CodecCapabilityDetector';
import {getH264HardwareProfilesSync} from '@app/features/voice/utils/GpuEncoderCapabilities'; import {getH264HardwareProfilesSync} from '@app/features/voice/utils/GpuEncoderCapabilities';
import {setNoiseSuppressionScopeGuildId} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionSelection'; import {setNoiseSuppressionScopeGuildId} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionSelection';
import { import {SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS} from '@app/features/voice/utils/ScreenShareOptions';
SCREEN_SHARE_DELIVERY_MAX_VIDEO_BITRATE_BPS,
SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS,
} from '@app/features/voice/utils/ScreenShareOptions';
import { import {
clearScreenShareDecodeFailures, clearScreenShareDecodeFailures,
getVideoDecoderExclusionsSync, getVideoDecoderExclusionsSync,
@@ -151,9 +146,7 @@ function createWebAudioMixOption(): RoomOptions['webAudioMix'] {
function createRoomPublishDefaults(): RoomOptions['publishDefaults'] { function createRoomPublishDefaults(): RoomOptions['publishDefaults'] {
return { return {
screenShareEncoding: { screenShareEncoding: {
maxBitrate: ScreenShareDeliveryRollout.enabled maxBitrate: SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS,
? SCREEN_SHARE_DELIVERY_MAX_VIDEO_BITRATE_BPS
: SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS,
maxFramerate: 30, maxFramerate: 30,
priority: 'high', priority: 'high',
}, },
@@ -164,7 +157,6 @@ function createRoomPublishDefaults(): RoomOptions['publishDefaults'] {
function createRoomOptions( function createRoomOptions(
e2eeKey: string | null, e2eeKey: string | null,
subscriberVideoCodecExclusions: RoomOptions['subscriberVideoCodecExclusions'], subscriberVideoCodecExclusions: RoomOptions['subscriberVideoCodecExclusions'],
screenShareDelivery: boolean,
): { ): {
roomOptions: RoomOptions; roomOptions: RoomOptions;
e2eeKeyProvider: ExternalE2EEKeyProvider | null; e2eeKeyProvider: ExternalE2EEKeyProvider | null;
@@ -176,8 +168,7 @@ function createRoomOptions(
webAudioMix: createWebAudioMixOption(), webAudioMix: createWebAudioMixOption(),
publishDefaults: createRoomPublishDefaults(), publishDefaults: createRoomPublishDefaults(),
subscriberVideoCodecExclusions, subscriberVideoCodecExclusions,
screenShareDelivery, h264HardwareProfiles: getH264HardwareProfilesSync()?.profiles,
h264HardwareProfiles: screenShareDelivery ? getH264HardwareProfilesSync()?.profiles : undefined,
}; };
let e2eeKeyProvider: ExternalE2EEKeyProvider | null = null; let e2eeKeyProvider: ExternalE2EEKeyProvider | null = null;
let e2eeWorker: Worker | null = null; let e2eeWorker: Worker | null = null;
@@ -196,19 +187,11 @@ function createRoomOptions(
return {roomOptions, e2eeKeyProvider, e2eeWorker}; return {roomOptions, e2eeKeyProvider, e2eeWorker};
} }
function createRoomConnectOptions(screenShareDelivery: boolean): RoomConnectOptions { function createRoomConnectOptions(): RoomConnectOptions {
const connectOptions: RoomConnectOptions = { const connectOptions: RoomConnectOptions = {
autoSubscribe: false, autoSubscribe: false,
}; };
assert.equal(connectOptions.autoSubscribe, false, 'LiveKit connect options must not auto-subscribe'); assert.equal(connectOptions.autoSubscribe, false, 'LiveKit connect options must not auto-subscribe');
if (!screenShareDelivery && isElectronPlatform()) {
connectOptions.rtcConfig = {iceTransportPolicy: 'relay'};
assert.equal(
connectOptions.rtcConfig.iceTransportPolicy,
'relay',
'Electron LiveKit connects must force relay ICE',
);
}
return connectOptions; return connectOptions;
} }
@@ -564,12 +547,7 @@ export class VoiceEngineV2AppConnectionHostAdapter extends Store {
logger.warn('Aborting LiveKit room creation after codec probing because attempt is stale', {attemptId}); logger.warn('Aborting LiveKit room creation after codec probing because attempt is stale', {attemptId});
return; return;
} }
const screenShareDelivery = ScreenShareDeliveryRollout.enabled; const {roomOptions, e2eeKeyProvider, e2eeWorker} = createRoomOptions(e2eeKey, subscriberVideoCodecExclusions);
const {roomOptions, e2eeKeyProvider, e2eeWorker} = createRoomOptions(
e2eeKey,
subscriberVideoCodecExclusions,
screenShareDelivery,
);
const room = new LiveKitRoom(roomOptions); const room = new LiveKitRoom(roomOptions);
ownE2EEWorker(room, e2eeWorker); ownE2EEWorker(room, e2eeWorker);
let roomClosed = false; let roomClosed = false;
@@ -602,7 +580,7 @@ export class VoiceEngineV2AppConnectionHostAdapter extends Store {
return; return;
} }
logger.info('Attempting to connect to LiveKit', {endpoint, guildId, channelId: resolvedChannelId}); logger.info('Attempting to connect to LiveKit', {endpoint, guildId, channelId: resolvedChannelId});
const connectOptions = createRoomConnectOptions(screenShareDelivery); const connectOptions = createRoomConnectOptions();
room room
.connect(endpoint, token, connectOptions) .connect(endpoint, token, connectOptions)
.then(() => { .then(() => {
@@ -704,14 +682,12 @@ export class VoiceEngineV2AppConnectionHostAdapter extends Store {
const connectionId = raw.connection_id ?? null; const connectionId = raw.connection_id ?? null;
this.abortHotSwap(); this.abortHotSwap();
const cachedExclusions = getVideoDecoderExclusionsSync(); const cachedExclusions = getVideoDecoderExclusionsSync();
const screenShareDelivery = ScreenShareDeliveryRollout.enabled;
const roomOptions: RoomOptions = { const roomOptions: RoomOptions = {
adaptiveStream: false, adaptiveStream: false,
dynacast: true, dynacast: true,
webAudioMix: createWebAudioMixOption(), webAudioMix: createWebAudioMixOption(),
publishDefaults: createRoomPublishDefaults(), publishDefaults: createRoomPublishDefaults(),
subscriberVideoCodecExclusions: cachedExclusions && cachedExclusions.length > 0 ? cachedExclusions : undefined, subscriberVideoCodecExclusions: cachedExclusions && cachedExclusions.length > 0 ? cachedExclusions : undefined,
screenShareDelivery,
}; };
if (!this.isLatestConnectionAttempt(attemptId) || this.connectionState.room !== existingRoom) { if (!this.isLatestConnectionAttempt(attemptId) || this.connectionState.room !== existingRoom) {
logger.warn('Region hot-swap: aborted before room creation because attempt is stale', {attemptId}); logger.warn('Region hot-swap: aborted before room creation because attempt is stale', {attemptId});
@@ -728,7 +704,7 @@ export class VoiceEngineV2AppConnectionHostAdapter extends Store {
this.abortHotSwap(); this.abortHotSwap();
} }
}); });
const connectOptions = createRoomConnectOptions(screenShareDelivery); const connectOptions = createRoomConnectOptions();
logger.info('Region hot-swap: connecting to new endpoint', {endpoint, guildId, channelId}); logger.info('Region hot-swap: connecting to new endpoint', {endpoint, guildId, channelId});
newRoom newRoom
.connect(endpoint, token, connectOptions) .connect(endpoint, token, connectOptions)
@@ -63,7 +63,6 @@ import ActiveScreenShareSource, {
type PublishedScreenShareSource, type PublishedScreenShareSource,
} from '@app/features/voice/state/ActiveScreenShareSource'; } from '@app/features/voice/state/ActiveScreenShareSource';
import LocalVoiceState from '@app/features/voice/state/LocalVoiceState'; import LocalVoiceState from '@app/features/voice/state/LocalVoiceState';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import VoiceSettings from '@app/features/voice/state/VoiceSettings'; import VoiceSettings from '@app/features/voice/state/VoiceSettings';
import { import {
prepareHighFidelityScreenShareAudioTrack, prepareHighFidelityScreenShareAudioTrack,
@@ -183,7 +182,6 @@ class VoiceEngineV2AppScreenShareExecutionAdapter extends Store {
encoderVerificationTimer: (() => void) | null = null; encoderVerificationTimer: (() => void) | null = null;
private screenShareTrackingHolds = 0; private screenShareTrackingHolds = 0;
private screenShareCapturePaused = false; private screenShareCapturePaused = false;
private screenShareDeliveryArmed = false;
private screenShareTrackingGeneration = 0; private screenShareTrackingGeneration = 0;
private readonly reconciledCodecPairs = new Set<string>(); private readonly reconciledCodecPairs = new Set<string>();
private readonly verifiedCodecCorrectionsByTrack = new WeakMap<MediaStreamTrack, number>(); private readonly verifiedCodecCorrectionsByTrack = new WeakMap<MediaStreamTrack, number>();
@@ -679,7 +677,6 @@ class VoiceEngineV2AppScreenShareExecutionAdapter extends Store {
preferredTrack?: LocalVideoTrack, preferredTrack?: LocalVideoTrack,
): void { ): void {
this.cleanupActiveScreenShareEndListenerInternal(); this.cleanupActiveScreenShareEndListenerInternal();
this.screenShareDeliveryArmed = ScreenShareDeliveryRollout.enabled;
const publication = preferredTrack ? undefined : participant.getTrackPublication(Track.Source.ScreenShare); const publication = preferredTrack ? undefined : participant.getTrackPublication(Track.Source.ScreenShare);
const videoTrack = preferredTrack ?? publication?.videoTrack; const videoTrack = preferredTrack ?? publication?.videoTrack;
const mediaStreamTrack = videoTrack?.mediaStreamTrack; const mediaStreamTrack = videoTrack?.mediaStreamTrack;
@@ -730,15 +727,11 @@ class VoiceEngineV2AppScreenShareExecutionAdapter extends Store {
}; };
const onMuted = (): void => reportCapturePaused(true); const onMuted = (): void => reportCapturePaused(true);
const onUnmuted = (): void => reportCapturePaused(false); const onUnmuted = (): void => reportCapturePaused(false);
const tracksCapturePauses = this.screenShareDeliveryArmed;
mediaStreamTrack.addEventListener('ended', onEnded); mediaStreamTrack.addEventListener('ended', onEnded);
if (tracksCapturePauses) { mediaStreamTrack.addEventListener('mute', onMuted);
mediaStreamTrack.addEventListener('mute', onMuted); mediaStreamTrack.addEventListener('unmute', onUnmuted);
mediaStreamTrack.addEventListener('unmute', onUnmuted);
}
this.activeScreenShareEndListener = () => { this.activeScreenShareEndListener = () => {
mediaStreamTrack.removeEventListener('ended', onEnded); mediaStreamTrack.removeEventListener('ended', onEnded);
if (!tracksCapturePauses) return;
mediaStreamTrack.removeEventListener('mute', onMuted); mediaStreamTrack.removeEventListener('mute', onMuted);
mediaStreamTrack.removeEventListener('unmute', onUnmuted); mediaStreamTrack.removeEventListener('unmute', onUnmuted);
}; };
@@ -1152,7 +1145,7 @@ class VoiceEngineV2AppScreenShareExecutionAdapter extends Store {
nextConstraints.frameRate = {ideal: resolution.frameRate, max: resolution.frameRate}; nextConstraints.frameRate = {ideal: resolution.frameRate, max: resolution.frameRate};
} }
if (JSON.stringify(currentConstraints) === JSON.stringify(nextConstraints)) return 'unchanged'; if (JSON.stringify(currentConstraints) === JSON.stringify(nextConstraints)) return 'unchanged';
if (this.screenShareDeliveryArmed && screenShareSourceHasClonedTracks(screenShareTrack)) { if (screenShareSourceHasClonedTracks(screenShareTrack)) {
logger.warn('Screen share capture geometry needs a restart while a backup codec holds a clone of the source', { logger.warn('Screen share capture geometry needs a restart while a backup codec holds a clone of the source', {
resolution, resolution,
}); });
@@ -1202,7 +1195,6 @@ class VoiceEngineV2AppScreenShareExecutionAdapter extends Store {
this.ensureScreenShareKeepAliveSinkInternal(participant); this.ensureScreenShareKeepAliveSinkInternal(participant);
updateLocalParticipantFromRoom(room); updateLocalParticipantFromRoom(room);
this.syncLocalScreenShareAudioStateInternal(participant, participant.isScreenShareEnabled); this.syncLocalScreenShareAudioStateInternal(participant, participant.isScreenShareEnabled);
if (!this.screenShareDeliveryArmed) return true;
return geometryUpdate !== 'needs-capture-restart' && geometryUpdate !== 'failed'; return geometryUpdate !== 'needs-capture-restart' && geometryUpdate !== 'failed';
} }
@@ -1225,7 +1217,6 @@ class VoiceEngineV2AppScreenShareExecutionAdapter extends Store {
} }
resetStreamTracking(): void { resetStreamTracking(): void {
this.screenShareDeliveryArmed = false;
this.setScreenShareCapturePausedInternal(false); this.setScreenShareCapturePausedInternal(false);
this.clearScreenShareKeepAliveSinkInternal(); this.clearScreenShareKeepAliveSinkInternal();
this.setStreamingPriorityInternal(false); this.setStreamingPriorityInternal(false);
@@ -32,11 +32,6 @@ const voiceStates: Record<string, Record<string, Record<string, unknown>>> = {};
const settingsUpdate = vi.fn(); const settingsUpdate = vi.fn();
const openPremiumModal = vi.fn(); const openPremiumModal = vi.fn();
vi.mock('@app/features/voice/state/ScreenShareDeliveryRollout', () => ({
ScreenShareDeliveryRollout: {enabled: true},
default: {enabled: true},
}));
vi.mock('@app/features/voice/utils/GpuEncoderCapabilities', () => ({ vi.mock('@app/features/voice/utils/GpuEncoderCapabilities', () => ({
getGpuEncoderReportSync: () => gpuReport, getGpuEncoderReportSync: () => gpuReport,
getH264HardwareProfilesSync: () => h264HardwareProfiles, getH264HardwareProfilesSync: () => h264HardwareProfiles,
@@ -6,13 +6,11 @@ import {
stopMediaTrack, stopMediaTrack,
stopUnselectedStreamTracks, stopUnselectedStreamTracks,
} from '@app/features/voice/engine/voice_screen_share_manager/shared'; } from '@app/features/voice/engine/voice_screen_share_manager/shared';
import ActiveScreenShareSource from '@app/features/voice/state/ActiveScreenShareSource';
import {rememberCapturedDisplayAudioTrack} from '@app/features/voice/utils/NativeAudioCaptureBridge'; import {rememberCapturedDisplayAudioTrack} from '@app/features/voice/utils/NativeAudioCaptureBridge';
import {ScreenShareAudioCaptureError} from '@app/features/voice/utils/ScreenShareAudioCaptureError'; import {ScreenShareAudioCaptureError} from '@app/features/voice/utils/ScreenShareAudioCaptureError';
import type {ScreenShareCaptureOptions} from 'livekit-client'; import type {ScreenShareCaptureOptions} from 'livekit-client';
type DisplayMediaVideoConstraints = MediaTrackConstraints & { type DisplayMediaVideoConstraints = MediaTrackConstraints & {
colorSpace?: string;
cursor?: 'always' | 'motion' | 'never'; cursor?: 'always' | 'motion' | 'never';
displaySurface?: 'browser' | 'monitor' | 'window'; displaySurface?: 'browser' | 'monitor' | 'window';
}; };
@@ -20,37 +18,12 @@ type DisplayMediaAudioConstraints = MediaTrackConstraints & {
restrictOwnAudio?: boolean; restrictOwnAudio?: boolean;
suppressLocalAudioPlayback?: boolean; suppressLocalAudioPlayback?: boolean;
}; };
type DisplayMediaTrackSettings = MediaTrackSettings & {
cursor?: 'always' | 'motion' | 'never';
displaySurface?: 'browser' | 'monitor' | 'window';
};
function resolveDisplayMediaCursorCapture( function resolveDisplayMediaCursorCapture(
displaySurface: DisplayMediaVideoConstraints['displaySurface'], displaySurface: DisplayMediaVideoConstraints['displaySurface'],
): 'always' | 'motion' | 'never' { ): 'always' | 'motion' | 'never' {
return displaySurface === 'window' ? 'never' : 'always'; return displaySurface === 'window' ? 'never' : 'always';
} }
function getRequestedDisplayMediaVideoConstraints(
options: ScreenShareCaptureOptions | undefined,
): DisplayMediaVideoConstraints | null {
if (typeof options?.video !== 'object' || !options.video) return null;
return options.video as DisplayMediaVideoConstraints;
}
function resolveCapturedDisplayMediaCursorCapture(
track: Pick<MediaStreamTrack, 'getSettings'>,
options?: ScreenShareCaptureOptions,
): 'always' | 'motion' | 'never' {
const requestedVideo = getRequestedDisplayMediaVideoConstraints(options);
const requestedCursor = requestedVideo?.cursor;
const requestedDisplaySurface = requestedVideo?.displaySurface;
if (requestedCursor && requestedCursor !== resolveDisplayMediaCursorCapture(requestedDisplaySurface)) {
return requestedCursor;
}
const settings = track.getSettings() as DisplayMediaTrackSettings;
return resolveDisplayMediaCursorCapture(settings.displaySurface ?? requestedDisplaySurface);
}
export function getDisplayMediaOptions(options?: ScreenShareCaptureOptions): DisplayMediaStreamOptions { export function getDisplayMediaOptions(options?: ScreenShareCaptureOptions): DisplayMediaStreamOptions {
let videoConstraints: MediaTrackConstraints | boolean = options?.video ?? true; let videoConstraints: MediaTrackConstraints | boolean = options?.video ?? true;
const resolution = options?.resolution; const resolution = options?.resolution;
@@ -95,21 +68,6 @@ export function getDisplayMediaOptions(options?: ScreenShareCaptureOptions): Dis
} as DisplayMediaStreamOptions; } as DisplayMediaStreamOptions;
} }
function buildCapturedDisplayMediaConstraints(
displayMediaOptions: DisplayMediaStreamOptions,
cursor: 'always' | 'motion' | 'never',
): MediaTrackConstraints {
const requestedVideo =
typeof displayMediaOptions.video === 'object' && displayMediaOptions.video
? (displayMediaOptions.video as DisplayMediaVideoConstraints)
: undefined;
const constraints: DisplayMediaVideoConstraints = {colorSpace: 'rec709', cursor};
if (requestedVideo?.width !== undefined) constraints.width = requestedVideo.width;
if (requestedVideo?.height !== undefined) constraints.height = requestedVideo.height;
if (requestedVideo?.frameRate !== undefined) constraints.frameRate = requestedVideo.frameRate;
return constraints;
}
export async function createDisplayScreenShareTracks( export async function createDisplayScreenShareTracks(
options?: ScreenShareCaptureOptions, options?: ScreenShareCaptureOptions,
captureContext?: DisplayScreenShareCaptureContext, captureContext?: DisplayScreenShareCaptureContext,
@@ -127,12 +85,6 @@ export async function createDisplayScreenShareTracks(
if (options?.contentHint) { if (options?.contentHint) {
videoTrack.contentHint = options.contentHint; videoTrack.contentHint = options.contentHint;
} }
if (ActiveScreenShareSource.getTarget()?.delivery !== true) {
const cursor = resolveCapturedDisplayMediaCursorCapture(videoTrack, options);
await videoTrack
.applyConstraints(buildCapturedDisplayMediaConstraints(displayMediaOptions, cursor))
.catch(() => undefined);
}
const capturedAudioTrack = stream.getAudioTracks()[0]; const capturedAudioTrack = stream.getAudioTracks()[0];
const audioTrack = capturedAudioTrack?.readyState === 'live' ? capturedAudioTrack : undefined; const audioTrack = capturedAudioTrack?.readyState === 'live' ? capturedAudioTrack : undefined;
rememberCapturedDisplayAudioTrack(capturedAudioTrack, Boolean(capturedAudioTrack) && !audioTrack); rememberCapturedDisplayAudioTrack(capturedAudioTrack, Boolean(capturedAudioTrack) && !audioTrack);
@@ -4,7 +4,6 @@ import {getDesktopTroubleshootingSettings} from '@app/features/devtools/utils/De
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
import ScreenShareCodecNegotiation from '@app/features/voice/engine/ScreenShareCodecNegotiation'; import ScreenShareCodecNegotiation from '@app/features/voice/engine/ScreenShareCodecNegotiation';
import ActiveScreenShareSource from '@app/features/voice/state/ActiveScreenShareSource'; import ActiveScreenShareSource from '@app/features/voice/state/ActiveScreenShareSource';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import SoftwareEncoderWarning from '@app/features/voice/state/SoftwareEncoderWarning'; import SoftwareEncoderWarning from '@app/features/voice/state/SoftwareEncoderWarning';
import VoiceSettings from '@app/features/voice/state/VoiceSettings'; import VoiceSettings from '@app/features/voice/state/VoiceSettings';
import { import {
@@ -23,7 +22,6 @@ import {
resolveScreenShareLayering, resolveScreenShareLayering,
resolveScreenShareSenderCodec, resolveScreenShareSenderCodec,
resolveScreenShareTarget, resolveScreenShareTarget,
SCREEN_SHARE_DELIVERY_MAX_VIDEO_BITRATE_BPS,
SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS, SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS,
type ScreenShareContext, type ScreenShareContext,
type ScreenShareLayering, type ScreenShareLayering,
@@ -195,11 +193,13 @@ export async function releaseScreenShareCaptureCleanup(snapshot: ScreenShareCapt
} }
} }
function clampScreenShareEncoding(encoding: VideoEncoding, delivery: boolean): VideoEncoding { function clampScreenShareEncoding(encoding: VideoEncoding): VideoEncoding {
const ceiling = delivery ? SCREEN_SHARE_DELIVERY_MAX_VIDEO_BITRATE_BPS : SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS;
return { return {
...encoding, ...encoding,
maxBitrate: typeof encoding.maxBitrate === 'number' ? Math.min(encoding.maxBitrate, ceiling) : encoding.maxBitrate, maxBitrate:
typeof encoding.maxBitrate === 'number'
? Math.min(encoding.maxBitrate, SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS)
: encoding.maxBitrate,
priority: encoding.priority ?? 'high', priority: encoding.priority ?? 'high',
}; };
} }
@@ -208,15 +208,10 @@ export function resolveActiveScreenShareContext(): ScreenShareContext {
return ActiveScreenShareSource.getShareContext() ?? 'display'; return ActiveScreenShareSource.getShareContext() ?? 'display';
} }
function resolveScreenShareDeliveryArm(): boolean {
return ActiveScreenShareSource.getTarget()?.delivery ?? ScreenShareDeliveryRollout.enabled;
}
export function resolveConfiguredScreenShareTarget( export function resolveConfiguredScreenShareTarget(
context: ScreenShareContext, context: ScreenShareContext,
sourceDimensions: {width: number; height: number} | null, sourceDimensions: {width: number; height: number} | null,
): ScreenShareTarget { ): ScreenShareTarget {
const delivery = resolveScreenShareDeliveryArm();
return resolveScreenShareTarget({ return resolveScreenShareTarget({
mode: VoiceSettings.getStreamingMode(), mode: VoiceSettings.getStreamingMode(),
storedResolution: VoiceSettings.getScreenshareResolution(), storedResolution: VoiceSettings.getScreenshareResolution(),
@@ -225,13 +220,8 @@ export function resolveConfiguredScreenShareTarget(
context, context,
sourceDimensions, sourceDimensions,
hintSetting: VoiceSettings.getScreenShareContentHint(), hintSetting: VoiceSettings.getScreenShareContentHint(),
delivery, codec: getPreferredScreenShareCodec(),
...(delivery softwareEncoderClamp: ActiveScreenShareSource.isSoftwareEncoderClamped(),
? {
codec: getPreferredScreenShareCodec(),
softwareEncoderClamp: ActiveScreenShareSource.isSoftwareEncoderClamped(),
}
: {}),
}); });
} }
@@ -242,7 +232,6 @@ export function resolveActiveScreenShareTarget(
} }
function recommitClampedScreenShareTarget(committed: ScreenShareTarget): ScreenShareTarget { function recommitClampedScreenShareTarget(committed: ScreenShareTarget): ScreenShareTarget {
if (committed.delivery !== true) return committed;
if (committed.softwareEncoderClamped) return committed; if (committed.softwareEncoderClamped) return committed;
const resolved = resolveActiveScreenShareTarget(committed.context); const resolved = resolveActiveScreenShareTarget(committed.context);
if (!resolved.softwareEncoderClamped) return committed; if (!resolved.softwareEncoderClamped) return committed;
@@ -270,7 +259,7 @@ export function getStatsKind(
function resolveScreenShareEncoding(target: ScreenShareTarget, publishOptions?: TrackPublishOptions): VideoEncoding { function resolveScreenShareEncoding(target: ScreenShareTarget, publishOptions?: TrackPublishOptions): VideoEncoding {
if (publishOptions?.screenShareEncoding) { if (publishOptions?.screenShareEncoding) {
return clampScreenShareEncoding(publishOptions.screenShareEncoding, target.delivery === true); return clampScreenShareEncoding(publishOptions.screenShareEncoding);
} }
return {maxBitrate: target.maxBitrate, maxFramerate: target.frameRate, priority: 'high'}; return {maxBitrate: target.maxBitrate, maxFramerate: target.frameRate, priority: 'high'};
} }
@@ -863,27 +852,22 @@ export function startScreenShareEncoderMonitor(options: ScreenShareEncoderMonito
let framesEncoded: number | null = null; let framesEncoded: number | null = null;
let sendSnapshot: ScreenShareSendSnapshot | null = null; let sendSnapshot: ScreenShareSendSnapshot | null = null;
let cpuLimitedTicks = 0; let cpuLimitedTicks = 0;
const delivery = ActiveScreenShareSource.getTarget()?.delivery === true;
const tick = async (): Promise<void> => { const tick = async (): Promise<void> => {
const sender = options.track.sender; const sender = options.track.sender;
if (!sender) return; if (!sender) return;
const stats = await sender.getStats(); const stats = await sender.getStats();
if (delivery) { syncScreenShareCaptureSize(sender);
syncScreenShareCaptureSize(sender);
}
const encoded = countEncodedVideoFrames(stats); const encoded = countEncodedVideoFrames(stats);
ActiveScreenShareSource.setEncoding(encoded !== null && framesEncoded !== null && encoded > framesEncoded); ActiveScreenShareSource.setEncoding(encoded !== null && framesEncoded !== null && encoded > framesEncoded);
framesEncoded = encoded; framesEncoded = encoded;
if (delivery) { const snapshot = collectScreenShareSendSnapshot(stats);
const snapshot = collectScreenShareSendSnapshot(stats); cpuLimitedTicks = snapshot.cpuLimited ? cpuLimitedTicks + 1 : 0;
cpuLimitedTicks = snapshot.cpuLimited ? cpuLimitedTicks + 1 : 0; const limit = classifyScreenShareSendLimit(sendSnapshot, snapshot, cpuLimitedTicks);
const limit = classifyScreenShareSendLimit(sendSnapshot, snapshot, cpuLimitedTicks); sendSnapshot = snapshot;
sendSnapshot = snapshot; if (limit !== ActiveScreenShareSource.getLimit()) {
if (limit !== ActiveScreenShareSource.getLimit()) { logger.info('Screen share send limit changed', {limit, codec: options.codec});
logger.info('Screen share send limit changed', {limit, codec: options.codec});
}
ActiveScreenShareSource.setLimit(limit);
} }
ActiveScreenShareSource.setLimit(limit);
if (!verified) { if (!verified) {
const verification = verifyScreenShareEncoderStart(stats, options.codec, options.onEncodeFailure); const verification = verifyScreenShareEncoderStart(stats, options.codec, options.onEncodeFailure);
if (verification === 'failed') return; if (verification === 'failed') return;
@@ -907,8 +891,6 @@ export function startScreenShareEncoderMonitor(options: ScreenShareEncoderMonito
cancelled = true; cancelled = true;
clearTimeout(timer); clearTimeout(timer);
ActiveScreenShareSource.setEncoding(false); ActiveScreenShareSource.setEncoding(false);
if (delivery) { ActiveScreenShareSource.setLimit(null);
ActiveScreenShareSource.setLimit(null);
}
}; };
} }
@@ -70,11 +70,6 @@ class ActiveScreenShareSource {
this.encoding = false; this.encoding = false;
return; return;
} }
if (target.delivery !== true) {
this.target = target;
this.encoding = false;
return;
}
const degradationPreference = this.frozenDegradationPreference ?? target.degradationPreference; const degradationPreference = this.frozenDegradationPreference ?? target.degradationPreference;
this.frozenDegradationPreference = degradationPreference; this.frozenDegradationPreference = degradationPreference;
this.softwareEncoderClamped = this.softwareEncoderClamped || target.softwareEncoderClamped; this.softwareEncoderClamped = this.softwareEncoderClamped || target.softwareEncoderClamped;
@@ -1,22 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import ExperimentAssignments from '@app/features/experiment/state/ExperimentAssignments';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {readScreenShareDeliveryAssignment} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
const logger = new Logger('ScreenShareDeliveryRollout');
class ScreenShareDeliveryRolloutSelector {
get enabled(): boolean {
try {
return readScreenShareDeliveryAssignment(ExperimentAssignments.response).enabled;
} catch (err) {
logger.warn('Failed to resolve screen share delivery assignment:', err);
return false;
}
}
}
export const ScreenShareDeliveryRollout = new ScreenShareDeliveryRolloutSelector();
export default ScreenShareDeliveryRollout;
@@ -3,7 +3,6 @@
import AppStorage from '@app/features/platform/state/PersistentStorage'; import AppStorage from '@app/features/platform/state/PersistentStorage';
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
import {makePersistent} from '@app/features/platform/utils/MobXPersistence'; import {makePersistent} from '@app/features/platform/utils/MobXPersistence';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import type { import type {
CodecPreference, CodecPreference,
ScreenShareContentHint, ScreenShareContentHint,
@@ -263,9 +262,6 @@ function applyStreamingModeDefaultMigrationV1(parsed: Record<string, unknown>):
} }
function applyScreenShareFrameRateFloorMigrationV1(parsed: Record<string, unknown>): boolean { function applyScreenShareFrameRateFloorMigrationV1(parsed: Record<string, unknown>): boolean {
if (!ScreenShareDeliveryRollout.enabled) {
return false;
}
if (parsed.screenShareFrameRateFloorMigratedV1 === true) { if (parsed.screenShareFrameRateFloorMigratedV1 === true) {
return false; return false;
} }
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource'; import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import type {TrackPublishOptions} from 'livekit-client'; import type {TrackPublishOptions} from 'livekit-client';
export const OPUS_MAX_AUDIO_BITRATE_BPS = 510000; export const OPUS_MAX_AUDIO_BITRATE_BPS = 510000;
@@ -40,9 +39,7 @@ export function buildMicrophonePublishOptions(
export const SCREEN_SHARE_AUDIO_PUBLISH_OPTIONS: TrackPublishOptions = { export const SCREEN_SHARE_AUDIO_PUBLISH_OPTIONS: TrackPublishOptions = {
audioPreset: { audioPreset: {
get maxBitrate(): number { maxBitrate: SCREEN_SHARE_AUDIO_BITRATE_BPS,
return ScreenShareDeliveryRollout.enabled ? SCREEN_SHARE_AUDIO_BITRATE_BPS : OPUS_MAX_AUDIO_BITRATE_BPS;
},
priority: 'high', priority: 'high',
}, },
dtx: false, dtx: false,
@@ -9,7 +9,6 @@ import {
isFirefoxBrowser, isFirefoxBrowser,
type NativePlatform, type NativePlatform,
} from '@app/features/ui/utils/NativeUtils'; } from '@app/features/ui/utils/NativeUtils';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import VoiceSettings from '@app/features/voice/state/VoiceSettings'; import VoiceSettings from '@app/features/voice/state/VoiceSettings';
import {getGpuEncoderReportSync, type HardwareEncodeAnswer} from '@app/features/voice/utils/GpuEncoderCapabilities'; import {getGpuEncoderReportSync, type HardwareEncodeAnswer} from '@app/features/voice/utils/GpuEncoderCapabilities';
import { import {
@@ -26,10 +25,7 @@ import {
type ScreenShareCodecRanking, type ScreenShareCodecRanking,
} from '@app/features/voice/utils/ScreenShareCodecSelection'; } from '@app/features/voice/utils/ScreenShareCodecSelection';
import {normaliseStreamingModeForContext} from '@app/features/voice/utils/ScreenShareOptions'; import {normaliseStreamingModeForContext} from '@app/features/voice/utils/ScreenShareOptions';
import { import {getProbedVideoDecoderExclusionsSync} from '@app/features/voice/utils/VideoDecoderCapabilities';
getProbedVideoDecoderExclusionsSync,
getVideoDecoderExclusionsSync,
} from '@app/features/voice/utils/VideoDecoderCapabilities';
import type {TrackPublishDefaults, TrackPublishOptions} from 'livekit-client'; import type {TrackPublishDefaults, TrackPublishOptions} from 'livekit-client';
import {BackupCodecPolicy, supportsVideoCodec, type VideoCodec, type VideoEncoding} from 'livekit-client'; import {BackupCodecPolicy, supportsVideoCodec, type VideoCodec, type VideoEncoding} from 'livekit-client';
@@ -98,7 +94,6 @@ let cachedReportNativeHardwareEncoderKey: object | null | undefined;
let cachedReportHardwareAccelerationDisabled: boolean | undefined; let cachedReportHardwareAccelerationDisabled: boolean | undefined;
let cachedReportAv1OptIn: boolean | undefined; let cachedReportAv1OptIn: boolean | undefined;
let cachedReportHevcOptIn: boolean | undefined; let cachedReportHevcOptIn: boolean | undefined;
let cachedReportDelivery: boolean | undefined;
const runtimeEncodeFailureCodecs = new Set<VideoCodec>(); const runtimeEncodeFailureCodecs = new Set<VideoCodec>();
const observedSoftwareEncodeCodecs = new Set<VideoCodec>(); const observedSoftwareEncodeCodecs = new Set<VideoCodec>();
@@ -214,21 +209,15 @@ function getScreenShareCodecPolicyUnsupported(
return null; return null;
} }
function hasPublishPathNativeHardwareEncoder( function hasPublishPathNativeHardwareEncoder(codec: VideoCodec, context: CodecPolicyContext): boolean {
codec: VideoCodec,
context: CodecPolicyContext,
delivery: boolean,
): boolean {
if (!hasNativeHardwareEncoder(codec)) return false; if (!hasNativeHardwareEncoder(codec)) return false;
const backend = getNativeHardwareEncoderCapabilitiesSync()?.backend; const backend = getNativeHardwareEncoderCapabilitiesSync()?.backend;
if (!delivery) return backend !== 'videotoolbox' && backend !== 'nvenc';
if (backend === 'videotoolbox') return true; if (backend === 'videotoolbox') return true;
if (backend === 'nvenc') return context.platform !== 'linux'; if (backend === 'nvenc') return context.platform !== 'linux';
return false; return false;
} }
function buildReport(): CodecCapabilityReport { function buildReport(): CodecCapabilityReport {
const delivery = ScreenShareDeliveryRollout.enabled;
const {caps, probedSuccessfully} = probeRawCapabilities(); const {caps, probedSuccessfully} = probeRawCapabilities();
const context = buildScreenShareCodecPolicyContext(); const context = buildScreenShareCodecPolicyContext();
const gpuReport = getGpuEncoderReportSync(); const gpuReport = getGpuEncoderReportSync();
@@ -243,17 +232,11 @@ function buildReport(): CodecCapabilityReport {
if (observedSoftwareEncodeCodecs.has(codec)) { if (observedSoftwareEncodeCodecs.has(codec)) {
return 'software'; return 'software';
} }
if (!delivery) {
if (hasPublishPathNativeHardwareEncoder(codec, context, delivery)) {
return 'hardware';
}
return gpuReport ? gpuReport[codec] : 'unknown';
}
const measured = gpuReport ? gpuReport[codec] : 'unknown'; const measured = gpuReport ? gpuReport[codec] : 'unknown';
if (measured !== 'unknown') { if (measured !== 'unknown') {
return measured; return measured;
} }
return hasPublishPathNativeHardwareEncoder(codec, context, delivery) ? 'hardware' : 'unknown'; return hasPublishPathNativeHardwareEncoder(codec, context) ? 'hardware' : 'unknown';
} }
type DescribedUnsupported = Omit<CodecSupportInfo, 'hardwareAccelerated'>; type DescribedUnsupported = Omit<CodecSupportInfo, 'hardwareAccelerated'>;
function unsupported(codec: keyof CodecCapabilities, info: DescribedUnsupported): CodecSupportInfo { function unsupported(codec: keyof CodecCapabilities, info: DescribedUnsupported): CodecSupportInfo {
@@ -271,8 +254,8 @@ function buildReport(): CodecCapabilityReport {
detail: 'This codec failed while publishing during the current session.', detail: 'This codec failed while publishing during the current session.',
}); });
} }
const supportedByNativeHardware = hasPublishPathNativeHardwareEncoder(codec, context, delivery); const supportedByNativeHardware = hasPublishPathNativeHardwareEncoder(codec, context);
if (caps[codec] || (!delivery && supportedByNativeHardware)) { if (caps[codec]) {
return { return {
supported: true, supported: true,
reason: 'supported', reason: 'supported',
@@ -348,10 +331,8 @@ export function getCodecCapabilityReport(): CodecCapabilityReport {
const hardwareAccelerationDisabled = isDesktopHardwareAccelerationDisabled(); const hardwareAccelerationDisabled = isDesktopHardwareAccelerationDisabled();
const av1OptIn = VoiceSettings.getScreenShareAv1OptIn(); const av1OptIn = VoiceSettings.getScreenShareAv1OptIn();
const hevcOptIn = VoiceSettings.getScreenShareHevcOptIn(); const hevcOptIn = VoiceSettings.getScreenShareHevcOptIn();
const delivery = ScreenShareDeliveryRollout.enabled;
if ( if (
cachedReport && cachedReport &&
cachedReportDelivery === delivery &&
cachedReportGpuKey === currentGpu && cachedReportGpuKey === currentGpu &&
cachedReportNativeHardwareEncoderKey === currentNativeHardwareEncoder && cachedReportNativeHardwareEncoderKey === currentNativeHardwareEncoder &&
cachedReportHardwareAccelerationDisabled === hardwareAccelerationDisabled && cachedReportHardwareAccelerationDisabled === hardwareAccelerationDisabled &&
@@ -365,7 +346,6 @@ export function getCodecCapabilityReport(): CodecCapabilityReport {
cachedReportHardwareAccelerationDisabled = hardwareAccelerationDisabled; cachedReportHardwareAccelerationDisabled = hardwareAccelerationDisabled;
cachedReportAv1OptIn = av1OptIn; cachedReportAv1OptIn = av1OptIn;
cachedReportHevcOptIn = hevcOptIn; cachedReportHevcOptIn = hevcOptIn;
cachedReportDelivery = delivery;
return cachedReport; return cachedReport;
} }
@@ -538,9 +518,7 @@ export function getVideoPublishCodecDenial(codec: VideoCodec): VideoPublishCodec
if (!supportsVideoCodec(codec)) return 'sender-cannot-encode'; if (!supportsVideoCodec(codec)) return 'sender-cannot-encode';
if (getScreenShareCodecPolicyUnsupported(codec, buildScreenShareCodecPolicyContext())) return 'policy'; if (getScreenShareCodecPolicyUnsupported(codec, buildScreenShareCodecPolicyContext())) return 'policy';
if (runtimeEncodeFailureCodecs.has(codec)) return 'runtime-failed'; if (runtimeEncodeFailureCodecs.has(codec)) return 'runtime-failed';
const decoderExclusions = ScreenShareDeliveryRollout.enabled const decoderExclusions = getProbedVideoDecoderExclusionsSync();
? getProbedVideoDecoderExclusionsSync()
: getVideoDecoderExclusionsSync();
if (decoderExclusions?.includes(codec) === true) return 'decoder-excluded'; if (decoderExclusions?.includes(codec) === true) return 'decoder-excluded';
return null; return null;
} }
@@ -701,7 +679,6 @@ export function resetCachedCodecCapabilities(): void {
cachedReportHardwareAccelerationDisabled = undefined; cachedReportHardwareAccelerationDisabled = undefined;
cachedReportAv1OptIn = undefined; cachedReportAv1OptIn = undefined;
cachedReportHevcOptIn = undefined; cachedReportHevcOptIn = undefined;
cachedReportDelivery = undefined;
runtimeEncodeFailureCodecs.clear(); runtimeEncodeFailureCodecs.clear();
observedSoftwareEncodeCodecs.clear(); observedSoftwareEncodeCodecs.clear();
resetNativeHardwareEncoderCapabilities(); resetNativeHardwareEncoderCapabilities();
@@ -2,7 +2,6 @@
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
import {getElectronAPI, isDesktop} from '@app/features/ui/utils/NativeUtils'; import {getElectronAPI, isDesktop} from '@app/features/ui/utils/NativeUtils';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import type {GpuDeviceInfo, GpuInfo} from '@app/types/electron.d'; import type {GpuDeviceInfo, GpuInfo} from '@app/types/electron.d';
import type {VideoCodec} from 'livekit-client'; import type {VideoCodec} from 'livekit-client';
@@ -238,12 +237,6 @@ export const H264_ENCODE_PROBE_CONTENT_TYPES: ReadonlyArray<string> = H264_PROBE
(profileLevelId) => `video/H264;level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=${profileLevelId}`, (profileLevelId) => `video/H264;level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=${profileLevelId}`,
); );
const CONTROL_H264_PROBE_PROFILE_LEVEL_IDS: ReadonlyArray<string> = ['640028', '4d0028', '420028', '42e028'];
const CONTROL_H264_ENCODE_PROBE_CONTENT_TYPES: ReadonlyArray<string> = CONTROL_H264_PROBE_PROFILE_LEVEL_IDS.map(
(profileLevelId) => `video/H264;level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=${profileLevelId}`,
);
export const WEBRTC_ENCODE_PROBE_CONTENT_TYPES: Record<VideoCodec, ReadonlyArray<string>> = { export const WEBRTC_ENCODE_PROBE_CONTENT_TYPES: Record<VideoCodec, ReadonlyArray<string>> = {
av1: ['video/AV1'], av1: ['video/AV1'],
h265: ['video/H265'], h265: ['video/H265'],
@@ -415,24 +408,17 @@ export async function probeWebRtcEncodeEfficiency(
): Promise<Record<VideoCodec, HardwareEncodeAnswer> | null> { ): Promise<Record<VideoCodec, HardwareEncodeAnswer> | null> {
const mediaCapabilities = getMediaCapabilities(); const mediaCapabilities = getMediaCapabilities();
if (!mediaCapabilities) return null; if (!mediaCapabilities) return null;
const delivery = ScreenShareDeliveryRollout.enabled;
const video = resolveEncodeProbeVideoConfig(config); const video = resolveEncodeProbeVideoConfig(config);
const codecs: ReadonlyArray<VideoCodec> = ['av1', 'h265', 'h264', 'vp9', 'vp8']; const codecs: ReadonlyArray<VideoCodec> = ['av1', 'h265', 'h264', 'vp9', 'vp8'];
const answers = await Promise.all( const answers = await Promise.all(
codecs.map((codec) => codecs.map((codec) =>
probeContentTypesEncodeEfficiency( probeContentTypesEncodeEfficiency(mediaCapabilities, WEBRTC_ENCODE_PROBE_CONTENT_TYPES[codec], video),
mediaCapabilities,
codec === 'h264' && !delivery
? CONTROL_H264_ENCODE_PROBE_CONTENT_TYPES
: WEBRTC_ENCODE_PROBE_CONTENT_TYPES[codec],
video,
),
), ),
); );
const result = {} as Record<VideoCodec, HardwareEncodeAnswer>; const result = {} as Record<VideoCodec, HardwareEncodeAnswer>;
codecs.forEach((codec, index) => { codecs.forEach((codec, index) => {
const codecAnswers = answers[index] ?? []; const codecAnswers = answers[index] ?? [];
if (delivery && codec === 'h264') recordH264HardwareProfileProbe(codecAnswers, video); if (codec === 'h264') recordH264HardwareProfileProbe(codecAnswers, video);
result[codec] = collapseEncodeProbeAnswers(codecAnswers); result[codec] = collapseEncodeProbeAnswers(codecAnswers);
}); });
return result; return result;
@@ -460,7 +446,6 @@ let pendingPromise: Promise<HardwareEncodeReport | null> | null = null;
function fetchReport(): Promise<HardwareEncodeReport | null> { function fetchReport(): Promise<HardwareEncodeReport | null> {
if (!isDesktop()) { if (!isDesktop()) {
if (!ScreenShareDeliveryRollout.enabled) return Promise.resolve(null);
return probeH264HardwareProfiles().then(() => null); return probeH264HardwareProfiles().then(() => null);
} }
const electron = getElectronAPI(); const electron = getElectronAPI();
@@ -9,12 +9,7 @@ import {
markScreenShareDecodeFailure, markScreenShareDecodeFailure,
resetVideoDecoderExclusions, resetVideoDecoderExclusions,
} from '@app/features/voice/utils/VideoDecoderCapabilities'; } from '@app/features/voice/utils/VideoDecoderCapabilities';
import {afterEach, describe, expect, it, vi} from 'vitest'; import {afterEach, describe, expect, it} from 'vitest';
vi.mock('@app/features/voice/state/ScreenShareDeliveryRollout', () => ({
ScreenShareDeliveryRollout: {enabled: true},
default: {enabled: true},
}));
function buildStats(inbound: Record<string, unknown>): RTCStatsReport { function buildStats(inbound: Record<string, unknown>): RTCStatsReport {
return new Map<string, unknown>([ return new Map<string, unknown>([
@@ -1,14 +1,12 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import SoftwareEncoderWarning from '@app/features/voice/state/SoftwareEncoderWarning'; import SoftwareEncoderWarning from '@app/features/voice/state/SoftwareEncoderWarning';
import {classifyVideoDecoderAcceleration} from '@app/features/voice/utils/VideoAccelerationClassification'; import {classifyVideoDecoderAcceleration} from '@app/features/voice/utils/VideoAccelerationClassification';
import type {VideoCodec} from 'livekit-client'; import type {VideoCodec} from 'livekit-client';
const logger = new Logger('ScreenShareCodecDiagnostics'); const logger = new Logger('ScreenShareCodecDiagnostics');
const DECODE_SAMPLE_INTERVAL_MS = 5000; const DECODE_SAMPLE_INTERVAL_MS = 5000;
const DECODER_VERIFICATION_DELAY_MS = 5000;
const UNKNOWN_DECODER_IMPLEMENTATION = 'software decoder'; const UNKNOWN_DECODER_IMPLEMENTATION = 'software decoder';
interface CodecStatsEntry { interface CodecStatsEntry {
@@ -144,97 +142,6 @@ function collectInboundVideoStats(stats: RTCStatsReport): {
return {codecs, reports}; return {codecs, reports};
} }
function findSoftwareVideoDecoderInStats(stats: RTCStatsReport): SoftwareVideoDecoderInfo | null {
const {codecs, reports} = collectInboundVideoStats(stats);
for (const report of reports) {
if (getStatsKind(report, codecs) !== 'video') continue;
const implementation =
typeof report.decoderImplementation === 'string' && report.decoderImplementation.length > 0
? report.decoderImplementation
: null;
const powerEfficientDecoder =
typeof report.powerEfficientDecoder === 'boolean' ? report.powerEfficientDecoder : null;
if (!isSoftwareVideoStats(implementation, powerEfficientDecoder)) continue;
return {
codec: getCodecLabel(report.codecId ? codecs.get(report.codecId)?.mimeType : undefined),
implementation: implementation ?? UNKNOWN_DECODER_IMPLEMENTATION,
powerEfficientDecoder,
};
}
return null;
}
function findStalledVideoDecoderInStats(stats: RTCStatsReport): StalledVideoDecoderInfo | null {
const {codecs, reports} = collectInboundVideoStats(stats);
for (const report of reports) {
if (getStatsKind(report, codecs) !== 'video') continue;
const framesDecoded = finiteNumber(report.framesDecoded);
if (framesDecoded === null || framesDecoded > 0) continue;
const framesReceived = finiteNumber(report.framesReceived);
if (framesReceived === null || framesReceived < 1) continue;
const mimeType = report.codecId ? codecs.get(report.codecId)?.mimeType : undefined;
const codec = getVideoCodecFromMimeType(mimeType);
if (!codec) continue;
return {
codec,
mimeType,
packetsReceived: finiteNumber(report.packetsReceived) ?? 0,
bytesReceived: finiteNumber(report.bytesReceived) ?? 0,
framesDecoded,
framesReceived,
framesDropped: finiteNumber(report.framesDropped),
};
}
return null;
}
function scheduleScreenShareDecoderVerification(
getStats: () => Promise<RTCStatsReport | undefined>,
onDecodeFailure?: (failure: StalledVideoDecoderInfo) => void,
): () => void {
let cancelled = false;
let timer: ReturnType<typeof setTimeout> | null = null;
const scheduleConfirmation = (first: StalledVideoDecoderInfo): void => {
timer = setTimeout(async () => {
timer = null;
try {
const stats = await getStats();
const confirmed = confirmDecodeStall(first, stats ? findStalledVideoDecoderInStats(stats) : null);
if (cancelled || !confirmed) return;
logger.warn('Screen share video decode is stalled', confirmed);
onDecodeFailure?.(confirmed);
} catch (error) {
logger.debug('Failed to confirm the screen share decode stall', {error});
}
}, DECODER_VERIFICATION_DELAY_MS);
};
timer = setTimeout(async () => {
timer = null;
let firstStall: StalledVideoDecoderInfo | null = null;
try {
const stats = await getStats();
if (!stats) return;
firstStall = findStalledVideoDecoderInStats(stats);
const decoder = findSoftwareVideoDecoderInStats(stats);
if (!decoder) return;
logger.warn('Screen share is using a software decoder', decoder);
SoftwareEncoderWarning.triggerDecoderWarning(decoder.codec, decoder.implementation);
} catch (error) {
logger.debug('Failed to verify screen share decoder', {error});
} finally {
if (!cancelled && firstStall) {
scheduleConfirmation(firstStall);
}
}
}, DECODER_VERIFICATION_DELAY_MS);
return () => {
cancelled = true;
if (!timer) return;
clearTimeout(timer);
timer = null;
};
}
export function findInboundVideoDecodeSample(stats: RTCStatsReport): InboundVideoDecodeSample | null { export function findInboundVideoDecodeSample(stats: RTCStatsReport): InboundVideoDecodeSample | null {
const {codecs, reports} = collectInboundVideoStats(stats); const {codecs, reports} = collectInboundVideoStats(stats);
for (const report of reports) { for (const report of reports) {
@@ -338,7 +245,6 @@ export function monitorScreenShareDecodeHealth(
getStats: () => Promise<RTCStatsReport | undefined>, getStats: () => Promise<RTCStatsReport | undefined>,
onDecodeStall?: (failure: StalledVideoDecoderInfo) => void, onDecodeStall?: (failure: StalledVideoDecoderInfo) => void,
): () => void { ): () => void {
if (!ScreenShareDeliveryRollout.enabled) return scheduleScreenShareDecoderVerification(getStats, onDecodeStall);
let cancelled = false; let cancelled = false;
let timer: ReturnType<typeof setTimeout> | null = null; let timer: ReturnType<typeof setTimeout> | null = null;
let previousSample: InboundVideoDecodeSample | null = null; let previousSample: InboundVideoDecodeSample | null = null;
@@ -8,14 +8,7 @@ import {
resolveScreenShareLayering, resolveScreenShareLayering,
resolveScreenShareTarget, resolveScreenShareTarget,
} from '@app/features/voice/utils/ScreenShareOptions'; } from '@app/features/voice/utils/ScreenShareOptions';
import {afterEach, describe, expect, it, vi} from 'vitest'; import {describe, expect, it, vi} from 'vitest';
const rollout = vi.hoisted(() => ({enabled: false}));
vi.mock('@app/features/voice/state/ScreenShareDeliveryRollout', () => ({
ScreenShareDeliveryRollout: rollout,
default: rollout,
}));
vi.mock('@app/features/voice/utils/NativeAudioCaptureBridge', () => ({ vi.mock('@app/features/voice/utils/NativeAudioCaptureBridge', () => ({
rememberCapturedDisplayAudioTrack: () => undefined, rememberCapturedDisplayAudioTrack: () => undefined,
@@ -29,10 +22,6 @@ const {getDisplayMediaOptions} = await import(
'@app/features/voice/engine/voice_screen_share_manager/DisplayMediaCapture' '@app/features/voice/engine/voice_screen_share_manager/DisplayMediaCapture'
); );
afterEach(() => {
rollout.enabled = false;
});
function collectKeys(value: unknown, keys: Set<string>): Set<string> { function collectKeys(value: unknown, keys: Set<string>): Set<string> {
if (typeof value !== 'object' || value === null) return keys; if (typeof value !== 'object' || value === null) return keys;
for (const [key, entry] of Object.entries(value)) { for (const [key, entry] of Object.entries(value)) {
@@ -87,29 +76,19 @@ describe('display capture constraints', () => {
}); });
}); });
describe('the screen share delivery experiment', () => { describe('screen share quality', () => {
it('keeps the source preset and the 90 and 120 FPS rungs off the experiment', () => { it('uses the 1080p30 preset and lands the faster rungs on 60 FPS', () => {
expect(targetOf()).toMatchObject({resolution: 'source', frameRate: 15});
expect(resolveScreenShareFrameRate(120)).toBe(120);
expect(resolveScreenShareFrameRate(90)).toBe(90);
expect(resolveScreenShareFrameRate(60)).toBe(60);
});
it('moves the preset to 1080p30 and lands the faster rungs on 60 FPS on the experiment', () => {
rollout.enabled = true;
expect(targetOf()).toMatchObject({resolution: 'high', frameRate: 30}); expect(targetOf()).toMatchObject({resolution: 'high', frameRate: 30});
expect(resolveScreenShareFrameRate(120)).toBe(60); expect(resolveScreenShareFrameRate(120)).toBe(60);
expect(resolveScreenShareFrameRate(90)).toBe(60); expect(resolveScreenShareFrameRate(90)).toBe(60);
expect(resolveScreenShareFrameRate(60)).toBe(60); expect(resolveScreenShareFrameRate(60)).toBe(60);
}); });
it('reads the rung table off the experiment and the pixel budget on it', () => { it('reads the bitrate off the pixel budget', () => {
expect(getScreenShareBitrateBps('source', 60)).toBe(6_000_000);
rollout.enabled = true;
expect(getScreenShareBitrateBps('source', 60)).toBe(9_000_000); expect(getScreenShareBitrateBps('source', 60)).toBe(9_000_000);
}); });
it('publishes the stored frame rate and the rung bitrate off the experiment', () => { it('publishes the resolved frame rate and the pixel budget bitrate', () => {
const {publishOptions} = buildScreenShareOptions({ const {publishOptions} = buildScreenShareOptions({
resolution: 'source', resolution: 'source',
frameRate: 90, frameRate: 90,
@@ -118,26 +97,18 @@ describe('the screen share delivery experiment', () => {
sourceDimensions: {width: 3840, height: 2160}, sourceDimensions: {width: 3840, height: 2160},
}); });
expect(publishOptions.screenShareEncoding).toEqual({ expect(publishOptions.screenShareEncoding).toEqual({
maxBitrate: 6_000_000, maxBitrate: 9_000_000,
maxFramerate: 90, maxFramerate: 60,
priority: 'high', priority: 'high',
}); });
expect(publishOptions.degradationPreference).toBe('maintain-resolution'); expect(publishOptions.degradationPreference).toBe('maintain-resolution');
}); });
it('holds the motion hint for every surface off the experiment and only for a camera on it', () => { it('holds the motion hint only for a camera', () => {
expect(targetOf({mode: 'gaming'}).contentHint).toBe('motion');
rollout.enabled = true;
expect(targetOf({mode: 'gaming'}).contentHint).toBeUndefined(); expect(targetOf({mode: 'gaming'}).contentHint).toBeUndefined();
}); });
it('ignores the software H.264 clamp off the experiment', () => { it('applies the software H.264 clamp', () => {
expect(targetOf({mode: 'gaming', softwareEncoderClamp: true})).toMatchObject({
resolution: 'ultra',
frameRate: 60,
softwareEncoderClamped: false,
});
rollout.enabled = true;
expect(targetOf({mode: 'gaming', softwareEncoderClamp: true})).toMatchObject({ expect(targetOf({mode: 'gaming', softwareEncoderClamp: true})).toMatchObject({
resolution: 'medium', resolution: 'medium',
frameRate: 30, frameRate: 30,
@@ -147,15 +118,7 @@ describe('the screen share delivery experiment', () => {
}); });
describe('screen share degradation preference', () => { describe('screen share degradation preference', () => {
it('holds the resolution for every share off the experiment', () => { it('keeps device shares balanced', () => {
expect(
resolveScreenShareDegradationPreference({
context: 'display',
rung: 'medium',
contentHint: undefined,
maxBitrate: 3_000_000,
}),
).toBe('maintain-resolution');
expect( expect(
resolveScreenShareDegradationPreference({ resolveScreenShareDegradationPreference({
context: 'device', context: 'device',
@@ -167,7 +130,6 @@ describe('screen share degradation preference', () => {
}); });
it('refuses maintain-framerate below the initial frame dropper cliff', () => { it('refuses maintain-framerate below the initial frame dropper cliff', () => {
rollout.enabled = true;
expect( expect(
resolveScreenShareDegradationPreference({ resolveScreenShareDegradationPreference({
context: 'display', context: 'display',
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import type {ScreenshareResolution, StreamingMode} from '@app/features/voice/state/VoiceSettings'; import type {ScreenshareResolution, StreamingMode} from '@app/features/voice/state/VoiceSettings';
import type { import type {
ScreenShareContentHint, ScreenShareContentHint,
@@ -23,8 +22,7 @@ const DIMENSIONS: Record<
ultra: {width: 2560, height: 1440}, ultra: {width: 2560, height: 1440},
source: {width: 3840, height: 2160}, source: {width: 3840, height: 2160},
}; };
export const SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS = 6_000_000; export const SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS = 9_000_000;
export const SCREEN_SHARE_DELIVERY_MAX_VIDEO_BITRATE_BPS = 9_000_000;
export const SUPPORTED_SCREEN_SHARE_FRAME_RATES = [15, 30, 60, 90, 120] as const; export const SUPPORTED_SCREEN_SHARE_FRAME_RATES = [15, 30, 60, 90, 120] as const;
export type SupportedScreenShareFrameRate = (typeof SUPPORTED_SCREEN_SHARE_FRAME_RATES)[number]; export type SupportedScreenShareFrameRate = (typeof SUPPORTED_SCREEN_SHARE_FRAME_RATES)[number];
@@ -52,15 +50,7 @@ const BITRATE_RUNGS = [
'source', 'source',
] as const satisfies ReadonlyArray<ScreenshareResolution>; ] as const satisfies ReadonlyArray<ScreenshareResolution>;
function isScreenShareDeliveryEnabled(delivery: boolean | undefined): boolean { export function resolveScreenShareFrameRate(frameRate: number): SupportedScreenShareFrameRate {
return delivery ?? ScreenShareDeliveryRollout.enabled;
}
export function resolveScreenShareFrameRate(frameRate: number, delivery?: boolean): SupportedScreenShareFrameRate {
if (!isScreenShareDeliveryEnabled(delivery)) {
if (frameRate >= 120) return 120;
if (frameRate >= 90) return 90;
}
if (frameRate >= 60) return 60; if (frameRate >= 60) return 60;
if (frameRate >= 30) return 30; if (frameRate >= 30) return 30;
return 15; return 15;
@@ -83,12 +73,10 @@ function computeScreenShareBitrateBps(
pixels: number, pixels: number,
rung: ScreenshareResolution, rung: ScreenshareResolution,
frameRate: SupportedScreenShareFrameRate, frameRate: SupportedScreenShareFrameRate,
delivery: boolean,
): number { ): number {
const rungFloor = BITRATE_KBPS[rung][frameRate] * 1000; const rungFloor = BITRATE_KBPS[rung][frameRate] * 1000;
if (!delivery) return rungFloor;
const computed = Math.round(SCREEN_SHARE_BITS_PER_PIXEL_PER_FRAME * pixels * frameRate); const computed = Math.round(SCREEN_SHARE_BITS_PER_PIXEL_PER_FRAME * pixels * frameRate);
return Math.min(SCREEN_SHARE_DELIVERY_MAX_VIDEO_BITRATE_BPS, Math.max(rungFloor, computed)); return Math.min(SCREEN_SHARE_MAX_VIDEO_BITRATE_BPS, Math.max(rungFloor, computed));
} }
export function getScreenShareBitrateBps( export function getScreenShareBitrateBps(
@@ -98,15 +86,9 @@ export function getScreenShareBitrateBps(
width: number; width: number;
height: number; height: number;
} | null, } | null,
delivery?: boolean,
): number { ): number {
const fit = resolveEffectiveScreenShareDimensions(resolution, sourceDimensions); const fit = resolveEffectiveScreenShareDimensions(resolution, sourceDimensions);
return computeScreenShareBitrateBps( return computeScreenShareBitrateBps(fit.width * fit.height, fit.rung, frameRate);
fit.width * fit.height,
fit.rung,
frameRate,
isScreenShareDeliveryEnabled(delivery),
);
} }
export const STREAMING_MODE_PRESETS: Record< export const STREAMING_MODE_PRESETS: Record<
@@ -115,16 +97,6 @@ export const STREAMING_MODE_PRESETS: Record<
resolution: ScreenshareResolution; resolution: ScreenshareResolution;
frameRate: SupportedScreenShareFrameRate; frameRate: SupportedScreenShareFrameRate;
} }
> = {
gaming: {resolution: 'ultra', frameRate: 60},
screenshare: {resolution: 'source', frameRate: 15},
};
const SCREEN_SHARE_DELIVERY_STREAMING_MODE_PRESETS: Record<
Exclude<StreamingMode, 'custom'>,
{
resolution: ScreenshareResolution;
frameRate: SupportedScreenShareFrameRate;
}
> = { > = {
gaming: {resolution: 'ultra', frameRate: 60}, gaming: {resolution: 'ultra', frameRate: 60},
screenshare: {resolution: 'high', frameRate: 30}, screenshare: {resolution: 'high', frameRate: 30},
@@ -152,7 +124,6 @@ export interface ScreenShareBuildConfig {
includeAudio: boolean; includeAudio: boolean;
contentHint?: ScreenShareCaptureOptions['contentHint']; contentHint?: ScreenShareCaptureOptions['contentHint'];
degradationPreference?: NonNullable<TrackPublishOptions['degradationPreference']>; degradationPreference?: NonNullable<TrackPublishOptions['degradationPreference']>;
delivery?: boolean;
sourceDimensions?: { sourceDimensions?: {
width: number; width: number;
height: number; height: number;
@@ -208,10 +179,9 @@ export function resolveEffectiveScreenShareDimensions(
} }
export function buildScreenShareOptions(config: ScreenShareBuildConfig): BuiltScreenShareOptions { export function buildScreenShareOptions(config: ScreenShareBuildConfig): BuiltScreenShareOptions {
const delivery = isScreenShareDeliveryEnabled(config.delivery);
const {width, height, rung} = resolveEffectiveScreenShareDimensions(config.resolution, config.sourceDimensions); const {width, height, rung} = resolveEffectiveScreenShareDimensions(config.resolution, config.sourceDimensions);
const resolvedFrameRate = resolveScreenShareFrameRate(config.frameRate, delivery); const resolvedFrameRate = resolveScreenShareFrameRate(config.frameRate);
const maxBitrate = computeScreenShareBitrateBps(width * height, rung, resolvedFrameRate, delivery); const maxBitrate = computeScreenShareBitrateBps(width * height, rung, resolvedFrameRate);
const video: ScreenShareVideoOptions = { const video: ScreenShareVideoOptions = {
cursor: resolveScreenShareCursorCapture(config.preferredDisplaySurface), cursor: resolveScreenShareCursorCapture(config.preferredDisplaySurface),
...(config.preferredDisplaySurface ? {displaySurface: config.preferredDisplaySurface} : {}), ...(config.preferredDisplaySurface ? {displaySurface: config.preferredDisplaySurface} : {}),
@@ -235,7 +205,6 @@ export function buildScreenShareOptions(config: ScreenShareBuildConfig): BuiltSc
contentHint: config.contentHint, contentHint: config.contentHint,
maxBitrate, maxBitrate,
degradationPreference: config.degradationPreference, degradationPreference: config.degradationPreference,
delivery,
}), }),
screenShareEncoding: {maxBitrate, maxFramerate: resolvedFrameRate, priority: 'high'}, screenShareEncoding: {maxBitrate, maxFramerate: resolvedFrameRate, priority: 'high'},
}, },
@@ -270,17 +239,14 @@ export function resolveStreamingModeSettings(
customResolution: ScreenshareResolution, customResolution: ScreenshareResolution,
customFrameRate: number, customFrameRate: number,
hasHigherQuality: boolean, hasHigherQuality: boolean,
delivery?: boolean,
): { ): {
resolution: ScreenshareResolution; resolution: ScreenshareResolution;
frameRate: SupportedScreenShareFrameRate; frameRate: SupportedScreenShareFrameRate;
} { } {
const enabled = isScreenShareDeliveryEnabled(delivery);
const presets = enabled ? SCREEN_SHARE_DELIVERY_STREAMING_MODE_PRESETS : STREAMING_MODE_PRESETS;
const resolved = const resolved =
mode === 'custom' mode === 'custom'
? {resolution: customResolution, frameRate: resolveScreenShareFrameRate(customFrameRate, enabled)} ? {resolution: customResolution, frameRate: resolveScreenShareFrameRate(customFrameRate)}
: (hasHigherQuality ? presets : FREE_STREAMING_MODE_PRESETS)[mode]; : (hasHigherQuality ? STREAMING_MODE_PRESETS : FREE_STREAMING_MODE_PRESETS)[mode];
if (hasHigherQuality) { if (hasHigherQuality) {
return resolved; return resolved;
} }
@@ -319,7 +285,6 @@ export interface ScreenShareQualityInput {
storedFrameRate: number; storedFrameRate: number;
entitled: boolean; entitled: boolean;
context: ScreenShareContext; context: ScreenShareContext;
delivery?: boolean;
} }
export interface ScreenShareTargetInput extends ScreenShareQualityInput { export interface ScreenShareTargetInput extends ScreenShareQualityInput {
@@ -341,7 +306,6 @@ export interface ScreenShareTarget {
contentHint: ScreenShareCaptureOptions['contentHint']; contentHint: ScreenShareCaptureOptions['contentHint'];
degradationPreference: NonNullable<TrackPublishOptions['degradationPreference']>; degradationPreference: NonNullable<TrackPublishOptions['degradationPreference']>;
softwareEncoderClamped: boolean; softwareEncoderClamped: boolean;
delivery?: boolean;
presetOwned: boolean; presetOwned: boolean;
tierLimited: boolean; tierLimited: boolean;
deviceMapped: boolean; deviceMapped: boolean;
@@ -353,15 +317,11 @@ export interface ScreenShareDegradationInput {
contentHint: ScreenShareCaptureOptions['contentHint']; contentHint: ScreenShareCaptureOptions['contentHint'];
maxBitrate: number; maxBitrate: number;
degradationPreference?: NonNullable<TrackPublishOptions['degradationPreference']>; degradationPreference?: NonNullable<TrackPublishOptions['degradationPreference']>;
delivery?: boolean;
} }
export function resolveScreenShareDegradationPreference( export function resolveScreenShareDegradationPreference(
target: ScreenShareDegradationInput, target: ScreenShareDegradationInput,
): NonNullable<TrackPublishOptions['degradationPreference']> { ): NonNullable<TrackPublishOptions['degradationPreference']> {
if (!isScreenShareDeliveryEnabled(target.delivery)) {
return target.context === 'device' ? 'balanced' : 'maintain-resolution';
}
if (target.degradationPreference) return target.degradationPreference; if (target.degradationPreference) return target.degradationPreference;
if (target.context === 'device') return 'balanced'; if (target.context === 'device') return 'balanced';
if (target.rung === 'source') return 'maintain-resolution'; if (target.rung === 'source') return 'maintain-resolution';
@@ -393,17 +353,14 @@ function clampToSoftwareH264Budget(quality: {
return {mode: quality.mode, resolution: cappedResolution, frameRate: cappedFrameRate}; return {mode: quality.mode, resolution: cappedResolution, frameRate: cappedFrameRate};
} }
function resolveEffectiveScreenShareQuality( function resolveEffectiveScreenShareQuality(input: ScreenShareQualityInput): {
input: ScreenShareQualityInput,
delivery: boolean,
): {
mode: StreamingMode; mode: StreamingMode;
resolution: ScreenshareResolution; resolution: ScreenshareResolution;
frameRate: SupportedScreenShareFrameRate; frameRate: SupportedScreenShareFrameRate;
} { } {
const mode = normaliseStreamingModeForContext(input.mode, input.context); const mode = normaliseStreamingModeForContext(input.mode, input.context);
const resolution = normaliseResolutionForContext(input.storedResolution, input.context, input.entitled); const resolution = normaliseResolutionForContext(input.storedResolution, input.context, input.entitled);
const settings = resolveStreamingModeSettings(mode, resolution, input.storedFrameRate, input.entitled, delivery); const settings = resolveStreamingModeSettings(mode, resolution, input.storedFrameRate, input.entitled);
return {mode, resolution: settings.resolution, frameRate: settings.frameRate}; return {mode, resolution: settings.resolution, frameRate: settings.frameRate};
} }
@@ -420,23 +377,20 @@ function resolveScreenShareContentHint(
mode: StreamingMode, mode: StreamingMode,
hintSetting: ScreenShareContentHint, hintSetting: ScreenShareContentHint,
context: ScreenShareContext, context: ScreenShareContext,
delivery: boolean,
): ScreenShareCaptureOptions['contentHint'] { ): ScreenShareCaptureOptions['contentHint'] {
const hint = resolveScreenShareContentHintForMode(mode, hintSetting); const hint = resolveScreenShareContentHintForMode(mode, hintSetting);
if (!delivery) return hint;
return hint === 'motion' && context !== 'device' ? undefined : hint; return hint === 'motion' && context !== 'device' ? undefined : hint;
} }
export function resolveScreenShareTarget(input: ScreenShareTargetInput): ScreenShareTarget { export function resolveScreenShareTarget(input: ScreenShareTargetInput): ScreenShareTarget {
const delivery = isScreenShareDeliveryEnabled(input.delivery); const clamped = input.softwareEncoderClamp === true || shouldClampToSoftwareH264(input.codec);
const clamped = delivery && (input.softwareEncoderClamp === true || shouldClampToSoftwareH264(input.codec)); const configured = resolveEffectiveScreenShareQuality(input);
const configured = resolveEffectiveScreenShareQuality(input, delivery);
const effective = clamped ? clampToSoftwareH264Budget(configured) : configured; const effective = clamped ? clampToSoftwareH264Budget(configured) : configured;
const configuredOnDisplay = resolveEffectiveScreenShareQuality({...input, context: 'display'}, delivery); const configuredOnDisplay = resolveEffectiveScreenShareQuality({...input, context: 'display'});
const onDisplay = clamped ? clampToSoftwareH264Budget(configuredOnDisplay) : configuredOnDisplay; const onDisplay = clamped ? clampToSoftwareH264Budget(configuredOnDisplay) : configuredOnDisplay;
const fit = resolveEffectiveScreenShareDimensions(effective.resolution, input.sourceDimensions); const fit = resolveEffectiveScreenShareDimensions(effective.resolution, input.sourceDimensions);
const maxBitrate = computeScreenShareBitrateBps(fit.width * fit.height, fit.rung, effective.frameRate, delivery); const maxBitrate = computeScreenShareBitrateBps(fit.width * fit.height, fit.rung, effective.frameRate);
const contentHint = resolveScreenShareContentHint(effective.mode, input.hintSetting, input.context, delivery); const contentHint = resolveScreenShareContentHint(effective.mode, input.hintSetting, input.context);
return { return {
mode: effective.mode, mode: effective.mode,
resolution: effective.resolution, resolution: effective.resolution,
@@ -452,16 +406,14 @@ export function resolveScreenShareTarget(input: ScreenShareTargetInput): ScreenS
rung: fit.rung, rung: fit.rung,
contentHint, contentHint,
maxBitrate, maxBitrate,
delivery,
}), }),
softwareEncoderClamped: clamped, softwareEncoderClamped: clamped,
delivery,
presetOwned: effective.mode !== 'custom', presetOwned: effective.mode !== 'custom',
tierLimited: tierLimited:
!input.entitled && !input.entitled &&
input.mode === 'custom' && input.mode === 'custom' &&
(PREMIUM_SCREEN_SHARE_RESOLUTIONS.includes(input.storedResolution) || (PREMIUM_SCREEN_SHARE_RESOLUTIONS.includes(input.storedResolution) ||
resolveScreenShareFrameRate(input.storedFrameRate, delivery) > FREE_TIER_MAX_FRAME_RATE), resolveScreenShareFrameRate(input.storedFrameRate) > FREE_TIER_MAX_FRAME_RATE),
deviceMapped: deviceMapped:
effective.mode !== onDisplay.mode || effective.mode !== onDisplay.mode ||
effective.resolution !== onDisplay.resolution || effective.resolution !== onDisplay.resolution ||
@@ -570,13 +522,12 @@ export function resolveScreenShareSenderCodec(
} }
export function buildScreenShareSenderParameters(input: ScreenShareSenderParametersInput): ScreenShareSenderParameters { export function buildScreenShareSenderParameters(input: ScreenShareSenderParametersInput): ScreenShareSenderParameters {
const delivery = input.target.delivery === true;
const degradationPreference = resolveScreenShareDegradationPreference(input.target); const degradationPreference = resolveScreenShareDegradationPreference(input.target);
const targetPixels = input.target.width * input.target.height; const targetPixels = input.target.width * input.target.height;
const capturePixels = input.capture ? input.capture.width * input.capture.height : null; const capturePixels = input.capture ? input.capture.width * input.capture.height : null;
const baseScale = Math.min(...input.encodings.map((encoding) => encoding.scaleResolutionDownBy ?? 1)); const baseScale = Math.min(...input.encodings.map((encoding) => encoding.scaleResolutionDownBy ?? 1));
const targetScale = const targetScale =
capturePixels === null || (delivery && degradationPreference === 'maintain-framerate') capturePixels === null || degradationPreference === 'maintain-framerate'
? 1 ? 1
: Math.max(1, Math.sqrt(capturePixels / targetPixels)); : Math.max(1, Math.sqrt(capturePixels / targetPixels));
const sentPixels = capturePixels === null ? null : capturePixels / (targetScale * targetScale); const sentPixels = capturePixels === null ? null : capturePixels / (targetScale * targetScale);
@@ -584,12 +535,7 @@ export function buildScreenShareSenderParameters(input: ScreenShareSenderParamet
sentPixels !== null && sentPixels < targetPixels * SENT_PIXEL_FILL_RATIO sentPixels !== null && sentPixels < targetPixels * SENT_PIXEL_FILL_RATIO
? Math.min( ? Math.min(
input.target.maxBitrate, input.target.maxBitrate,
computeScreenShareBitrateBps( computeScreenShareBitrateBps(sentPixels, resolveBitrateRungForPixels(sentPixels), input.target.frameRate),
sentPixels,
resolveBitrateRungForPixels(sentPixels),
input.target.frameRate,
delivery,
),
) )
: input.target.maxBitrate; : input.target.maxBitrate;
const bitrates = distributeScreenShareBitrate(input.encodings, maxBitrate); const bitrates = distributeScreenShareBitrate(input.encodings, maxBitrate);
@@ -628,9 +574,8 @@ export function resolveScreenShareQualityPick(
input: ScreenShareQualityInput, input: ScreenShareQualityInput,
pick: ScreenShareQualityPick, pick: ScreenShareQualityPick,
): ScreenShareQualityPatch | null { ): ScreenShareQualityPatch | null {
const delivery = isScreenShareDeliveryEnabled(input.delivery); const effective = resolveEffectiveScreenShareQuality(input);
const effective = resolveEffectiveScreenShareQuality(input, delivery); const stored = resolveEffectiveScreenShareQuality({...input, mode: 'custom'});
const stored = resolveEffectiveScreenShareQuality({...input, mode: 'custom'}, delivery);
if (pick.axis === 'resolution') { if (pick.axis === 'resolution') {
if (normaliseResolutionForContext(pick.resolution, input.context, input.entitled) !== pick.resolution) return null; if (normaliseResolutionForContext(pick.resolution, input.context, input.entitled) !== pick.resolution) return null;
if (pick.resolution === effective.resolution) return null; if (pick.resolution === effective.resolution) return null;
@@ -8,7 +8,6 @@ import {resolveConfiguredScreenShareTarget} from '@app/features/voice/engine/voi
import ActiveScreenShareSource from '@app/features/voice/state/ActiveScreenShareSource'; import ActiveScreenShareSource from '@app/features/voice/state/ActiveScreenShareSource';
import {clearDesktopSourceIntent, setDesktopSourceIntent} from '@app/features/voice/state/DesktopSourceIntent'; import {clearDesktopSourceIntent, setDesktopSourceIntent} from '@app/features/voice/state/DesktopSourceIntent';
import LocalVoiceState from '@app/features/voice/state/LocalVoiceState'; import LocalVoiceState from '@app/features/voice/state/LocalVoiceState';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import VoiceSettings from '@app/features/voice/state/VoiceSettings'; import VoiceSettings from '@app/features/voice/state/VoiceSettings';
import { import {
filterRoutableLinuxAudioSources, filterRoutableLinuxAudioSources,
@@ -806,7 +805,6 @@ export async function switchConfiguredDisplayScreenShare(
} }
export async function restartActiveScreenShareCapture(): Promise<boolean> { export async function restartActiveScreenShareCapture(): Promise<boolean> {
if (!ScreenShareDeliveryRollout.enabled) return false;
if (!didScreenShareStart()) return false; if (!didScreenShareStart()) return false;
const publishedSource = ActiveScreenShareSource.getPublishedSource(); const publishedSource = ActiveScreenShareSource.getPublishedSource();
const sourceId = ActiveScreenShareSource.getSourceId(); const sourceId = ActiveScreenShareSource.getSourceId();
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
import ScreenShareDeliveryRollout from '@app/features/voice/state/ScreenShareDeliveryRollout';
import type {VideoCodec} from 'livekit-client'; import type {VideoCodec} from 'livekit-client';
const logger = new Logger('VideoDecoderCapabilities'); const logger = new Logger('VideoDecoderCapabilities');
@@ -150,7 +149,7 @@ export function getVideoDecoderExclusionsSync(): Array<VideoCodec> | null {
} }
export function markScreenShareDecodeFailure(codec: VideoCodec, reason: string): boolean { export function markScreenShareDecodeFailure(codec: VideoCodec, reason: string): boolean {
if (ScreenShareDeliveryRollout.enabled && isBaselineWebRtcDecodeCodec(codec)) { if (isBaselineWebRtcDecodeCodec(codec)) {
logger.warn('Keeping a baseline codec advertised despite a local screen share decode stall', {codec, reason}); logger.warn('Keeping a baseline codec advertised despite a local screen share decode stall', {codec, reason});
return false; return false;
} }
@@ -35,7 +35,6 @@ Missing settings use the defaults documented below. Invalid stored configuration
| sso | [SSO configuration](#sso-configuration-object) object | Single sign-on settings | | sso | [SSO configuration](#sso-configuration-object) object | Single sign-on settings |
| gateway_rollout | [Gateway rollout configuration](#gateway-rollout-configuration-object) object | Gateway admission and dispatch tuning | | gateway_rollout | [Gateway rollout configuration](#gateway-rollout-configuration-object) object | Gateway admission and dispatch tuning |
| voice_noise_suppression | [voice noise suppression configuration](#voice-noise-suppression-configuration-object) object | Client-side noise suppression rollout | | voice_noise_suppression | [voice noise suppression configuration](#voice-noise-suppression-configuration-object) object | Client-side noise suppression rollout |
| screen_share_delivery | [screen share delivery configuration](#screen-share-delivery-configuration-object) object | Reworked screen share delivery rollout |
| push_service_delivery | [push service delivery configuration](#push-service-delivery-configuration-object) object | Push service delivery rollout | | push_service_delivery | [push service delivery configuration](#push-service-delivery-configuration-object) object | Push service delivery rollout |
| experiment_delivery | [experiment delivery configuration](#experiment-delivery-configuration-object) object | Cadence every client polls the experiments route on | | experiment_delivery | [experiment delivery configuration](#experiment-delivery-configuration-object) object | Cadence every client polls the experiments route on |
| registration | [registration configuration](#registration-configuration-object) object | Registration policy, issued URLs, and pending registrations | | registration | [registration configuration](#registration-configuration-object) object | Registration policy, issued URLs, and pending registrations |
@@ -122,27 +121,6 @@ Every field is present on read. An absent document or missing field uses the def
How often a client revalidates this rollout is not set here. It is set once for every experiment in the [experiment delivery configuration](#experiment-delivery-configuration-object) below. How often a client revalidates this rollout is not set here. It is set once for every experiment in the [experiment delivery configuration](#experiment-delivery-configuration-object) below.
## Screen share delivery configuration object
The instance rollout of the reworked screen share delivery path. [Experiments](/http-api/experiments/) defines what a client resolves from it.
### Structure
| Field | Type | Description |
| --- | --- | --- |
| enabled | boolean | Whether the rollout runs at all (default false) |
| config_version | integer | Revision counter, raised by Fluxer and never accepted from a request |
| rollout_basis_points | integer | Share of accounts the rollout selects, in basis points (0-10000, default 0) |
| rollout_salt | string | Salt of the sampling hash (1-64 characters, default `screen-share-delivery-v1`) |
| included_user_ids | array[snowflake] | Accounts the rollout always selects, up to 1000 entries (default empty) |
| excluded_user_ids | array[snowflake] | Accounts the rollout never selects, up to 1000 entries (default empty) |
Every field is present on read. An absent document or missing field uses the defaults above.
`excluded_user_ids` is applied before `included_user_ids`, so the rollout never selects an account in both.
How often a client revalidates this rollout is not set here. It is set once for every experiment in the [experiment delivery configuration](#experiment-delivery-configuration-object) below.
## Push service delivery configuration object ## Push service delivery configuration object
The instance rollout of push service delivery. The instance rollout of push service delivery.
@@ -568,7 +546,6 @@ The body has one optional object for each section. Fluxer leaves an absent secti
| sso?<sup>1</sup> | object | Every [SSO configuration](#sso-configuration-object) field except `client_secret_set` and `redirect_uri`, plus `client_secret` | | sso?<sup>1</sup> | object | Every [SSO configuration](#sso-configuration-object) field except `client_secret_set` and `redirect_uri`, plus `client_secret` |
| gateway_rollout? | object | Any subset of the [Gateway rollout configuration](#gateway-rollout-configuration-object) fields, each bound as documented there | | gateway_rollout? | object | Any subset of the [Gateway rollout configuration](#gateway-rollout-configuration-object) fields, each bound as documented there |
| voice_noise_suppression? | object | Any subset of the [noise suppression](#voice-noise-suppression-configuration-object) fields | | voice_noise_suppression? | object | Any subset of the [noise suppression](#voice-noise-suppression-configuration-object) fields |
| screen_share_delivery? | object | Any subset of the [screen share delivery](#screen-share-delivery-configuration-object) fields |
| push_service_delivery? | object | Any subset of the [push service delivery](#push-service-delivery-configuration-object) fields | | push_service_delivery? | object | Any subset of the [push service delivery](#push-service-delivery-configuration-object) fields |
| experiment_delivery? | object | Any subset of the [experiment delivery](#experiment-delivery-configuration-object) fields | | experiment_delivery? | object | Any subset of the [experiment delivery](#experiment-delivery-configuration-object) fields |
| registration? | object | `mode` and `admin_registration_urls_enabled` | | registration? | object | `mode` and `admin_registration_urls_enabled` |
@@ -583,8 +560,6 @@ The body has one optional object for each section. Fluxer leaves an absent secti
`voice_noise_suppression` takes every [voice noise suppression configuration](#voice-noise-suppression-configuration-object) field except `config_version`, each bound as documented there. Fluxer raises `config_version` by one on each request that supplies at least one of them. A section that is absent, or present with no field set, writes nothing and leaves `config_version` alone. `voice_noise_suppression` takes every [voice noise suppression configuration](#voice-noise-suppression-configuration-object) field except `config_version`, each bound as documented there. Fluxer raises `config_version` by one on each request that supplies at least one of them. A section that is absent, or present with no field set, writes nothing and leaves `config_version` alone.
`screen_share_delivery` works the same way, over the [screen share delivery configuration](#screen-share-delivery-configuration-object) fields and its own `config_version`.
`push_service_delivery` works the same way, over the [push service delivery configuration](#push-service-delivery-configuration-object) fields and its own `config_version`. `push_service_delivery` works the same way, over the [push service delivery configuration](#push-service-delivery-configuration-object) fields and its own `config_version`.
`experiment_delivery` takes both [experiment delivery configuration](#experiment-delivery-configuration-object) fields, each bound as documented there. It is a section of its own, so a write to it changes no `config_version` and changes no assignment, only the cadence on which clients ask for one. `experiment_delivery` takes both [experiment delivery configuration](#experiment-delivery-configuration-object) fields, each bound as documented there. It is a section of its own, so a write to it changes no `config_version` and changes no assignment, only the cadence on which clients ask for one.
@@ -623,7 +598,7 @@ Fluxer skips URL validation while the merged configuration leaves single sign-on
| 400 | [error response](/admin-api/#error-response) | A policy transition is refused, returned as `INSTANCE_POLICY_TRANSITION_NOT_ALLOWED` | | 400 | [error response](/admin-api/#error-response) | A policy transition is refused, returned as `INSTANCE_POLICY_TRANSITION_NOT_ALLOWED` |
:::caution[Sections are applied one after another] :::caution[Sections are applied one after another]
The order is `gateway_rollout`, `voice_noise_suppression`, `screen_share_delivery`, `push_service_delivery`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written. The order is `gateway_rollout`, `voice_noise_suppression`, `push_service_delivery`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written.
::: :::
### Side effects ### Side effects
@@ -6,7 +6,7 @@ description: The experiment assignments envelope, the revalidation and polling c
import RouteHeader from '@/components/RouteHeader.astro'; import RouteHeader from '@/components/RouteHeader.astro';
An experiment is an instance-wide rollout that an operator configures. For each account, Fluxer works out from that configuration whether the account is in the rollout and which settings the account receives. The single route on this page resolves every experiment the server defines and returns them in one envelope, together with the polling cadence they share. This server defines `voice_noise_suppression`, whose placement protocol [Voice](/voice/) defines, and `screen_share_delivery`. An experiment is an instance-wide rollout that an operator configures. For each account, Fluxer works out from that configuration whether the account is in the rollout and which settings the account receives. The single route on this page resolves every experiment the server defines and returns them in one envelope, together with the polling cadence they share. This server defines `voice_noise_suppression`, whose placement protocol [Voice](/voice/) defines.
Every assignment is advice. A client that ignores one behaves as it does with the rollout off, and no route and no Gateway event reports what a client actually ran. Every assignment is advice. A client that ignores one behaves as it does with the rollout off, and no route and no Gateway event reports what a client actually ran.
@@ -33,7 +33,6 @@ One entry per experiment. The envelope reports this object even when it is empty
| Field | Type | Description | | Field | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| voice_noise_suppression? | [noise suppression assignment](#noise-suppression-assignment-object) object | The caller's noise suppression assignment | | voice_noise_suppression? | [noise suppression assignment](#noise-suppression-assignment-object) object | The caller's noise suppression assignment |
| screen_share_delivery? | [screen share delivery assignment](#screen-share-delivery-assignment-object) object | The caller's screen share delivery assignment |
Ignore unknown experiments and treat a missing experiment as off. Ignore unknown experiments and treat a missing experiment as off.
@@ -102,18 +101,6 @@ One backend replacement scoped to one guild. While the caller is connected to a
An override naming a backend that is absent from `enabled_backends` is dropped before the response is written, so every entry is runnable. An override naming a backend that is absent from `enabled_backends` is dropped before the response is written, so every entry is runnable.
## Screen share delivery assignment object
One resolution of the instance screen share delivery rollout against one account. The field is present whenever the key is written.
### Structure
| Field | Type | Description |
| --- | --- | --- |
| enabled | boolean | Whether the caller runs the reworked screen share delivery path |
A caller is drawn either by the operator's allowlist or by the sampled share of the account population. `enabled` is false in every other case, the rollout being off included.
## Get experiment assignments ## Get experiment assignments
<RouteHeader method="GET" path="/v1/experiments" bot /> <RouteHeader method="GET" path="/v1/experiments" bot />
-1
View File
@@ -31,7 +31,6 @@
"fluxer_desktop/src/main/NotificationState.ts": ["exports", "types"], "fluxer_desktop/src/main/NotificationState.ts": ["exports", "types"],
"packages/schema/src/domains/admin/AdminUserSchemas.ts": ["exports"], "packages/schema/src/domains/admin/AdminUserSchemas.ts": ["exports"],
"packages/schema/src/domains/admin/PushServiceDeliverySchemas.ts": ["exports"], "packages/schema/src/domains/admin/PushServiceDeliverySchemas.ts": ["exports"],
"packages/schema/src/domains/admin/ScreenShareDeliverySchemas.ts": ["exports"],
"packages/schema/src/domains/admin/VoiceNoiseSuppressionSchemas.ts": ["exports"], "packages/schema/src/domains/admin/VoiceNoiseSuppressionSchemas.ts": ["exports"],
"packages/schema/src/domains/download/DownloadSchemas.ts": ["exports"], "packages/schema/src/domains/download/DownloadSchemas.ts": ["exports"],
"packages/schema/src/domains/geolocation/GeolocationSchemas.ts": ["exports"], "packages/schema/src/domains/geolocation/GeolocationSchemas.ts": ["exports"],
@@ -19,10 +19,6 @@ import {
PushServiceDeliveryConfigResponse, PushServiceDeliveryConfigResponse,
PushServiceDeliveryConfigUpdateRequest, PushServiceDeliveryConfigUpdateRequest,
} from '@fluxer/schema/src/domains/admin/PushServiceDeliverySchemas'; } from '@fluxer/schema/src/domains/admin/PushServiceDeliverySchemas';
import {
ScreenShareDeliveryConfigResponse,
ScreenShareDeliveryConfigUpdateRequest,
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import { import {
VoiceNoiseSuppressionConfigResponse, VoiceNoiseSuppressionConfigResponse,
VoiceNoiseSuppressionConfigUpdateRequest, VoiceNoiseSuppressionConfigUpdateRequest,
@@ -652,7 +648,6 @@ export const InstanceConfigResponse = z.object({
sso: SsoConfigResponse, sso: SsoConfigResponse,
gateway_rollout: GatewayRolloutConfigResponse, gateway_rollout: GatewayRolloutConfigResponse,
voice_noise_suppression: VoiceNoiseSuppressionConfigResponse, voice_noise_suppression: VoiceNoiseSuppressionConfigResponse,
screen_share_delivery: ScreenShareDeliveryConfigResponse,
push_service_delivery: PushServiceDeliveryConfigResponse, push_service_delivery: PushServiceDeliveryConfigResponse,
experiment_delivery: ExperimentDeliveryConfigResponse, experiment_delivery: ExperimentDeliveryConfigResponse,
registration: InstanceRegistrationResponse, registration: InstanceRegistrationResponse,
@@ -690,7 +685,6 @@ const InstancePolicyUpdateSchema = z.object({
export const InstanceConfigUpdateRequest = z.object({ export const InstanceConfigUpdateRequest = z.object({
gateway_rollout: GatewayRolloutConfigUpdateRequest.nullish(), gateway_rollout: GatewayRolloutConfigUpdateRequest.nullish(),
voice_noise_suppression: VoiceNoiseSuppressionConfigUpdateRequest.nullish(), voice_noise_suppression: VoiceNoiseSuppressionConfigUpdateRequest.nullish(),
screen_share_delivery: ScreenShareDeliveryConfigUpdateRequest.nullish(),
push_service_delivery: PushServiceDeliveryConfigUpdateRequest.nullish(), push_service_delivery: PushServiceDeliveryConfigUpdateRequest.nullish(),
experiment_delivery: ExperimentDeliveryConfigUpdateRequest.nullish(), experiment_delivery: ExperimentDeliveryConfigUpdateRequest.nullish(),
registration: z registration: z
@@ -1,194 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
resolveScreenShareDeliveryAssignment,
type ScreenShareDeliveryConfig,
ScreenShareDeliveryConfigSchema,
ScreenShareDeliveryConfigUpdateRequest,
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import {experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
import {describe, expect, test} from 'vitest';
const TARGETED_USER_ID = '1000000000000000001';
const OTHER_USER_ID = '1000000000000000002';
function createConfig(overrides: Partial<ScreenShareDeliveryConfig> = {}): ScreenShareDeliveryConfig {
return {
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
included_user_ids: [],
excluded_user_ids: [],
...overrides,
};
}
function syntheticUserIds(count: number): Array<string> {
const ids: Array<string> = [];
for (let index = 0; index < count; index++) {
ids.push((1400000000000000000n + BigInt(index)).toString());
}
return ids;
}
function targetedUserIds(config: ScreenShareDeliveryConfig, userIds: ReadonlyArray<string>): Set<string> {
const targeted = new Set<string>();
for (const userId of userIds) {
if (resolveScreenShareDeliveryAssignment(config, userId).enabled) {
targeted.add(userId);
}
}
return targeted;
}
describe('screen share delivery configuration', () => {
test('derives defaults from the schema with independently owned arrays', () => {
const first = ScreenShareDeliveryConfigSchema.parse({});
const second = ScreenShareDeliveryConfigSchema.parse({});
expect(first).toEqual(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG);
first.included_user_ids.push(TARGETED_USER_ID);
first.excluded_user_ids.push(OTHER_USER_ID);
expect(second).toEqual(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG);
});
test('defaults to disabled with an empty rollout', () => {
expect(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG.enabled).toBe(false);
expect(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG.rollout_basis_points).toBe(0);
});
test.each([{}, {enabled: false}, {enabled: undefined}, {rollout_basis_points: 2500}])(
'keeps partial updates free of configuration defaults: %j',
(patch) => {
expect(ScreenShareDeliveryConfigUpdateRequest.parse(patch)).toEqual(patch);
},
);
test('does not accept a client-provided configuration version', () => {
expect(ScreenShareDeliveryConfigUpdateRequest.parse({config_version: 12})).toEqual({});
});
test.each([
{rollout_basis_points: -1},
{rollout_basis_points: 10001},
{rollout_salt: ' '},
{included_user_ids: ['not-an-id']},
{excluded_user_ids: ['not-an-id']},
])('applies the same validation to stored configuration and updates: %j', (value) => {
expect(ScreenShareDeliveryConfigSchema.safeParse(value).success).toBe(false);
expect(ScreenShareDeliveryConfigUpdateRequest.safeParse(value).success).toBe(false);
});
test('rejects more than a thousand targeted user ids', () => {
const ids = syntheticUserIds(1001);
expect(ScreenShareDeliveryConfigSchema.safeParse({included_user_ids: ids}).success).toBe(false);
expect(ScreenShareDeliveryConfigSchema.safeParse({included_user_ids: ids.slice(0, 1000)}).success).toBe(true);
});
});
describe('resolveScreenShareDeliveryAssignment', () => {
test('the inert assignment is disabled', () => {
expect(INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT.enabled).toBe(false);
});
test('returns the inert assignment when the master switch is off', () => {
const config = createConfig({
enabled: false,
rollout_basis_points: 10000,
included_user_ids: [TARGETED_USER_ID],
});
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID)).toEqual(
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
);
});
test('returns the inert assignment for the default config', () => {
expect(resolveScreenShareDeliveryAssignment(createConfig(), TARGETED_USER_ID)).toEqual(
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
);
});
test('never hands back the shared inert object', () => {
const assignment = resolveScreenShareDeliveryAssignment(createConfig(), TARGETED_USER_ID);
expect(assignment).not.toBe(INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT);
});
test('denylist beats allowlist', () => {
const config = createConfig({
enabled: true,
included_user_ids: [TARGETED_USER_ID],
excluded_user_ids: [TARGETED_USER_ID],
});
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID).enabled).toBe(false);
});
test('denylist beats the bucket', () => {
const config = createConfig({
enabled: true,
rollout_basis_points: 10000,
excluded_user_ids: [TARGETED_USER_ID],
});
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID).enabled).toBe(false);
expect(resolveScreenShareDeliveryAssignment(config, OTHER_USER_ID).enabled).toBe(true);
});
test('allowlist beats the bucket', () => {
const config = createConfig({
enabled: true,
rollout_basis_points: 0,
included_user_ids: [TARGETED_USER_ID],
});
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID).enabled).toBe(true);
expect(resolveScreenShareDeliveryAssignment(config, OTHER_USER_ID).enabled).toBe(false);
});
test.each([
{basisPoints: 0, enabled: false},
{basisPoints: 10000, enabled: true},
])('a rollout of $basisPoints basis points targets $enabled', ({basisPoints, enabled}) => {
const config = createConfig({enabled: true, rollout_basis_points: basisPoints});
for (const userId of syntheticUserIds(200)) {
expect(resolveScreenShareDeliveryAssignment(config, userId).enabled).toBe(enabled);
}
});
test('the bucket boundary is exclusive at the low end and inclusive one point above', () => {
const salt = DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG.rollout_salt;
const bucket = experimentBucket(TARGETED_USER_ID, salt);
expect(
resolveScreenShareDeliveryAssignment(
createConfig({enabled: true, rollout_basis_points: bucket}),
TARGETED_USER_ID,
).enabled,
).toBe(false);
expect(
resolveScreenShareDeliveryAssignment(
createConfig({enabled: true, rollout_basis_points: bucket + 1}),
TARGETED_USER_ID,
).enabled,
).toBe(true);
});
test('raising the rollout basis points only ever adds users', () => {
const userIds = syntheticUserIds(2000);
const atOneThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 1000}), userIds);
const atTwoThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 2000}), userIds);
expect(atOneThousand.size).toBeGreaterThan(0);
for (const userId of atOneThousand) {
expect(atTwoThousand.has(userId)).toBe(true);
}
expect(atTwoThousand.size).toBeGreaterThan(atOneThousand.size);
});
test('the targeted set follows the salt', () => {
const userIds = syntheticUserIds(2000);
const first = targetedUserIds(
createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'screen-share-delivery-v1'}),
userIds,
);
const second = targetedUserIds(
createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'screen-share-delivery-v2'}),
userIds,
);
expect(first).not.toEqual(second);
});
});
@@ -1,68 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {EXPERIMENT_BUCKET_RESOLUTION, experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
import {z} from 'zod';
const SCREEN_SHARE_DELIVERY_ROLLOUT_BASIS_POINTS_MAX = EXPERIMENT_BUCKET_RESOLUTION;
const SCREEN_SHARE_DELIVERY_MAX_TARGETED_USERS = 1000;
const DEFAULT_SCREEN_SHARE_DELIVERY_SALT = 'screen-share-delivery-v1';
const ScreenShareDeliveryTargetIdSchema = z.string().regex(/^\d{1,20}$/u);
const ScreenShareDeliveryTargetedUserIdsSchema = z
.array(ScreenShareDeliveryTargetIdSchema)
.max(SCREEN_SHARE_DELIVERY_MAX_TARGETED_USERS);
const screenShareDeliveryConfigFields = {
enabled: z.boolean(),
config_version: z.number().int().min(0),
rollout_basis_points: z.number().int().min(0).max(SCREEN_SHARE_DELIVERY_ROLLOUT_BASIS_POINTS_MAX),
rollout_salt: z.string().trim().min(1).max(64),
included_user_ids: ScreenShareDeliveryTargetedUserIdsSchema,
excluded_user_ids: ScreenShareDeliveryTargetedUserIdsSchema,
};
export const ScreenShareDeliveryConfigSchema = z.object({
enabled: screenShareDeliveryConfigFields.enabled.default(false),
config_version: screenShareDeliveryConfigFields.config_version.default(0),
rollout_basis_points: screenShareDeliveryConfigFields.rollout_basis_points.default(0),
rollout_salt: screenShareDeliveryConfigFields.rollout_salt.default(DEFAULT_SCREEN_SHARE_DELIVERY_SALT),
included_user_ids: screenShareDeliveryConfigFields.included_user_ids.default([]),
excluded_user_ids: screenShareDeliveryConfigFields.excluded_user_ids.default([]),
});
export type ScreenShareDeliveryConfig = z.infer<typeof ScreenShareDeliveryConfigSchema>;
export const DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG: ScreenShareDeliveryConfig = ScreenShareDeliveryConfigSchema.parse(
{},
);
export const ScreenShareDeliveryConfigUpdateRequest = z
.object(screenShareDeliveryConfigFields)
.omit({config_version: true})
.partial();
export type ScreenShareDeliveryConfigUpdateRequest = z.infer<typeof ScreenShareDeliveryConfigUpdateRequest>;
export const ScreenShareDeliveryConfigResponse = ScreenShareDeliveryConfigSchema;
export type ScreenShareDeliveryConfigResponse = z.infer<typeof ScreenShareDeliveryConfigResponse>;
export const ScreenShareDeliveryAssignmentResponse = z.object({
enabled: screenShareDeliveryConfigFields.enabled,
});
export type ScreenShareDeliveryAssignmentResponse = z.infer<typeof ScreenShareDeliveryAssignmentResponse>;
export const INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT: ScreenShareDeliveryAssignmentResponse = {
enabled: false,
};
export function resolveScreenShareDeliveryAssignment(
config: ScreenShareDeliveryConfig,
userId: string,
): ScreenShareDeliveryAssignmentResponse {
if (!config.enabled) return {...INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT};
if (config.excluded_user_ids.includes(userId)) return {...INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT};
if (config.included_user_ids.includes(userId)) return {enabled: true};
return {enabled: experimentBucket(userId, config.rollout_salt) < config.rollout_basis_points};
}
@@ -1,9 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
ScreenShareDeliveryAssignmentResponse,
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
import { import {
INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
VoiceNoiseSuppressionAssignmentResponse, VoiceNoiseSuppressionAssignmentResponse,
@@ -46,7 +42,6 @@ export type ExperimentDeliveryConfigResponse = z.infer<typeof ExperimentDelivery
const ExperimentAssignmentsSchema = z.object({ const ExperimentAssignmentsSchema = z.object({
voice_noise_suppression: VoiceNoiseSuppressionAssignmentResponse.optional(), voice_noise_suppression: VoiceNoiseSuppressionAssignmentResponse.optional(),
screen_share_delivery: ScreenShareDeliveryAssignmentResponse.optional(),
}); });
export const ExperimentAssignmentsResponse = z.object({ export const ExperimentAssignmentsResponse = z.object({
@@ -68,9 +63,3 @@ export function readVoiceNoiseSuppressionAssignment(
): VoiceNoiseSuppressionAssignmentResponse { ): VoiceNoiseSuppressionAssignmentResponse {
return response.assignments.voice_noise_suppression ?? INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT; return response.assignments.voice_noise_suppression ?? INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT;
} }
export function readScreenShareDeliveryAssignment(
response: ExperimentAssignmentsResponse,
): ScreenShareDeliveryAssignmentResponse {
return response.assignments.screen_share_delivery ?? INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT;
}