feat(widgets): Add the controlledMediaOutput url parameter to the VirtualElementCallWidgetOptions.

This is used to configure EC on devices that need to control media outputs on their own (android, ios).
If set, EC will display a list of devices provided by the app.
This commit is contained in:
Timo
2025-05-16 14:17:40 +02:00
committed by Damir Jelić
parent ac2c7f431c
commit ea4c9a41f8
2 changed files with 66 additions and 14 deletions

View File

@@ -248,6 +248,11 @@ pub struct VirtualElementCallWidgetOptions {
/// Sentry [environment](https://docs.sentry.io/concepts/key-terms/key-terms/)
/// Supported since Element Call v0.9.0. Only used by the embedded package.
pub sentry_environment: Option<String>,
//// - `true`: The webview should show the list of media devices it detects using
//// `enumerateDevices`.
/// - `false`: the webview shows a a list of devices injected by the
/// client. (used on ios & android)
pub controlled_media_devices: bool,
}
impl From<VirtualElementCallWidgetOptions> for matrix_sdk::widget::VirtualElementCallWidgetOptions {
@@ -271,6 +276,7 @@ impl From<VirtualElementCallWidgetOptions> for matrix_sdk::widget::VirtualElemen
rageshake_submit_url: value.rageshake_submit_url,
sentry_dsn: value.sentry_dsn,
sentry_environment: value.sentry_environment,
controlled_media_devices: value.controlled_media_devices,
}
}
}

View File

@@ -70,6 +70,7 @@ struct ElementCallParams {
/// Supported since Element Call v0.9.0. Only used by the embedded package.
sentry_environment: Option<String>,
hide_screensharing: bool,
controlled_media_devices: bool,
}
/// Defines if a call is encrypted and which encryption system should be used.
@@ -193,6 +194,11 @@ pub struct VirtualElementCallWidgetOptions {
/// Sentry [environment](https://docs.sentry.io/concepts/key-terms/key-terms/)
/// This is only used by the embedded package of Element Call.
pub sentry_environment: Option<String>,
//// - `true`: The webview should show the list of media devices it detects using
//// `enumerateDevices`.
/// - `false`: the webview shows a a list of devices injected by the
/// client. (used on ios & android)
pub controlled_media_devices: bool,
}
impl WidgetSettings {
@@ -253,6 +259,7 @@ impl WidgetSettings {
sentry_environment: props.sentry_environment,
rageshake_submit_url: props.rageshake_submit_url,
hide_screensharing: props.hide_screensharing,
controlled_media_devices: props.controlled_media_devices,
};
let query =
@@ -288,6 +295,7 @@ mod tests {
rageshake: bool,
sentry: bool,
intent: Option<Intent>,
controlle_output: bool,
) -> WidgetSettings {
let mut props = VirtualElementCallWidgetOptions {
element_call_url: "https://call.element.io".to_owned(),
@@ -298,6 +306,7 @@ mod tests {
confine_to_room: Some(true),
encryption: encryption.unwrap_or(EncryptionSystem::PerParticipantKeys),
intent,
controlled_media_devices: controlle_output,
..VirtualElementCallWidgetOptions::default()
};
@@ -345,13 +354,13 @@ mod tests {
}
#[test]
fn new_virtual_element_call_widget_base_url() {
let widget_settings = get_widget_settings(None, false, false, false, None);
fn test_new_virtual_element_call_widget_base_url() {
let widget_settings = get_widget_settings(None, false, false, false, None, false);
assert_eq!(widget_settings.base_url().unwrap().as_str(), "https://call.element.io/");
}
#[test]
fn new_virtual_element_call_widget_raw_url() {
fn test_new_virtual_element_call_widget_raw_url() {
const CONVERTED_URL: &str = "
https://call.element.io#\
?userId=$matrix_user_id\
@@ -370,9 +379,10 @@ mod tests {
&preload=true\
&perParticipantE2EE=true\
&hideScreensharing=false\
&controlledMediaDevices=false\
";
let mut url = get_widget_settings(None, false, false, false, None).raw_url().clone();
let mut url = get_widget_settings(None, false, false, false, None, false).raw_url().clone();
let mut gen = Url::parse(CONVERTED_URL).unwrap();
assert_eq!(get_query_sets(&url).unwrap(), get_query_sets(&gen).unwrap());
url.set_fragment(None);
@@ -383,8 +393,11 @@ mod tests {
}
#[test]
fn new_virtual_element_call_widget_id() {
assert_eq!(get_widget_settings(None, false, false, false, None).widget_id(), WIDGET_ID);
fn test_new_virtual_element_call_widget_id() {
assert_eq!(
get_widget_settings(None, false, false, false, None, false).widget_id(),
WIDGET_ID
);
}
fn build_url_from_widget_settings(settings: WidgetSettings) -> String {
@@ -406,7 +419,7 @@ mod tests {
}
#[test]
fn new_virtual_element_call_widget_webview_url() {
fn test_new_virtual_element_call_widget_webview_url() {
const CONVERTED_URL: &str = "
https://call.element.io#\
?parentUrl=https%3A%2F%2Fcall.element.io\
@@ -423,9 +436,11 @@ mod tests {
&clientId=io.my_matrix.client\
&perParticipantE2EE=true\
&hideScreensharing=false\
&controlledMediaDevices=false\
";
let gen =
build_url_from_widget_settings(get_widget_settings(None, false, false, false, None));
let gen = build_url_from_widget_settings(get_widget_settings(
None, false, false, false, None, false,
));
let mut url = Url::parse(&gen).unwrap();
let mut gen = Url::parse(CONVERTED_URL).unwrap();
@@ -438,7 +453,7 @@ mod tests {
}
#[test]
fn new_virtual_element_call_widget_webview_url_with_posthog_rageshake_sentry() {
fn test_new_virtual_element_call_widget_webview_url_with_posthog_rageshake_sentry() {
const CONVERTED_URL: &str = "
https://call.element.io#\
?parentUrl=https%3A%2F%2Fcall.element.io\
@@ -462,8 +477,11 @@ mod tests {
&rageshakeSubmitUrl=https%3A%2F%2Frageshake.element.io\
&sentryDsn=SENTRY_DSN\
&sentryEnvironment=SENTRY_ENV\
&controlledMediaDevices=false\
";
let gen = build_url_from_widget_settings(get_widget_settings(None, true, true, true, None));
let gen = build_url_from_widget_settings(get_widget_settings(
None, true, true, true, None, false,
));
let mut url = Url::parse(&gen).unwrap();
let mut gen = Url::parse(CONVERTED_URL).unwrap();
@@ -476,7 +494,7 @@ mod tests {
}
#[test]
fn password_url_props_from_widget_settings() {
fn test_password_url_props_from_widget_settings() {
{
// PerParticipantKeys
let url = build_url_from_widget_settings(get_widget_settings(
@@ -485,6 +503,7 @@ mod tests {
false,
false,
None,
false,
));
let query_set = get_query_sets(&Url::parse(&url).unwrap()).unwrap().1;
let expected_elements = [("perParticipantE2EE".to_owned(), "true".to_owned())];
@@ -505,6 +524,7 @@ mod tests {
false,
false,
None,
false,
));
let query_set = get_query_sets(&Url::parse(&url).unwrap()).unwrap().1;
let expected_elements = ("perParticipantE2EE".to_owned(), "false".to_owned());
@@ -523,6 +543,7 @@ mod tests {
false,
false,
None,
false,
));
let query_set = get_query_sets(&Url::parse(&url).unwrap()).unwrap().1;
let expected_elements = [("password".to_owned(), "this_surely_is_save".to_owned())];
@@ -538,11 +559,34 @@ mod tests {
}
#[test]
fn intent_url_props_from_widget_settings() {
fn test_controlled_output_url_props_from_widget_settings() {
{
// PerParticipantKeys
let url = build_url_from_widget_settings(get_widget_settings(
Some(EncryptionSystem::PerParticipantKeys),
false,
false,
false,
None,
true,
));
let controlled_media_element = ("controlledMediaDevices".to_owned(), "true".to_owned());
let query_set = get_query_sets(&Url::parse(&url).unwrap()).unwrap().1;
assert!(
query_set.contains(&controlled_media_element),
"The query elements: \n{:?}\nDid not contain: \n{:?}",
query_set,
controlled_media_element
);
}
}
#[test]
fn test_intent_url_props_from_widget_settings() {
{
// no intent
let url = build_url_from_widget_settings(get_widget_settings(
None, false, false, false, None,
None, false, false, false, None, false,
));
let query_set = get_query_sets(&Url::parse(&url).unwrap()).unwrap().1;
@@ -565,6 +609,7 @@ mod tests {
false,
false,
Some(Intent::JoinExisting),
false,
));
let query_set = get_query_sets(&Url::parse(&url).unwrap()).unwrap().1;
let expected_elements = ("intent".to_owned(), "join_existing".to_owned());
@@ -594,6 +639,7 @@ mod tests {
false,
false,
Some(Intent::StartCall),
false,
));
let query_set = get_query_sets(&Url::parse(&url).unwrap()).unwrap().1;